C³: Common Coding Conventions#

https://google.github.io/eng-practices/

https://github.com/tum-esi/common-coding-conventions

The goal of these conventions is to be concise, universal, and remarkable. It targets emerging code enthusiasts under time pressure and covers 7 topics:

  1. General Clarifications,
  2. Architecture,
  3. Implementation,
  4. Naming,
  5. Code Layout,
  6. Documentation, and
  7. Languages.

To follow this guide, you should already have heard about Object Oriented Programming and know basic programming rules, such as writing loops and meaningful functions instead of copy pasting instructions. In this Readme, we will shortly summarize the most important rules for every topic.

Since our brains are sieves, try to remember the underlying philosophy of this guide:

Keep it simple and solid, let the toolchain be smart,
code correctness is the duty, readability the art.

General Clarifications#

Be consistent with the existent.#

“Consistency with this guide is important. Consistency within a project is more important. Consistency within one module or function is the most important” [PEP8].

Break rules if it enhances clarity.#

Do not blindly follow this guide but think for yourself. The goal of software development is keeping the code complexity low. Period. None of the fancy patterns matters if the code becomes impossible to maintain.

Leave code cleaner than you found it.#

We all have seen bad code, we all have written bad code. Code tends to get messy over time. Therefore, do not only complain about bad code but improve it if you know how. If you don’t care to do it, why should anyone else?

Terminology#

To be universal, we group several concepts under these broad identifiers:

  • Scope = {Module, File, Namespace, Subprogram}
  • Subprogram = {Procedure, Function, Method}
  • Type = {Primitive, Collection, Struct, Class, …}
  • Collection = {Array, List, Tuple, Dict, Map, …}

Architecture#

To manage complexity, divide your software into smaller parts (scopes) such as modules, classes, and subprograms. Where to separate and where to connect scopes is the art of good architecture.

Two common mistakes in architecture design are the creation of a monster class that has too many responsibilities and letting each class communicate with too many other classes:

Bad Architecture

Instead, limit the amount and direction of information exchange between classes and create larger architectures from the following building blocks:

Good Architecture

Aim for low coupling between classes.#

You may have many classes but each class should communicate with as few others as possible. If any two classes communicate at all, they should exchange as little information as possible.

Aim for coherent abstraction levels.#

Each scope should reflect a single coherent level of abstraction that corresponds to its hierarchy level [ClCd]. In your UML, abstraction should decrease from top to bottom. In your code, the deeper you are in a call tree, the more specific your instructions can be. Avoid state variables at high abstraction levels.

Bad ❌

engine.start()
nStartUps += 1
if fuelTank.isEmpty():
  fuelGaugeUI.setRedLED()

Better ✔

engine.start()
engine.runTest():
warnings = engine.warnings()
dashboard.show( warnings )

Caution: Mixing abstraction levels creates confusion and introduces unnecessary dependencies.

Additional Architecture Rules#

Be open for extension, but closed for modification.#

If you need to add a new feature (logic), the architecture should only be extended with no or little modification. How? Separate class methods that could have several behaviors and encapsulate them behind a new interface. Creating multiple, specific interfaces is better than one giant, general-purpose interface that needs to change. Ask yourself: If you add a new class (e.g. new feature) to your design, how many classes have to change?

Separate models from views and control (MVC).#

A model (program logic+states) is the heart of each module and operations on the open heart are dangerous. Therefore, larger modules should protect the model from

  • direct input by having a controller that preprocesses requests
  • direct output by having one or more views that provide different output formats

External modules can access the controller (if they want to change the model’s state) or access the views (if they want to know the model’s state). See MVC.

Depend on abstractions, not implementations#

Abstract superclasses and interfaces allow you to hide implementation details, which reduces dependencies. In UML, associations between semantically different objects should preferably point to interfaces, instead of concrete classes. In code, statements such as include or import should mostly bring interfaces and abstract classes into the scope. E.g. instead of calling display3.setLED(2, red) directly, use an abstract GUI.warnUser(). If you depend on concrete classes, make sure they are small, singletons, closely related, or really stable (e.g. String in Java).

Implementation#

Once you have an initial plan for the architecture, you can start writing the code for classes and subprograms.

Don’t Repeat Yourself (DRY).#

