Error Handling#
Effective error handling is critical for building robust and maintainable software. This section outlines the best practices for handling errors in this project, ensuring that all modules use a consistent approach. The general principles of error handling should be applied throughout the codebase, focusing on clear communication of errors and minimizing crashes.
General Guidelines for Error Handling#
Return Error Codes: Functions should return error codes whenever an error occurs. Use negative values for failure and zero for success. Avoid returning -1 or 1 generically—use specific error codes to indicate the nature of the failure.
Use `errno.h` Error Codes: Where appropriate, use standard POSIX error codes from errno.h (e.g., EINVAL, ENOMEM, EIO). These standardized codes help communicate common error conditions clearly.
Example:
#include <errno.h> int process_data(void *data) { if (!data) { return -EINVAL; /* Invalid argument */ } /* Process the data */ return 0; }
Handle Errors at the Point of Failure: Always handle errors as close as possible to where they occur. If a function returns an error code, the caller should check that value and handle the error appropriately.
Example:
int ret = process_data(data); if (ret < 0) { /* Handle error, log message or clean up */ return ret; }
Never Ignore Return Values: Always check the return value of a function, particularly when dealing with I/O, memory allocation, or any function that could fail.
Bad Example:
malloc(100); /* No check if memory allocation succeeded */
Good Example:
void *ptr = malloc(100); if (!ptr) { /* Handle memory allocation failure */ return -ENOMEM; }
Use `NULL` for Pointer Return Failures: When a function returns a pointer, return NULL to indicate failure. Ensure the caller checks for NULL before dereferencing the pointer.
Example:
void *allocate_buffer(size_t size) { void *buffer = malloc(size); if (!buffer) { return NULL; /* Return NULL on allocation failure */ } return buffer; }
Error Propagation: If a function cannot resolve an error condition, propagate the error code back to the caller rather than masking it.
Clean Up on Failure: Ensure that any allocated resources (e.g., memory, file handles, network sockets) are cleaned up appropriately when an error occurs. Use a consistent strategy, such as goto for clean-up sections at the end of functions, to handle error exits efficiently.
Example:
int do_something(void) { int *ptr = malloc(sizeof(int)); if (!ptr) { return -ENOMEM; } FILE *file = fopen("data.txt", "r"); if (!file) { free(ptr); return -EIO; } /* Do some work */ fclose(file); free(ptr); return 0; }
Using goto for Clean-up#
In C, the goto statement can be useful for simplifying error handling when dealing with resource cleanup. Use goto to jump to a clean-up section at the end of the function where all allocated resources are safely released.
Example:
int read_file(const char *path) { FILE *file = NULL; char *buffer = NULL; file = fopen(path, "r"); if (!file) { return -EIO; } buffer = malloc(1024); if (!buffer) { fclose(file); return -ENOMEM; } /* Do work with file and buffer */ cleanup: if (file) { fclose(file); } if (buffer) { free(buffer); } return 0; }
ESP32-Specific Error Handling#
When working with ESP32 and ESP-IDF, you should use ESP32’s built-in macros and functions for error handling:
ESP_ERROR_CHECK(): Use ESP_ERROR_CHECK() to check return values from ESP-IDF functions that return esp_err_t. This macro will terminate the program if an error occurs and log the error message.
Example:
esp_err_t ret = esp_wifi_init(&cfg); ESP_ERROR_CHECK(ret);
ESP_ERROR_CHECK_WITHOUT_ABORT(): This macro works like ESP_ERROR_CHECK() but logs the error without terminating the program. Use this when you want to handle the error gracefully without stopping the system.
Example:
esp_err_t ret = esp_wifi_init(&cfg); if (ESP_ERROR_CHECK_WITHOUT_ABORT(ret) != ESP_OK) { /* Handle the error */ }
Error Return Codes in ESP-IDF: The ESP-IDF uses its own set of return codes, usually starting with ESP_ERR_. Always return these codes directly when writing ESP-IDF functions, and check them using the macros above.
Best Practices#
Consistent Error Handling: Always return specific error codes and handle them consistently in your code.
Use Standard Error Codes: Where possible, use standard POSIX error codes from errno.h to avoid ambiguity.
Never Ignore Return Values: Always check the return values of functions, especially those that can fail (e.g., malloc, fopen).
Clear and Actionable Error Messages: Log clear and actionable error messages to help diagnose issues quickly.
Clean Up on Failure: Always free allocated memory or close file handles when an error occurs.
General Guidelines#
Return Error Codes: Functions should return error codes where applicable.
Use `NULL` for Pointer Failures: Return NULL for pointer failures to indicate allocation issues.
ESP32 Macros: Use ESP_ERROR_CHECK() for ESP-IDF error handling to log errors and terminate or handle gracefully.
Resource Cleanup: Use goto for clean-up at the end of functions when handling errors.