Best Practices
To ensure that the code we write is readable, maintainable, and reusable, several best practices have been set up. This page introduces these best practices.
Version Control
- Commit your code early and often
- Keep commits focused on a particular change
- Don't combine multiple big changes together in one commit
-
Keep commit messages informative and consise
Bad commit message example:
'running demo'
'2d and 3d map' -
Add files that are necessary to build your package
- Don't add machine/code generated files to repository -- use .gitignore
- Large data files should be hosted in a public/private web hosting site (e.g. big bag files)
- Use issue trackers for each repository for:
- Bug reports
- Enhancement requests
- Task assignments (to yourself if needed)
- Issues should include:
- Instructions for reproducing the bug
- Information about your system (e.g. relevant package versions)
- Use the right issue tracker to report the issue (the one closest to the package
- Use release version of packages whenever possible
- Don't copy packages to new repositories
- Fork repositories if necessary (vs copy & commit in one commit)
- Original repository might disappear (fork keeps commit history)
- Keep control when original version is updated
- Use separate workspaces per developer (does not mean to divide workspace between developers)
- Use as many workspaces as you need
- Use workspace overlaying
- Never have changes on robot config files not backed by a VCS
- Create repositories to hold Linux config files
- Always commit those changes
- Create dedicated packages for msgs, srvs, and actions
- Use ROS metapackages only when relevant
- Use .rosinstall files to share workspaces
- Use branches only if relevant
- When you want to add a new feature of fix bugs
- Never use branches for simulation vs real HW
- Never use branches for specific developers or applications
- Create high level config packages for apps/demos
- Config packages should only include .launch and config files
- Ideally apps/demos should run on real HW and simulator without any changes
Workspace Tools
- Commands to manipulate ROS workspaces
Init
,set
/change
,merge
,update
,remove
workspaces
- vcstool:
- Does not require a configuration file
- Simply operates on your filesystem
- Accepts same format of .rosinstall file
- Uses '.repos' files or '.rosinstall' file
- Can be used with ros2
- Completely independent from ROS
- https://github.com/dirk-thomas/vcstool
Example of .repo file:
repositories:
airlabDemo/src/airlabdemo2020:
type: git
url: https://gitlab.tudelft.nl/mspahn/airlabdemo2020.git
version: master
behavior_control/src/behavior_control:
type: git
url: https://gitlab.tudelft.nl/corradopezzato/behavior_control.git
version: master
demo_manipulation/src/demo_manipulation:
type: git
url: https://gitlab.tudelft.nl/mimre/demo_manipulation.git
version: master
mobile_mpc/src/mobile_mpc:
type: git
url: https://gitlab.tudelft.nl/mspahn/mobile_mpc.git
version: master
ros_mm/src/ros_mm:
type: git
url: https://gitlab.tudelft.nl/mspahn/ros_mm.git
version: master
Naming Conventions
These naming conventions are just that; conventions. While there are different conventions for different systems/groups/languages, try to choose one (preferably this one!) and stick with it.
- For ROS1 try to stick to the current Google C++ style guide
- For ROS2 check ROS2 code style guide
- For ROS cpp nodes check ROSCPP Style Guide
-
Choose descriptive names
Bad name: demo2020
Good name: demo_banana_pick_and_place -
Coding styles:
Coding Style Entity snake_case Packages Nodes Topics Actions Parameters Variables Namespaces File names (except .msg, .srv, .action) Arguments PascalCase Messages Services Actions Classes Type names camelCase Functions Class Methods -
Do not use the entity name in the name of the entity
Foo.action
vsFooAction.action
Bar.msg
vsBarMsg.msg
- Services should have method-like rather than data-type names
SetMap
vsMap
- Acronyms remain capitals regardless of coding style
class HokuyoURGLaser
- Member variables have a trailing underscore
member_variable_
- Function names should follow the action it does
dumpDataToFile()
vsdataFile()
- Package names should be descriptive
planner
VSwavefront_planner
- STL iterator variables should indicate what they're iterating over (not just ij)
- Global variables have a leading
g_
g_shutdown
- Contants follow KCamelCase style???
- If a file primarily implements a class, name the file after the class
class ActionServer
→action_server.h
- Avoid catchall names for packages such as
utils
,manager
,motion
,vision
,planner
- Prefixing package names is recommended only when the package deals with a specific use case (e.g. HW) and difficult to reuse
pr2_
oralbert_
prefixes
- Prefixing a package name with
ros
is redundant - A name can be subfixed with
ros
when it is wrapping an existing library - Package names are global to the entire ROS ecosystem
Package Organisation
- Define separate packages whenever it make sense (can be re-used)
- Avoid combining nodes that pull in mutually unneeded dependencies
- Avoid combining nodes that are often used separately
- Package dependency graph must be acyclic, i.e. no package may depend on another that directly
or indirectly depends on it
- A depends on B & B depends on C → C should not depend on A or B
- Create separate packages that contain only messages, services and actions
- Group packages in meta-pkg when it makes sense
- Node names are local within their package
- There is a single name space for packages but nodes name live in the package Namespace
Nodes
- Type VS Name
- Type is the name of the executable to launch the node
- Name is what is passed to other ROS nodes when it starts up
- Type names are local within their package
- Type name: better short because they are scoped by the package name
- Node name should be unique while running ROS app with different nodes
- Don't prefix the node name with a package name
- Each node is responsible for one job
Topics
- Use topics for publishing continuous streams of data, e.g. sensor data, continuous detection results, ..
- Used to publish state of object unless it is a long process to compute this state
- Data might be published and subscribed at any time independent of any senders/receivers
- Callbacks receive data once it is available
- Many to many connection
- The publisher decides when data is sent
Services
- Use services only for short calculations
- Use service for Simple blocking call
- e.g. for doing a quick calculation such as IK
- Never be used for longer running processes → use actions
Actions
- Use actions for long, complex tasks like execution of robot actions
- Use actions for longer running processes
- e.g. grasping, navigation, perception
- e.g. initiating a lower-level control mode
- Should be used for any action that moves a robot or that runs for a longer time
- Actions provide feedback during execution
- Action client can start action and cancel goals
- Actions can be preempted/cancelled. Preemption should be implemented cleanly by action servers
- Client can choose to wait for results or not
Message Files
- The message should degine how the data should be structured, not what the data should be used for, or how a component must interpret it
- Give message files descriptive names!
- e.g.
simpleMpc.action
→MoveRobot.action
→MoveAlbert.action
- e.g.
Parameters
- Use parameters for values that are known at launch
- Use dynamic parameters (dynamic_reconfigure) for parameters that are likely to change during run time
- Use a hierarchical scheme for parameters
camera/left/name
VScamera_left_name
- Easier to share/load values
- It is better to use ROS parameter server over command line parameters
- It is better to organize the parameters in YAML files and load them via the rosparam tag in the launch file
- You can overlay/write parameters default values in launch files after loading them by passing arguments
TF
- Frame_ids should be globally unique
- Use the tf_prefix parameter →
/[tf_prefix/]frame_name
- e.g. in case of using two robots:
/robot1/base_link
,/robot_2/base_link
- e.g. in case of using two robots:
- Exception are globally unique frames:
/earth/map
- Coordinate frames
- Coordinate Frames for Mobile Platforms
- REP:105
- e.g.
map
,odom
,base_link
- Coordinate Frames for Humanoid Robots
- REP:120
- e.g.
base_link
,base_footprint
,torso
- Coordinate Frames for Mobile Platforms
Libraries
- Don't put ROS dependencies in the core of your algorithm
- Use standalone libraries with no ROS dependencies
- Develop ROS wrappers for independent libraries
- If possible, try to use libraries from Debian packages
- Don't copy libraries into packages that need them
- Specify include directory in cmake
Launch Files
- Use launch files for applications/demos
- Use launch to bringup complex systems
- Place launch files in the relevant packages
- a launch file to view a robot on rviz can be placed in robot_description package
- Use different launch files to bringup a robot in simulation and to start a real robot
- Launch file names should reflect what they do/launch
Example:
demo_gazebo.launch
does not describe what it is used for. - Design tips:
- Should be as reusable as possible
- Top-level launch files should be short, and consist of include's to other files corresponding to subcomponents of the application, and commonly changed ROS parameters.
Node Handlers
- Subscribers: usually public node handle
- Publishers:
- Public for globally-used data (i.e., /odom topic)
- Private node handles for most output/visualization
- Parameters: almost always private node handle unless you share on namespace
- Never use global names (e.g
/topic
)- Global names can't be namespaced
- Can't run more than one of your node at a time properly
- Can't use multiple robots on the same master
Coding Tips
- Use Exceptions for Error-reporting
- Don't throw exceptions from callbacks that you don't invoke directly (e.g subscriber callback)
- You can't deal with these exceptions
- Use assertions to check preconditions and data structure integrity
- Conditions should always be true e.g. avoid dividing over zero
- ROS_ASSERT(x > y)
- ROS_ASSERT_MSG(x > 0, "Uh oh, x went negative. Value = %d", x)
- ROS_ASSERT_CMD(x > 0, handleError(...))
- Only call exit() at a well-defined exit point for the application
- Never call exit() in a library
- Use standard data types whenever it is possible
- Use standard standard/common ROS messages whenever it is possible (fewer .msg files)
- More re-usable
- Rviz visualizes standard messages, custom messages need a plugins to be visualized
- Complex messages are built through composition
- Don't lump everything together (union)
- E.g. PoseWithCovarianceStamped
- Avoid global variables
- Make code less reusable
- Prevent multiple instantiations of a piece of code
Documentation
- Do write documentation! (mentioned in this year’s ROSCon too)
- All code should be documented according to QAProcess(a bit old )
- Create a clear and complete README.md
- Wiki for each package what the node does (not always needed)
- Topics, services and actions that are required and provided
- ROS parameters and their default values, A template for the README.md is provided here
- Provide example launch files in README.md
- What to document in README
- Node functionality
- Link examples
- References to relevant papers
- Command-line arguments
- Major assumptions and magic thresholds
- How to run launch files
- Exceptions that can be thrown by your package, on each function/method
- Use a documentation tool to genreate APIs documentation
- Externally visible code-level APIs
- Externally visible ROS-level APIs (topics, services, parameters - default values)
- Functions, classes and variables exported by a library
- Documentation tools:
- rosdoc_lite (a ROS wrapper for doxygen)
- MKDocs (used for this webpage)
- Sphinx
Workspace Overlaying
- Never edit files in /opt/ros, want to override or customize packages?
- Work with multiple workspaces?
- Use overlaying to chain workspaces
- Use workspace overlaying to avoid building shared packages multiple times
- Place shared package at lower layers, will be build once
- Lower layers can't depend on higher layers
- Main reasons:
- Build all dependencies packages once, in bottom layer
- Each time there change you need to only build your top workspace
- Moves packages between upper/lower layer if relevant
- Overlay packages on top layer if they are shared with other workspaces and you can't move them to top level
Dependencies
-
Keep dependencies clean:
- Only depend on what you need
- Specify all dependencies
- Do not use implicit dependencies
-
Explicitly list all dependencies that the package directly depends on
-
Scenario 1
if A depends on B and C:
B depends on C
C should be added explicitly as a dependency for A even if B grantee that C should be there to build the workspace, because If, for any reason, B gets removed of being dependency for A, A will still need C there -
Scenario 2
if A depends only on B (not on C)
B depends on C
C should not be added as a dependency for A (it is indirect dependency), because if, for any reason, B is no longer a dependency of A, A won't need C
-
-
Add ependencies in cmakelist.txt instead of running catkin_make/catkin build several times
- Example: If you have a package which builds messages and/or services as well as executables that use these, you need to create an explicit dependency on the automatically-generated message target so that they are built in the correct order. (some_target is the name of the target set by add_executable())
- 33% of ROS bugs are Dependency Errors1 (roscon2019)
- The package dependency graph must be acyclic
- i.e. no package depends on another that directly or indirectly depends on it
- List only direct dependencies
- For your package to compile, the CMakeLists.txt must resolve all of referenced headers and library
- Install all dependencies automatically using the 'rosdep install' command
- Use 'rqt_dep' to visualize dependencies tree
- buid_depend
- Specify which packages are needed to build this package
- build_export_depend
- Specify which packages are needed to build libraries against this package
- exec_depend
- Specify which packages are needed to run code in this package
References
- https://github.com/leggedrobotics/ros_best_practices/wiki
- http://wiki.ros.org/DevelopersGuide
- http://wiki.ros.org/CppStyleGuide
- http://wiki.ros.org/ROS/Patterns/Conventions#Naming_ROS_Resources
- http://wiki.ros.org/rosdoc
- http://wiki.ros.org/Parameter%20Server
- http://wiki.ros.org/roslaunch/Tutorials/Roslaunch%20tips%20for%20larger%20projects