Keep it simple and solid, let the toolchain be smart,
code correctness is the duty, readability the art.
The goal of these conventions is to be concise, universal, and remarkable. It targets emerging code enthusiasts under time pressure and covers 7 topics:
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.
“Consistency with this guide is important. Consistency within a project is more important. Consistency within one module or function is the most important” [PEP8].
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 rules and fancy patterns matters if the code becomes impossible to maintain.
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?
To be universal, we group several concepts under these broad identifiers:
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:
Instead, limit the amount and direction of information exchange between classes and create larger architectures from the following building blocks:
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.
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 ❌ | Better ✔ | Better ✔ |
```python engine.start() nStartUps += 1 if fuelTank.isEmpty(): fuelGaugeUI.setRedLED() ``` | ```python engine.start() engine.runTest() warnings = engine.warnings() dashboard.show( warnings ) ``` | ```python sys.test(): engine.test(): sparkPlug.test(): testIgnition() ``` |
⚠ Caution: Mixing abstraction levels creates confusion and introduces unnecessary dependencies.
Once you have an initial plan for the architecture, you can start writing the code for classes and subprograms.
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.
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.
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 destination);
isNight = (T_NIGHT_MIN < t.h && t.h < T_NIGHT_MAX);
isSpeeding = (limit < speed);
if (isSpeeding && isNight){ ... }
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.
Bad ❌ | Better ✔ |
```c makeCircle(double r, double x, double y) ``` | ```c makeCircle(Point center, double radius) ``` |
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 ❌ | Better ✔ |
```c totalIncome = 0 // ... long code to get contract totalIncome += contract.salary // ... long code to access taxoffice totalIncome -= taxoffice[id].tax ``` | ```c Int totalIncome(employee){ salary = getSalary(employee) tax = getTax(employee) return (salary - tax) } ``` |
Code should communicate behavior to other humans with lower complexity than the behavior it inherits. Since code mostly consists of custom names, your ability to abstract concepts with meaningful names is most important for readability.
Each concept should be described by a single word. Do not use send()
, write()
, and transmit()
as synonyms for the same process, such as sending a message over a bus. Same holds true for handleRequest()
, processQuery()
, or manageIncomingPacket()
.
Each word should only describe a single concept. Do not use activate()
for setting a boolean variable and sending an activation packet to another system component. The first procedure is safe and instant, the second could fail, block execution, or run asynchronously. Better call it sendActivationMsg()
.
is/has/can
prefix.Clean code reads like prose and these prefixes achieve clear and concise naming.
There is no difference between the naming of variables, e.g. hasElements
, and functions, e.g. hasElements()
, except that functions can also answer true/false questions regarding arguments, such as isFile(path)
or user.hasAccessTo(file)
.
can
to express abilities/possibilities, e.g. canWalk
.Each/Any
before the prefix, e.g. isEachLightOn
/ isAnyLightOn
isActive
not isInactive
to avoid confusing negations (!isInactive
). Same for hasElements
(not isEmpty
) and isEnabled
(not isDisabled
).does
, are
, was
, will
, should
.Rule | Bad ❌ | Better ✔ |
---|---|---|
Consider can |
isAllowedToWalk , hasWalkAbility , isWalkable |
canWalk |
Avoid not |
isNotEmpty , !isEmpty |
hasElements |
Avoid are : |
areAllLightsOn , isAllLightsOn , allLightsOn |
isEachLightOn |
Avoid was/had |
wasSend , hasBeenSent , hadArrived |
isSent |
Avoid does |
doesUseIO , mightUseIO |
isUsingIO , canUseIO |
Fun fact: The old jQuery 1.7 had a boolean called doesNotAddBorder
. Now they use isBorderBox
.
Procedures may return values, functions always return a value. Methods are subprograms of a class.
syncViews()
, list.addItem(x)
.time_ms(), sin(x), isFile(path)
Line.length()
, not Line.getLineLength()
⚠ Caution: Single noun subprograms should be pure functions! Never let e.g.
x.length()
change a state.
Integer
, Date
, Line2D
enum Color = {RED, GREEN, BLUE}
I
and can also be adjectives. E.g. IObservable
cars
, indices
n
or num
should be used for names representing the total number of objects in a collection. E.g. numCars
CFG_TEMPERATURE_MAX = 80.0
g_
Bad ❌ | Better ✔ |
```python score_list = [0] * scores for idx, val in score_list: score_list = val + 1 ``` | ```python scores = [0] * numScores for idx, score in scores: scores[idx] = score + 1 ``` |
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 |
⚠ Avoid inappropriate terms: Many organizations discourage the use of
master/slave
due to their negative association in different cultures. See 1 and 2.
⚠ Avoid useless noise-terms: E.g.data
,info
,process
. What would be the difference betweenProduct
,ProductData
, andProductInfo
?
A clear and consistent visual appearance of your code improves readability and readability helps to understand the code.
Language | Tool |
---|---|
Python | black |
C | uncrustify |
C++ | clang-format |
JavaScript | prettier.io |
English is the language of programming, so documentation should also be in English.
Choose your words carefully and comment only what the code cannot say, that is why you did it, maybe what you did, but never how. Change comments when code changes because “comments that contradict the code are worse than no comments” [PEP8].
Bad ❌ | Better ✔ |
```python if t.h > NIGHT_H: # Check if night if ifr.sense(): # detect person turnLightOn() # set Pin 13 lockLight(TIMEOUT) # TIMEOUT=5s ``` | ```python if (isNightTime() and isPersonPresent()): turnLightOn() lockLight(TIMEOUT) # avoid flickering ``` |
Further Don’ts:
S E P A R A T E D
letters because you cannot search for them.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.
There are two different interest groups for your code, so please make sure that your Readme addresses both.
print
. Use a library with log levels.Level | Use-case | Example |
---|---|---|
Fatal: | the system cannot continue operation | segfault |
Error: | some function is (currently) not working | no connection |
Warn: | unexpected event but retry/alternative works | dropped packet |
Notice: | notable events that do not happen often | new device |
Info: | usual business events relevant to the user | user connected |
Debug: | technological info relevant for developers | protocol stages |
Trace: | step-by-step implementation details | loop index |
"User connected"
) from parameters ("ip=%s", ip
)Bad ❌
print("User %s connected from %s", uid, ip)
Better ✔
log.info("P2P: User connected. [id=%s, ip=%s]", uid, ip)
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. However, do not provide author name or changelogs as this info belongs into the Version Control System.
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.
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.
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.
Students from TUM and other universities can read these books for free. Simply click the links below and login with your university credentials.