LIBPF® Coding Standard
Introduction
Intended audience
This document addresses those who wish to develop models with LIBPF® (model developers).
Scope
This document describes the suggested coding standard for LIBPF®, the LIBrary for Process Flowsheeting version 1.1 and UIPF, the User Interface for Process Flowsheeting version 1.1.
Also see (1), (2), (3), (4) and (5) for more generic stuff.
Prerequisites
- a basic knowledge of the C++14 programming language is required, see (9)
Files
Programming language: use C++14 (8), with no proprietary extensions.
File extensions: use h for headers, and cc for C++ sources.
One file per class, file name same as class name; exceptions: homogeneous, small classes and classes within namespaces can be grouped in a single file.
File encoding: use 7-bit US-ASCII (we don’t allow UNICODE in source files for this reason):
Include guard in header files, use PROJECT_FILENAME_H macro, replace PROJECT substring with {LIBPF, UIPF* and replace FILENAME substring with uppercase file name without extension.
Header file skeleton
Source file skeleton
Types and constants
-
character constants and internal character representation: UTF-8; if you need to encode UNICODE characters in string constants, use L’\u0000’ representation to UTF-16 encoded wchar_t - see (9) C.3.3 Large Character Sets. Then convert from wchar_t to internal representation UTF-8 char using ToUtf8 function, see UtfConverter documentation.
-
integers: except for interfacing with external APIs, use int because of (4), “integer types”. Do not use long, short.
-
real numbers: use double precision floating point numbers (double).
-
enumerations: use SmartEnumerator whenever possible.
Identifier naming conventions
-
For classes and enum classes: upper CamelCase (7):
MyClass
,MyEnum
; -
For instances, variables and function names: lower camelCase (7):
myClassInstance
,myString
or snake_case (11):my_class_instance
,my_string
-
Names of variables private or protected to a class: same but postfixed with underscore:
i_
,val_
,bufferTemp_
; do not use m_variable convention. -
One-letter uppercase names are preferred for template arguments (T, U, V, E ….).
-
All-uppercase names of more than one letter are reserved for preprocessor defines.
-
Acronyms are camelcased as in:
EosBase
,Nrtl
.
The order of the words is by decreasing importance (so that when classes or identifiers are sorted alphabetically, homogeneous items are naturally grouped), i.e. the reverse w.r.t. the intuitive ordering in English and German.
First example: Donau-Dampf-Schiff-Fahrt-Gesellschaft-Schiffs-Kapitän-Kabine class name should be: KabineKapitaenSchiffGesellschaftFahrtSchiffDampfDonau
.
Second example: the instance name for the yellow box is: boxYellow
.
Getter/setter: for getter use variable name, for setter use setVariable.
Comprehensive example:
class MyClass {
private:
long myVar_;
public:
long myVar(void);
void setMyVar(long i);
};
MyClass myInstance;
Abstract classes
Interface
-suffixed classes are for pure abstract types that provide no implementation (13).
Most other non-user-facing classes will be mixin classes: abstract types that provide an incomplete implementation, to be used as part of a derived or “including” concrete type to compose a complete implementation (14).
Modules
Module names are single word, lowecase.
Use singular for modules that provide a single class or functionality (core, flowsheet, persistency, utility, user, value) and plural for modules that provide a group of classes (components, phases, streams, units).
TODO flowsheet dove sta ??? TODO add module reactions ?
For API stability and ease of use, there is a single include file for each module:
- libpf/components.h (was: libpf/components/purecomps.h)
- libpf/core.h (was: libpf/core/libpf.h)
- libpf/flowsheets.h ??? (was libpf/flowsheet/flowsheets.h)
- libpf/persistency.h ???
- libpf/phases.h (was: libpf/phases/phases.h)
- libpf/streams.h (was: libpf/streams/streams.h)
- libpf/reactions/reactions.h ??? (was: libpf/units/reactions.h)
- libpf/units.h (was: libpf/units/units.h)
- libpf/user/ ???
- libpf/utility/ ???
- libpf/value.h (was: libpf/value/Value.h)
and a single global include file libpf/libpf.h
that includes all the per-module ones.
Namespaces
The whole library is within the Libpf
namespace. User-facing types must be in this flat namespace.
Nested namespaces should be used for module-internal, non-user-facing types.
The module namespace must be the module name, uppercased, singular:
-
utility module:
namespace Utility
-
phases module:
namespace Phase
Example class names for phases module:
-
user-facing:
PhaseActivity
PhaseEos
PhaseGerg2004
PhaseIapws
PhaseIdeal
PhasePcsaft
PhaseSimple
PhaseSimpleTotal
PhaseTotal
-
internal pure abstract:
Phase::Interface
(was: PhaseInterface)Phase::Activity::Interface
(was: Phase::Activity::Abstract)
-
internal mixins:
-
Phase::PropertiesInterface
(was: PhaseInterfaceProperties) -
Phase::Mass
(was:PhaseM) -
Phase::MassMolar
(was: PhaseMN) -
Phase::MassMolarProperties
(was: PhaseMNProperties) -
Phase::EosCubic
-
Phase::Gerg2004
-
Phase::Iapws
-
Phase::Ideal
-
Phase::Pcsaft
-
Phase::Total
-
-
internal mixins that should become private:
Phase::Activity::Nrtl
Phase::Activity::Unifac
Phase::Eos::Interface
Phase::Eos::Cubic
Phase::Eos::VanDerWaals
Phase::Eos::KwongPengRedlichRobinsonSoave
Phase::Eos::KwongRedlichSoave
Phase::Eos::PengRobinson
Phase::Eos::Gerg2004
Phase::Eos::Pcsaft
Compilation and link warnings
Follow the guidelines in (10).
In particular:
- try to avoid warnings messages as far as is reasonably practicable, even when compiled with the highest and most pedantic warning level, avoiding vendor specific extensions if possible
TODO update here !
-
gcc: -Wall -Wextra -pedantic -std=c++14
-
msvc: /W4
Formatting
Do not use tabs, use two blanks for indenting.
Uncrustify indenting standard: see file uncrustify.cfg, tested with uncrustify 0.48
In declarations and definitions use (void) rather than () for a function without parameters.
For an empty code block use { } not ; or {}.
Comments and documentation
Code reading helps
Use this 120-columns, 3-line heading to separate classes whenever there are several interfaces / implementations in one file:
/*====================================================================================================================*/
/*== ClassName =====================================================================================================*/
/*====================================================================================================================*/
Decoration: repeat the member function name after the closing curled bracket, skip return value, templates and arguments unless there are several overloads
template<class T> void MyClass<T>::Member(void) {
} // MyClass::Member
template<class T> void MyClass<T>::Member(int) {
} // MyClass::Member int
Group and highlight the getter/setter members, the pure virtual members, the implementations for parent classes’ pure virtual members.
Literate programming
Use doxygen-style literate programming (6), details follow.
-
In all files:
-
@file
-
@brief
-
@author
-
-
In header files:
-
for each class, brief description using the special C++ style on-line comment with ///
/// Only a short description for the class
-
for each method, brief description using the special postponed C++ style on-line comment with ///<
long var; ///< Only a short description after the member
-
-
In source files:
-
(if applicable) for a class, detailed description using a block of at least two C++ comment lines, i.e. the a C++ style multiline comment with ///
/// ... text ... /// ... text ...
-
(if applicable) for a method:
-
detailed description using the special C++ style multiline comment with ///
-
return using @return
-
parameter documentation using @param
-
documentation on thrown exceptions using @exception
-
-
Error reporting
To decide what error reporting mechanism to use in library production code, use this checklist:
-
for non-fatal errors, call the dedicated API (setError for LIBPF®) so that execution continues
-
for fatal errors, due to exceptional, unlikely, or erroneous situations, but not impossible situations (in particular due to errors in calling the API from the part of the library user or the function being unable to fulfill the promise that it made in the contract to the calling code), throw exceptions; the library user can catch them and perform stack unwinding to locate the problem; it should always be possible to produce a test case which exercises a given throw statement
-
to check something that should always be true regardless of the inputs or computations performed (= sanity check), use asserts as debugging aids in developing the algorithms and to document them; it should never be possible to produce a test case which causes an assertion to trip
-
use C++11 static_assert if the condition can be evaluated and checked at compile-time (11)
-
use the
assert
macro from the <cassert> header, also known as a C assert or run-time assertion, to check run-time conditions and abort execution if the condition is not met
-
References
-
Geotechnical Software Services, C++ Programming Style Guidelines, Version 4.7, October 2008
-
…
-
Herb Sutter and Andrei Alexandrescu, C++ Coding Standards
-
ISO, “Programming Language C++” ISO/IEC 14882:2014
-
Bjarne Stroustrup “The C++ Programming Language” 4th ed. Addison-Wesley 2013