When generating C code, please adhere to the following style guidelines to ensure the code is maintainable, debuggable, and easy to modify:
- Conceptualize programming as a sequence of data transformations, where specific input data is processed step-by-step to produce desired output data. For example, a program that handles HTTP requests can be understood as taking an HTTP request string and current database state to generate an HTTP response string. This perspective prioritizes concrete reasoning over abstract design principles often associated with object-oriented programming (OOP) or 'clean code' paradigms.
- Avoid object-oriented programming and prioritize Plain Old Data (POD) structures and procedural solutions.
- De-prioritize encapsulation as a design goal, focusing instead on simplicity and efficiency.
- Prefer explicit error code returns while avoiding errno and exceptions
- Avoid Deep Nesting: Do not use deeply nested if-statements. For sequential, dependent conditions, use boolean flags to control the flow instead of nesting multiple levels of if blocks.
- No Early Returns: Avoid using return statements early in functions. Ensure the function follows a linear flow from start to finish so that cleanup code or other operations at the end (e.g., memory management) are always executed.
- Use Flags for Sequential Conditions: For conditions that depend on each other, define boolean flags to track whether each step should proceed. Update these flags sequentially and use them to control subsequent blocks of code, keeping the logic linear.
- Use if-else for Mutually Exclusive Conditions: When handling mutually exclusive cases (e.g., different event types), use top-level if-else chains. Within each branch, maintain a linear flow and use flags if further conditions require checking, avoiding deep nesting.
- Explicit Dependencies: Use flags to make dependencies between code sections clear. Declare variables in an order that reflects these dependencies, so rearranging code incorrectly triggers compiler errors (e.g., using an undeclared variable). For independent sections, ensure their guarding conditions have no references to other scopes, allowing free rearrangement.
- Facilitate Debugging and Modifications: Structure the code to flow linearly, making it easy to step through in a debugger and inspect flag states to see which conditions were met. This also allows adding code (e.g., logging or memory management) at the function’s start or end with confidence it will execute.
- Accept Slight Verbosity: This style may increase code length slightly, but the benefits in readability, maintainability, and debuggability outweigh the verbosity.
Examples of Sequential Conditions
Avoid this (nested ifs):
void some_function() {
bool some_condition_a = other_func1();
if (some_condition_a) {
bool some_condition_b = other_func2();
if (some_condition_b) {
bool some_condition_c = other_func3();
if (some_condition_c) {
do_thing();
}
}
}
}
Avoid this (early returns):
void some_function() {
bool some_condition_a = other_func1();
if (!some_condition_a)
return;
bool some_condition_b = other_func2();
if (!some_condition_b)
return;
bool some_condition_c = other_func3();
if (!some_condition_c)
return;
do_thing();
}
Prefer this (flags with linear flow):
void some_function() {
bool proceed_to_b = false;
bool some_condition_a = other_func1();
if (some_condition_a) {
proceed_to_b = other_func2();
}
bool proceed_to_c = false;
if (proceed_to_b) {
proceed_to_c = other_func3();
}
if (proceed_to_c) {
do_thing();
}
}
Alternatively, for simple sequential cases, a single flag can be reused:
void some_function() {
bool proceed = other_func1();
if (proceed) {
proceed = other_func2();
}
if (proceed) {
proceed = other_func3();
}
if (proceed) {
do_thing();
}
}