Skip to main content

ROS Python Development & Style Guide

To maintain a clean, stable, and readable codebase, all contributors must adhere to the following development guidelines.

1. Version Control & Git Workflow

Main Branch is Sacred

The main (or master) branch should always be deployable and compilable.

  • NEVER push experimental code directly to main.
  • NEVER commit broken code to main.

Branching Strategy

Create a new branch for every feature, bug fix, or experiment. Use descriptive prefixes:

  • feature/add-lidar-node (New functionality)
  • fix/camera-latency (Bug repair)
  • experiment/new-pid-tuning (Testing ideas that might not work)

Commit Messages

Commit messages must be descriptive. A commit message should tell us what changed and why, not just that files were modified.

  • Bad: fix, update, working on stuff, typo
  • Good: Fix lidar timestamp synchronization issue, Add ROS parameter for max_speed, Refactor image processing into separate function

2. ROS Parameters vs. Hardcoded Values

Hardcoding values makes code rigid and requires recompilation/edits to change behavior.

If a variable might need to be changed during testing (e.g., speed, PID constants, topic names, timers), it must be a ROS parameter.

Example

❌ BAD (Hardcoded):

class Mover(Node):
def timer_callback(self):
# If we want to change speed, we have to edit the code!
msg.linear.x = 0.5
self.publisher.publish(msg)

✅ GOOD (ROS Parameters):


class Mover(Node):
def __init__(self):
super().__init__('mover')
# Declare the parameter with a default value
self.declare_parameter('target_speed', 0.5)

def timer_callback(self):
# Get the current value (can be changed live via command line or launch file)
speed = self.get_parameter('target_speed').value
msg.linear.x = speed
self.publisher.publish(msg)

3. No "Magic Numbers"

A "Magic Number" is a raw number used in code without context. It confuses readers and makes updating math difficult.

The Rule

Replace magic numbers with named constants (UPPER_CASE) or ROS parameters.

❌ BAD:

# What does 1.57 mean? Why 0.3?
if distance < 0.3:
turn_angle = 1.57

✅ GOOD:


import math

STOP_DISTANCE_M = 0.3
TURN_ANGLE_RAD = math.pi / 2 # 90 degrees

if distance < STOP_DISTANCE_M:
turn_angle = TURN_ANGLE_RAD

4. Modularity & Functions

Do not write "God Functions." A single function (especially a ROS callback) should not handle logic, calculation, logging, AND publishing.

Split code into reusable, logical blocks. A function should do one thing well.

❌ BAD (The "Everything" Callback):


def image_callback(self, msg):
# 50 lines of code converting image
# 20 lines of code detecting red pixels
# 10 lines of code calculating center point
# 5 lines of code publishing
pass

✅ GOOD (Modular):


def image_callback(self, msg):
cv_image = self.convert_ros_to_cv2(msg)
center_point = self.detect_red_object(cv_image)

if center_point:
self.publish_drive_command(center_point)

def convert_ros_to_cv2(self, msg):
# Only handles conversion
return cv_bridge.imgmsg_to_cv2(msg)

def detect_red_object(self, image):
# Only handles detection logic
# This logic can now be unit tested easily!
return (x, y)

5. Python Coding Standards (PEP 8)

We follow standard Python PEP 8 style guides.

Classes: CamelCase (e.g., class CameraNode(Node):)

Functions/Variables: snake_case (e.g., def calculate_speed():, lidar_data = ...)

Constants: UPPER_CASE (e.g., MAX_RPM = 5000)

6. Logging

Don't use Python's print() function. Use the ROS logger. print() output often gets lost in ROS launch logs and doesn't support severity levels.

❌ BAD:

print("Starting the robot...")

✅ GOOD:

self.get_logger().info("Starting the robot...")
self.get_logger().warn("Battery low!")
self.get_logger().error("Sensor connection failed.")