Project

General

Profile

Coding Styleguide » History » Version 7

quintus, 03/21/2020 12:32 PM

1 1 quintus
# Coding Styleguide
2
3 2 quintus
{{toc}}
4
5 1 quintus
Those who have worked on the TSC project will remember the dreaded discussion about coding style. This project will avoid the trap by using a consistent coding style right from the start. It is outlined in this document.
6
7
The game's source code is formatted according to the [1TBS](https://en.wikipedia.org/wiki/Indentation_style#Variant:_1TBS_(OTBS)) style, which is a variant of K&R, with slight adjustments. Details are documented below.
8
9
In general, try to keep your code readable. Specifically, it is often useful to leave empty lines to separate logically grouped statements from one another.
10 2 quintus
11 1 quintus
12
## Indentation, line length
13
14
Source code is indented with 4 spaces, tabs are not used. Lines should be broken around 80 characters, and the resulting continuation lines should be indented so that it makes sense to look at. Example:
15
16
~~~~~ c++
17
if (somecondition) {
18
    thisIsAVeryLongFunctionName(this_is_a_parameter,
19
                                this_is_another_parameter,
20
                                third_parameter);
21
}
22
~~~~~
23
24
## Commentary
25
26
Semantically, comments should normally reflect why code is written the way it is. Normally it is not required to explain what code does, unless it is an exceptionally complex part. Syntactically, use `//` for one and two lines of comments. Starting with the third line, comments should use the bock syntax `/* ... */`. In the block syntax, align each star with the one on the preceeding line. Terminate the comment block on the last line of the comment.
27
28
~~~~~ c++
29
// Careful: this is used in foobar() as well
30
31
/* This code is designed to specifically fit the purpose of an example.
32
 * It was not at all easy to come up with all this text, but for the
33
 * sake of an example, it was required to do so. */
