Skip to content

Coding Guidelines

Felipe Olmos edited this page Nov 10, 2023 · 1 revision

(In Progress)

Style & Conventions

Note: These rules do not apply to code generated by third party tools (ex. Lex & Yacc).

Line length

DO NOT exceed 120 characters in each line. However try to not exceed 100 characters, and go for 120 chars only if unavoidable.

Indentation Character

DO use tabs to indent blocks.

Comments

DO NOT use /* ... */ style comments. Use only // ... style comments.

Names

DO use camelCase style for naming variables, methods, functions and classes. Classes and methods names should start in upper case.

// Bad
int n_trees;
void compute_total() {};
void computeTotal() {};
class my_class {};
class myClass {};

// Good
int nTrees;
void ComputeTotal();
class MyClass();

Variable Name Prefixes

DO use Hungarian notation for variables referencing simple classes and types. Specifically, use the following prefix conventions:

Simple types:

  • n for int type
  • b for boolean type
  • c for char type
  • l for longint type
  • d for double type
  • s for ALString class and char* type
  • li for LoadIndex class (rare)

Generic object:

  • o for Object class

Container types:

  • io for IntObject class
  • do for DoubleObject class
  • lo for LongintObject class
  • oa for ObjectArray class
  • ol for ObjectList class
  • od for ObjectDictionary class
  • nkd for NumericKeyDictionary class
  • ld for LongintDictionary class
  • lnd for LongintNumericKeyDictionary class
  • sl for SortedList class

Vectors:

  • cv for CharVector class
  • sv for StringVector class
  • iv for IntVector class
  • lv for LongVector class
  • dv for DoubleVector class
  • liv for LoadIndexVector class (rare)
Exceptions

You may name int with single letter names such as i, j and k when they are iteration indices.

Braces

DO use the Allman style for braces in blocks of code:

  • The opening brace must be placed in the next line and at the same indentation level of the previous line
  • The closing brace is at the same indentation level of the opening brace
  • Code inside the block goes at the next level of indentation.

Example:

// Bad
while (x == y) {
    // body
}

// Good
while (x == y)
{
    // body
}

Header File Structure

The header files (.h) MUST contain the following sections (in this order):

  • A #pragma once statement
  • The forward declaration of all the classes implemented in the corresponding .cpp file
    • The order is that of the definitions within the file
  • All the necessary #include statements for the declared classes (ie. include what you use)
  • The code of all inlined methods and functions

Example:

#pragma once

class MyClass;                    // forward class declarations
class AnotherClass;

#include "Utility.h"              // includes
#include "SomeDependency.h"

////////////////////////////////
// Class MyClass
// Provides "My" services
class MyClass: Object             // class prototype definitions
{
    // body
};

///////////////////////////////
// Class AnotherClass
// Provides "Another" services
class AnotherClass: Object
{
    // body
};

/////////////////////////////////////
// inline methods and functions

inline int MyClass::SomeHeavilyUsedAccessor()
{
    // body
}

Class Declaration Structure

Class MUST be declared in a header files. A comment banner MUST precede them explaining their purpose. A class declaration MUST contain the following sections (in this order):

  • Public interface (public section)
    • Constructor and destructor declarations
    • Public method declaration
  • Implementation (protected section)
    • Banner
    • Protected methods
    • Parameters
    • State variables/objects

Constraints:

  • DO NOT use private sections
  • DO NOT put method implementations in the class declaration

Example:

/////////////////////
// Class Example
// Showcases the style of a class declaration
class Example: public Object
{
public:
  // Constructor
  Example();
  ~Example();

  // Context parametrization
  void SetContext(ContextObject newContext*);
  const ContextObject* GetContext() const;

  // Access to count
  int GetCount() const;
  void SetCount(int n);

  /////////////////////////////
  //// Implementation
protected:

  // Parameters

  // Context
  ContextObject* context;

  // Work variables

  // Count
  int nCount;
}

Exceptions

private sections may be used in low-level and/or tricky code.

Constructor Method Declaration

Method and Function Implementation

Non inlined functions or methods MUST be implemented in a .cpp file. All local variables MUST be declared at the beginning of the method in a single block before any code is executed, even their initialization.

Example:

void MyClass::DoSomething()
{
    int i;
    ALString sSomeWord;
    ObjectDictionary* odMyDict;
    SomeOtherClass* objectFromAnotherClass;

    i = 0;
    sSomeWord = "hola";

    // Rest of the code
}
Exceptions

TODO

Member Access

DO declare all class member object/variables as protected.

  • For class parameters DO implement both Get and Set accessors.
  • Internal and work objects may implement a Get accessor.

Within the class or subclasses DO prefer accessors to interact with class members instead of doing it directly.

