Programming Guidelines

From BibleTime
Jump to: navigation, search

BibleTime up to 1.6 never had strict rules for the implementation of the code. Looking at the code you'll see that the code is a total mess, not maintainable, not well put into an architecture. Part of the reasons were the lacking knowledge of the right techniques.

These guidelines are quite strict to enforce good code quality. They are mandatory for any newly written code.

Contents

Central repository

Types of branches

  • master - the main development branch for checking in code.
  • stable-* - branches for stable versions used for backporting fixes (e.g. stable-2.7).
  • archived-* - old stable branches for versions no longer being supported (e.g. archived-2.6).

Policy for commiting to central repository

All stable branches of our source code repository should always be in a releaseable state. That means that everything checked in has to compile and work properly, without breaking compatibility.

The master branch, which is the development branch, should always be in a near-releasable. This means, that unless explicitly coordinated with other developers, the master branch should also be kept as bug-free as possible. It should compile and work properly for the most part.

No commits should be done to the archived branches, ever!

Source code policies

Headers and implementations

It is good C++ practice to work with header and implementation files. The following rules apply:

  • One header and one implementation file for each major class
  • Classes declared within another class may be in the same file
  • The header files must #include only what is required for that particular header file. User forward declarations where possible.
  • Include everything needed for the class in the implementation file

All source files must have the following beginning:

CODE: Mandatory C++ file heading
/********* * * In the name of the Father, and of the Son, and of the Holy Spirit. * * This file is part of BibleTime's source code, http://www.bibletime.info/. * * Copyright 1999-2010 by the BibleTime developers. * The BibleTime source code is licensed under the GNU General Public License version 2.0. * **********/

Source code documentation

  • Document classes, member variables and member functions in the header files. There should be at least a brief information about each class and member function.
  • Implementation files should contain in-code documentation (à la "the next block does..."). Please document enough, so that other people can understand your code quickly.

Design and naming conventions

There are no rules for this (yet). For helpful hints, check out this trolltech article about a good API.

User-visible text

Texts in the UI that the user sees must be marked with QObject::tr() for translation.

Here is a list of old, internal names and their preferred equivalent that is to be used in existing and new code:

Old names Current name, to be used explanation
(sword) module work literary work, all kinds of books
main index bookshelf the place to store your works
(sword) module manager bookshelf manager the tool to download/update/remove works
presenter, display window read window the mdi windows where you can read your works

Read these instructions which are very enlightening and recommended for everyone working with user interface texts:

Text style and tone

  • Neutral tone, not aggressive or impolite but not overly polite ("please" is not necessary, "You have failed to..." is bad)
  • As short as possible - every word counts and must add new information
  • Avoid redundancy (in one sentence but also between UI elements)
  • Avoid obvious information which is easily deductible from the context (e.g. don't add the object of the action if user knows it already)
  • Tooltips end with period only if there are several sentences. If there are several sentences they all end with period. Single, short, even incomplete sentences are preferred.
  • Avoid snobbish or rare words or grammar, remember that the text must be understood by even non-native English users. (Example: use "indexes" instead of "indices", it's correct English and more understandable to the foreign users because it's a regular plural.)

Examples:

  • Bad tooltip: You can go to the next verse of the current work by clicking this button.
  • Better tooltip: Go to the next verse of the current work
  • Good tooltip: Next verse

Capitalization

Use title-style capitalization for titles, sentence-style capitalization for all other UI elements. For details, see MS Vista HIG, User Interface Text.

  • Window titles are title-style
  • Buttons, tabs, menu items, labels etc. are sentence-style
  • Even grouping headers are sentence-style
  • Bookshelf Manager is a title, use title-case in all places, even in menu

Coding style

In general, source code should be as readable as possible. In case following any of the rules below makes code look too bad, then sometimes rules may be ignored.

General class header file structure

This is the general class header file structure for class ClassName, which inherits from BaseClassName:

  1. Licence
  2. empty line
  3. #ifndef CLASSNAME_H
  4. #define CLASSNAME_H
  5. empty line
  6. #include "baseclassname.h"
  7. empty line
  8. Global includes, alphabetically ordered, e.g. #include <QObject>
  9. Local includes, alphabetically ordered, e.g. #include "someclassname.h"
  10. two empty lines
  11. Macros
  12. empty line
  13. Global forward declarations, alphabetically ordered
  14. empty line
  15. namespace ThisClassNamespace {
  16. empty line
  17. Local forward declarations, alphabetically ordered
  18. empty line
  19. Struct, class, typedef etc declarations separated by empty lines
  20. empty line
  21. Any other declarations for namespace, longer inlined or template code.
  22. empty line
  23. } // namespace ThisClassNamespace
  24. empty line
  25. Any other declarations, macros.
  26. empty line
  27. #endif CLASSNAME_H
  28. empty line

General class source file structure

This is the general class header file structure for class ClassName:

  1. Licence
  2. empty line
  3. #include "classname.h"
  4. empty line
  5. Global includes, alphabetically ordered, e.g. #include <QObject>
  6. Local includes, alphabetically ordered, e.g. #include "someclassname.h"
  7. two empty lines
  8. Macros and anonymous namespaces
  9. two empty lines
  10. namespace ThisClassNamespace {
  11. definitions of class static variables
  12. empty line
  13. constructor definitions (separated by empty lines)
  14. empty line
  15. destructor definition
  16. empty line
  17. method definitions (separated by empty lines)
  18. empty line
  19. } namespace ThisClassNamespace
  20. empty line

Line length

Please try to fit all source code lines to 80 characters. Local typedefs, using clauses and temporary references can help:

CODE: Line length limiting example
void print(const SomeClass &container) { using namespace this::is::very::nested; typedef std::map<Foo, std::list<Bar> > FBLM; typedef std::map<Foo, std::list<Bar> >::const_iterator FBLMCI; const FBLM &theMap = container.getMap(); for (FBLMCI it = theMap.begin(); it != theMap.end(); it++) { } }

If possible, try to use somewhat intuitive abbrevations, e.g. "FBLMCI" could stand for "FooBarListMapConstIterator", i.e. std::map<Foo, std::list<Bar> >::const_iterator.

Indentation

One indentation level is four spaces. We do not use tabs to ensure that all text editors have the same display result.

Preprocessor directives should also be indented to the same level as code. The preprocessor blocks themselves are not required to introduce more indentation.

CODE: Example of indenting preprocessor directives
void f() { #ifndef NDEBUG #if STUFF > 0 std::cerr << "STUFF is " << STUFF << std::endl; #endif #endif }

However, indentation may be used for clarity, i.e. in #if blocks, but keep in mind that regular code should also be indented. Here's how NOT to write the above example:

CODE: Bad (!!!) example of indenting preprocessor directives
void f() { #ifndef NDEBUG #if STUFF > 0 std::cerr << "STUFF is " << STUFF << std::endl; // SHOULD BE INDENTED!!! #endif #endif }

Namespaces

Namespaces are not indented and neither are their contents. An exception to this are forward declarations, which may be indented, if they're the only thing inside the namespace declaration:

CODE: Example of indenting forward declarations inside namespaces
namespace Foo { class FooBar; } class Bar;

In other cases, code inside namespaces is not indented:

CODE: Namespace indentation example
namespace Foo { struct Bar; struct Baz { int a; }; } // namespace Foo

If some namespace block spans many lines, a comment should be present after the closing brace, indicating what namespace it ends. See example above.

Classes

Class member visibility specifiers (public, protected, private, signals) as well as Qt class macros (e.g. Q_OBJECT) are indented once, all other members are indented twice. Each visibility specifier (except specifiers for signals and slots) should also have a comment with the text "Types:", "Methods:" or "Fields:" indicating the type of members that follow. Duplication of visibility specifiers is needed to group all all class members properly.

CODE: Class example
class Bar: public QObject { Q_OBJECT public: /* Types: */ typedef int Baz; public: /* Methods: */ Bar(); virtual ~Bar(); protected: /* Methods: */ virtual Baz setBaz(const Baz &baz); protected: /* Fields: */ Baz m_baz; }; // class Bar

For long class declarations, a comment should be present after the closing brace, indicating what class declaration it ends. See example above.

Switches

Cases are indented once, their contents are indented twice.

CODE: Switch example
switch (foo) { case 1: { Foo f = getFoo(); f.setValue(123); a.setFoo(f); break; } case 2: // Fall through case 3: a.setFoo(b.getFoo()); break; default: break; }

For long switch statements, a comment should be present after the closing brace, indicating what switch statement it ends.

Blocks

Braces should generally open the block on the same line as the preceding keyword or function signature. If the preceding code or function signature spans multiple lines, the opening brace should be put on a new line.

CODE: Block examples
inline function doFoo() { Foo::getInstance()->doSomething(); } function bar(const SomeNamespace::SomeClass &c, OtherNamespace::OtherClass *p) { Foo &f = c.foo(); if (p->canAccept(f)) { p->accept(f); } }

Operators

There always must be space around binary and ternary operators. For very long expressions, break lines on the same subexpression level, before the operator.

CODE: Long expression example
if ((a || b == 123 + c * 10) && (c + d < limit())) { doStuff(); }

Initializer lists

After the definition of the constructor, list all base class constructors and member variable inititializations in separate lines. Then comes the main constructor code block.

CODE: Initializer list example
Foo::Foo(const QObject *parent) : AbstractFoo(parent) , m_bar(Bar::DEFAULT) { // Intentionally empty }

Compiler strictness

In normal development builds, the compiler will perform a very strict error checking with the flags "-Wall -Werror -pedantic-errors". This means that all compiler warnings will abort the build. Therefore code is not allowed to produce any compiler warnings. For developers this may seem nasty at times, but I'm convinced this will prove very helpful in future (find bugs before they find you). One example where the stricter compiler checking can help find potential bugs is "inheritance / virtual functions".

Checkin restrictions

  • Checked in code has to compile
  • Don't check in commented out code
  • Do not check in code that is only needed for development and active by default
    • You can use a commandline switch like --debug to activate development-specific code. See main.cpp for an example (qDebug() messages will only be output if the user specified --debug as a commandline argument).

Mandatory programming principles

DRY (Don't Repeat Yourself)

This simple principle means that every piece of information (data or logic) may be represented only once in a system (= program).

RAII (Resource Acquisition Is Initialisation)

Resource Acquisition Is Initialisation (RAII) is a powerful technique for managing allocation and deallocation of resources that was invented and popularised by Bjarne Stroustrup, the creator of C++. It allows easier managing of resources, and is the *only* sensible way to make resources exception safe.

This is how this heap resources are usually used by developers:

CODE: Common style of coding in C and C++
{ allocate_resources use_resources delete_resources }

This however, is (1) error prone, because everything in delete_resources must be made to match that in allocate_resources, and if use_resources has an early exit condition it is even more difficult to program correctly; and (2) not exception safe, because use_resources might throw an exception, and the resources will be leaked.

The way RAII works, is by creating a wrapper class for each resource type, the class initializes or stores the resource in its constructor, and releases it in its destructor. Since C++ guarantees that objects will be destroyed when they fall out of scope, this will ensure that resources are freed automatically. Thus, code will look like this:

CODE: The effects of RAII
{ allocate_resources use_resources } // Resources are automatically freed on return

We use QSharedPointer from Qt for just about any scoped resource management of this type that is needed. BibleTime developers should use the RAII technique for *all* scoped resource allocation and deallocation, as it is the only legitimate way to ensure exception safety, and thus to satisfy one of the Abraham's exception safety rules (see http://www.gotw.ca/gotw/082.htm for information on the Abraham's exception safety rules).

Put simply,

CODE: Example of resource management without RAII
void f(int val) { MyClass *obj = new MyClass(); // Allocate resources obj->doSomething(val); // Use resources delete obj; // Free resources }

becomes:

CODE: Example of resource management with RAII
#include "<QSharedPointer>" void f(int val) { QSharedPointer<MyClass> obj = new MyClass(); // Allocate resources obj->doSomething(val); // Use resources } // Free resources