34
~~~~~
35
36
### Documentation comments
37
38
Where Doxygen is used to generate documentation, use Doxygen's `///` markers for short and the `/**` markers for long documentation. Other than that, the above advice applies.
39
40
## Case of identifiers
41
42 4 quintus
* Macros are ALL_IN_CAPS. They need to stand out.
43
* Class identifiers use CamelCase. Structure and enum identifiers are snake_case (makes it easy to spot whether this type is copy-by-value or copy-by-reference: every lowercase type is copy-by-value).
44
* Member function identifiers are CamelCase. This keeps function names short (they tend to be longer than member variable names).
45
* All variables and constants (unless the constants are macros, see above) are snake_case. This includes static member variables, even if they are constant. This way a non-function member is easily identifiable by being lowercase. The different variable types are distinguished by the prefix (see [below](#Abbreviated-Hungarian-Notation)).
46
47
~~~~~~ c++
48
#define THIS_IS_A_MACRO(x) foo_ ## x
49
50
struct my_struct;
51
enum class my_enum;
52
53
class MyClass {
54
    MyClass();
55
    ~MyClass();
56
57
    void MemberFunction();
58
    static int StaticMemberFunction();
59
60
    int m_member_variable;
61
    static int static_member;
62
};
63
64
void foo() {
65
    static int local_static_variable;
66
    float normal_local_var = 3.5f;
67
    // ...
68
}
69
~~~~~~
70 1 quintus
71
## Abbreviated Hungarian Notation
72
73
Identifiers of variables and constants begin with a short sequence of characters that encodes some important information about the variable in question. This is called [Hungarian Notation](https://en.wikipedia.org/wiki/Hungarian_notation), but in full, it is cumbersome to read and leads to long identifier names. The following prefix characters have been chosen with respect to two goals: Make variable scope immediately visible, and warn of "unusual" types.
74
75
| Prefix | Meaning                                                          |
76
|--------+------------------------------------------------------------------|
77
|        | No prefix: Local variable                                        |
78
| m      | Member variable                                                  |
79 5 quintus
| f      | File-local variable                                              |
80 1 quintus
| g      | Global variable                                                  |
81
| p      | Variable holds a pointer (both raw and managed pointers)         |
82
| a      | Variable holds a raw array (not: vector or other C++ containers) |
83
84
The scope prefix comes before the type prefix. Thus, `mp_foo` is a member variable holding a pointer, and `ga_argv` is a global variable holding a raw C array.
85 5 quintus
86
There are two special cases. First, member variables of structs and enums do not have a leading `m` prefix, because they do not normally contain functions, but are only accessed from the outside (whereas for classes as per the secrecy principle access to member variables from the outside is unusual), and it would be cumbersome to always add the extra `m`. Second, static member variables of classes do not have a scope prefix. Instead, they are always to be accessed via the class name.
87
88
~~~~~~~~~~~ c++
89
static int f_something; // File-local variable
90
extern int g_globvar;   // Global variable
91
92
class MyClass {
93
    void MyFunc() {
94
        int* p_int;                   // Local variable
95
        m_normal_member += "AAA";     // Accessing member variable
96
        DoSomething(MyClass::foobar); // Exception: accessing static member variable via class name, not directly
97
    }
98
99
    std::string m_normal_member;    // Normal member variable
100
    int* mp_int;                    // Member variable with pointer
101
    static const float foobar = 42; // Exception: Static member variable
102
};
103
104
struct point {
105
    int x;            // Struct members have no "m" prefix
106
    int y;
107
    int z;
108
    owner* p_owner;   // But they do have the type prefix if required.
109
};
110
111
point MoveUp(point p) {
112
    p.y -= 10;         // Access to struct member without "m"
113
    return p;
114
}
115 6 quintus
116
enum class color { red, green, blue }; // Exception: enum members do not have "m"
117
void MyFunc(color c) {
118
    if (c == color::red) { // Because they are accessed only from the outside.
119
      // ...
120
    }
121
}
122 5 quintus
~~~~~~~~~~~
123 1 quintus
124
## Compound Statements
125
126
The opening brace resides on the same line as the statement it applies to, regardless of whether this is a function, a control flow statement, or a class or enum declaration. The closing brace has a line on its own to ensure it is easily spottable where a block ends.
127
128
~~~~~~~~~~~ c++
129
class Foo {
130
};
131
132
if (condition) {
133
    // ...
134
}
135
136
while (condition) {
137
    // ...
138
}
139
140
void main() {
141
    // ...
142
}
143
~~~~~~~~~~~
144
145
The rare case of a terminal `while` has the `while` after the closing brace on the same line.
146
147
~~~~~~ c++
148
{
149
    // ...
150
} while (condition)
151
~~~~~~~
152
153
## Brace Cuddling
154
155
In an if/elsif/else statement, braces are cuddled to keep code compact.
156
157
~~~~~~~~~~ c++
158
if (condition1) {
159
    // ...
160
} else if (condition2) {
161
    // ...
162
} else {
163
    // ...
164
}
165
~~~~~~~~~~~
166
167
## Braces around short statements
168
169 3 quintus
Do not leave out braces even for one-line statements. This should prevent any accidental cutting of conditional clauses.
170 1 quintus
171
~~~~~~~~~ c++
172 3 quintus
// Short: required braces
173
if (condition) {
174 1 quintus
    doit();
175 3 quintus
}
176 1 quintus
177 3 quintus
// Also requires braces
178
if (condition1) {
179 1 quintus
    doit();
180 3 quintus
} else { // Thanks to brace cuddling, this is not as bad as in pure K&R
181 1 quintus
    doother();
182 3 quintus
}
183 1 quintus
184 3 quintus
// Requires braces in any style to keep clarity
185 1 quintus
if (condition1) {
186
    doit();
187
} else {
188
    doother1();
189
    if (condition2) {
190
        something();
191
    }
192
    else {
193
        andmore();
194
    }
195
}
196
~~~~~~~~~
197
198
## Parantheses and spacing
199
200
Between a keyword and the opening paranthesis is exactly one space. Between the closing paranthesis and the opening curly brace is exactly one space as well. *There is no space between a function name and the opening paranthesis of its argument list*, neither in declaration nor in calling of a function.
201
202
~~~~~~~~ c++
203 7 quintus
void Foo() { // No space between function name and (, but one space between ) and {
204 1 quintus
    if (condition) { // One space between keyword if and (, and one space between ) and {
205
        // ...
206
    }
207
}
208
~~~~~~~~
209 7 quintus
210
## Visibility Specifiers
211
212
In class declarations, visibility labels like `public` and `private` are on the same level like the corresponding `class` statement.
213
214
~~~~~~~ c++
215
class MyClass {
216
public:
217
    MyClass();
218
    ~MyClass();
219
220
private:
221
    void PrivateMethod();
222
};
223
~~~~~~~
224
225
## enum specifics
226
227
Names of `enum` identifiers are singular, not plural. If used as a type, `color var` reads more natural than `colors var`. Use `enum class` instead of raw `enum` whenever possible (this is C++11 specific and allows colliding enum identifiers in case you wonder that `enum class` is valid syntax).
228
229
~~~~~~ c++
230
enum class color { red, green, blue };
231
232
void Foo(color c) {
233
    // ...
234
}
235
~~~~~~
236
237
## File names
238
239
All source code files are in snake_case. C++ source code files end in `.cpp`, C++ headers end in `.hpp`. C source files end with `.c`, C headers in `.h`.
240
241
## Inclusion of headers
242
243
Each C/C++ source file includes only the headers it needs to compile, and all inclusions are at the top of the file. Inclusions are done in this order:
244
245
1. The header file corresponding to this `.c`/`.cpp` file. Doing this first ensures that the `.hpp` file is self-contained, because on compilation of the corresponding `.cpp` file the compiler will error out on the very first `#include` line then if the header is not self-contained. For `.hpp` files, this step is obviously missing.
246
2. Other internal headers.
247
3. External library headers.
248
4. Standard library headers.
249
250
The path delimiter for `#include` statements is always a forward slash, because this compiles on both Windows and Unix systems. External and standard library headers are included with angle `#include <>`, internal headers with quoted `#include ""`.
251
252
~~~~~~~ c++
253
// This is foo.cpp, it has a header foo.hpp.
254
#include "foo.hpp"
255
#include "../misc/internal_header.hpp"
256
#include <curl.h>
257
#include <vector>
258
#include <cstdlib>
259
~~~~~~~
260
261
## Forward declarations in headers
262
263
To increase compile times and keep the inclusion graph simple, headers should try hard to not require other internal headers. If an internal type is required in a header, it should be forward-declared in that very header. Note that it is possible to forward-declare classes, structs, and even enums. In most cases, forward-declarations are entirely sufficient. For instance, pointers, references, many templated types, and even smart pointers can be used with only the forward-declaration available.
264
265
~~~~~~~~~~ c++
266
// Forward declarations
267
class MyClass;
268
269
class MyOtherClass {
270
public:
271
    MyOtherClass(MyClass& mc) : m_mc(mc) {}
272
    void Foo(MyClass* p_mc){ /* ... */ }
273
private:
274
    MyClass& m_mc;
275
};
276
~~~~~~~~~~
277
278
The corresponding `.cpp` file will then have to include the internal header for `MyClass`:
279
280
~~~~~~ c++
281
#include "my_other_class.hpp"
282
#include "../misc/my_class.hpp" // <---
283
~~~~~~