sdi_toolBox
Coding rules

Naming Conventions

  • Classes/Structs: PascalCase (MotorController, SensorData)
  • Functions/Methods: camelCase (readSensor, calculateSpeed)
  • Variables: camelCase (sensorValue, maxSpeed)
  • Constants: UPPER_SNAKE_CASE (MAX_BUFFER_SIZE, DEFAULT_TIMEOUT)
  • Macros: UPPER_SNAKE_CASE (ENABLE_DEBUG, PIN_LED)
  • Namespaces: lowercase (sensors, communication)
  • Private members: prefix with m_ (m_temperature, m_isActive)
  • Pointers: prefix with p or suffix with Ptr (pBuffer, dataPtr)
  • Enums: PascalCase for type, UPPER_SNAKE_CASE for values
    enum class State { IDLE, RUNNING, ERROR };

File Organization

  • Headers: *.h* extension
  • Source: *.cpp* or *.c* extension
  • One class per file (except nested/helper classes)
  • File naming: match class name (MotorController.h, MotorController.cpp)
  • Header guards: use #pragma once

Header Files

  • Include order:
    • Corresponding header (in .cpp)
    • Project headers #include "file.h"
    • System headers #include <file.h>
  • Forward declarations: use when possible to reduce dependencies
  • Minimize includes: only include what you use
  • Separate interface from implementation

Code Formatting

  • Indentation: 2 spaces (no tabs)
  • Braces: Allman style (be consistent)
    // Allman
    if (condition)
    {
    doSomething();
    }
  • Line length: 80-120 characters maximum
  • Spaces: around operators (a + b, not a+b)
  • Pointer/reference: attach to variable (int *ptr, not int* ptr)

Memory Management

PC

  • Prefer RAII: Resource Acquisition Is Initialization
  • Smart pointers: use std::unique_ptr, std::shared_ptr or std::weak_ptr
  • Avoid raw new/delete: use smart pointers or containers
  • No memory leaks: every allocation must have deallocation

Microcontroller

  • Minimize dynamic allocation: prefer stack or static allocation
  • No new/delete in ISRs: Interrupt Service Routines
  • Pre-allocate buffers: avoid runtime allocation
  • Static memory pools: if dynamic allocation needed
  • Check heap fragmentation: monitor free heap regularly

Functions

  • Single Responsibility: one function, one task
  • Function length: maximum 50 lines (20-30 preferred)
  • Parameters: maximum 4-5 parameters (use structs if more)
  • Pass by reference: for non-primitive types (const Type&)
  • Return values: prefer return over out-parameters
  • Const correctness: mark const wherever applicable

Classes

  • Rule of Five: define or delete copy/move constructors and assignment
  • Virtual destructors: for base classes with virtual methods
  • Explicit constructors: use explicit for single-argument constructors
    class MyClass
    {
    MyClass() = default; // Default constructor
    virtual ~MyClass() = default; // Default destructor
    MyClass(const MyClass &other) = delete; // Copy constructor
    MyClass(MyClass &&other) noexcept = delete; // Move constructor
    MyClass &operator=(const MyClass &other) = delete; // Copy assignment
    MyClass &operator=(MyClass &&other) noexcept = delete; // Move assignment
    explicit MyClass(int val); // Constructor
    }

Modern C++ Features (PC)

  • Use auto: when type is obvious
  • Range-based loops: for (const auto& item : container)
  • nullptr: instead of NULL or 0
  • constexpr: for compile-time constants
  • Lambda functions: for short callbacks
  • Move semantics: implement when appropriate
  • std::array: instead of C-style arrays
  • enum class: instead of plain enums
  • Use C++ structured bingings
    std::map<std::string, int> ages = {{"Alice", 30}, {"Bob", 25}};
    // Before C++17
    for (const auto& pair : ages) {
    Serial.println(pair.first); // key
    Serial.println(pair.second); // value
    }
    // C++17: structured bindings
    for (const auto& [name, age] : ages) {
    Serial.println(name);
    Serial.println(age);
    }
    // With arrays
    int arr[][2] = {{1, 2}, {3, 4}, {5, 6}};
    for (auto& [a, b] : arr) {
    cout << a << " " << b << endl;
    }
    // With tuples
    std::vector<std::tuple<int, float, std::string>> data;
    for (const auto& [id, value, name] : data) {
    // Use id, value, name directly
    }
  • User C++ Attributes
    // [[nodiscard]] - Warn if return value ignored (C++17)
    [[nodiscard]] int calculateSum(int a, int b);
    calculateSum(3, 4); // Warning: result unused
    // [[maybe_unused]] - Suppress unused warnings
    void debug([[maybe_unused]] int value);
    // [[fallthrough]] - Intentional switch fallthrough (C++17)
    switch (value) {
    case 1:
    doSomething();
    [[fallthrough]];
    case 2:
    doSomethingElse();
    break;
    }
    // [[deprecated]] - Mark as deprecated
    [[deprecated("Use newFunction() instead")]]
    void oldFunction();
    // [[likely]] / [[unlikely]] - Branch prediction hints (C++20)
    if (condition) [[likely]] {
    // Most common path
    } else {
    // Rare path
    }

