Project

General

Profile

Coding Styleguide » History » Version 10

quintus, 03/24/2020 12:16 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 8 quintus
The game's source code is formatted mostly according to the [1TBS](http://astyle.sourceforge.net/astyle.html#_style=1tbs) style, which is a variant of K&R, with slight adjustments. Details are documented below. Most (not all!) rules can be followed by formatting with the [astyle](http://astyle.sourceforge.net) command like this:
8 1 quintus
9 8 quintus
~~~~
10
$ astyle --style=1tbs --indent=spaces=4 --indent-namespaces \
11 9 quintus
  --attach-classes --attach-namespaces --attach-closing-while --attach-inlines --attach-extern-c \
12 8 quintus
  --align-pointer=type --align-reference=type \
13
  --break-one-line-headers --add-braces --close-templates \
14
   yourfile.cpp
15
~~~~
16
17 1 quintus
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.
18
19 8 quintus
One of the design goals of this document is to keep the styling consequent and easy to follow. For instance, instead of the questionable distinction of attaching braces to structs, but not to classes in the 1TBS style, this style says to simply always attach the braces (the only exception being function definitions). In so far it includes elements from the Stroustrup style. Likewise, indenting every brace block is easier to remember than not to indent namespaces.
20 1 quintus
21 8 quintus
## Commentary
22 1 quintus
23 8 quintus
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.
24 1 quintus
25
~~~~~ c++
26 8 quintus
// Careful: this is used in foobar() as well
27
28
/* This code is designed to specifically fit the purpose of an example.
29
 * It was not at all easy to come up with all this text, but for the
30
 * sake of an example, it was required to do so. */
