-
Notifications
You must be signed in to change notification settings - Fork 1
Development Guidelines
This page contains guidelines that all project members should follow when producing new source code for the UnBall Robot Soccer Team.
Code style is definitely important to make the UnBall Robot Soccer Team software elegant, reusable, easy to understand and to maintain. It is strongly recommended that all code produced in this project follow the suggestions below whenever applicable.
These guidelines, however, are not immutable and may be changed if the developers agree and have strong reasons to do so. In this case, changes must be documented in this page and applied to all software.
The most important guideline is: be consistent and every little thing is gonna be alright.
All coding, documentation, tutorials, and any other materials should be written in English. This makes the project available to a broader number of people. When necessary, material may be produced in Brazilian Portuguese (e.g. presentations in University of Brasília) with possible English translations.
- Source files: Should have .cpp extension. This is the file where you implement classes and functions.
- Header files: Should have .hpp extension. This is the file where you describe classes and prototype functions for the associated .cpp file. Based on these discussions (1, 2, and 3) and on internal discussions by the development team, we agreed to use .hpp for C++ pure headers and .h for C pure/compatible headers.
- src/: Source files
- include/: Header files
- include/impl/: Templated implementation files
- config/: Configuration files
- data/: Multimedia data, such as images, videos, music etc
- launch/: ROS launch files
- Directory names: Always lowercase with underscores, such as "vision/kinect_camera.cpp"
- Identation: Indent 4 spaces at a time and use spaces only.
- Line Length: Each line should be 120 characters long.
- Whitespaces: Avoid unnecessary whitespaces whenever possible. Follow English language conventions for spacing. Do not put space between a function name, its parenthesis, and variables. Do not put trailing spaces to align comments or variables assignments. Example:
void f(bool b);
int i = 0;
int x[] = {0}; // Robots positions
- Braces: Both opening and closing braces go to their own lines in order to keep readability. Example:
int main(int argc, char **argv)
{
}
...
if (a < b)
{
// do stuff
}
-
Pointer declaration: Attach the asterisk to the variable name
int *pinstead of its typeint* p, since C/C++ parses the pointer only to the first variable (makingint* p1, *p2, *p3, ...necessary). -
Header guards: All header files must start with a
#ifndefguard in order to avoid prevent multiple inclusion. For example, a file "unball/src/foo/bar.h" must have the guard:
#ifndef UNBALL_FOO_BAR_H_
#define UNBALL_FOO_BAR_H_
...
#endif // UNBALL_FOO_BAR_H_
-
Names of includes: Header files should included using
#include <>. For example, "include/unball/base/logging.h" should be included as#include <unball/base/logging.h> -
Includes with .cpp: Whenever a .cpp file has an associated .hpp, all the includes should go to the header file, which is the only include in the .cpp file. Example:
src/foo/bar.cpp
#include <unball/foo/bar.hpp>
...
# rest of the code
include/unball/foo/bar.hpp
#include <string>
#include <iostream>
#include <ros/ros.h>
...
# rest of the code
- Order of includes: Include from the most general libraries to the most specific ones for readability and to avoid hidden dependencies. Use the following order as a reference: associated header file, C libraries, C++ libraries, ROS libraries, OpenCV libraries, UnBall ROS Messages' headers, other module's headers. Put a blank line between the header category to make it easier to maintain. Example for a file <unball/src/foo/public/fooserver.hpp>:
#include <cmath>
#include <sys/types.h>
#include <unistd.h>
#include <hash_map>
#include <vector>
#include <ros/ros.h>
#include <opencv2/opencv.hpp>
#include <unball/ExampleMessage.h>
#include <unball/base/basictypes.h>
#include <unball/base/commandlineflags.h>
#include <unball/foo/public/bar.h>
- Single-line conditionals and loops: Braces should be omitted if the block has only one line. However, a if-else that any condition have more than one line must use braces Example:
if (x < 0)
x = 0;
...
if (x < 0)
x = 0;
else if (x > 10)
x = 10;
...
if (x < 0)
{
x = 0;
}
else if (x > 10)
{
x = 10;
std::cout << x << std::endl;
}
...
for (int i = 0; i < 10; ++i)
std::cout << i << std::endl;
- Switch-cases: The cases must have one level of indentation and its procedures another level. Always use break statements in all cases and indent it in two levels as well. Include a default condition (which may print a warning or raise an error, for instance). Example:
switch (bar)
{
case 0:
++bar;
break;
case 1:
--bar;
break;
default:
std::cout << "Error: bar case not found." << std::endl;
break;
}
-
Classes: Define the public methods first, then the private attributes. Write getters and setters to the attributes when necessary. Use half-indentation for the
public:andprivate:keywords. Empty methods may be inlined.
class MyClass : public OtherClass
{
public:
MyClass();
explicit MyClass(int var);
~MyClass() {}
void SomeFunction();
private:
bool SomeInternalFunction();
int some_var_;
int some_other_var_;
};
-
Class: Should be cased starting with capital letter. Usually, classes names are composed by nouns. Example:
class Robot -
Class variables: Should be lowercase with underscores, including a trailing underscore. Do not use
this->to reference a class variable. Example:float wheel_size_ -
Function and methods: Should be cased starting with lowercase. All functions and methods perform actions, therefore, their names must explicitly explain what it produces. Start with a verb, such as
get,set,has,is,calculateetc. Example:float calculateAreaOfCircle(float radius) -
Variables: Should be lowercase with underscores. Example:
int game_state -
Constants: Should be uppercase with underscores. Example:
#define NUM_PLAYERS 6orconst static int MY_CONSTANT = 1000; -
Mnemonic names: Names should be meaningful and easy to understand. Avoid variable names with only one letter unless it is a iteration counter
i,jork. Avoid abbreviations such asint n_rqstinstead ofint num_requestunless it is widely used and its meaning is well-known, such asnum,msg,cpuetc.
-
Use rosconsole for output: Do not use
printforstd::cout. Example:ROS_INFO("Testing");
- Code must be documented: Undocumented code, however functional it may be, cannot be maintained. Use Doxygen to auto-document our code with rosdoc.
- Files: Every file must start with a documentation header following the standard below. .cpp files should explain the implementation details.
/**
* @file <name of the file>
* @author <name of the author>
* @date <date>
*
* @attention Copyright (C) 2014 UnBall Robot Soccer Team
*
* @brief <small description of the file purpose>
*
* <detailed description which may contain examples and test cases>
*/
- Functions: Should explain the purpose of the function, its parameters, and the expected return. Example:
/**
* A normal member taking two arguments and returning an integer value.
* @param a an integer argument.
* @param s a constant character pointer.
* @return The test results
*/
int testMe(int a,const char *s);
- Variables: When the name of the variable does not explain its usage clearly, comment it. The first line contains a brief, followed by a more detailed description. However, usually, the variable name should be meaningful enough to not required explanations.
/**
* Keeps track of the total number of entries in the table.
* Used to ensure we do not go over the limit. -1 means
* that we don't yet know how many entries the table has.
*/
int num_total_entries_;
- Non-trivial implementations: Always comment tricky workarounds that cannot be understood with one glance. This makes the work easier in the future. Example:
// Divide result by two, taking into account that x
// contains the carry from the add.
for (int i = 0; i < result->size(); i++)
{
x = (x << 8) + (*result)[i];
(*result)[i] = x >> 1;
x &= 1;
}
- TODO comments: Use TODO comments for code that is temporary, a short-term solution, or good-enough but not perfect. The programmer that writes a TODO must include either the name or e-mail for contact. If the TODO is due to a future code review or release, make sure to specify the time or occasion when it must be finished, as in "Fix by November 2014" or "Remove when all clients can handle IplImage" for instance.
// TODO(matheus.v.portela@gmail.com): Use a "*" here for concatenation operator.
Unit tests are small, simple tests written by the developer while writing a particular class or function. Each individual test exercises a class method or function using a set of “interesting” inputs. The objective is to verify that the class method or function being tested produces the correct state changes or output for the provided set of inputs.
Tests are great for those interesting and non-trivial functions (don't write tests for getters and setters unless they are non-trivial, for instance). If you need any guidelines on whether a method require a test, ask yourself the following questions. If one or more have a positive answer then consider writing a test.
- Is it useful to have any example demonstrating the usage of this method?
- Are you unsure that this function will work?
- Are there three or more parameters?
- Did you spend more than 20 minutes writing this function?
- Is it faster to write a test case than to think whether it's worth testing or not?
- Start by most common errors, then go to other tests.
- Write at least one test that shows the function working in the general case, with valid inputs.
- Write at least one test that shows the function handling a set of invalid inputs.
- Test boundary conditions (e.g. sort an empty list, list with single element etc).
- Test for proper state changes and to ensure that data structures remain consistent (e.g. flags are reset appropriately based on object state).
- Numbers: zero, extreme positive, extreme negative
- Data structures: empty, exactly one element, maximum number of elements, duplicate elements, circular structures (that points to itself)
- Searching: no match found, one match found, multiple matches found, everything matches
- Pointers: valid, NULL, 0
The default test framework for the UnBall Robot Soccer Team is gtest integrated with rostest, which is included in the complete ROS installation. Tutorials and documentation are available here:
It is not easy to develop software with many people in a team: everyone needs to be working (preferably) on the same version, and, sometimes, changes across files may conflict. Also, it may be necessary to consult a previous version of a file in order to undo some changes. Therefore, it is a standard approach to use a Version Control System (VCS).
A VCS allows you to: revert files back to a previous state, revert the entire project back to a previous state, review changes made over time, see who last modified something that might be causing a problem, who introduced an issue and when, and more. Using a VCS also means that if you screw things up or lose files, you can generally recover easily. In addition, you get all this for very little overhead.
Git is the version control software used by the UnBall Robot Soccer Team.
A project is usually hosted on a Git server, which may be on GitHub, Google Code or even your own computer. Cloning is the act of getting a project from the server and place it in a directory. This is achieved by the command git clone <url>, which need to be executed only once.
Example:
git clone https://github.com/unball/ieee-very-small.git places the project on the "ieee-very-small" directory.
If you already have a cloned repository, it is important to synchronize it to the latest version whenever you start to work. This will avoid merge conflicts due to different versions of the project. The command to get the latest changes is git pull.
Differently from a Dropbox folder, placing a new file in your project directory is not enough to add it to the project. This is a useful feature since there are lots of files which we should avoid to insert to the project, for instance, executables or object files (only important during compilation). To insert a new file or directory, use the command git add <file or dir>. Note that adding a directory is recursive, which means that all files inside it will be added.
Example:
git add hello_word.cpp adds the recently created file "hello_word.cpp" to the repository.
After working on the project, you should tell the other developers what you changed what the reasoning behind it. In order to do it, you should commit using the command git commit. You may commit file by file or all changes using the flag -a.
A text editor will appear for you to write a log, a small message that describes the modifications. It is really important to write clear and direct log messages, so the others may quickly understand what was modified, and also to show what this commit is about in the project history. The log message may also be written in the command line by using the flag -m.
Example:
git commit -a -m "Created a new class in the simulator for the ball. All tests passed.".
After committing, there is one last step necessary to upload your modifications to the Git server: the push. Git allows programmers to work offline, which means that all your commits will stay in your computer until they are pushed to the server. This is done by the command git push. Usually, it will require your username and password for the GitHub server (where the UnBall Robot Soccer Team is hosted).
- Commits: Should be small and change only one thing in the project. It is better to have hundreds of small commits, that are easily reverted if anything goes wrong, than few colossal commits that changes several files at the same time.
- Log messages: Make the log messages clear and instructive. Any developer should be able to understand what was changed and why by only reading the log.
- Never commit broken code: Your code must have passed all tests and yield zero warnings before it can be committed to the project.
- Always pull the latest changes before start working on the project: This will avoid merge conflicts.
- Do not add object or executable files to the project: They are changed each time the project is compiled and will keep appearing on the commit logs.
- Google C++ Style Guide
- CMU Tartan Racing Guidelines