Any piece of data or logic should have a single source. Duplicated code is difficult to maintain and represents a missed opportunity for abstraction. As a rule of thumb: If you repeat more than 2 statements more than 2 times, write a new subprogram. At best, any atomic change in program logic should only affect a single line of code.

There are two code smells that should remind you of this rule:

  • Copy & Paste: Every time you take your mouse and mark lines with the intention to copy them, you are going to violate this rule. Instead of slightly adjusting copied lines, think about the common pattern between those lines and create a new function.

  • Repeated if-else and switch-case statements: If you are testing the same conditions at different locations (e.g. state variables), you can abstract these differences with Polymorphism and the Strategy Pattern.

Keep all scopes (file/class/function) small and sorted.#

Define subprograms and variables in the smallest scope possible and limit their exposure to external code. Put declarations at the beginning of each scope and initialize variables directly at the declaration. Do not reuse variables in nested scopes or for different purposes.

Express ideas in code: use domain-specific names.#

Avoid magic numbers (literal numbers with unclear origin/purpose) but always create constants with meaningful names. Create new types or derive subtypes from primitives to create more specific names, especially for physical quantities.

Bad ❌

double limit = 13.89;        // unit not clear
from_to(int x,  int y,
        int x2, int y2);     // vague argument names 

if (speed > limit && 
   t.h > 22 && t.h < 6){ ... }  // 22 and 6 are magic numbers

Better ✔

MeterPerSecond limit = SPEED_LIMIT_NIGHT;
drive(Point origin, Point dest);

isNight    = (T_NIGHT_MIN < t.h && t.h < T_NIGHT_MAX);
isSpeeding = (limit < speed);
if (isSpeeding && isNight){ ... }

Sort arguments and limit them to 0 – 4 per call.#

If the argument list of a subprogram grows too long, try to combine related arguments in a new data structure. For example, instead of passing x, y, z coordinates individually, use a single vector.

Do not change the same variable in steps but compose once from parts.#

Within a subprogram, do not modify the same variable in several steps, e.g. by summing up an amount using total += ... multiple times. Instead, call functions that return some part of the final value and then compose the final value of these parts in one instruction at the end. E.g. total = partA + partB.

Bad ❌

totalIncome = 0 
// ... long code to get contract
totalIncome += contract.salary  
// ... long code to access taxoffice
totalIncome -= taxoffice[id].tax

Better ✔

Int totalIncome(employee){      
    salary = getSalary(employee)
    tax    = getTax(employee)   
    return (salary - tax)       
}

Additional Implementation Rules#

Prefer early (compile-time) checking over late (run-time) checking#

If possible, use all of the following mechanisms:

  1. See the compiler as your friend and care for its warnings. The goal is not to write code that compiles but code that is correct (and readable).
  2. Use a linter. A linter will check your code while you type and provides the earliest feedback.
  3. Use static typing with many individual types to detect type mismatches early.
  4. Use static asserts to detect unintended changes to your code.
  5. Use exceptions with meaningful trace-output as your last line of defense. Consider any OS-Error(e.g. segfault) or direct microcontroller-reset as the worst-case and as an indication that your workflow – not only your code – is flawed.

Code simple, not smart (KISS) and only what you need (YAGNI)#

Keep it simple and stupid (KISS): Write straightforward code to achieve your goal. If you use a compiler, do not try to outsmart it. Compilers are really sophisticated and can optimize simple code much better than you.

You Ain’t Gonna Need It (YAGNI): Don’t code out every little detail that you can possibly think of. Instead, implement only what is absolutely necessary but be open for extensions.

Structure Files into 1. Imports, 2. Constants, 3. Publics, 4. Privates#

Structure each code file into parts: 1. imports/includes, 2. lobal constants, 3. public classes/functions, 4. private classes/functions, and optional 5. direct instructions in the main file. Keep these parts in order and separated, e.g. do not include files or define globals within a function declaration. Provide comment headings for these parts such that it easy to identify them when scrolling through the file.

Subprograms should either handle states or calculate results#

Most languages do not distinguish between procedures and functions but it is useful. Procedures modify states and may return a value. Pure functions are stateless and always return a value (or throw an exception otherwise). At best, a function should only work with the input variables to make clear on which data it depends. If your subprogram does change a state and returns a result, make sure to name it as a procedure (see 3.1{reference-type=“ref” reference=“name:subs”}).

