Skip to content

Style manual

hagantsa edited this page Mar 5, 2024 · 2 revisions

In order to have easily readable and professional-looking code in Kactus2, the coding style should be the same regardless of the programmer. This manual defines the coding style and practices used in developing Kactus2 to ensure unified outfit and style for all source code files.

1. General

1.1. Language

The natural language used for comments, naming variables is English.

1.2. Indentation

For indentation, four (4) spaces are always used. Using tabs for indentation is prohibited.

1.3. Line Width

The maximum width for a source code line is 115 characters.

2. Comments

Kactus2 source code is commented in a way that allows auto-generation of Doxygen documentation. Doxygen tags are used extensively to make the documentation as comprehensible as possible. JavaDoc notation (@param) is preferred over the classic Doxygen notation (\param).

2.1. General

All comments must be full sentences, i.e., they begin with a capitalized letter and end to a period, exclamation mark or question mark.

2.2. File Comment

All C++ source files begin with the following file comment:

    //-----------------------------------------------------------------------------
    // File: File.h
    //-----------------------------------------------------------------------------
    // Project: Kactus2
    // Author: <Name>
    // Date: <Date in the format dd.mm.yyyy>
    //
    // Description:
    // <Short description of the class/file contents>
    //-----------------------------------------------------------------------------

2.3. Function Comments

Functions are commented extensively in header files. Following example illustrates a typical function comment:

    /*!
     *  Stores a new command to the edit stack.
     *
     *      @param [in] command   The command to add.
     */
    void addCommand(QSharedPointer<QUndoCommand> command);

The function comment can contain the following tags with the following syntax:

  • @param [in]/[out]/[in/out]
  • @return (Only if the function returns something)
  • @remarks <Important notes about the function behavior and special pre/post-conditions> (Only if necessary)
  • @throw (Only if the function throws exceptions)

The parameter or return value descriptions must not state explicitly whether the parameter is a pointer, a reference or a value. This information is already visible in the parameter declaration and should not be repeated in the comment, because it adds unnecessary burden for e.g. cases when the parameter is changed from a reference to a pointer.

Avoid out and in/out parameters whenever possible. An out parameter can be often replaced by a return value.

In source code files, the functions have a minimal start comment which follows the style:

    //-----------------------------------------------------------------------------
    // Function: GenericEditProvider::addCommand()
    //-----------------------------------------------------------------------------
    void GenericEditProvider::addCommand(QSharedPointer<QUndoCommand> command)
    {
        ...
    }

2.4. Comments for Enhancing Appearance

The appearance of the source code files can be enhanced using separator comments:

Different sections in the source code can also be separated using a section comment:

    //-----------------------------------------------------------------------------
    // <Section description>.
    //-----------------------------------------------------------------------------

2.5. Data Type Comments

When a class, a structure or an enumeration is defined in a header file, it is commented using a section comment (notice the exclamation mark before the description):

    //-----------------------------------------------------------------------------
    //! <Class description>.
    //-----------------------------------------------------------------------------
    class ClassName : public BaseClassName

2.6. Commenting Variables and Types

    //! The history size for undo/redo stack.
    unsigned int historySize_;
    
    //! Point list type.
    typedef QList<QPointF> PointList;

Variables in a structure, on the other hand, can be commented on the same line as the actual variable definition with the following format:

    bool redoing_;      //!< Boolean flag for redoing.

3. Naming Conventions

3.1. Files

Following extensions are used for each file type:

  • Implementation file: .cpp
  • Header file: .h
  • Inline implementation file: .inl (if inline implementations are needed for performance reasons)

The names of the C++ source code files are derived from the file contents. All words are written with a starting upper-case letter. For example, if the source file contains the implementation of class GenericEditProvider, the source files are named GenericEditProvider.cpp and GenericEditProvider.h.

3.2. Variables

The name of the variables is written so that the first word is in lower-case and the next ones start with an upper-case letter. Member variables of both classes and structs are always post-fixed with an underscore. Parameters and local variables are written without the underscore.

Examples: pointList_ (member), currentState (non-member)

3.3. Pointers and References

The * and & characters are part of the pointer and reference types.

