DuxReiNummariae 1.0.0-alpha.23
Simple and powerful budgeting application
 
Loading...
Searching...
No Matches
Standards

DRN Coding Style Guide

1. File Organisation

1.1 Copyright Header

  • Each file must start with the standard copyright notice
  • Use the provided comment block format with asterisks

1.2 File-name Structure

  • C++ Declaration files use .hpp extension
  • C++ Non-template definition files use .cpp extension
  • C++ Template Implementation files use .tpp extension
  • C Declaration files use .h extension
  • C Non-template definition files use .c extension
  • Unit test files use -unit-tests.cpp suffix
  • File-names should follow the overall concept it contains, not necessarily the specific class it has.

1.3 Declaration Structure Order

  • Include guards use uppercase with underscores: DRN_NAMESPACE_FILE_NAME_HPP.
  • Type Declarations
  • Functions
    • The signature must match the definition, even if not a language requirement.
  • Variables
  • Definitions must not be present in the declaration files.
  • Static functions then static variables come after type declarations and before the instance functions.

1.4 Definition Structure Order

  • Anonymous Namespace
    • Type Declarations & Definitions
    • Variables
    • Functions
  • Inner Type Definitions
  • Static Member Definition Functions
  • Static Member Definition Variables
  • Instance Member Definition Functions

1.5 Include Organization

  • Group includes in the following order, separated by newlines:
    1. Primary header to declaration the file
    2. Third-party includes (Qt, pecunia)
    3. Standard library includes
    4. Project includes (drn::*)
    5. Local library directory
  • Use forward declarations for types only used as references/pointers
  • Each include group should be sorted alphabetically
  • Use the include guards over #pragma once
  • Use angle brackets (<>) for the system and external library includes
  • Use quotes ("") for the local library directory includes
  • Prefer forward declarations over complete types in declaration files

1.6 Conditional Compilation

  • Use #ifdef only for:
    • Include guards
    • Platform-specific code
  • The ifdef should be indented to fit with the rest of the code, with the # starting the line
  • Platform-specific code structure:
    # ifdef Q_OS_WIN
    // Windows-specific implementation
    # elif defined(Q_OS_MACOS)
    // macOS-specific implementation
    # else
    // Default implementation
    # endif
  • Feature toggles through CMake configuration

1.7 File Organisation

  • Maximum file guidelines:
    • Declaration: 500 lines of unique code
    • Definition: 1000 lines of unique code
    • Split larger files by functionality into smaller types
  • Source Code Organisation:
    • Internal headers in internal/ subdirectory
    • External API headers at top level
    • One dedicated major class per include (with related helpers)

1.8 Library Organisation

  • Library structure:
    • external/namespace/ - Public API Source Code
    • internal/ - Private API Source Code
    • testing/ - Library Mocks & Mock Test Helper Source Code
    • unit-tests/ - Test Source Code
  • Minimize public dependencies
  • Source Code organisation:
    • Group related functionality
    • Use feature-based organisation
    • Keep implementation details private

2. Naming Conventions

2.1 Types and Classes

  • Use PascalCase (e.g., MappingSurvey, BalanceChange)
  • Class names should be nouns
  • Use meaningful and descriptive names

2.2 Variables

  • Use camelCase
  • Boolean variables should use question form (e.g., isIncrease_)
  • Use meaningful names that describe the purpose

2.3 Functions

  • Use camelCase
  • Action verbs for functions that perform operations

2.4 Namespaces

  • Use snake case with meaningful hierarchical structure
  • Main namespace is 'drn'
  • Sub-namespaces indicate feature areas (e.g., drn::surveying, drn::banking)

2.5 Constants

  • Use UPPER_CASE for macro constants
  • Use PascalCase for enum names and values
  • Use camelCase for variables

2.6 Anonymous Namespaces

  • Use in C & C++ definition files only
  • Place at the top of the file after includes
  • Contains:
    1. Type declarations & definitions
    2. Variables
    3. Functions
  • Use for implementation details not needed outside the file
  • No special naming conventions for contained elements
  • Use anonymous namespace over static for file-scope entities
  • Test anonymous namespace contents through public interfaces
  • Use anonymous namespace for:
    • Helper functions used by multiple methods in the file
    • File-local constants and types
    • Implementation details that shouldn't be exposed

