Best Practices

Comprehensive guide to best practices for developing robust, maintainable, and efficient Nexus applications.

Overview

Following best practices ensures code quality, reliability, and maintainability. This guide covers coding standards, design patterns, and development workflows for Nexus.

Key Principles:

  • Write clean, readable code

  • Design for testability

  • Handle errors gracefully

  • Document thoroughly

  • Optimize wisely

  • Test comprehensively

Code Organization

Project Structure

Recommended Structure:

my_project/
├── src/                    # Source files
│   ├── main.c
│   ├── app/                # Application logic
│   │   ├── app_init.c
│   │   ├── app_task.c
│   │   └── app_config.c
│   ├── drivers/            # Custom drivers
│   │   └── sensor_driver.c
│   └── utils/              # Utility functions
│       └── helpers.c
├── include/                # Header files
│   ├── app/
│   ├── drivers/
│   └── utils/
├── tests/                  # Unit tests
│   ├── test_app.cpp
│   └── test_drivers.cpp
├── docs/                   # Documentation
├── CMakeLists.txt
├── .config                 # Kconfig configuration
└── README.md

Module Organization:

/* sensor_driver.h - Public interface */
#ifndef SENSOR_DRIVER_H
#define SENSOR_DRIVER_H

/**
 * \file            sensor_driver.h
 * \brief           Temperature sensor driver interface
 * \author          Nexus Team
 */

#include <stdint.h>

/**
 * \brief           Initialize sensor
 * \return          0 on success, negative on error
 */
int sensor_init(void);

/**
 * \brief           Read temperature
 * \param[out]      temp: Temperature in degrees Celsius
 * \return          0 on success, negative on error
 */
int sensor_read_temperature(float* temp);

#endif /* SENSOR_DRIVER_H */
/* sensor_driver.c - Implementation */
/**
 * \file            sensor_driver.c
 * \brief           Temperature sensor driver implementation
 * \author          Nexus Team
 * \version         1.0.0
 * \date            2026-01-25
 */

#include "sensor_driver.h"
#include "hal/nx_i2c.h"
#include "log/log.h"

#define LOG_MODULE "sensor"

/*---------------------------------------------------------------------------*/
/* Private Definitions                                                       */
/*---------------------------------------------------------------------------*/

#define SENSOR_I2C_ADDR     0x48
#define SENSOR_TEMP_REG     0x00

/*---------------------------------------------------------------------------*/
/* Private Variables                                                         */
/*---------------------------------------------------------------------------*/

static nx_i2c_t* i2c = NULL;

/*---------------------------------------------------------------------------*/
/* Private Functions                                                         */
/*---------------------------------------------------------------------------*/

/**
 * \brief           Read sensor register
 */
static int read_register(uint8_t reg, uint8_t* value)
{
    /* Implementation */
    return 0;
}

/*---------------------------------------------------------------------------*/
/* Public Functions                                                          */
/*---------------------------------------------------------------------------*/

/**
 * \brief           Initialize sensor
 * \details         Configures I2C and verifies sensor presence
 */
int sensor_init(void)
{
    /* Implementation */
    return 0;
}

Naming Conventions

Follow Consistent Naming:

/* Functions: snake_case */
void initialize_system(void);
int read_sensor_data(uint8_t* buffer, size_t len);

/* Variables: snake_case */
int sensor_count = 0;
float temperature_celsius = 0.0f;

/* Constants: UPPER_SNAKE_CASE */
#define MAX_BUFFER_SIZE 256
#define DEFAULT_TIMEOUT 1000

/* Types: snake_case_t */
typedef struct {
    uint32_t id;
    char name[32];
} device_info_t;

/* Enums: UPPER_SNAKE_CASE */
typedef enum {
    STATE_IDLE = 0,
    STATE_ACTIVE,
    STATE_ERROR,
} system_state_t;

/* Private functions: static */
static void internal_helper(void);

/* Module prefix for public API */
int sensor_init(void);
int sensor_read(void);

Error Handling

Return Status Codes

Always Return Status:

/* Good: Return status code */
int initialize_device(device_t* device)
{
    if (!device) {
        return -EINVAL;  /* Invalid parameter */
    }

    if (!device_is_present()) {
        return -ENODEV;  /* Device not found */
    }

    if (device_init_hardware() != 0) {
        return -EIO;  /* I/O error */
    }

    return 0;  /* Success */
}

/* Usage */
int result = initialize_device(&my_device);
if (result != 0) {
    LOG_ERROR("Device initialization failed: %d", result);
    return result;
}

Check All Return Values

Never Ignore Errors:

