Every project has its own style guide: a set of conventions about how to write code for that project. Some managers choose basic coding rules, others prefer very advanced ones and for many projects, no coding rules are specified, and each developer uses his style.
It is much easier to understand a large codebase when all the code in it is in a consistent style.
Many resources exist talking about the better coding rules to adopt, we can learn good coding rules from :
- Reading a book or a magazine.
- Websites.
- From a colleague.
- Doing a training.
We can also work with an expert for few months to elevate the coding skills of the team. However, it’s not easy to find the right person and it could cost a lot of money to the company. But why searching for an expert, if we can be inspired by genius developers like Linus Torvalds. Indeed, you have just to explore the source code developed or maintained by him to have a good idea of how we must develop an efficient C code.
Linus Torvalds is genius because it’s the creator, and for a long time, principal developer of the Linux kernel and he also created the most popular distributed revision control system Git. That’s it, no need to search for other arguments 🙂
Inside the git source code
Let’s take a look at a code snippet from git:
Here are some remarks about this code:
- The functions are declared as static.
- The functions return an error code.
- The function has few parameters.
- The function exit as early as possible.
- The variables are declared as static.
- The variable naming is easy to understand.
- The methods are very short.
- It’s well indented.
- No extra comments in the body. The code explains easily itself.
- The function bodies are well indented.
- The define guards are clear.
If we navigate across all the git source code we can remark the coherence of the implementation. The same best practice rules are applied to each function. To be sure let’s search for the static functions:
from m in Methods where m.IsStatic select m
The treemap is very useful to have a good idea of code elements concerned by a CQLinq query, the blue rectangles represent the result.
Almost all functions are declared as static to be visible only in the translation unit where there are declared.
Inside the Linux kernel source code
Let’s switch to the Linux source code and take as an example this function implementation:
The code looks very clean, indeed the function
- Has only a few lines of code.
- The signature is well defined.
- It’s well commented.
- It’s well indented.
- The variable names are very clear.
- the const correctness is satisfied.
- Check the entry parameters and warn if they not satisfy some conditions.
The same function could be implemented by another developer like this
The coding style has a big impact on the source code readability, investing some hours to train developers, and doing periodically a code review is always good to make the code easy to maintain and evolve.
Let’s go inside the Linux kernel source code using CppDepend and discover some basic coding rules adopted by their developers.
Modularity
Modularity is a software design technique that increases the extent to which software is composed of separate parts, you can manage and maintain modular code easily.
For a procedural language like C where no logical artifacts like namespace, component or class do not exist, we can modularize by using directories and files.
Here are some possible scenarios :
- Put all the source files in one directory
- Isolate files related to a module or a submodule into a specific directory.
In case of the Linux kernel, directories and subdirectories are used to modularize the kernel source code.
Encapsulation
Encapsulation is the hiding of functions and data which are internal to an implementation. In C, encapsulation is performed by using the keyword static. These entities are called file-scope functions and variables.
Let’s search for all static functions by executing the following CQLinq query
We can use the Metric view to have a good idea how many functions are concerned. In the Metric View, the code base is represented by a Treemap. Treemapping is a method for displaying tree-structured data by using nested rectangles. The tree structure used in a CppDepend treemap is the usual code hierarchy:
- Projects contain directories.
- Directories contain files.
- Files contains struects, functions and variables.
The treemap view provides a useful way to represent the result of a CQLinq request, so we can visually see the types concerned by the request.
As we can observe many functions are declared as static.
Let’s search now for the static fields:
The same remark as functions, many variables are declared as static.
In the Linux kernel source code, the encapsulation is used whenever the functions and variables must be private to the file scope.
Use structs to store your data model
In C programming the functions use variables to achieve their treatments, these variables could be:
- Static variables.
- Global variables.
- Local variables
- Variables from structs.
Each project has its data model which could be used by many source files, using global variables is a solution but not the good one, using structs to group data is more recommended.
Let’s search for global variables with a primitive type:
only very few variables are concerned, and maybe we can group some of them into structs, like (elfcorehdr_addr and elfcorehdr_size) or (pm_freezing and pm_nosig_freezing).
Let function be short and sweet
Here’s from the linux coding style web page, an advice about the length of functions:
Functions should be short and sweet, and do just one thing. They should fit on one or two screenfuls of text (the ISO/ANSI screen size is 80x24, as we all know), and do one thing and do that well. The maximum length of a function is inversely proportional to the complexity and indentation level of that function. So, if you have a conceptually simple function that is just one long (but simple) case-statement, where you have to do lots of small things for a lot of different cases, it's OK to have a longer function.
Let’s search for functions where the number of lines of code is more than 30
Only a few methods have more than 30 lines of code.
Function Number of parameters
Functions where NbParameters > 8 might be painful to call and might degrade performance. Another alternative is to provide a structure dedicated to handling arguments passing.
only 2 methods have more than 8 parameters.
Number of local variables
Methods, where NbVariables is higher than 8, are hard to understand and maintain. Methods, where NbVariables is higher than 15, are extremely complex and should be split into smaller methods (except if they are automatically generated by a tool).
only 5 functions have more than 15 local variables.
Avoid defining complex functions
Many metrics exist to detect complex functions, NBLinesOfCode, Number of parameters and number of local variables are the basic ones.
There are other interesting metrics to detect complex functions:
- Cyclomatic complexity is a popular procedural software metric equal to the number of decisions that can be taken in a procedure.
- Nesting Depth is a metric defined on methods that is relative to the maximum depth of the more nested scope in a method body.
- Max Nested loop equals the maximum level of loop nesting in a function.
The max value tolerated for these metrics depends more on the team choices, there are no standard values.
Let’s search for functions candidate to be refactored:
only very few functions could be considered as complex.
Naming convention
There’s no standard for the naming convention, each project managers could choose what they think it’s better, however, what’s very important is to respect the chosen convention to have an homegenous naming.
In case of Linux, the structs must began with a lower case, and we can check if it’s true for the whole kernel source code, let’s execute the following query:
Only 4 structs began with “_” instead of a lower case letter.
Indentation
The indentation is very useful to make the code easy to read, here’s from the linux coding style web page the motivations behind the indentation:
Rationale: The whole idea behind indentation is to clearly define where a block of control starts and ends. Especially when you've been looking at your screen for 20 straight hours, you'll find it a lot easier to see how the indentation works if you have large indentations. Now, some people will claim that having 8-character indentations makes the code move too far to the right, and makes it hard to read on a 80-character terminal screen. The answer to that is that if you need more than 3 levels of indentation, you're screwed anyway, and should fix your program.
Conclusion
Exploring some known open source projects is always good to elevate your programming skills, especially if they are developed and maintained by experts. No need to download and build the project, you can just discover the code from GitHub.