2.7 Macro Usage

  • Use UPPER_CASE for macro names
  • Prefer constexpr, templates, and functions over macros
  • Multi-line macros are continued with a double space followed by the slash.

2.8 Projects

  • Use kebab-cased
  • Must describe a meaningful collection of the code it contains
  • Should be a noun or a collective noun

3. Class Structure

3.1 Class Access Specifiers Order

  • Friend declarations.
  • Private implementation details
    • Prefer anonymous functions in the definition file where possible over private member functions.
  • Protected implementation details (if needed)
  • Public implementation details
    • Public member variables are allowed, i.e. its type has no additional verifications.

3.2 Struct Access Specifiers Order

  • All members should be public or protected
    • If a private is warranted, convert the type to a class.

3.3 Structure Order

  • Nested Type Declarations
  • Friend Declarations
  • Static Member Functions
  • Static Member Variables
  • Instance Member Functions
  • Instance Member Variables
  • Functions, which are documented, i.e. free standing, public, or protected, do not have any newlines between them.
  • Functions, which are not documented, i.e. private or anonymous, have a single newline between them.

3.4 Member Declaration

  • One member variable and function per line
  • Initialise member variables in constructor initialisation list or in the declaration, not mixed
  • Use trailing underscore for member variables (e.g., reconciled_, expectedBalance_)
    • Accessors use the exact name of the member variable without underscore.
    • Mutators use the exact name of the member variable without underscore, prefixed with set.
      • Favour semantic member functions over mutator functions
  • Use const for member functions that don't mutate object state

3.5 Class Design Guidelines

  • Virtual destructors required for base classes
  • Override all virtual functions with override keyword
  • Operators must be class members when modifying state
  • Friend declarations allowed only for:
    • Unit tests
    • Operator<< for logging
    • Closely related helper classes
  • Prefer composition over inheritance
  • Interface classes must:
    • Have pure virtual destructor
    • Should be named to describe their most generic form.
      • Derived types will be named in the specific form.
    • May contain functionality common to ___all___ derived types.
  • Multiple inheritance allowed only for:
    • Interface classes
    • Mix-in classes
    • QWidget classes

4. Function Guidelines

4.1 Parameters

  • Use const references for large objects
  • Pass by const-value for small objects
  • Use std::optional for optional parameters
  • Use std::function for functors
  • Document parameters with Doxygen-style comments

4.2 Return Values

  • Use meaningful return types
  • Prefer returning by value for small objects

4.3 Error Handling

  • Exception classes must end with "Error" (e.g., BankError)
  • Exception hierarchy:
  • Use exceptions for error conditions that prevent post-condition from being fulfilled.
  • Use exceptions for error conditions that prevent returning a valid value.
  • Document the specific exceptions thrown by the function
  • Use assertions for internal logic validation that prevent pre-conditions from being fulfilled.
  • Error messages must:
    • Be translatable using QObject::tr()
    • Include relevant context (e.g., account names, amounts)
    • Not expose sensitive information
    • Error messages must be written in a manner readable by the end-user.
  • Exception safety guarantees:
    • Basic guarantee for most operations
    • Strong guarantee for critical operations (documented)
    • Nothrow guarantee for destructors and move operations
  • Log exceptions with:
    • Exception type and message
    • Context information
    • Stack traces
  • Log messages must be written as readable English sentences.
  • Error message format:
    • Use complete sentences
    • Include specific values in quotes
  • Nested exception handling:
    • Use std::nested_exception
    • Preserve exception chain
    • Log complete exception hierarchy
  • Error codes vs exceptions:
    • Use exceptions for errors that cannot be resolved within the function
    • Use error codes only when it is an expected outcome of the function, capture in an Expected, e.g. HTTP status code from a web request.
    • Document error code meanings

5. Formatting

5.1 Indentation and Braces

  • Use tabs for indentation
  • Opening brace on its own line for functions and control structures
  • Closing brace on its own line
  • Always use braces for control structures when there would be ambiguity from the indentation.

5.2 Spacing

  • Space after keywords (if, for, while)
  • Space around operators
  • No space between function name and opening parenthesis
  • Space after commas in parameter lists

5.3 Line Length

  • Lines must be under 100 characters
  • Break long lines at logical points
  • Double indent all line continuations