Sort conditions (<) and cases (expected first)#

Conditional statements should be written in the form (3 < x) instead of (x > 3) even if you think “x larger 3”. For most cultures, numbers increase from left to right and thus it is easier to understand, especially when you add an upper bound: (3 < x) && (x < 7). Handle the nominal or expected case first and avoid negations, e.g. use if hasElement() instead of if not isEmpty().

Return often and soon, if errors ascend, but valid result, just once at the end.#

If an input is invalid or if any other error occurs, immediately return to avoid unnecessary code execution and nested errors. However, valid results should be returned only at the very end of a function body. By having a single success exit, it is easier to verify data validity. Furthermore, fail immediately and loudly. For any severe exception, your program should immediately crash and output the cause.

Use what you define and avoid unreachable/dead code.#

There should be no unused declarations of variables, arguments, functions, or classes. Furthermore, avoid unreachable code (e.g. after return) and dead code, which has no effect (e.g. is redundant).

Naming#

Code should communicate behavior to other humans with lower complexity than the behavior it inherits. Abstracting with meaningful names is therefore most important for readability.

Avoid inappropriate terms: Many organizations discourage the use of master/slave due to their negative association in different cultures. See 1 and 2.

Subprograms (=Procedure/Function)#

Procedures may return values, functions always return a value. Methods are subprograms of a class.

  • procedure names should start with a verb. e.g. syncViews(), list.addItem(x).
  • function names should describe the result and, if suitable, its type. e.g. time_ms(), sin(x)
  • class methods should not repeat or include the name of the class. Define Line.length(), not Line.getLineLength()

Caution: Single noun subprograms should be pure functions! Never let e.g. x.length() change a state.

Types (=Class/Struct/Subtypes)#

  • type names should be capitalized nouns. E.g. Integer, Date, Line2D
  • enums/structs are types and named as types without a special prefix/suffix. E.g. enum Color = {RED, GREEN, BLUE}
  • interface names can start with a capital I and can also be adjectives. E.g. IObservable

Variables#

  • variables with a large scope should have long names, variables with a small scope may have short names [CdCm].
  • collections (set, array, dict) should have a plural name. E.g. cars, indices
  • the prefix n or num should be used for names representing the total number of objects in a collection. E.g. numCars
  • boolean variables should start with a is/has/can/does prefix (e.g. isEmpty, doesUseIO).
  • write constant names in capitals. E.g CFG_TEMPERATURE_MAX = 80.0
  • prefix global variables with g_

Bad ❌

score_list = [0] * scores 
for idx, val in score_list:
  score_list = val + 1

Better ✔

scores = [0] * numScores 
for idx, score in scores:
  scores[idx] = score + 1

Use word pairs (opposites, antonyms).#

If you “start” something, you should “stop” it and not “end” it [CdCm]. While most opposites can be created by using un- or de- prefixes (lock/unlock), some are more distinct and allow code alignment:

Verb pairs with same length:

set send query insert attach show split enter accept
get recv reply delete detach hide merge leave reject

Verb pairs that differ by one character are more visually distinct but still easy to align with one extra space:

open read load push start create grant hit prepend empty
close write store pop stop destroy deny miss append full

Noun and adjective pairs with same/similar length:

max next head new row ack front source client primary leader
min prev tail old col nak rear target server replica follower

Additional Naming Suggestions#

Class Name Suffixes#

Class names often have a suffix term that indicates the purpose (and not only the topic) of that class. While there seems to be no common agreement on the notation, we find the following distinction useful:

  • Factory/Builder: creates objects and transfers ownership (e.g. to a Manager).
  • Manager: stateless, owns and provides access to same-type objects (e.g. SensorManager).
  • Controller: stateful, handles communication between different objects (e.g. FlightController).
  • Handler: waits for and processes a single-type object, without owning it (e.g. EventHandler).

High-level Scopes (= Module/Package/Namespace)#

The names for packages or namespaces, which should prevent name clashes, should be short as they are used often within the code. In this case, choosing an abbreviation or acronym makes sense.

Use physical units as type names.#

In embedded systems, you often have to deal with physical quantities. To make clear which unit a certain variable has, you should define your own (sub)types for physical units based on numerical type, such as Meter and Second from Float, and then declare variables as Meter safety_distance = 5.0;.

