Skip to content

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

  1. Commit your code early and often
  2. Keep commits focused on a particular change
  3. Don't combine multiple big changes together in one commit
  4. Keep commit messages informative and consise

    Bad commit message example:
    'running demo'
    '2d and 3d map'

  5. Add files that are necessary to build your package

  6. Don't add machine/code generated files to repository -- use .gitignore
  7. Large data files should be hosted in a public/private web hosting site (e.g. big bag files)
  8. Use issue trackers for each repository for:
    1. Bug reports
    2. Enhancement requests
    3. Task assignments (to yourself if needed)
  9. Issues should include:
    1. Instructions for reproducing the bug
    2. Information about your system (e.g. relevant package versions)
  10. Use the right issue tracker to report the issue (the one closest to the package
  11. Use release version of packages whenever possible
  12. Don't copy packages to new repositories
  13. Fork repositories if necessary (vs copy & commit in one commit)
    1. Original repository might disappear (fork keeps commit history)
    2. Keep control when original version is updated
  14. Use separate workspaces per developer (does not mean to divide workspace between developers)
  15. Use as many workspaces as you need
  16. Use workspace overlaying
  17. Never have changes on robot config files not backed by a VCS
    1. Create repositories to hold Linux config files
    2. Always commit those changes
  18. Create dedicated packages for msgs, srvs, and actions
  19. Use ROS metapackages only when relevant
  20. Use .rosinstall files to share workspaces
  21. Use branches only if relevant
    1. When you want to add a new feature of fix bugs
    2. Never use branches for simulation vs real HW
    3. Never use branches for specific developers or applications
  22. Create high level config packages for apps/demos
  23. Config packages should only include .launch and config files
  24. Ideally apps/demos should run on real HW and simulator without any changes

Workspace Tools

  1. Commands to manipulate ROS workspaces
    1. Init, set/change, merge, update, remove workspaces
  2. vcstool:
    1. Does not require a configuration file
    2. Simply operates on your filesystem
    3. Accepts same format of .rosinstall file
    4. Uses '.repos' files or '.rosinstall' file
    5. Can be used with ros2
    6. Completely independent from ROS
    7. 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.

  1. For ROS1 try to stick to the current Google C++ style guide
  2. For ROS2 check ROS2 code style guide
  3. For ROS cpp nodes check ROSCPP Style Guide
  4. Choose descriptive names

    Bad name: demo2020
    Good name: demo_banana_pick_and_place

  5. 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
  6. Do not use the entity name in the name of the entity

    1. Foo.action vs FooAction.action
    2. Bar.msg vs BarMsg.msg
  7. Services should have method-like rather than data-type names
    1. SetMap vs Map
  8. Acronyms remain capitals regardless of coding style
    1. class HokuyoURGLaser
  9. Member variables have a trailing underscore
    1. member_variable_
  10. Function names should follow the action it does
    1. dumpDataToFile() vs dataFile()
  11. Package names should be descriptive
    1. planner VS wavefront_planner
  12. STL iterator variables should indicate what they're iterating over (not just ij)
  13. Global variables have a leading g_
    1. g_shutdown
  14. Contants follow KCamelCase style???
  15. If a file primarily implements a class, name the file after the class
    1. class ActionServeraction_server.h
  16. Avoid catchall names for packages such as utils, manager, motion, vision, planner
  17. Prefixing package names is recommended only when the package deals with a specific use case (e.g. HW) and difficult to reuse
    1. pr2_ or albert_ prefixes
  18. Prefixing a package name with ros is redundant
  19. A name can be subfixed with ros when it is wrapping an existing library
  20. Package names are global to the entire ROS ecosystem

Package Organisation

  1. Define separate packages whenever it make sense (can be re-used)
  2. Avoid combining nodes that pull in mutually unneeded dependencies
  3. Avoid combining nodes that are often used separately
  4. Package dependency graph must be acyclic, i.e. no package may depend on another that directly or indirectly depends on it
    1. A depends on B & B depends on C → C should not depend on A or B
  5. Create separate packages that contain only messages, services and actions
  6. Group packages in meta-pkg when it makes sense
  7. Node names are local within their package
  8. There is a single name space for packages but nodes name live in the package Namespace

Nodes

  1. Type VS Name
    1. Type is the name of the executable to launch the node
    2. Name is what is passed to other ROS nodes when it starts up
  2. Type names are local within their package
  3. Type name: better short because they are scoped by the package name
  4. Node name should be unique while running ROS app with different nodes
  5. Don't prefix the node name with a package name
  6. Each node is responsible for one job

Topics

  1. Use topics for publishing continuous streams of data, e.g. sensor data, continuous detection results, ..
  2. Used to publish state of object unless it is a long process to compute this state
  3. Data might be published and subscribed at any time independent of any senders/receivers
  4. Callbacks receive data once it is available
  5. Many to many connection
  6. The publisher decides when data is sent

Services

  1. Use services only for short calculations
  2. Use service for Simple blocking call
    1. e.g. for doing a quick calculation such as IK
  3. Never be used for longer running processes → use actions

Actions

  1. Use actions for long, complex tasks like execution of robot actions
    1. Use actions for longer running processes
    2. e.g. grasping, navigation, perception
    3. e.g. initiating a lower-level control mode
    4. Should be used for any action that moves a robot or that runs for a longer time
  2. Actions provide feedback during execution
  3. Action client can start action and cancel goals
  4. Actions can be preempted/cancelled. Preemption should be implemented cleanly by action servers
  5. Client can choose to wait for results or not

Message Files

  1. 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
  2. Give message files descriptive names!
    1. e.g. simpleMpc.actionMoveRobot.actionMoveAlbert.action

Parameters

  1. Use parameters for values that are known at launch
  2. Use dynamic parameters (dynamic_reconfigure) for parameters that are likely to change during run time
  3. Use a hierarchical scheme for parameters
    1. camera/left/name VS camera_left_name
    2. Easier to share/load values
  4. It is better to use ROS parameter server over command line parameters
  5. It is better to organize the parameters in YAML files and load them via the rosparam tag in the launch file
  6. You can overlay/write parameters default values in launch files after loading them by passing arguments

TF

  1. Frame_ids should be globally unique
  2. Use the tf_prefix parameter → /[tf_prefix/]frame_name
    1. e.g. in case of using two robots: /robot1/base_link, /robot_2/base_link
  3. Exception are globally unique frames: /earth/map
  4. Coordinate frames
    1. Coordinate Frames for Mobile Platforms
      1. REP:105
      2. e.g. map, odom, base_link
    2. Coordinate Frames for Humanoid Robots
      1. REP:120
      2. e.g. base_link, base_footprint, torso

Libraries

  1. Don't put ROS dependencies in the core of your algorithm
  2. Use standalone libraries with no ROS dependencies
  3. Develop ROS wrappers for independent libraries
  4. If possible, try to use libraries from Debian packages
  5. Don't copy libraries into packages that need them
    1. Specify include directory in cmake

Launch Files

  1. Use launch files for applications/demos
  2. Use launch to bringup complex systems
  3. Place launch files in the relevant packages
    1. a launch file to view a robot on rviz can be placed in robot_description package
  4. Use different launch files to bringup a robot in simulation and to start a real robot
  5. Launch file names should reflect what they do/launch Example: demo_gazebo.launch does not describe what it is used for.
  6. Design tips:
    1. Should be as reusable as possible
    2. 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

  1. Subscribers: usually public node handle
  2. Publishers:
    1. Public for globally-used data (i.e., /odom topic)
    2. Private node handles for most output/visualization
  3. Parameters: almost always private node handle unless you share on namespace
  4. Never use global names (e.g /topic)
    1. Global names can't be namespaced
    2. Can't run more than one of your node at a time properly
    3. Can't use multiple robots on the same master

Coding Tips

  1. Use Exceptions for Error-reporting
  2. Don't throw exceptions from callbacks that you don't invoke directly (e.g subscriber callback)
    1. You can't deal with these exceptions
  3. Use assertions to check preconditions and data structure integrity
    1. Conditions should always be true e.g. avoid dividing over zero
    2. ROS_ASSERT(x > y)
    3. ROS_ASSERT_MSG(x > 0, "Uh oh, x went negative. Value = %d", x)
    4. ROS_ASSERT_CMD(x > 0, handleError(...))
  4. Only call exit() at a well-defined exit point for the application
  5. Never call exit() in a library
  6. Use standard data types whenever it is possible
  7. Use standard standard/common ROS messages whenever it is possible (fewer .msg files)
    1. More re-usable
    2. Rviz visualizes standard messages, custom messages need a plugins to be visualized
  8. Complex messages are built through composition
    1. Don't lump everything together (union)
    2. E.g. PoseWithCovarianceStamped
  9. Avoid global variables
    1. Make code less reusable
    2. Prevent multiple instantiations of a piece of code

Documentation

  1. Do write documentation! (mentioned in this year’s ROSCon too)
  2. All code should be documented according to QAProcess(a bit old )
  3. Create a clear and complete README.md
  4. Wiki for each package what the node does (not always needed)
    1. Topics, services and actions that are required and provided
    2. ROS parameters and their default values, A template for the README.md is provided here
  5. Provide example launch files in README.md
  6. What to document in README
    1. Node functionality
    2. Link examples
    3. References to relevant papers
    4. Command-line arguments
    5. Major assumptions and magic thresholds
    6. How to run launch files
    7. Exceptions that can be thrown by your package, on each function/method
  7. Use a documentation tool to genreate APIs documentation
    1. Externally visible code-level APIs
    2. Externally visible ROS-level APIs (topics, services, parameters - default values)
    3. Functions, classes and variables exported by a library
  8. Documentation tools:
    1. rosdoc_lite (a ROS wrapper for doxygen)
    2. MKDocs (used for this webpage)
    3. Sphinx

Workspace Overlaying

  1. Never edit files in /opt/ros, want to override or customize packages?
    1. Use workspace overlaying
  2. Work with multiple workspaces?
    1. Use overlaying to chain workspaces
  3. Use workspace overlaying to avoid building shared packages multiple times
  4. Place shared package at lower layers, will be build once
  5. Lower layers can't depend on higher layers
  6. Main reasons:
    1. Build all dependencies packages once, in bottom layer
    2. Each time there change you need to only build your top workspace
  7. Moves packages between upper/lower layer if relevant
  8. Overlay packages on top layer if they are shared with other workspaces and you can't move them to top level

Dependencies

  1. Keep dependencies clean:

    1. Only depend on what you need
    2. Specify all dependencies
    3. Do not use implicit dependencies
    4. Explicitly list all dependencies that the package directly depends on

      1. 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

      2. 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

  2. Add ependencies in cmakelist.txt instead of running catkin_make/catkin build several times

    1. 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())
  3. 33% of ROS bugs are Dependency Errors1 (roscon2019)
  4. The package dependency graph must be acyclic
    1. i.e. no package depends on another that directly or indirectly depends on it
  5. List only direct dependencies
  6. For your package to compile, the CMakeLists.txt must resolve all of referenced headers and library
  7. Install all dependencies automatically using the 'rosdep install' command
  8. Use 'rqt_dep' to visualize dependencies tree
  9. buid_depend
    1. Specify which packages are needed to build this package
  10. build_export_depend
    1. Specify which packages are needed to build libraries against this package
  11. exec_depend
    1. Specify which packages are needed to run code in this package

References

  1. https://github.com/leggedrobotics/ros_best_practices/wiki
  2. http://wiki.ros.org/DevelopersGuide
  3. http://wiki.ros.org/CppStyleGuide
  4. http://wiki.ros.org/ROS/Patterns/Conventions#Naming_ROS_Resources
  5. http://wiki.ros.org/rosdoc
  6. http://wiki.ros.org/Parameter%20Server
  7. http://wiki.ros.org/roslaunch/Tutorials/Roslaunch%20tips%20for%20larger%20projects