10 Coding styles for a cleaner C code
In this post we are going to see multiple conventions that enhances code readability, making it easier for both the original author and other team members to understand and maintain the codebase.
Simple coding style plays a crucial role in the development of efficient and maintainable software. By adhering to a simple coding style, developers ensure that their code is clear, readable, and easy to understand. This simplicity enhances collaboration within the development team, as well as aids in code reviews and debugging processes. A simple coding style also reduces the likelihood of introducing bugs and makes it easier to identify and fix issues when they arise. Additionally, simplicity in coding leads to more maintainable code, as it is easier to modify, refactor, and extend in the future. This is key for code quality, team productivity, and contributes to the long-term success of software projects.
Over the years I've tried multiple coding style in different projects. Some of them looked appealing at the beginning, but after working for some time using them, I realized that they were not adding clarity to the code. So after some trial and error, I ended up with the following list of conventions for my C coding style:
typedef
Compare this:
// Clearer
typedef struct User {
int number;
} User;
void test1(User* user) {
User* tmp = user;
}
void test2(User* user) {
User* tmp = user;
}
to this:
// More cumbersome
struct User {
int number;
};
void test1(struct User* user) {
struct User* tmp = user;
}
void test2(struct User* user) {
struct User* tmp = user;
}
Braces
Opening brace
Compare this:
int main(int argc, char* argv[])
{
if (argc == 1)
{
for (int i=0; i<5; ++i)
{
print("%d\n", i);
}
}
else
{
for (int i=0; i<10; ++i)
{
print("%d\n", i);
}
}
}
to this:
int main(int argc, char* argv[]) {
if (argc == 1) {
for (int i=0; i<5; ++i) {
print("%d\n", i);
}
} else {
for (int i=0; i<10; ++i) {
print("%d\n", i);
}
}
}
Always braces
There are some coding styles that advice you to not use braces for the cases of conditions and loops containing one line only. Although it might look cleaner, there is a problem with this approach. If you refactor your code, and by mistake you indent a line after the condition together with the condition line, it is difficult to see exactly what statements are going to be executed if the condition is met:
void function() {
int x = 1;
if (x == 2)
x++
printf("%d\n", x);
}
If you change the identation of the printf statement, it is difficult to see whether its execution is included or not in the condition:
void function() {
int x = 1;
if (x == 2)
x++
printf("%d\n", x);
}
However, if we use braces instead, there is no doubt about what statements are included in the execution of the condition:
void function() {
int x = 1;
if (x == 2) {
x++
}
printf("%d\n", x);
}
Functions
Snake case is my preferred naming convention for function names as it enhances readability and ensures clear separation between words, allowing for easier understanding and improved code comprehension.
void filter_user() {
}
void print_email() {
}
Types
For the case of types I prefer Pascal case as it promotes clarity and consistency by capitalizing the first letter of each word, resulting in more descriptive and distinguishable type declarations.
typedef struct TreeNode TreeNode;
typedef struct HashMap HashMap;
Static
User* new_User() {
}
void delete_User() {
}
static
void User_filter_name(User* u) {
}
Methods
Object reference argument
Type prefix
User* new_User() {
}
void delete_User() {
}
static
void User_filter_name(User* u) {
}
Free functions
static
int find_split(int x) {
}
Function Types
typedef void (*ListPrintFn)(void* item);
void List_print(List* l, ListPrintFn print);
Return multiple values
- Multiple inputs: arguments
- One output: returned value
typedef struct Value {
void* data;
size_t size;
} Value;
Value Container_get(Container* c, int index);
Small names
Compare this:
int calcAverageOfArray(Array* a);
to this:
int Array_avg(Array* a);