/* Bad: Ignoring return value */
void bad_example(void)
{
    osal_mutex_lock(mutex, OSAL_WAIT_FOREVER);
    /* What if lock failed? */
}

/* Good: Check return value */
void good_example(void)
{
    osal_status_t status = osal_mutex_lock(mutex, OSAL_WAIT_FOREVER);
    if (status != OSAL_OK) {
        LOG_ERROR("Failed to acquire mutex: %d", status);
        return;
    }

    /* Critical section */

    osal_mutex_unlock(mutex);
}

Use Assertions

Validate Preconditions:

#include "hal/nx_assert.h"

void process_buffer(const uint8_t* buffer, size_t len)
{
    /* Assert preconditions */
    NX_ASSERT(buffer != NULL);
    NX_ASSERT(len > 0);
    NX_ASSERT(len <= MAX_BUFFER_SIZE);

    /* Process buffer */
    for (size_t i = 0; i < len; i++) {
        process_byte(buffer[i]);
    }
}

Graceful Degradation

Handle Failures Gracefully:

void sensor_task(void* arg)
{
    int retry_count = 0;
    const int MAX_RETRIES = 3;

    while (1) {
        float temperature;
        int result = sensor_read_temperature(&temperature);

        if (result == 0) {
            /* Success */
            LOG_INFO("Temperature: %.1f°C", temperature);
            retry_count = 0;
        } else {
            /* Failure */
            LOG_WARN("Sensor read failed: %d", result);
            retry_count++;

            if (retry_count >= MAX_RETRIES) {
                LOG_ERROR("Sensor failed after %d retries", MAX_RETRIES);
                /* Enter safe mode or use default value */
                temperature = DEFAULT_TEMPERATURE;
                retry_count = 0;
            }
        }

        osal_task_delay(1000);
    }
}

Resource Management

RAII Pattern

Acquire Resources in Initialization:

/* Good: RAII-style resource management */
int process_data_file(const char* filename)
{
    int result = -1;
    file_t* file = NULL;
    uint8_t* buffer = NULL;

    /* Acquire resources */
    file = file_open(filename, FILE_MODE_READ);
    if (!file) {
        LOG_ERROR("Failed to open file: %s", filename);
        goto cleanup;
    }

    buffer = osal_malloc(BUFFER_SIZE);
    if (!buffer) {
        LOG_ERROR("Failed to allocate buffer");
        goto cleanup;
    }

    /* Use resources */
    size_t bytes_read = file_read(file, buffer, BUFFER_SIZE);
    process_buffer(buffer, bytes_read);

    result = 0;  /* Success */

cleanup:
    /* Release resources in reverse order */
    if (buffer) {
        osal_free(buffer);
    }
    if (file) {
        file_close(file);
    }

    return result;
}

Avoid Resource Leaks

Always Release Resources:

void use_gpio(void)
{
    nx_gpio_write_t* led = nx_factory_gpio_write('D', 12);
    if (!led) {
        return;
    }

    /* Use GPIO */
    led->write(led, 1);

    /* Always release */
    nx_factory_gpio_release((nx_gpio_t*)led);
}

void use_uart(void)
{
    nx_uart_t* uart = nx_factory_uart(0);
    if (!uart) {
        return;
    }

    /* Use UART */
    nx_tx_sync_t* tx = uart->get_tx_sync(uart);
    tx->send(tx, data, len, 1000);

    /* Always release */
    nx_factory_uart_release(uart);
}

Concurrency

Protect Shared Resources

Use Mutexes for Shared Data:

/* Shared resource */
static int shared_counter = 0;
static osal_mutex_handle_t counter_mutex;

void init_shared_resource(void)
{
    osal_mutex_create(&counter_mutex);
}

void increment_counter(void)
{
    osal_mutex_lock(counter_mutex, OSAL_WAIT_FOREVER);
    shared_counter++;
    osal_mutex_unlock(counter_mutex);
}

int get_counter(void)
{
    int value;
    osal_mutex_lock(counter_mutex, OSAL_WAIT_FOREVER);
    value = shared_counter;
    osal_mutex_unlock(counter_mutex);
    return value;
}

Minimize Critical Sections

Hold Locks Briefly:

/* Bad: Long critical section */
void process_data_bad(void)
{
    osal_mutex_lock(data_mutex, OSAL_WAIT_FOREVER);

    /* Long operation while holding lock */
    for (int i = 0; i < 1000; i++) {
        process_item(shared_data[i]);
    }

    osal_mutex_unlock(data_mutex);
}

