ros Best Practices
This guide outlines the essential practices for developing robust, performant, and maintainable ROS 2 applications. Adherence to these rules ensures consistency, leverages ROS 2's advanced features, and aligns with the project's quality standards.
1. Code Quality and Static Analysis (The ament Way)
Mandate ament_lint_auto: Always integrate ament_lint_auto and ament_lint_common into your package build process. This is non-negotiable for catching style violations and common bugs early.
- ❌ BAD: Relying on manual checks or pre-commit hooks that can be bypassed.
- ✅ GOOD: Automatic enforcement during every build.
# CMakeLists.txt
# ...
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
ament_lint_auto_find_test_dependencies()
endif()
# ...
<!-- package.xml -->
<package format="2">
<!-- ... -->
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<!-- ... -->
</package>
C++ Style Enforcement: Use ament_cpplint and ament_cppcheck. Follow Google C++ Style Guide and modern C++17/20 features. Prioritize RAII and explicit smart pointer ownership.
- ❌ BAD: Raw pointers for resource management; ignoring
cpplint warnings.
- ✅ GOOD:
std::unique_ptr, std::shared_ptr for ownership.
// ❌ BAD: Manual memory management, potential leak
MyObject* obj = new MyObject();
// ...
delete obj; // Easy to forget or double-delete
// ✅ GOOD: RAII with smart pointers
std::unique_ptr<MyObject> obj = std::make_unique<MyObject>();
// Resource automatically managed
Python Style Enforcement: Adhere strictly to REP-8, which extends PEP 8. Use black for formatting and flake8 for linting. Ensure proper naming, imports, and docstrings.
- ❌ BAD: Inconsistent formatting, missing docstrings,
camelCase for variables.
- ✅ GOOD:
snake_case for variables/functions, clear docstrings, black-formatted code.
# ❌ BAD
def calculateVelocity(pos1, pos2, time):
# ...
return vel
# ✅ GOOD
def calculate_velocity(position1: float, position2: float, time_delta: float) -> float:
"""Calculates average velocity."""
# ...
return velocity
Copyright and Licensing: Use ament_copyright to ensure all source files have correct copyright and license headers.
- Run
ament_copyright --add-missing "Your Name" apache2 to automate this.
2. Code Organization and Structure
Python Package Layout (Catkin): Adopt the recommended src/<package_name>/__init__.py structure. This ensures proper Python package discovery and avoids namespace collisions.
- ❌ BAD: Scattering Python files directly in the package root or relying on
roslib.load_manifest.
- ✅ GOOD: Centralized, importable Python code.
my_package/
├── CMakeLists.txt
├── package.xml
├── setup.py
└── src/
└── my_package/
├── __init__.py
└── my_module.py
# setup.py for the above structure
from distutils.core import setup
from catkin_pkg.python_setup import generate_distutils_setup
d = generate_distutils_setup(
packages=['my_package'],
package_dir={'': 'src'},
)
setup(**d)
Node/Script Installation: Use catkin_install_python for ROS nodes and scripts. Install to ${CATKIN_PACKAGE_BIN_DESTINATION} to make them accessible via rosrun without polluting the global PATH.
- ❌ BAD: Installing scripts directly to
/usr/bin via setup.py scripts argument.
- ✅ GOOD: Package-isolated executables.
# CMakeLists.txt
# ...
catkin_install_python(PROGRAMS
nodes/my_node.py
DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)
# ...
3. Performance Considerations
Embrace Node Composition: Always use node composition for performance-critical applications, especially those with large messages (images, point clouds) or resource-constrained environments. This enables intra-process communication (IPC) for zero-copy data transfer.
- ❌ BAD: Running every node in its own process, leading to high inter-process communication overhead.
- ✅ GOOD: Grouping related nodes into a single composed process.
// Example: Composing nodes in C++ (simplified)
#include "rclcpp/rclcpp.hpp"
#include "my_package/my_camera_node.hpp" // Component
#include "my_package/my_processor_node.hpp" // Component
int main(int argc, char * argv[]) {
rclcpp::init(argc, argv);
rclcpp::executors::SingleThreadedExecutor exec; // Or MultiThreadedExecutor
auto camera_node = std::make_shared<my_package::MyCameraNode>();
auto processor_node = std::make_shared<my_package::MyProcessorNode>();
exec.add_node(camera_node);
exec.add_node(processor_node); // IPC if topics match
exec.spin();
rclcpp::shutdown();
return 0;
}
Choose the Right Executor: For complex applications with multiple nodes or long-running callbacks, use rclcpp::executors::MultiThreadedExecutor. For simple, single-purpose nodes, SingleThreadedExecutor is sufficient.
- ❌ BAD: Using
SingleThreadedExecutor for a node with many subscriptions or services that could block.
- ✅ GOOD:
MultiThreadedExecutor for concurrent callback processing.
// ✅ GOOD: MultiThreadedExecutor for responsive systems
rclcpp::executors::MultiThreadedExecutor exec(rclcpp::ExecutorOptions(), 4); // 4 threads
exec.add_node(my_complex_node);
exec.spin();
4. Common Pitfalls and Gotchas
Thread Safety Analysis (C++): Enable Clang's Thread Safety Analysis (-Wthread-safety) for multithreaded C++ code. Annotate mutex-protected data with RCPPUTILS_TSA_GUARDED_BY and functions with RCPPUTILS_TSA_REQUIRES. Use libcxx for std::mutex annotations.
- ❌ BAD: Unprotected shared data access, leading to data races and deadlocks.
- ✅ GOOD: Compiler-assisted detection of threading issues.
# CMakeLists.txt
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wthread-safety)
# add_compile_options(-Wthread-safety-negative) # For negative capability analysis
endif()
// ✅ GOOD: Thread-safe data access with annotations
#include <rcpputils/thread_safety_annotations.hpp>
#include <mutex>
class MyThreadSafeClass {
public:
void increment() RCPPUTILS_TSA_REQUIRES(mutex_) {
std::lock_guard<std::mutex> lock(mutex_);
data_ RCPPUTILS_TSA_GUARDED_BY(mutex_)++;
}
private:
mutable std::mutex mutex_;
int data_ RCPPUTILS_TSA_GUARDED_BY(mutex_) = 0;
};
5. Testing Approaches
Unit Tests: Every package must include unit tests. Use gtest for C++ and pytest for Python. Ensure high code coverage.
- Integrate tests into your
CMakeLists.txt using ament_add_gtest or ament_add_pytest.
- ❌ BAD: Untested code, relying solely on integration tests.
- ✅ GOOD: Small, focused unit tests for individual components.
# CMakeLists.txt for C++ gtest
if(BUILD_TESTING)
find_package(ament_cmake_gtest REQUIRED)
ament_add_gtest(my_cpp_test test/test_my_module.cpp)
target_link_libraries(my_cpp_test PRIVATE my_package_library)
endif()
Continuous Integration (CI): Implement CI pipelines (GitHub Actions, Azure Pipelines) to automatically build, lint, and test your code on every push/pull request. This is crucial for maintaining code quality and catching regressions.
- Ensure CI runs
colcon test --packages-select <your_package> --event-handlers console_direct+.
Documentation: Generate documentation (Doxygen for C++, Sphinx for Python) and keep it up-to-date. Clear documentation is vital for maintainability and onboarding.
- Integrate documentation generation into your build or CI process.
This comprehensive guide ensures your ROS 2 projects are built on a solid foundation of quality, performance, and maintainability. Adhere to these principles to contribute to a robust and reliable robotics ecosystem.
1---2name: ros3description: "[Applies to: **/*] Definitive guidelines for writing high-quality, performant, and maintainable ROS 2 code, leveraging modern C++ and Python best practices, `ament` tooling, and efficient architectural patterns like node composition and multithreaded executors."4source: "cursor_mdc"5---67# ros Best Practices89This guide outlines the essential practices for developing robust, performant, and maintainable ROS 2 applications. Adherence to these rules ensures consistency, leverages ROS 2's advanced features, and aligns with the project's quality standards.1011## 1. Code Quality and Static Analysis (The `ament` Way)1213* **Mandate `ament_lint_auto`**: Always integrate `ament_lint_auto` and `ament_lint_common` into your package build process. This is non-negotiable for catching style violations and common bugs early.14 * ❌ BAD: Relying on manual checks or pre-commit hooks that can be bypassed.15 * ✅ GOOD: Automatic enforcement during every build.1617 ```cmake18 # CMakeLists.txt19 # ...20 if(BUILD_TESTING)21 find_package(ament_lint_auto REQUIRED)22 ament_lint_auto_find_test_dependencies()23 endif()24 # ...25 ```2627 ```xml28 <!-- package.xml -->29 <package format="2">30 <!-- ... -->31 <test_depend>ament_lint_auto</test_depend>32 <test_depend>ament_lint_common</test_depend>33 <!-- ... -->34 </package>35 ```3637* **C++ Style Enforcement**: Use `ament_cpplint` and `ament_cppcheck`. Follow Google C++ Style Guide and modern C++17/20 features. Prioritize RAII and explicit smart pointer ownership.38 * ❌ BAD: Raw pointers for resource management; ignoring `cpplint` warnings.39 * ✅ GOOD: `std::unique_ptr`, `std::shared_ptr` for ownership.4041 ```cpp42 // ❌ BAD: Manual memory management, potential leak43 MyObject* obj = new MyObject();44 // ...45 delete obj; // Easy to forget or double-delete4647 // ✅ GOOD: RAII with smart pointers48 std::unique_ptr<MyObject> obj = std::make_unique<MyObject>();49 // Resource automatically managed50 ```5152* **Python Style Enforcement**: Adhere strictly to REP-8, which extends PEP 8. Use `black` for formatting and `flake8` for linting. Ensure proper naming, imports, and docstrings.53 * ❌ BAD: Inconsistent formatting, missing docstrings, `camelCase` for variables.54 * ✅ GOOD: `snake_case` for variables/functions, clear docstrings, `black`-formatted code.5556 ```python57 # ❌ BAD58 def calculateVelocity(pos1, pos2, time):59 # ...60 return vel6162 # ✅ GOOD63 def calculate_velocity(position1: float, position2: float, time_delta: float) -> float:64 """Calculates average velocity."""65 # ...66 return velocity67 ```6869* **Copyright and Licensing**: Use `ament_copyright` to ensure all source files have correct copyright and license headers.70 * Run `ament_copyright --add-missing "Your Name" apache2` to automate this.7172## 2. Code Organization and Structure7374* **Python Package Layout (Catkin)**: Adopt the recommended `src/<package_name>/__init__.py` structure. This ensures proper Python package discovery and avoids namespace collisions.75 * ❌ BAD: Scattering Python files directly in the package root or relying on `roslib.load_manifest`.76 * ✅ GOOD: Centralized, importable Python code.7778 ```79 my_package/80 ├── CMakeLists.txt81 ├── package.xml82 ├── setup.py83 └── src/84 └── my_package/85 ├── __init__.py86 └── my_module.py87 ```8889 ```python90 # setup.py for the above structure91 from distutils.core import setup92 from catkin_pkg.python_setup import generate_distutils_setup9394 d = generate_distutils_setup(95 packages=['my_package'],96 package_dir={'': 'src'},97 )98 setup(**d)99 ```100101* **Node/Script Installation**: Use `catkin_install_python` for ROS nodes and scripts. Install to `${CATKIN_PACKAGE_BIN_DESTINATION}` to make them accessible via `rosrun` without polluting the global `PATH`.102 * ❌ BAD: Installing scripts directly to `/usr/bin` via `setup.py` `scripts` argument.103 * ✅ GOOD: Package-isolated executables.104105 ```cmake106 # CMakeLists.txt107 # ...108 catkin_install_python(PROGRAMS109 nodes/my_node.py110 DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}111 )112 # ...113 ```114115## 3. Performance Considerations116117* **Embrace Node Composition**: Always use node composition for performance-critical applications, especially those with large messages (images, point clouds) or resource-constrained environments. This enables intra-process communication (IPC) for zero-copy data transfer.118 * ❌ BAD: Running every node in its own process, leading to high inter-process communication overhead.119 * ✅ GOOD: Grouping related nodes into a single composed process.120121 ```cpp122 // Example: Composing nodes in C++ (simplified)123 #include "rclcpp/rclcpp.hpp"124 #include "my_package/my_camera_node.hpp" // Component125 #include "my_package/my_processor_node.hpp" // Component126127 int main(int argc, char * argv[]) {128 rclcpp::init(argc, argv);129 rclcpp::executors::SingleThreadedExecutor exec; // Or MultiThreadedExecutor130131 auto camera_node = std::make_shared<my_package::MyCameraNode>();132 auto processor_node = std::make_shared<my_package::MyProcessorNode>();133134 exec.add_node(camera_node);135 exec.add_node(processor_node); // IPC if topics match136137 exec.spin();138 rclcpp::shutdown();139 return 0;140 }141 ```142143* **Choose the Right Executor**: For complex applications with multiple nodes or long-running callbacks, use `rclcpp::executors::MultiThreadedExecutor`. For simple, single-purpose nodes, `SingleThreadedExecutor` is sufficient.144 * ❌ BAD: Using `SingleThreadedExecutor` for a node with many subscriptions or services that could block.145 * ✅ GOOD: `MultiThreadedExecutor` for concurrent callback processing.146147 ```cpp148 // ✅ GOOD: MultiThreadedExecutor for responsive systems149 rclcpp::executors::MultiThreadedExecutor exec(rclcpp::ExecutorOptions(), 4); // 4 threads150 exec.add_node(my_complex_node);151 exec.spin();152 ```153154## 4. Common Pitfalls and Gotchas155156* **Thread Safety Analysis (C++)**: Enable Clang's Thread Safety Analysis (`-Wthread-safety`) for multithreaded C++ code. Annotate mutex-protected data with `RCPPUTILS_TSA_GUARDED_BY` and functions with `RCPPUTILS_TSA_REQUIRES`. Use `libcxx` for `std::mutex` annotations.157 * ❌ BAD: Unprotected shared data access, leading to data races and deadlocks.158 * ✅ GOOD: Compiler-assisted detection of threading issues.159160 ```cmake161 # CMakeLists.txt162 if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")163 add_compile_options(-Wthread-safety)164 # add_compile_options(-Wthread-safety-negative) # For negative capability analysis165 endif()166 ```167168 ```cpp169 // ✅ GOOD: Thread-safe data access with annotations170 #include <rcpputils/thread_safety_annotations.hpp>171 #include <mutex>172173 class MyThreadSafeClass {174 public:175 void increment() RCPPUTILS_TSA_REQUIRES(mutex_) {176 std::lock_guard<std::mutex> lock(mutex_);177 data_ RCPPUTILS_TSA_GUARDED_BY(mutex_)++;178 }179 private:180 mutable std::mutex mutex_;181 int data_ RCPPUTILS_TSA_GUARDED_BY(mutex_) = 0;182 };183 ```184185## 5. Testing Approaches186187* **Unit Tests**: Every package must include unit tests. Use `gtest` for C++ and `pytest` for Python. Ensure high code coverage.188 * Integrate tests into your `CMakeLists.txt` using `ament_add_gtest` or `ament_add_pytest`.189 * ❌ BAD: Untested code, relying solely on integration tests.190 * ✅ GOOD: Small, focused unit tests for individual components.191192 ```cmake193 # CMakeLists.txt for C++ gtest194 if(BUILD_TESTING)195 find_package(ament_cmake_gtest REQUIRED)196 ament_add_gtest(my_cpp_test test/test_my_module.cpp)197 target_link_libraries(my_cpp_test PRIVATE my_package_library)198 endif()199 ```200201* **Continuous Integration (CI)**: Implement CI pipelines (GitHub Actions, Azure Pipelines) to automatically build, lint, and test your code on every push/pull request. This is crucial for maintaining code quality and catching regressions.202 * Ensure CI runs `colcon test --packages-select <your_package> --event-handlers console_direct+`.203204* **Documentation**: Generate documentation (Doxygen for C++, Sphinx for Python) and keep it up-to-date. Clear documentation is vital for maintainability and onboarding.205 * Integrate documentation generation into your build or CI process.206207This comprehensive guide ensures your ROS 2 projects are built on a solid foundation of quality, performance, and maintainability. Adhere to these principles to contribute to a robust and reliable robotics ecosystem.