CodingStandard
| Languages | English | עִברִית |
|---|
This page describes the standard coding style throughout the GnuCash source code.
Contents
C and C++ Format
Consistent formatting makes for more readable code. Over the many years of GnuCash development dozens of authors have used variations of style that are still apparent in different files. When changing or writing new code in an existing file follow the formatting of the rest of the file. When creating a new file consult the style of other files in the same directory and use similar styling conventions.
Layout
GnuCash's preferred style follows the GNU format except that we use four spaces instead of two for the indents, and align braces in if/else statements with the beginning of the statement instead of indenting it. To summarize, a properly formatted function will look like this:
guint
gnc_account_foo (Account *account, gpointer bar)
{
Split *baz;
guint salt;
if (gnc_split_waldo (baz) > 0)
{
salt = gnc_split_pepper (baz);
}
return salt;
}
Please keep lines under 120 characters. If you need to wrap, line up the function arguments like this:
gnc_account_function_with_a_lot_of_paramters (LongTypeName foo, LongerTypeName *bar,
TypeName baz)
{
}
Only curly braces should appear alone on a line: Parentheses should open on the same line as the function name or keyword and close after the last parameter or condition. Unlike some Gnome projects we don't do the wide separation of names and aligned parameters, so don't do this:
void gnc_account_foo (Account *bar,
Split *baz,
gpointer waldo);
Split *gnc_account_pepper (Account *salt,
Transaction *sausage);
Instead, do it this way:
void gnc_account_foo (Account *bar, Split *baz, gpointer waldo);
Split *gnc_account_pepper (Account *salt, Transaction *sausage);
Break up long if statements like this:
if (gtk_tree_path_get_depth ((GtkTreePath *) selected->data) == 1 &&
get_action_for_path (selected->data, model) == GNCImport_ADD)
Capitalization
- C++ Namespaces shall be all lower case single words, and the outermost one shall be named gnc.
- Identifier formats:
- Typenames (i.e. class, struct, enum, and POD type-aliases) shall be camel-cased.
- Function and object names shall be lower-case and shall have words separated by underscores.
- Preprocessor Macros and enumeration names shall be upper-case with words separated by underscores.
Naming
Certain types of variables shall have prefixes denoting their roles. This is required for C++ classes (including structs) and encouraged for C structs:
- Member variables: m_.
- Static member variables (a.k.a class variables): c_.
- Static constants: k_.
- File-scope static variables: s_.
- Public free function names should begin with
gnc_. If it's a class function (C++ or GObject) the class name should follow, then a descriptive name. - File-scope functions should use only a descriptive name. It's often helpful to give callbacks a
_cbsuffix. - If the file you're working in uses a different convention follow that convention. In particular the original engine classes use an
xaccprefix and camel-case function names and the Query Object Framework uses aqof_prefix.
Other Formatting
- Single-line standalone comments can use either C++ style or C style. End-of-line comments should always use C++ style. Short block comments may be either style, longer ones should use C-style with every line introduced with a
*aligned with the one above. The closing*/should be on its own line. - Headers and implementation files should be named for the class they declare or implement. In general any file should declare or implement only one public class.
Coding Guidance
Asserts
The C standard library's assert macro and GLib's g_assert macro both abort the program if their condition fails. They can be disabled at build time with preprocessor macros, but GnuCash's build system doesn't do that, and besides disabling the assert means that whatever precondition you were trying to ensure isn't ensured anymore. Aborting at least annoys users and can cause them to lose work so it's something to be avoided.
- C++'s
static_assertis good for preconditions that can be tested then. Careful observance of the type system and preferring references to pointers help to ensure that preconditons are testable at compile time. Unfortunately GObject relies heavily on void pointers and runtime type checks so compile time checking isn't always feasible. - GLib provides several logging functions that check a condition and return if it fails logging an error in the Tracefile along the way.
- When something fails consider displaying a message dialog to the user telling them about it.
C
GnuCash specifies C 11 in stable and C 23 in future. Consequently:
- While much of GnuCash's C code observes the pre-C98 convention of placing all variable declarations at the top of a block there is no need to do this. Prefer to declare and initialize variables in a single statement at the point of first use.
- Prefer the
booltype to macros likegbooleanexcept when interfacing with another C library that doesn't use it. Note that you must#include <stdbool.h>in thestablebranch when usingbool. C 23 made it part of the language so that's not needed infuture.
C++
Write modern (meaning C++11 or later), idiomatic C++ making as much use as possible of the maximum C++ standard supported in the branch being developed for; check the CMAKE_CXX_STANDARD setting in CMakeLists.txt to see what features you may use (currently C++17 in stable and C++23 in future). In particular:
- Prefer curly braces for initializers and auto for variable declarations.
- Declare and initialize variables in one statement at the point that they're first used.
- Make liberal use of aliases (
using, don't usetypedefunless the alias will be used from C) to convert template types into self-documenting names. - Use templates instead of copying code and especially instead of preprocessor macros and use syntactic sugar (e.g. lamdas and auto parameters) when appropriate.
- Prefer passing references to pointers.
- Do not ever pass void* (aka gpointer) in C++ code unless writing a callback for a C function. Use templates or class hierarchies to enforce type safety.
- Prefer algorithms like
std::for_eachorstd::accumulateto raw loops. - Use C++ standard smart pointers whenever possible. Use shared pointers only in the rare cases where you're sharing ownership: Otherwise pass a reference to a
std::unique_ptror when passing to a C function pass a copy of the raw pointer. When making an STL container of objects consider letting the container manage the objects' lifetimes and using iterators to access the objects. - Pay attention to return value types to avoid copying (RVO can help). Use STL containers'
emplacemethods to construct objects in place when appropriate.
Everywhere
- Keep functions short! Around 50 lines is a reasonable size, bigger than 100 lines is way too big.
- Interior unconditional blocks are a glaring indication that the code within should be extracted to a separate function.
- If there's a bunch of variables that would need to be passed around to helper functions, put them in a
structand if you're writing C++ consider making the functions class members. - Functions with more than one boolean argument are error prone. Convert the booleans into a bitmask enum (where the values are powers of two) and
orthem together in the function call or create a regular enum whose values meaningfully describe the different possible combinations of true and false values of the booleans. - Public functions should be described with their parameters and return values using Doxygen syntax. In GnuCash those comments are generally in the header file immediately before each function declaration. Additional explanations of modules and classes may be added to aid other developers in using your work. Doxygen provides a rich syntax for formatting and grouping those comments in the resulting documentation. Make full use of those features.
- When translators might need additional guidance for translating a string insert a comment beginning with
Translators:immediately before thegettextcall with the US English version of the string. That comment will be inserted into the translation file and presented with the string in the translation program. - Otherwise comments should be sparse and terse. Explanations of why you made a change belong in the commit message, not in comments. In general code should speak for itself without needing a comment to explain it. If it doesn't then your energy will be better spent writing clearer code than writing comments.
Converting a C file to C++
Do this in a series of commits before making functional changes. Compile and run the tests after each step. If test coverage of the file is poor write tests first to make it easier to guard against introducing bugs.
- change the file extension and make any changes needed to get it to compile and pass tests and nothing else.
- change any C structs to C++ classes (remember that C++'s
classandstructdiffer only in the default privacy). ChangeNULLtonullptr. - convert the initialization and destruction functions into proper constructors and destructors, paying special attention to correctly managing memory and other resources.
- consider converting free functions that take the class as a parameter into member functions.
Function Naming
GnuCash code uses three prefixes: xacc, qof, and gnc. All should be used only for functions added to the public interface; local file-scope or namespaced functions should not use one of those prefixes. The first two are legacies: xacc and its attendant camel-case function naming style were used in GnuCash's original, pre-Gnome incarnation, X Accounting, and qof is a part of the engine that was once intended to be spun off as an independent library called The Query Object Framework. They should be used sparingly and only when adding to classes that already use them.
Note that in Scheme code gnc takes two forms. Scheme doesn't have operators so it can use - and : in symbol names. GnuCash uses a tool called SWIG to make the C/C++ API available to Scheme and it converts the _ separator to - in the Scheme wrapper so we reserve the prefix gnc- for wrapped C/C++ functions and use gnc: for public pure-Scheme functions.
The gnc prefix is in general just the first part of a function's prefix: The next part is the class or type that is the function's primary parameter, so for example gnc_numeric_add takes a gnc_numeric primary paramter. Note that gnc_numeric is a legacy class name and doesn't conform to the camel case convention for types.
C++ calss member functions do not need and should not use a prefix because in normal use they are called from a variable reference or pointer, e.g.
GncFoo bar{};
auto pepper{bar.waldo(3, 5)};
Framework
Gnucash is a Gtk+ project. It's design is object-oriented. The current object orientation is implemented mostly with Gnome's GObject C-language framework and makes heavy use of GLib. While this is a rich eco-system, it brings with it a huge number of dependencies which makes GnuCash difficult to port to other operating systems. Consequently the developers have decided to migrate all of GnuCash except the GUI to C++. No new GObject or GLib-dependent code should be written; instead use C++, the C++ standard library, and Boost libraries.
Guile and Scheme
Gnucash is partly implemented in a Scheme dialect called Guile. (It was originally written mostly in Guile, but that implementation was largely replaced with C several years ago.) In particular, the reports system and part of the business system are written in Guile. To support that, most of the core "engine" API is wrapped and accessible from Guile.
Ideally reports and infrastructure code will be coded using a lisp-specific editor such as Emacs. This will facilitate the indenting of parentheses, which will greatly ease readability and maintenance. Emacs coding style is very useful.
Rules for infrastructure code and reports:
- most reports should be self-contained;
- a function designed for multiple reports may be added into infrastructure code e.g. report-utilities.scm but must be documented properly;
- aim to create adequate tests using srfi-64;
- aim for a maximum line width of 78.
Graphical User Interface
See GUI Guidelines.
Python
The standard is PEP 8 -- Style Guide for Python Code.
Automation
We have from time to time used Artistic Style to reformat code. For simple re-indentation
astyle --indent=spaces=4 --brackets=break --convert-tabs --suffix=none <files>
suffices. More thorough reformatting can be done with
astyle --indent-spaces=4 --brackets=break --pad-first-paren-out --pad-header --pad-oper --unpad-paren --align-pointer=name --min-conditional-indent=0 --max-continuation-indent=60 --break-after-logical --max-code-length=79 --keep-one-line-blocks --convert-tabs --suffix=none <files>
That will force a space between operators (like if or while) and function names and the initial parenthesis and remove all other padding around parentheses, break lines after 79 characters and break long conditionals on a logical operator (|| or &&) with the logical operators at the ends of lines. Using this command will create a lot of changes in most files so it should be used sparingly and in general only before making extensive code changes.
- The rationale for the arguments is contained in this email.
For those who prefer clang-format satisfactory results can be obtained with
clang-format --style='{BasedOnStyle: GNU, AccessModifierOffset: -4, IndentWidth: 4, IndentExternBlock: NoIndent, PointerAlignment: Left, SortIncludes: Never, BraceWrapping: IndentBraces: false}'
But note that unlike astyle, clang-format isn't selective about what it formats: If you use the first astyle invocation above it will fix indentation and put the curly braces in the right place without touching anything else, but clang-format will enforce the entire GNU format spec as modified by the other options.
If you intend to use either of these on your own contributions you should first run the command on the unmodified files and commit any changes with an appropriate summary, e.g. "Reformatted with astyle --indent=spaces=4 --brackets=break --convert-tabs", to separate the reformatting from the consequential changes, then run astyle or clang-format again *before* committing a change. Installing clang-format also provides git-clang-format that can reformat just your changes and can even be set up as a pre-commit hook to automate the formatting.