Comments and Documentation

In the header comment every coherent block of functions related between them (e.g. getters and setters usually are commented as a block).

Classes must have a comment describing its functions and services

DO comment your code as if it were a document: A comment for each paragraph of code. A paragraph can be short if the method called is complex.

C++ Features

The C++ subset that Khiops uses is very minimal and most common C++ features and libraries are not allowed.

const keyword

DO use const whenever possible:

  • const methods definitions to check at compile-time that they do not change the state of the calling instance
  • const arguments in functions/methods to check at compile-time that they are not modified by the method

An object is modified if it changes the value of one of its member fields or if it calls a non-const method.

Note

Due to a technical difficulty, the containers' accessors methods such as ObjecArray::GetAt(int n) return non-const objects. So it may happen that you are unable to use const is parts of the code even if it is semantically correct.

override keyword

DO use the override keyword in the implementation of inherited methods. The compiler will generate an error if the method does not exist in the base.

Method Overloading

DO NOT use method overloading. Instead find specific names for functions and method.

// Bad
void MyString::Append(MyString sTail);
void MyString::Append(char cTail);

// Good
void MyString::Append(MyString sTail);
void MyString::AppendChar(char cTail);

C++ References

DO NOT use C++ references.

Basic types (int, longint, double, boolean) are handled without pointers whereas complex classes are handled with simple C pointers.

Exceptions

  • The ALString class which is handled as follows:

    • They are declared without pointers
    • As function parameters they are passed as C++ references
    • As functions return values:
      • Use const ALString& if the called object have its memory responsibility (ex: accessors)
      • Use const ALString if the callee object have its memory responsibility (eg. Creator pattern)
  • The KWSymbol class also may use references.

Inheritance

DO NOT use multiple inheritance.

All classes MUST must inherit from a single class derived from Object or by Object itself. The Object class implements many utilities such as logging (eg. AddError). Inheritance MUST be public.

Note

Before of making a sub-class to reuse code, think if there is any way to achieve the same by composition.

Method and Function Inlining

DO use inline in methods that a-priori can be called often. Good candidates are:

  • accessors
  • critical code called within a loop

When in doubt DO NOT inline.

std library

DO NOT use std libraries.

Exceptions

  • fstream classes and its related cout and cin built-in streams
  • regex in the regular expression support module
  • chrono for timer and testing code

C++ Templates

DO NOT use.

C++ Exceptions

DO NOT use.

C API

DO NOT use.

Exceptions

Very specific low level code (rare).

Third party libraries

DO NOT use.

This includes the popular Boost library. All the basic and utility classes (eg. strings, containers) are implemented in the Norm library.

Exceptions

  • MSMPI for Windows
  • MPICH in Linux and MacOS
  • Googletest for unit testing (development dependency only)

Guidelines Implemented with the Norm Library

The Norm base library provides many functionalities that are key for the Khiops development process.

Programming by Contract (Assertions)

DO follow the programming by contract paradigm.

To this end, then Norm library provides assertion macros to test the state of the program. For example:

assert(oInstance != NULL)

will make the program exit abruptly if oInstance is a NULL pointer.

Norm provides the following three macros:

  • assert: Generic assertion to be used anywhere in the code
  • require: Asserts a method pre-condition (must be put after variable declarations)
  • ensure: Assert a method post-condition (must be put before the return statement)

Note that while semantically different, the mechanism of each assertion on failure is the same: exit abruptly the program.

Do not hesitate to add assertions to make explicit hypotheses on the state of the program as they can be seen as executable comments.

Example

// Divides two positive numbers
int SomeClass::SlowDivide(int nDividend, int nDivisor) const
{
    int nResult;
    int nRemainder;
    int i;

    require(nDividend >= 0);  // Assert the positivity pre-condition
    require(nDivisor > 0);

    // Return zero if the divisor is larger
    if (nDividend < nDivisor)
    {
        nResult = 0;
        nRemainder = nDivisor - nDividend;
    }
    // Try each multiple of divisor until finding the result
    else
    {
        for (i = 1; i <= nDividend; i++)
        {
          assert(i * nDivisor <= nDividend);  // Assert the loop invariant
          if ((i+1) * nDivisor > nDividend)
          {
              nResult = i;
              nRemainder = nDividend - (i * nDivisor);
          }
        }
    }

    ensure(nResult * nDivisor + nRemainder == nDividend);  // Assert the results post-condition

    return nResult;

}

Note

The Khiops codebase has thousands of assertions, many executing the Check method inherited from Object, which may contain exhaustive checks an object's state. For this reason assert macros are available in debug builds. In release builds they are converted to empty statements to favor execution speed.

TODO

  • private vs protected
  • paragraph comments
  • constructeur vide