5.4 Comments

  • Use Doxygen-style comments, multi form /** */ or ///.
  • All public code must be documented.
  • Use // for single-line non-documentation comments
  • Keep comments up-to-date with code
  • Document complex algorithms and business rules
  • Doxygen tags should follow the following order (when relevant):
    1. 2.
      Exceptions
      Precondition
    2. Template Parameters
      Parameters
      6.
      Postcondition
    3. Returns
  • Regions of code are groups are opened using //{ Description
  • Regions of code are groups are closed using //}
  • Regions starting and ending are surrounded by newlines:
    ... non-region code above
    //{ Region Short Description
    ... region code
    //}
    ... non-region code below

6. Modern C++ Features

6.1 Language Features

  • Use nullptr instead of NULL
  • Use auto where it improves readability
  • Use override for virtual functions
  • Use [[nodiscard]] for accessors and functions returning values
  • Use noexcept for accessors
  • Use this-> for referencing all instance members
  • Use class name for referencing all static members.

6.2 Memory Management

  • Always use smart pointers over raw pointers, favour the most restrictive use
    • std::unique_ptr for exclusive ownership
    • std::shared_ptr only when shared ownership required
    • std::weak_ptr for breaking circular references
    • ObserverPtr for non-ownership
    • Use the QtPtr variants that match the same ownership semantics.
    • Never use a naked/raw pointer
  • Use RAII principles
  • Implement move semantics where appropriate
  • Container guidelines:
    • Reserve space when size is known
    • Use emplace operations over insert
  • Custom deleters:
    • Required for C-style resources
    • Must be noexcept
    • Should be stateless when possible

6.3 Const Correctness

  • Mark member functions as const when they don't modify object state
  • Use const references for parameters that shouldn't be modified
  • Use const auto& for range-based for loops on non-primitive types
  • Use const on all variables that are not modified

6.4 Testing

  • Name test files with -unit-tests.cpp suffix
  • One assert per test case
  • Test file structure:
    • One test class per tested class
    • Group related test cases in test suites
  • Test naming:
    • Class tests: {ClassName}UT
    • Test cases: methodName_scenario_expectedOutcome
  • Mock usage:
    • Use Qt-mocks for testing
    • Use appropriate test macros (QVERIFY_THROWS_EXCEPTION, QVERIFY_THAT, etc.)
    • Document mock behavior requirements
    • Reset mocks between test cases
  • Test coverage:
    • Aim for 100% branch coverage
    • Document intentionally uncovered code

6.5 Template Guidelines

  • Use PascalCase for template parameters (e.g., template<typename Value>)
    • No specific prefix or suffixes required
  • Template definition files (.tpp) must follow declaration structure:
    template<typename Value>
    qint32 drn::namespace::IndexedIncreaseDecreases<Value>::function()
    {
    ...
    }
  • Template specialisations are placed in the C++ non-template definition files.
  • All template parameters must be documented with
    Template Parameters

7. Versioning and API Compatibility

  • Follow semantic versioning (MAJOR.MINOR.PATCH)
  • Mark deprecated features with [[deprecated]]
    • Deprecated functions exist for one minor cycle before removal.
  • Source compatibility is required within minor versions
  • Binary compatibility is not required.
  • Deprecation process:
    1. Mark with [[deprecated]] and document replacement
    2. Remove in next major version
  • Document breaking changes in ChangeLog
  • File Storage Format Versioning:
    • Support one major version back for file formats
    • Use revision numbers for data schema changes
    • Use explicit version numbers
    • Include upgrade path in schema changes
    • Document file format changes in ChangeLog

8. CMake Guidelines

  • Use modern CMake (target-based approach)
  • Library naming conventions:
    • Static libraries: drn-{name}
    • Shared libraries: drn-{name}
  • Variable naming:
    • Project variables: ${PROJECT_NAME}_VARIABLE_NAME
    • Cache variables: Use CACHE with appropriate type
  • Compiler flags:
    • Platform-specific flags in conditional blocks
    • Common flags in shared configuration
    • Enable maximum warning levels
  • Dependencies:
    • Use find_package for external dependencies
    • Specify version requirements
    • Use imported targets

9. Documentation Standards

  • API Documentation:
    • All public interfaces must be documented
    • Include usage examples for complex interfaces
    • Document thread safety guarantees when relevant
    • Specify performance characteristics when relevant
  • Implementation Documentation:
    • Document complex algorithms with diagrams/flowcharts
    • Explain business rules and their rationale
    • Document any assumptions or invariants
  • Documentation Format:
    • Use consistent terminology throughout
    • Keep documentation close to the code it describes
    • Update documentation with code changes
    • Use proper grammar and complete sentences