Examples: PointList* pointList; GenericEditProvider& editProvider; NOT PointList *pointList;

3.4. Functions

Functions start with a lower-case word and the next words start with an upper-case letter.

Example: void doSomething();

3.5. Namespaces, Classes, Structures and Enumerations

All words in namespace, class, structure or enumeration names start with an upper-case letter.

Examples: namespace General, class GenericEditProvider, enum State, struct Point

3.6. Enumeration Values and Constants

Values of an enumeration and constants are written using capitalized words. Words are separated from each other using underscores.

Examples:

    enum HighlightMode {HIGHLIGHT_OFF, HIGHLIGHT_HOVER};
    int const MAX_HISTORY_SIZE = 100;

4. Code Appearance

4.1. Control Statements

Control statements (if, while, for) must always be encapsulated using brackets even if there is only one statement inside the control statement.

4.2. Code Block Brackets

Code block brackets are always placed on their own lines.

Example:

    if (qFuzzyCompare(dir, QVector2D(0.0f, -1.0f)))
    {
        setRotation(0.0);
    }

4.3. Whitespace in Control Statements

The following example illustrates how whitespaces are placed in a control statement:

    if (statement1 && statement2)
      ^           ^  ^

5. Practices

5.1. Global Variables

Global variables must not be used. Only global constants are allowed.

5.2. Const-correctness

Const-correctness must be used at all times. Const is used in member functions that do not modify the object's data. For variables, the place of const is after the type and before the &-sign.

Examples: PointList const& pointList;

5.3. Friends

Friend classes are not allowed since it breaks the modularity of the code.

5.4. Contents of One Compilation Unit

Usually, only one class should be defined in a compilation unit (meaning one header and one source file). However, if the class requires some enumerations, structures or constants, they can be defined in the same compilation unit.

5.5. Function Implementations in Header Files

Function implementations are never written into the header files. Normal functions are implemented in the .cpp files and inline functions in the .inl files.

5.6. Forward Declarations

Forward declarations are used in header files always when it is possible. This is the situation when only pointers or references to the class instances are used in the header file.

5.7. Disallowing Copying

All classes that should not be copied must have private a copy constructor and an assignment operator
removed. Prefer C++11 delete mechanism over C++98 convention, see example below.

    class DiagramConnectionEndPoint
    {
    public:
    // Disable copying in C++11. Always prefer this.
    DiagramConnectionEndPoint(DiagramConnectionEndPoint const& rhs) = delete;
    DiagramConnectionEndPoint& operator=(DiagramConnectionEndPoint const& rhs) = delete;
    
    ...
    
    private:
    // Disable copying in C++98.
    DiagramConnectionEndPoint(DiagramConnectionEndPoint const& rhs);
    DiagramConnectionEndPoint& operator=(DiagramConnectionEndPoint const& rhs);
    };

Copying instances should be avoided, so this is the default policy for any classes.

5.8. Header File Guards

Header files must be guarded using pre-processor statements with the following format:

    #ifndef CLASSNAME_H
    #define CLASSNAME_H
    ...
    #endif // CLASSNAME_H

5.9. Organization of Implementation Files

The functions must be implemented in the implementation files in the same order as they have been declared in the header files.

5.10. Using statements must not be used in header files.

Using namespace should only be used with extra caution in the implementation files. It is preferred to use using statements only to retrieve a specific class, structure or other type from a namespace.

5.11. Initializing Variables

All variables are initialized when they are declared. All member variables must be initialized in the class constructor. Member initialization list must be used if it is possible.

5.12. #include Statements

A forward slash (/) must be used instead of a backslash as the #include directory separator to allow cross-compatible code.

5.13. C-style Coding

C-style coding is not allowed. Using, for example, functions abort(), exit(), malloc() and dealloc() or goto statements is prohibited.

5.14. Type Casts

Implicit or C-style style type casts must not be used. Instead, static_cast<>, dynamic_cast<> and reinterpret_cast<> should be used. const_cast<> should be avoided.

5.15. Function Parameters

Large function parameters are always passed as references or pointers.

5.16. Class Organization

All member variables are private. If access to private member variables is required in a derived class, protected access functions are used. A class contains first the public part, then the protected part and last, the private part.