/* Good: Short critical section */
void process_data_good(void)
{
    uint8_t local_copy[1000];

    /* Copy data while holding lock */
    osal_mutex_lock(data_mutex, OSAL_WAIT_FOREVER);
    memcpy(local_copy, shared_data, sizeof(local_copy));
    osal_mutex_unlock(data_mutex);

    /* Process local copy without lock */
    for (int i = 0; i < 1000; i++) {
        process_item(local_copy[i]);
    }
}

Avoid Deadlocks

Acquire Locks in Consistent Order:

/* Define lock order */
static osal_mutex_handle_t mutex_a;
static osal_mutex_handle_t mutex_b;

/* Always acquire in same order: A then B */
void function_1(void)
{
    osal_mutex_lock(mutex_a, OSAL_WAIT_FOREVER);
    osal_mutex_lock(mutex_b, OSAL_WAIT_FOREVER);

    /* Critical section */

    osal_mutex_unlock(mutex_b);
    osal_mutex_unlock(mutex_a);
}

void function_2(void)
{
    /* Same order: A then B */
    osal_mutex_lock(mutex_a, OSAL_WAIT_FOREVER);
    osal_mutex_lock(mutex_b, OSAL_WAIT_FOREVER);

    /* Critical section */

    osal_mutex_unlock(mutex_b);
    osal_mutex_unlock(mutex_a);
}

Memory Management

Prefer Static Allocation

Use Static Allocation When Possible:

/* Good: Static allocation */
#define MAX_DEVICES 10
static device_t devices[MAX_DEVICES];
static size_t device_count = 0;

device_t* allocate_device(void)
{
    if (device_count >= MAX_DEVICES) {
        return NULL;
    }
    return &devices[device_count++];
}

Check Allocation Failures

Always Check malloc/new:

void process_large_data(void)
{
    uint8_t* buffer = osal_malloc(LARGE_SIZE);
    if (!buffer) {
        LOG_ERROR("Failed to allocate %d bytes", LARGE_SIZE);
        /* Handle error - use smaller buffer or fail gracefully */
        return;
    }

    /* Use buffer */
    process_buffer(buffer, LARGE_SIZE);

    /* Free buffer */
    osal_free(buffer);
}

Initialize Variables

Always Initialize:

/* Good: Initialize variables */
void good_example(void)
{
    int count = 0;
    float temperature = 0.0f;
    char buffer[64] = {0};
    device_t* device = NULL;

    /* Use variables */
}

/* Bad: Uninitialized variables */
void bad_example(void)
{
    int count;  /* Undefined value */
    float temperature;  /* Undefined value */
    /* Using these causes undefined behavior */
}

Logging and Debugging

Use Appropriate Log Levels

Choose Correct Level:

#define LOG_MODULE "app"
#include "log/log.h"

void application_function(void)
{
    /* TRACE: Detailed flow information */
    LOG_TRACE("Entering application_function");

    /* DEBUG: Development information */
    LOG_DEBUG("Processing %d items", item_count);

    /* INFO: Normal operation */
    LOG_INFO("Application started successfully");

    /* WARN: Recoverable issues */
    if (retry_count > 0) {
        LOG_WARN("Operation required %d retries", retry_count);
    }

    /* ERROR: Failures */
    if (result != 0) {
        LOG_ERROR("Operation failed with error %d", result);
    }

    /* FATAL: Critical errors */
    if (critical_failure) {
        LOG_FATAL("Critical system failure - halting");
    }
}

Add Context to Logs

Include Relevant Information:

/* Bad: Insufficient context */
LOG_ERROR("Failed");

/* Good: Detailed context */
LOG_ERROR("Failed to initialize sensor %d: error code %d, retry %d/%d",
          sensor_id, error_code, retry_count, MAX_RETRIES);

/* Good: Include state information */
LOG_DEBUG("Processing packet: type=%d, len=%d, seq=%lu, crc=0x%04X",
          packet->type, packet->len, packet->sequence, packet->crc);

Use Module Tags

Organize Logs by Module:

/* sensor_driver.c */
#define LOG_MODULE "sensor"
#include "log/log.h"

void sensor_init(void)
{
    LOG_INFO("Initializing sensor");  /* [sensor] Initializing sensor */
}

/* network.c */
#define LOG_MODULE "network"
#include "log/log.h"

void network_connect(void)
{
    LOG_INFO("Connecting");  /* [network] Connecting */
}

Testing

Write Testable Code

Design for Testability:

/* Bad: Hard to test (direct hardware access) */
void bad_example(void)
{
    GPIOD->ODR |= (1 << 12);  /* Direct register access */
}

/* Good: Testable (uses abstraction) */
void good_example(nx_gpio_write_t* led)
{
    led->write(led, 1);  /* Can mock in tests */
}

