Coding Guidelines
=================
:Date: Autumn 2024
:toc:


.Basic Tenet
Code is written for humans. Use your judgement and common sense above all.



Style Guide
-----------
The Lumiera project uses GNU indentation style with slight adaptations.

- *no tabs* please. +
  The typical ``semi indent'' of GNU style thus becomes 2 spaces.
- maximum line length is rather around *110 characters*.footnote:[This is not a hard
  limit, rather a guideline -- however, you should never try to stuff several distinct
  topics into a single line...]
- originally, GNU style focused on plain-C code. +
  We thus apply some relaxations and clarifications ...
  
  * the braces for a class scope are indented by 2 spaces
  * the access modifiers start at this brace level, while all declarations and definitions
    within the class are again indented by 2 spaces
  * the braces for free-standing functions however are not indented and thus placed directly
    below the name of the function in the definition. This holds also for member functions
    defined out-line and not within the class body.
  * the line breaking rules are relaxed. Definitions and statements may be written as single line,
    provided that the length remains below 110 chars and the result _remains legible_.
    More specifically...
  
    ** function declarations may be written in one line
    ** same for definitions with just a single statement in the body
    ** same for simple if-statements without else-branch.
    ** If in doubt however, we prefer to wrap the lines.
  
  * it is OK to write if-then-else without braces, but only as long as the structure
    is uttermost clear and poses no danger for maintenance. Use your common sense on this one.
    Often, a ternary operator is a viable alternative. When a `?` ... `:` becomes long and
    hard to read, the alternative clause starting with `:` is placed directly below the `?`
  * the space between function name and opening parenthesis of the argument list is not
    enforced when this doesn't make sense, notably for argument-less functions, chained calls
    or constructor syntax. But in all other cases, we really value this additional space,
    it improves legibility.
  * template argument declarations are _always_ written on a separate line, above the
    return type declaration. This rule holds even if the rest of a definition can be
    written within a single line.
  * the body of λ-functions is indented beyond the capture clause, i.e. it is rather
    starkly offset with respect to the regular code. When capture clauses become complicated
    beyond just a short list of names -- especially when including initialisers -- they are
    preferably written on a separate line, directly above the argument list in parenthesis  
  * the opening brace of namespaces is placed on the same line. Optionally, the namespace
    body may be indented, as long as this helps underpinning the nesting structure. But
    there is no need to use 3 indents on a 3 level nested namespace. One level is enough
    to highlight the presence of a nesting.

- the shorthand type names `uint`, `ulong`, `uchar`, `short`, `ushort`, `llong`, `ullong`
  are preferred, as it is clearer to write the fundamental type in a single word. Index
  variables _should always be unsigned._ Use `size_t` whenever applicable.footnote:[yet it is OK
  to use `uint` in a for-loop where the limits are clear; likewise it is acceptable to use
  a direct conversion signed ⟷ unsigned when the situation is simple and obvious. No need
  to be pedantic on everything]
  When the bit-size matters, please use explicit designations like `int32_t`, `uint64_t`.
- we prefer to highlight the _typing as distinct_ from the names and entities; notably
  the `*` pointer and `&` reference modifier are attached post-fix to the type, as opposed
  to sticking it to the name: write ```int* thing`'', not ```int *thing`''. Whenever types
  become difficult to parse, we introduce a type alias (`using Alias =`... with an uppercase
  type alias name). This is especially relevant with function pointers, where we prefer
  to define a type alias for the _function signature_ first.
- in a similar vein, we distinguish between an ```const object`'' which _is really in-itself_
  unmodifiable, as opposed to _taking a const-reference,_ which is always spelled out
  post-fix as `object const&`, or a constant-pointer spelled as `const*`.
- we use the textual form of the boolean operators `and`, `or` and `not`, because this
  allows to make the code resemble complete meaningful sentences in human language.
  We _do use_ the »line noise« variant however for all low-level bit manipulation,
  `&`,`|`, `^`, sometimes also for `!` -- using those is taken as an indication of
  entering the ``danger zone''...


Spelling
~~~~~~~~
Lumiera uses _British spelling._ Please set your spell checker accordingly.