5.17. Constructors and Destructors

Every class must have a constructor and a destructor. When applicable, declare destructor default in C++11 style.

    ~classX() = default;

5.18. Use override and final in inherited functions

All overriding functions in inherited classes should be declared override. Compiler will check
that the function signature matches the base class signature and raise an error, if not.

Use final to mark functions that shall not be overridden by inheriting classes.

5.19. Prefer scoped enumerations over unscoped enumerations

Unscoped enumerations leak to their containing namespace and may be implicitly converted to integral types.
Prefer scoped enumerations (add keyword class) where the name of the enumeration also becomes
the containing namespace. Scoped enumerations are strongly typed and less likely to cause name conflicts.

Example:

    namespace Highlight
    {
        // Scoped enumeration.
        enum class Modes {HIGHLIGHT_OFF, HIGHLIGHT_HOVER};
    
        // Unscoped enumeration.
        enum HighlightMode {HIGHLIGHT_OFF, HIGHLIGHT_HOVER};
    };
    
    ...
    
    //Use scoped enumeration:
    if (mode == Highlight::Modes::HIGHLIGHT_OFF)
    {
        ...
    }
    else if (mode == 42) // Compiler error, cannot compare Modes and int.
    {
        ...
    }
    
    //Compared to use of scoped enumeration:
    if (old_mode == Highlight::HIGHLIGHT_OFF)
    {
        ...
    }
    else if (old_mode == 42) // Implicit conversion, may introduce odd behavior.
    {
        ...
    }

5.20. Prefer alias over typedef

Aliases are better for communicating intent and template functions. See example below.

Example:

    // Define type alias (C++11):
    using fileList = QVector<QFileInfo>;
    
    // Typedef (C++98) equivalent:
    typedef QVector<QFileInfo> fileList;

5.21. Prefer nullptr over 0 and NULL

Using nullptr avoids implicit conversions from 0 which is an integer.

6. Qt performance pitfalls

6.1. Repeated construction and copying contents of QString objects may impact performance significantly. The following examples can be used to avoid this.

    // This function will copy the characters in the QString constructor every time when executing the function.
    QString classX::getType() const
    {
        return "int";
    }
    // The function should be written like this. This will create the QString only once at compilation time.
    QString classX::getType() const
    {
        return QStringLiteral("int");
    }
    
    // This function will copy the characters in the QString constructor every time when executing the function.
    bool classX::isInstanceScope(QString const& scope) const
    {
        if (scope == "instance")
        {
            return true;
        }
    
        return false;
    }
    // The function should be written like this. This will calculate the string length at compilation time and allows faster comparison.
    bool classX::isInstanceScope(QString const& scope) const
    {
        if (scope == QLatin1String("instance"))
        {
            return true;
        }
    
        return false;
    }
    
    // This function will repeatedly call QString constructor.
    QString VLNV::toString() const
    {
        QString result;       //<! 1. call.
        result += vendor_;    //<! 2. call.
        result += ":";        //<! 3. call.
        result += library_;   //<! 4. call.
        result += ":";        //<! 5. call.
        ...
        return result;
    }
    // The function should be written like this. This will use the internal QStringBuilder to avoid multiple memory allocations.
    QString VLNV::toString() const
    {
        return vendor_ + ":" + library_ + ":" ... ;
    }

6.2. Iterating through directory structure with QDir may be extremely slow if not done correctly. See the following example on how to avoid this.

    // This function constructs QFileInfo on every iteration of the foreach loop.
    void classX::searchDirectories() const
    {
        QDir directory = ...;
    
        for (QString const& entryName : directory.entryList())
        {
            QFileInfo fileInfo(directory.filePath(entryName));   //<! Repeated constructor calls for QFileInfo.
            if (fileInfo.isDir())
            {
                ...
            }
        }
    }
    // The function should be written like this to avoid needless copying.
    void classX::searchDirectories() const
    {
        QDir directory = ...;
    
        for (QFileInfo const& fileInfo : directory.entryInfoList())  //<! call to entryInfoList() avoids repeated construction.
        {
            if (fileInfo.isDir())
            {
                ...
            }
        }
    }
Clone this wiki locally