/* Test */
TEST(AppTest, GoodExample) {
    MockGPIO mock_led;
    good_example(&mock_led);
    EXPECT_EQ(mock_led.last_value, 1);
}

Test Edge Cases

Test Boundary Conditions:

TEST(BufferTest, EdgeCases) {
    /* Test empty buffer */
    EXPECT_EQ(process_buffer(NULL, 0), -EINVAL);

    /* Test maximum size */
    uint8_t large_buffer[MAX_SIZE];
    EXPECT_EQ(process_buffer(large_buffer, MAX_SIZE), 0);

    /* Test overflow */
    EXPECT_EQ(process_buffer(large_buffer, MAX_SIZE + 1), -EINVAL);

    /* Test minimum size */
    uint8_t small_buffer[1];
    EXPECT_EQ(process_buffer(small_buffer, 1), 0);
}

Test Error Paths

Test Failure Scenarios:

TEST(SensorTest, ErrorHandling) {
    /* Test sensor not present */
    mock_sensor_present(false);
    EXPECT_EQ(sensor_init(), -ENODEV);

    /* Test I2C failure */
    mock_i2c_fail(true);
    EXPECT_EQ(sensor_read(), -EIO);

    /* Test timeout */
    mock_sensor_timeout(true);
    EXPECT_EQ(sensor_read(), -ETIMEDOUT);
}

Documentation

Document Public APIs

Use Doxygen Comments:

/**
 * \brief           Initialize the sensor subsystem
 * \param[in]       config: Configuration structure
 * \return          0 on success, negative error code on failure
 * \note            Must be called before any other sensor functions
 * \warning         Not thread-safe during initialization
 */
int sensor_subsystem_init(const sensor_config_t* config);

Document Complex Logic

Explain Non-Obvious Code:

/**
 * \brief           Calculate CRC-16 using polynomial 0x1021
 * \details         Uses table-driven algorithm for performance.
 *                  The lookup table is generated at compile time.
 */
uint16_t calculate_crc16(const uint8_t* data, size_t len)
{
    uint16_t crc = 0xFFFF;

    for (size_t i = 0; i < len; i++) {
        /* XOR byte into CRC */
        uint8_t index = (crc >> 8) ^ data[i];
        crc = (crc << 8) ^ crc_table[index];
    }

    return crc;
}

Maintain README

Keep README Updated:

# My Project

## Description
Brief description of the project.

## Features
- Feature 1
- Feature 2

## Requirements
- CMake 3.16+
- ARM GCC toolchain
- Python 3.8+

## Building
```bash
cmake -B build
cmake --build build
```

## Configuration
See `.config` for configuration options.

## Testing
```bash
python scripts/test/test.py
```

## License
MIT License

Performance

Profile Before Optimizing

Measure First:

/* Identify bottlenecks before optimizing */
void profile_application(void)
{
    PROFILE_START(data_processing);
    process_data();
    PROFILE_END(data_processing);

    PROFILE_START(network_send);
    send_network_data();
    PROFILE_END(network_send);

    /* Optimize the slowest part first */
}

Optimize Hot Paths

Focus on Frequently Executed Code:

/* Optimize inner loops */
void process_samples(const int16_t* samples, size_t count)
{
    /* This loop runs millions of times - optimize it */
    for (size_t i = 0; i < count; i++) {
        /* Use efficient operations */
        int32_t value = samples[i];
        value = (value * gain) >> 8;  /* Shift instead of divide */
        output[i] = (int16_t)value;
    }
}

Don't Sacrifice Readability

Balance Optimization and Clarity:

/* Bad: Overly optimized, hard to understand */
void bad_example(uint8_t* d, uint8_t* s, size_t n)
{
    while (n--) *d++ = *s++;
}

/* Good: Clear and efficient */
void good_example(uint8_t* dest, const uint8_t* src, size_t len)
{
    memcpy(dest, src, len);  /* Clear intent, compiler optimizes */
}

Code Review Checklist

Before Submitting Code:

  • [ ] Code follows style guidelines

  • [ ] All functions documented

  • [ ] Error handling implemented

  • [ ] Resources properly managed

  • [ ] No memory leaks

  • [ ] Thread-safe where needed

  • [ ] Tests written and passing

  • [ ] No compiler warnings

  • [ ] Code reviewed by peer

  • [ ] Documentation updated

Review Checklist:

  • [ ] Code is readable and maintainable

  • [ ] Logic is correct

  • [ ] Edge cases handled

  • [ ] Error paths tested

  • [ ] Performance acceptable

  • [ ] Security considerations addressed

  • [ ] No code duplication

  • [ ] Follows project conventions

See Also