Blog 4 min read

Exploring the SQLite Codebase: Improve Your C++ Skills

Share this article
Exploring the SQLite Codebase: Improve Your C++ Skills

Sixteen years after its first check-in, SQLite is the most widely deployed database engine in the world. An open-source project like this is a great resource for learning how to make your code easy to understand and maintain.

Let’s discover some facts about the SQLite code base, beginning with the following code snippet:

sqlite

Here are a few observations about this function:

  • The function is declared as static.
  • The function returns an error code.
  • The function has only a few parameters.
  • The function exits as early as possible.
  • Assertions are used to check certain conditions.
  • No global variables are used.
  • The  variable names are easy to understand.
  • The method is short.
  • There are no unnecessary comments in the body.
  • The function body is well indented.

If we navigate through the SQLite source code, we can see the consistency of the implementation. The same best-practice rules are applied to each function.

Here are some best practices to learn from the SQLite codebase:

Use structs to store your data model

In C programming, functions use variables to carry out their processing. These variables could be:

  • Static variables.
  • Global variables.
  • Local variables
  • Variables from structs.

Each project has its data model, which can be used by many source files. Using global variables is one option, but it is not a good one; grouping data into structs is generally preferable.

Let’s search using CQLinq and CppDepend for the defined structs:

sqlite22

Many structs are used to specify the data model.

Keep Functions Short and Sweet

Here’s some advice on function length from the linux coding style web page:

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 less than 30.

sqlite5

More than 90% of functions have fewer than 30 lines of code.

Encapsulation

Encapsulation  is the hiding of functions and data which are internal to an implementation.  In C, encapsulation is performed by using the static keyword. These entities are called file-scope functions and variables.

Let’s search for all static functions by executing the following CQLinq query.

sqlite

As we can observe, many functions are declared as static.

Number of function parameters

Functions where NbParameters > 8 might be painful to call and might degrade performance. Another alternative is to provide a structure dedicated to handling argument passing.

sqlite6

Only a few functions have more than 8 parameters.

Number of local variables

Functions where NbVariables is greater than 8 can be difficult to understand and maintain. Functions where NbVariables is greater than 15 are extremely complex and should be split into smaller functions (unless they are automatically generated by a tool).

sqlite7

Only a few functions have more than 15 local variables.

Avoid defining complex functions

Many metrics exist to detect complex functions; NBLinesOfCode, the number of parameters, and the number of local variables are among the basic ones.

There are other interesting metrics to detect complex functions:

  • Cyclomatic complexity is a popular procedural software metric whose result is equal to the number of decisions that can be taken in a procedure.
  • Nesting Depth is a metric defined for functions that represents the maximum depth of nested scopes in a function body.
  • Max Nested Loop is the maximum level of loop nesting in a function.

The maximum value tolerated for these metrics depends mostly on the team’s choices, as there are no standard values.

Let’s search for possible functions to be refactored:

sqlite8

Very few functions could be considered complex.

Be Const-Correct

C provides the const keyword to indicate that an object cannot change and that a function doesn’t modify its parameter. Using const in all the right places is called “const correctness.” It’s hard at first, but using const really tightens up your coding style.

Let’s search for functions having at least one const parameter:

sqlite9

Function coupling

Functions that call many other functions can be difficult to understand and maintain. It’s recommended  that you minimize the efferent coupling of your functions.

For SQLite, very few functions have a high efferent coupling:

sqlite10

Exit a Function Early When You Can

Early exits out of a function, especially through guard clauses at the top of a function, are preferred since they simplify the logic further down in the function.

In the SQLite source code, this best practice is applied to almost all functions.

Conclusion

Exploring well-known open source projects is always a good way to improve your programming skills. There is no need to download and build the project—you can simply explore the code on GitHub.

Share this article