Naming conventions
~~~~~~~~~~~~~~~~~~
Naming conventions are used to characterise the kind of element at hand and give a visual
clue to the reader. We use our own conventions -- there is no point in arguing that this
and that library or language uses other conventions.

- type names start with an uppercase letter
- variable and function names start with lowercase.
- fields within a class, especially the private ones are decorated with a trailing underscore
- a leading underscore may be used to emphasise the strictly internal or technical nature of a type,
  variable or function
- namespaces are all-lowercase
- macros and constants are preferably all-caps (at least where this makes sense)

There is a preference for *CamelCase* -- yet underscores are acceptable, especially when the
name is more like a sentence than just a compound term.

plain-C names
^^^^^^^^^^^^^
Since C has no namespaces, we strictly require a +lumiera_+ prefix on all non-local names and definitions.
Generally, names should be formed according to the following pattern:

 namespace[_object][_verb[_subjects]][_version]

In case a definition actually denotes an object, there should be

- a basic struct name: `typedef struct namespace_foo_struct namespace_foo;`
- plus an object pointer/handle: `typedef namespace_foo* NamespaceFoo;`

The object pointer/handle should be passed as 1^st^ argument with the name +self+


General Code Arrangement and Layout
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Headers and translation units are named `*.hpp` and `*.cpp` rsp. `*.h` and `*.c` +
  Multilingual headers are called `*.h`
- Each header should be named according to the primary facility it exposes. For the filesystem name,
  the +CamelCaseWords+ of this type are translated into +camel-case-words.hpp+
- Each file should start with the GNU licence preamble. The headline should give a one-line summary.
  The primary author(s) and the year of the initial copyright claim should be mentioned.
- Each header should be focused on a specific purpose. Preferably it starts with a file-level
  doxygen comment explaining the intention and anything not obvious from reading the code.
  At lest a `@file` tag with one line of classification in a doxygen comment at the top of every
  file is mandatory.footnote:[This rule stands simply because, without such a file-level doxygen
  comment, doxygen will _ignore all contents_ of this file (really, might be surprising, yet it is
  the way it is...)]
- when arranging headers and compilation units, please take care of the compilation times and the
  code size. Avoid unnecessary includes. Use forward declarations where applicable.
  Yet still, _all immediately required direct dependencies should be mentioned_, even if already
  included by another dependency. See the extensive discussion of these
  link:{ldoc}/technical/code/linkingStructure.html#_imports_and_import_order[issues of code organisation]