Use abbreviations cautiously and only common ones.#

To abbr. or not to abbreviate? Truth is, neither GetTheLatestWebSiteAccessTimeInMilliseconds(), nor pm_rcv_bat_stat() are reader friendly. Use abbreviations only for small scopes, function arguments, or in combination with full names (move_cmd). Avoid abbreviations if they only save one character (usr), are too cryptic (cmp), or can be confused with other terms (tmp). If you use them, stick to the following common abbreviations (already a lot):

Abbr. Meaning Abbr. Meaning
arg argument mid middle
app application min minimum
auth authentication mem memory
avg average mon monitor
bat battery msg message
buf buffer net network
cb callback num number
cfg config. obj object
clk clock pkg package
col column pkt packet
cnt counter pos position
cmd command pt point
ctx context ptr pointer
dev device pwr power
doc document px pixel
drv driver rnd round
dt delta time reg register
el element rot rotation
env environment sep separator
err error std standard
exc exception str string
fh file handler sys system
fmt format tmr timer
hdr header ts timestamp
hex hexadecimal val value
img image var variable
idx index win window
len length
lib library
lvl level
max maximum

Abbreviations and Acronyms in Embedded Systems:#

  • Sensors: IMU (=ACC, GYR, MAG), BARO, TEMP, GPS (= lat, lon, alt)
  • Buses: USB, CAN, SPI, I2C, UART, ETH, PCI
  • Processor: CPU, GPU, MCU: RAM, ROM, ISR, ADC, DAC, RTC, CLK, DMA
  • Software: API, CLI, GUI, HAL, OS, VM, PID, UID, LSB, MSB, CRC

Code Layout#

A clear and consistent visual appearance of your code improves readability and readability helps to understand the code.

  • Existing Project: Stick to the existing recommendations and tools.
  • New Project: Use an automatic code formatter. Examples:
Language Tool
Python black
C uncrustify
C++ clang-format
JavaScript prettier.io
  • aim for one statement and less than 80 characters per line
  • indent with spaces not tabs because editors do not agree on tab width
  • surround top-level function and class definitions with two or three blank lines.
  • surround binary operators (=, ==, +) with a single space, except when nested inline
  • break lines before operators and align operators vertically

Documentation#

English is the language of programming, so documentation should also be in English.

Write brief comments of high quality.#

Choose your words carefully. “Comments that contradict the code are worse than no comments” [PEP8]. Change comments when code changes. Comment only what the code cannot say, that is why you did it, maybe what you did, but never how.

Further Don’ts:

  • Don’t comment out code. Just remove.
  • Don’t create headings with S E P A R A T E D letters because you cannot search for them.
  • Don’t assume insider knowledge but write simple comments for anybody on the planet.
  • Don’t make jokes in comments. Tell them in person.

Use TODO and FIXME tags.#

Comment unfinished work with TODO: or FIXME:, which allows to search & find these lines later. Some IDEs will automatically highlight these tags via extensions. A TODO is more urgent and needs to be done, a FIXME would be nice to have but is not required.

Write Readme files.#

There are two different interest groups for your code, so please make sure that your Readme addresses both.

  • Users: How to install and run your code with examples. Supported OS. Release versions and change logs.
  • Developers: How to compile. Module structure, dependencies, contribution rules, where to contact developers.

Write file headers for header files.#

Each code file with interfaces (e.g. .h files in C) should start with a block comment that briefly explains what this module/class/lib does.

Use Docstrings for public APIs#

Docstrings are specially formatted comments that can be converted into a code documentation. This is useful as soon as other people start to depend on your interfaces.

/**
 * Solves equations of the form a * x = b
 * @example
 * // returns 2
 * globalNS.method(5, 10);
 * @example
 * // returns 3
 * globalNS.method(5, 15);
 * @returns {Number} Returns the value of x for the equation.
 */
globalNS.method = function (a, b) {
    return b / a;
};

Languages#

Each programming language has special mechanisms and some rules are only applicable to a certain language. We also try to give an overview of language-specific rules, but the following list is unfinished and work in progress.

References#

This guide is partly based on the principles that are explained in the following books and documents and we can recommend them as further reading material.

General Design Principles#

Students from TUM and other universities can read these books for free. Simply click the links below and login with your university credentials.

Language Specific Coding Conventions#