31
~~~~~
32
33
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.
34
35
## Indentation and line length
36
37
Source code is indented with 4 spaces, tabs are not used. All blocks enclosed in braces are indented, including namespaces. Do not indent labels, including visibility labels like `public` and `private`; they should line up with the enclosing statement. Preprocessor statements are not indented.
38
39
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ c++
40
#ifndef MY_HEADER
41
#define MY_HEADER
42
43
namespace MyNamespace {
44
    class MyClass {
45
    public:
46
        MyClass();
47
       ~MyClass();
48
    };
49
50
    struct mystruct {
51
        int a;
52
        int b;
53
    };
54
}
55
56
void MyFunc()
57
{
58 10 quintus
    Foo();
59 8 quintus
    if (something) {
60 10 quintus
        Bar();
61
        Baz();
62 8 quintus
    }
63
}
64
#endif
65
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
66
67
Lines should be broken around 80 characters, and the resulting continuation lines should be indented so that it makes sense to look at. Example:
68
69
~~~~~ c++
70 1 quintus
if (somecondition) {
71 10 quintus
    ThisIsAVeryLongFunctionName(this_is_a_parameter,
72 1 quintus
                                this_is_another_parameter,
73
                                third_parameter);
74
}
75
~~~~~
76
77 8 quintus
This is not a hard requirement. You should use whatever conveys the meaning best. That's why the `astyle` command above does not include a hard line wrap option.
78 1 quintus
79 8 quintus
## Placement of braces
80 1 quintus
81 9 quintus
Braces are placed on the same line like the statement they belong to, be that a class or namespace declaration, an `extern C`, or anything else. The only exception from this are ordinary function definitions: for them the opening brace is broken into the next line. In any case, the closing brace always has its own line (makes it easy to spot the end of a block). `if/elseif/else` is cuddled to keep code compact, and the rare case of a trailing `while` has the `while` attached to the brace. Do not leave out braces even for one-line statements. This should prevent any accidental cutting of conditional clauses. To keep style consistent, also do not use all-in-one-line conditionals (this violates the expectation that the closing brace for each block can be found on its own line at the relevant indentation level).
82 1 quintus
83 8 quintus
Keen readers will notice that for in-function statements this uses the 1TBS style, and for out-of-function statements the Stroustrup style. Examples below.
84 1 quintus
85 8 quintus
~~~~~~~~~~~ c++
86
// Exception: function definition
87
void main()
88
{
89
    // ...
90 1 quintus
}
91
92 9 quintus
// Member functions also fall under the exception.
93
void Foo::SetX(int x)
94
{
95
    m_x = x;
96
}
97
98 8 quintus
// Everything else: attach the braces.
99
class Foo {
100
    // ...
101
};
102 10 quintus
struct Foo {
103 8 quintus
    // ...
104
};
105 10 quintus
enum class Foo {
106 8 quintus
    // ...
107
};
108
if (condition) {
109
    // ...
110
}
111
while (condition) {
112
    // ...
113 1 quintus
}
114
115 9 quintus
// A function definition inside a class definition does not count as an "ordinary" function. Attach.
116
// This is mostly useful for defining short class constructors or getters/setters.
117
class Foo {
118
  Foo(int x) : m_x {}
119
  inline int GetX() { return m_x; }
120
};
121
122 8 quintus
// Trailing while attached
123
{
124
    // ...
125
} while (condition)
126
127
// Brace kuddling from 1TBS
128
if (condition1) {
129
    // ...
130
} else if (condition2) {
131
    // ...
132
} else {
133
    // ...
134
}
135
136
// Required braces around one-line statements
137
if (condition) {
138
    doit();
139
}
140
141
// Also requires braces
142
if (condition1) {
143
    doit();
144
} else { // Thanks to brace cuddling, this is not as bad as in pure K&R
145
    doother();
146
}
147
148
// DO NOT DO THIS
149 10 quintus
if (condition) { Oneliner(); }
150 8 quintus
// Break it.
151
if (condition) {
152 10 quintus
    Oneliner();
153 8 quintus
}
154
~~~~~~~~~~~
155
156
## Initial assignment
157
158
Assign an initial value to any variable you declare. This prevents undefined values from coming from a forgotten assignment and eases debugging quite a bit, because it obviates the question "is this a real value or was this variable never assigned?".
159
160
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ c++
161
int a;     // BAD
162
int a = 0; // GOOD
163
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
164
165
## Pointer and reference alignment, multi-variable declarations
166
167
Place the `*` and `&` next to the type, not to the name. They belong semantically to the type. Do not declare multiple variables in one line if one of them is a pointer, it causes confusion:
168
169
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ c++
170
int i = 0, j = 0; // Okay, no pointer
171
172
int* p  = nullptr;         // Pointer to int
173
int* p1 = nullptr, p2 = 0; // DONT DO THIS. p2 is not a pointer!
174
175
// Instead, break the lines up.
176
int* p1 = nullptr;
177
int  p2 = 0;
178
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
179
180
Multiple variable assignments should be aligned at the `=`.
181
182
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ c++
183
int foo    = 0;
184
int foobar = 0;
185
int la     = 0;
186
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
187
188
## Parantheses and spacing
189
190
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*.
191
192
Template angles and index brackets do not have any surrounding space.
193
194
~~~~~~~~ c++
195
void Foo(int myparam) // No spaces around the "(" and ")"
196
{
197
    if (condition) { // One space between keyword if and "(", and one space between ")" and "{"
198
        MyFunc(5);   // No spaces around the "(" and ")"
199
    }
200
}
201
202
vector[3] = foo;                // No space between "vector" and "[3]"
203 1 quintus
map<string, vector<int>> mymap; // No spaces around <> (C++11 syntax)
204 8 quintus
~~~~~~~~
205 1 quintus
206
## Case of identifiers
207
208 10 quintus
* Macros are ALL_IN_CAPS. They need to stand out, because macro expansion can have surprises which may explain cryptic error messages.
209
* Variable identifiers use snake_case, including identifiers for variables declared `const` and/or `static`. All other identifiers (e.g. identifiers for classes, structs, enums, functions) use CamelCase. This way it's immediately clear from looking at the identifier if this is a variable or not. Function names tend to be more verbose than variable names, so CamelCasing them also helps to keep the code more compact.
210
* The different variable types are distinguished by the prefix (see [below](#Abbreviated-Hungarian-Notation)).
211 1 quintus
212
~~~~~~ c++
213
#define THIS_IS_A_MACRO(x) foo_ ## x
214
215 10 quintus
struct MyStruct;
216
enum class MyEnum;
217 1 quintus
218
class MyClass {
219
    MyClass();
220
    ~MyClass();
221
222
    void MemberFunction();
223
    static int StaticMemberFunction();
224
225
    int m_member_variable;
226
    static int static_member;
227 3 quintus
};
228 1 quintus
229 10 quintus
void Foo()
230 8 quintus
{
231 3 quintus
    static int local_static_variable;
232 1 quintus
    float normal_local_var = 3.5f;
233 3 quintus
    // ...
234 1 quintus
}
235 3 quintus
~~~~~~
236
237 1 quintus
## Abbreviated Hungarian Notation
238 3 quintus
239 1 quintus
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.
240 3 quintus
241 1 quintus
| Prefix | Meaning                                                          |
242 3 quintus
|--------+------------------------------------------------------------------|
243 1 quintus
|        | No prefix: Local variable                                        |
244
| m      | Member variable                                                  |
245
| f      | File-local variable                                              |
246
| g      | Global variable                                                  |
247
| p      | Variable holds a pointer (both raw and managed pointers)         |
248
| a      | Variable holds a raw array (not: vector or other C++ containers) |
249
250
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.
251
252
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.
253
254
~~~~~~~~~~~ c++
255
static int f_something; // File-local variable
256
extern int g_globvar;   // Global variable
257
258 8 quintus
class MyClass {
259 9 quintus
    void MyFunc() {
260 1 quintus
        int* p_int;                   // Local variable
261
        m_normal_member += "AAA";     // Accessing member variable
262
        DoSomething(MyClass::foobar); // Exception: accessing static member variable via class name, not directly
263
    }
264 7 quintus
265 1 quintus
    std::string m_normal_member;    // Normal member variable
266
    int* mp_int;                    // Member variable with pointer
267
    static const float foobar = 42; // Exception: Static member variable
268
};
269
270 10 quintus
struct Point {
271 1 quintus
    int x;            // Struct members have no "m" prefix
272 7 quintus
    int y;
273
    int z;
274
    owner* p_owner;   // But they do have the type prefix if required.
275
};
276
277 8 quintus
point MoveUp(point p)
278
{
279 1 quintus
    p.y -= 10;         // Access to struct member without "m"
280
    return p;
281 7 quintus
}
282
283 10 quintus
enum class Color { red, green, blue }; // Exception: enum members do not have "m"
284 8 quintus
void MyFunc(color c)
285
{
286 10 quintus
    if (c == Color::red) { // Because they are accessed only from the outside.
287 7 quintus
      // ...
288
    }
289
}
290
~~~~~~~~~~~
291
292
## enum specifics
293
294
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).
295
296
~~~~~~ c++
297 10 quintus
enum class Color { red, green, blue };
298 7 quintus
299 10 quintus
void Foo(Color c)
300 8 quintus
{
301 7 quintus
    // ...
302
}
303
~~~~~~
304
305
## File names
306
307
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`.
308
309
## Inclusion of headers
310
311
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:
312
313
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.
314
2. Other internal headers.
315
3. External library headers.
316
4. Standard library headers.
317
318
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 ""`.
319
320
~~~~~~~ c++
321
// This is foo.cpp, it has a header foo.hpp.
322
#include "foo.hpp"
323
#include "../misc/internal_header.hpp"
324
#include <curl.h>
325
#include <vector>
326
#include <cstdlib>
327
~~~~~~~
328
329
## Forward declarations in headers
330
331
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.
332
333
~~~~~~~~~~ c++
334
// Forward declarations
335
class MyClass;
336
337
class MyOtherClass {
338
public:
339 8 quintus
    MyOtherClass(MyClass& mc) : m_mc(mc){}
340 7 quintus
    void Foo(MyClass* p_mc){ /* ... */ }
341
private:
342
    MyClass& m_mc;
343
};
344
~~~~~~~~~~
345
346
The corresponding `.cpp` file will then have to include the internal header for `MyClass`:
347 1 quintus
348
~~~~~~ c++
349
#include "my_other_class.hpp"
350
#include "../misc/my_class.hpp" // <---
351
~~~~~~