- The include block starts with our own dependencies, followed by a second block with the library
  dependencies. After that, optionally some symbols may be brought into scope (through +using+ clauses).
  Avoid cluttering top-level namespaces. Never import full namespaces.footnote:[No `using namespace gtk;`
  or `using namespace boost` please! Experience shows, in the end you'll be using 5 names or so, but
  pull in all the others just for sake of laziness. Just type the f**g `using` clause for every
  import individually, and we'll all be better off...]
- the includes for our own dependencies shall be given relative to source-root (or test root). Don't use
  relative includes for headers located in the same directory, or -- worse still -- in the parent directory.
- sometimes, the actual translation units will combine several facilities for technical reasons.footnote:[To
  elaborate, there can be ``headers'', which are in fact only intended for inclusion at one or two distinct
  places. This should be mentioned in the file-level comment, but generally is an acceptable practice,
  and better then lumping everything into a 1000 lines header. As a guideline, if you expect a rather
  technical concern not to be of much interest for most readers of a header, then better extract it into
  a separate self-contained header and include it. E.g., you might be sharing an implementation-level
  class or even singleton instance and some constant definitions.
  Just be sure not to include definitions several times.]
  Anonymous namespaces should be used liberally to avoid unnecessary exports.
- template code mostly needs to reside in headers. (same for metaprogramming code).
  We employ the simple inclusion model (``Borland model'') for template instantiation.
- But in some specific situations it is preferable to drive explicit instantiations from within
  a +*.cpp+ file. Most notably this is the case when defining some core class hierarchies.
  Such explicit instantiations should be limited to just a view obvious places. They should be
  written into a block at the end of some central implementation file. See +assetmanager.cpp+
  for an example.
- deliberately there is __no single top-level namespace.__footnote:[We do not want to encourage
  the emergence of _central locations,_ where global definitions for each and everyone might be
  stuffed and buried. If you want to share something, then please create an interface and expose
  a service, together with definitions for your clients to use.]
  The +namespace lumiera+ is the root of our _exported interfaces_ -- intended for use by external
  scripts and libraries. Everything implementation related is arranged in per-subsystem trees of
  namespaces. The APIs of the subsystems are exported explicitly into the +lumiera+ namespace.


Design Guidelines and Values
----------------------------
Code is written for *being read by humans*; code shall convey its meaning even to the casual reader.
On the long run, this language nature of code is more important than any performance tweaks. Recall,
every idiot can figure out how to make a computer perform something. Yet the real challenge is to
write legible code. Code that operates exactly the way you'd guess just from reading it.
Black magic and all kinds of surprise trickery and cleverness are nothing to be proud off.

-> please have a look at the link:/project/background/CleanCodeDevelopment.html[Clean Code] page
for a coherent system of design principles

Recommendations at code level
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

- *Inversion of Control* is the leading design principle
  +
  * ``don't call us, we call you...''
  * avoid lumping everything into a single point-and-shot action
  * decompose into _Services_ meaningful as such, which are self-contained and can be tested;
    _ask_ for services instead of fumbling with other part's innards.
  * avoid _shared data models_ and _coordination via flags_ -- prefer messaging
    and represent processes and interactions as first-class entities.
  +
- clearly distinguish between *value semantics* and *reference semantics*
  +
  * if something can be distinguished as an entity, has a distinct identity, needs to be built,
    managed and tracked, then treat it with reference semantics.
  * always consider the _ownership_ and _lifecycle_ -- value objects can not have any, should
    ideally be immutable, and stored inline or avoid heap allocation. If in doubt, decompose
    and turn unclear and changing parts into a service / dependency, attached by (value) handle
  * objects with reference semantics should be made _non-copyable_, while value objects should use
    default copy semantics. Use 'lib/nocopy.hpp' to express the flavours of move only / no copy.
  * equality comparisons of ref objects are to be based on their identity solely, while for
    value objects, all properties must be included which are tangible and independently variable.
  +
- make liberal use of interfaces and abstractions; provide points of access
  +
  * if implementation instances with reference semantics need to be obtained or registered,
    then create a static factory function called `create` or `register` or `attach`
  * if some dependency is ``just required'', then use a static accessor `instance`, which
    can be implemented with Lumiera's dependency helper 'lib/depend.hpp'
  +
- avoid implicit assumptions -- better express them as type
  +
  * if is something is ``just a number'' yet has some specific meaning,
    better use a lightweight wrapper object to tag it with semantics
  * if a common pattern works by involving distinct, unrelated entities,
    then better use generic programming or even higher-kinded types,
    instead of forcing unrelated types to inherit from some supertype.
  * avoid downcasts, `void*` and switch-on-type programming; this
    programming style bears an attitude of carelessness and tends to produce
    highly tangled, scattered code hard to maintain over time.footnote:[There
    are valid exceptions to that rule though; in some parts, Lumiera has to deal
    with low-level and high-performance computing where every extra byte needs
    to be justified. However, in such cases it is always possible to _encapsulate_
    the performance critical parts into _opaque types,_ which are clearly fenced
    by visibility rules and thus recognisable as highly coupled implementation details.]
  +
- care for lifecycle issues
  +
  * avoid performing anything non-local during the startup- or shutdown phase
  * especially avoid as much as possible to do anything substantial in destructors
  * if something depends on contextual state, better make that explicit
  * however -- even better to avoid state at all: prefer a builder or use a new type
    at the point of state transition
  +
- be precise and consider error situations at every point
  +
  * if you feel like assuming something, document this through assertions
  * if you can not be sure, then better check and abort by throwing
  * be prepared for exceptions to be thrown everywhere -- embrace RAII
  * exceptions are fine, but never build logic on top of exceptions or depend
    on them: the so called ``checked exceptions'' tend to create unnecessary coupling.
  * use exceptions only for exceptional situations, never for signalling something.
  * only _handle_ errors which you _really can handle right here and now_
  * avoid to ``fix'' or ``amend'' a situation by assumptions -- _let it crash_
  * however -- ``crash'' always means a clean wind-down -- never leak anything


