Blog 4 min read

SQLite: The Art of Keeping It Simple

Share this article
SQLite: The Art of Keeping It Simple

16 years after its first check-in, SQLite is the most widely deployed database engine in the world. Such an open-source project is an excellent resource for learning how to make code easy to understand and maintain.

Let's explore some facts about the SQLite codebase. For that, let's begin with the following code snippet:

sqlite

Here are some 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 explore the SQLite source code, we can notice the coherence of the implementation. The same best practices 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 could be used by many source files. Using global variables is one solution, but not a good one; using structs to group data is preferable.

Let’s search using CQlinq and CppDepend for defined structs:

sqlite22

Many structs are used to specify the data model.

Keep Functions Short and Sweet

Here's, from the Linux coding style web page, some 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 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.

Function Number of parameters

Functions where NbParameters > 8 might be painful to call and might degrade performance. An 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 higher than 8 are hard to understand and maintain. Functions 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).

sqlite7

Only a few 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 relates to the maximum depth of the most nested scope in a method body.
  • Max Nested Loops is equal to the maximum level of loop nesting in a function.

The maximum acceptable value for these metrics depends on the team's choices; there are no standard values.

Let's search for functions that are candidates for refactoring:

sqlite8

Very few functions could be considered complex.

Be Const-Correct

C provides the const keyword to allow passing objects that cannot change as parameters, and to indicate when a method doesn't modify its object. 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 use many others are very difficult to understand and maintain. It's advisable to minimize the efferent coupling of your functions.

In 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 elevate your programming skills. There is no need to download and build the project — you can simply explore the code on GitHub.

Share this article