Embedded / Microcontroller Specific

  • Avoid exceptions: often disabled on MCUs (use error codes)
  • Avoid RTTI: disable Runtime Type Information (-fno-rtti)
  • Minimize STL: use selectively (some implementations are heavy)
  • Volatile keyword: for hardware registers and ISR-shared variables
  • ISR constraints:
    • Keep ISRs short (< 10 microseconds if possible)
    • No blocking operations
    • Avoid dynamic memory allocation
    • Minimize shared variable access
  • Optimization pragmas: use carefully
  • Inline functions: for time-critical code
  • Bit manipulation: use for register operations
    PORTB |= (1 << PB5); // Set bit
    PORTB &= ~(1 << PB5); // Clear bit

Constants and Macros

  • Prefer const/constexpr over define
  • Macros: use UPPERCASE and parentheses
    #define MAX(a, b) ((a) > (b) ? (a) : (b))
  • Configuration constants: group in header
    namespace Config
    {
    constexpr uint32_t BAUD_RATE = 115200;
    constexpr uint8_t BUFFER_SIZE = 64;
    }

Error Handling

PC

  • Exceptions: for exceptional conditions
  • Error codes: for expected failures
  • RAII: for resource cleanup

Microcontroller

  • Return codes: primary error handling method
  • Error enums: define clear error types
    enum class ErrorCode { OK, TIMEOUT, INVALID_PARAM, HW_ERROR };
  • Assert macros: for development/debugging
  • Watchdog timer: implement for fault recovery

Comments and Documentation

  • Doxygen format: for API documentation
  • Self-documenting code: prefer clear names over comments
  • TODO/FIXME: mark incomplete or problematic code
  • Explain "why": not "what" (code shows what)
  • Update comments: keep in sync with code

Code Quality

  • No magic numbers: use named constants
    constexpr int MAX_RETRIES = 3;
    if (retries > MAX_RETRIES)
    {
    /* ... */
    }
  • Avoid global variables: use singletons or dependency injection
  • Initialize variables: always initialize at declaration
  • Avoid deep nesting: maximum 3-4 levels
  • Guard clauses: early return for error conditions
    if (!isValid)
    return ERROR_INVALID;
    // main logic here

Concurrency (where applicable)

  • Atomic operations: for shared variables
  • Mutexes: protect critical sections (PC)
  • Disable interrupts: for critical sections (MCU)
  • Volatile: for interrupt-shared variables
  • Lock-free algorithms: when possible on MCU

Performance

  • Avoid floating-point: on MCUs without FPU
  • Fixed-point arithmetic: for fractional values on MCU
  • Lookup tables: for complex calculations
  • Inline critical functions: use inline keyword
  • Profile before optimizing: measure, don't guess
  • Compiler optimization flags: -O2, -O3 (test thoroughly)

Testing

  • Unit tests: for all public interfaces
  • Mock hardware: for embedded testing
  • Test on target: always test on actual hardware
  • Boundary conditions: test edge cases
  • Code coverage: aim for >80%

Version Control

  • Meaningful commits: descriptive commit messages
  • Feature branches: for new features
  • Code review: before merging
  • Avoid generated files: in repository (.gitignore)

Build System

  • CMake: as soon as possible
  • Compiler warnings: enable all (-Wall -Wextra -Wpedantic)
  • Treat warnings as errors: -Werror
  • Debug/Release configs: separate configurations

Lexicon

RTTI (Run-time type information): run-time type information is a feature that exposes information about an object's data type at runtime. RTTI can be used to do safe typecasts using the dynamic_cast<> operator, and to manipulate type information at runtime using the typeid operator and std::type_info class.

RAII (Resource acquisition is initialization): Resource acquisition is initialization is a programming idiom used in several object-oriented, statically typed programming languages to describe a particular language behavior. Resource acquisition (allocation) is done during object creation by the constructor, while resource deallocation (release) is done during object destruction by the destructor. In other words, resource acquisition must succeed for initialization to succeed. The resource is guaranteed to be held between the end of initialization and the starts of finalization, and only as long as the object is active. Thus, if there are no object leaks, there are no resource leaks.