10. Qt Guidelines

10.1 General Guidelines

  • Qt Property Usage:
    • Document notify signals
    • Use Q_PROPERTY for exposed properties
    • Define meta-types for custom types
  • Widget Hierarchy:
    • Set proper parent-child relationships
    • Clean up non-Qt resources in destructors
    • Use Qt's parent-child memory management
  • Do not use Qt containers
  • Use Qt's implicit sharing features

10.2 Signal Naming Conventions

  • Use present tense verbs to indicate what is happening
  • Start with a descriptive verb
  • Follow with subject/object if needed
  • Examples:
    // State changes
    void valueChanged(qint32 newValue);
    void statusUpdated(Status newStatus);
    void balanceIncreased(Money amount);
    // Occurring events
    void transactionPosted(Transaction transaction);
    void surveyCompleted(AccountNumber account);
    void errorOccurred(QString message);
    // Completion signals
    void calculationFinished();
    void importSucceeded();
    void downloadFailed(QString reason);

10.3 Slot Naming

  • Use imperative form (commands)
  • Should describe the action being taken
  • Name should reflect the response to a signal
  • Examples:
    // Action slots
    void updateBalance(Money newBalance);
    void processTransaction(Transaction transaction);
    void reconcileAccount(AccountNumber account);
    // Response slots
    void handleError(QString message);
    void refreshDisplay();
    void validateInput(QString text);
    // State change slots
    void setCurrentAccount(AccountNumber account);
    void markAsReconciled(TransactionId id);
    void enableEditMode(bool enable = true);

10.4 Signal-Slot Pairs

  • Use matching names for related signals/slots
  • Examples:
    // In sender class
    signals:
    void accountSelected(AccountNumber account);
    void transactionRequested(Money amount);
    void surveyInitiated(QDate date);
    // In receiver class
    public slots:
    void handleAccountSelection(AccountNumber account);
    void processTransactionRequest(Money amount);
    void startSurvey(QDate date);

10.5 Common Patterns

  • State Change Pattern:
    signals:
    void propertyChanged(Type newValue);
    public slots:
    void setProperty(Type value);
    void updateProperty(Type value);
  • Action-Response Pattern:
    signals:
    void actionRequested(Parameters params);
    void actionCompleted(Results results);
    public slots:
    void performAction(Parameters params);
    void handleActionResults(Results results);
  • Progress Pattern:
    signals:
    void operationStarted();
    void progressUpdated(qint32 percentage);
    void operationFinished(Results results);
    public slots:
    void startOperation();
    void updateProgress(qint32 percentage);
    void finishOperation(Results results);

10.6 Naming Rules

  1. Signal names should:
    • Indicate what happened/is happening
    • Use active voice
    • Be in present tense
    • Be clear and specific
  2. Slot names should:
    • Indicate what action will be taken
    • Use imperative form
    • Be specific about their purpose
    • Reflect their response nature
  3. Both signals and slots should:
    • Use camelCase naming
    • Be descriptive but concise
    • Include parameter types in name when relevant
    • Follow the general function naming guidelines

10.7 Widget Guidelines

  • Widget Initialization:
    • Initialise all member variables in construction
    • Use Qt Designer .ui files for complex layouts
  • Layout Management:
    • Prefer layout managers over fixed positions
    • Use consistent margins and spacing
    • Handle widget resize gracefully

11. Build System Standards

  • Target Organisation:
    • One library per major feature
    • Group related sub-features
    • Separate test targets
  • Use feature toggles for optional components
  • Installation:
    • Define component-based installation
    • Include required runtime dependencies
    • Support different installation prefixes
  • Build Types:
    • Debug: Full debug info, sanitisers
    • RelWithDebInfo: debug info, no sanitisers

12. Performance Guidelines

  • Resource Management:
    • Cache expensive computations
    • Minimise memory allocations
    • Use appropriate container types
  • Threading:
    • Document thread safety requirements
    • Use Qt's thread primitives
    • Avoid blocking operations in GUI thread
  • Optimisation:
    • Profile before optimising
    • Document performance requirements
    • Balance readability with performance