Skip to content
This repository has been archived by the owner on Jul 17, 2024. It is now read-only.

Latest commit

 

History

History
153 lines (105 loc) · 8.21 KB

StyleGuide.md

File metadata and controls

153 lines (105 loc) · 8.21 KB

Embedded Learning Library (ELL) style guide

Python

We use PEP 8 with one exception, we extend the 80 char line limit to 120.

C++

File names and extensions

Header files use the extension ".h". Definitions of functions that must be in the header should be moved to the end of the header file in a region surrounded by #pragma region implementation/#pragma endregion implementation blocks. Source code files use the extension ".cpp" if they are compiled into a .lib or .exe. Each file should typically contain a single class and its name should match the class name.

Projects

A project is a set of source files that compile into a single executable or library, or that implement a single template library.

Project naming:

Projects have camelCase names, preferably single word, such as "model", "utilities", "compile".

Directory, namespace, output naming:

All of the source code in a project should be defined in a namespace that shares the project name. All of the source files associated with a project should be contained in a directory that shares the project name. The executable or library generated by a project should share the project name.

Project directory structure:

Each project is contained in a directory. The directory contains a CMakeLists.txt file. The directory typically contains the following subdirectories:

  • "include", for h files
  • "src" for cpp files (unless the project defines a template library without cpp files)
  • "test" for source files that define unit tests, invoked via ctest
  • "doc" for documentation that does not fit in the source files themselves. For example, complex math algorithms may require detailed documentation in a LaTeX file, which would live in the doc directory

Additionally, the directory can contain a README.md file.

Naming

Names should be descriptive and self-evident, rather than terse. Use whole words rather than abbreviations. Example: use GetFunctionDefinition instead of GetFuncDef. Exceptions to this rule are:

  • Use "Num" as abbreviation for "Number"
  • Use Size for a member function that returns the size of an object (instead of GetSize)

Classes, structs, enum classes: use PascalCase, e.g., class MyClass {};

use camelCase for name of enum values e.g., enum MyEnum { valueOne, valueTwo };

Functions, member and non-member: use PascalCase, e.g., int GetValue() const { return _value; }

When possible, function names should be imperative mood verbs, e.g., GetValue() and Permute(). Specifically, accessor member functions should start with Get, e.g., GetOutputVector(). Exceptions to this rule are:

  • member functions that returns the size of an object, which can simply be Size()
  • type conversion functions, which can start with To, e.g., ToString()
  • functions that return the number of elements in a collection, which can start with Num, e.g. NumVariables()

Method and Function arguments: camelCase: e.g., int GetValue(int rowIndex, int columnIndex);

Member variables: Use _ (underscore) followed by camelCase, e.g., _myMemberVariable

Template typenames: Use PascalCase and add the suffix "Type", e.g., template <typename SparseVectorType>

Template variables that are not typenames: Use camelCase, e.g., template <size_t size>

File structure

All source code files should start with a header that specifies project name, file name, and author list (see example below)

.h files should indicate #pragma once immediately after the header

Next, indicate all #include statements. First, include files from the local project. Second, include files from other projects, grouping by project, ideally in an alphabetical order. Next include third-party library headers, like LLVM. Finally, include standard libraries.

////////////////////////////////////////////////////////////////////////////////////////////////////
//
//  Project:  Embedded Learning Library (ELL)
//  File:     FileName.h (libraryName)
//  Authors:  Author1, Author2
//
////////////////////////////////////////////////////////////////////////////////////////////////////

#pragma once

#include "OtherClassFromThisProject.h"

#include <proj1/include/ClassFromProjectProj1.h>
#include <proj1/include/AnotherClassFromProj1.h>

#include <proj2/include/ClassFromProj2.h>

#include <vector>
#include <string>

In certain cases, normal #ifndef-style include guards are required instead of using #pragma once. In this case, add the guard name in a comment after the #endif:

#ifndef MATRIX_H
#define MATRIX_H

...

#endif // MATRIX_H

Function implementations

Almost all function implementations belong in .cpp and in the implementation region of .h files. The exception is short single-instruction implementations of parameterless functions, which should appear in the .h file on the same line as the function declaration. For example:

double GetValue() const { return _value; }  // inline implementation in .h file

double SetValue(double value);  // function has parameters - implementation belongs in .cpp or the implementation region of the .h file

void Next() { _iterator++; _count++; }  // wrong: multi-instruction implementations belong in .cpp or the implementation region of the .h files

Virtual functions

virtual functions declared in derived classes should use the override or final keywords, not virtual. This is because override and final imply virtual. Removing virtual also protects against unknowingly adding another virtual member function instead of overriding a member function from a base class, especially in the case where a virtual function has been removed from the base class.

Unused variables

Variables that are declared or defined but never read from will emit an unused variable warning on all compilers. Avoid unused variables as much as possible. If you cannot remove the unused variable, then mark all such variables with the UNUSED macro. If a variable is only used in code that conditionally compiles for debug builds (such as assert expressions), it should be marked as such with the DEBUG_USED macro, instead of the UNUSED macro. Both UNUSED and DEBUG_USED are defined in Unused.h in the utilities library.

Formatting

Use clang-format to enforce correct formatting. Use the definitions in the file .clang-format, which is found in the main project directory.

Documentation

All public classes, functions, and variables that are declared in .h files should be preceded by an XML documentation block. The only exception is functions that are defined with =default or =delete, which should not be documented. Each documentation row should begin with three slashes (///) and the first line in a documentation block should contain a summary tag. Follow this example:

/// <summary> Mathematical power operation. </summary>
///
/// <param name="base"> The base number. </param>
/// <param name="exponent"> The exponent number. </param>
///
/// <returns> Base number to the power of the exponent number. </returns>
double Power(double base, double exponent);

/// <summary> Performs an in-place sort. </summary>
///
/// <typeparam name="RandomAccessContainerType"> The container type, which must implement a square bracket operator. </typeparam>
/// <param name="container"> [in,out] The container being sorted. </param>
template <typename RandomAccessContainerType>
void InplaceSort(RandomAccessContainerType& container);

Error Handling

The c "assert" function usually results in immediate termination of the program, so this should only be used in cases where it should never happen unless there is a logic error in our code. To this end assert documents the existing invariants, preconditions and post conditions in the code.

Bad input parameters from our public API or bad data read from a file should not result in this kind of assert termination because it is hard to debug, and it provides no useful information to the caller. Instead, throw the appropriate type of exception as defined in ~/libraries/utilities/include/Exception.h. This includes the notImplemented case, which you can raise using throw utilities::LogicException(utilities::LogicExceptionErrors::notImplemented);