July 19, 2020
Author: Vilarion
Version 1.0
The team of Illarion developers has grown over time. And while everyone has their preferred coding style, it is useful to agree on a single one. This makes it easier for others to read your code, understand and modify it. These rules do not only cover the appearance of code, but also how the coding itself is performed, which functionality to use or avoid and best practices. The purpose of this document is not to hamper you in your coding efforts with an overly exhaustive set of rules, but to guide you towards clean code for the benefit of all.
There are many languages involved in Illarion development. They range from mark-up languages like HTML or the typesetting language LaTeX to a whole array of different programming languages like C++ , Java, Lua, PHP or GLSL. Examples given, either conforming or non-conforming, will be in C++ and Lua. You can easily derive how they would look like in the language you are currently using. Rules are designed to be as general as possible to apply to most languages used in Illarion development.
Examples are tagged with icons to annotate whether or not they are conforming. Non-conforming examples are designed such, that only the current issue is being violated. They are correct in every other aspect, so that you can focus on the issue at hand. This is what correct examples look like:
C++ | Lua | |
✔️ |
for (auto &item: items) {
item.age();
} |
for _, item in pairs(items) do
item.age()
end |
A violating example looks like this:
C++ | Lua | |
❌ |
for (auto &item: items)
{
item.age();
} |
for _, item in pairs(items)
do
item.age()
end |
- Write lines not longer than 120 characters
- Write one statement per line
- Frame code blocks with one line on each end
- Seperate adjacent code blocks with an empty line
- Indent four spaces inside code blocks
- Use space after a delimiter
- Use space before and after binary operators
- Avoid space after function names
- Avoid trailing white space
- Use lowerCamelCase for variable and function names
- Use UpperCamelCase for class names
- Use proper British English
- Use block comments for license headers
- Select expressive names
- Write only useful comments
- Avoid deprecated code
- Avoid duplicating code
- Use libraries
- Keep it as local as possible
- Write short, expressive functions
- Avoid unnecessary code and magic numbers
- Refrain from using goto
- Use correct license headers
Long lines of code tend to get difficult to read. If your lines get longer than 120 characters, reconsider what you are writing. In fact, most statements should fit into a line of 80 characters. Maybe there is a better way to express what you want to do. If you still need longer lines, break them up, indenting them further than the next statement.
This rule does not apply to continuous text e.g. in HTML. Breaking up continuous text can lead to strange diffs where changing a single word could put a whole paragraph into the diff.
On the other hand, do not cram statements into one line until it reaches its length limit. One statement per line allows for good readability. Compare:
C++ | Lua | |
✔️ |
auto strength = 10;
auto wisdom = 8; |
local strength = 10
local wisdom = 8 |
❌ |
auto strength = 10; auto wisdom = 8; |
local strength = 10 local wisdom = 8 |
Each local code block should be framed by one line on each end:
C++ | Lua | |
✔️ |
for (auto &item: items) {
item.age();
} |
for _, item in pairs(items) do
item.age()
end |
❌ |
for (auto &item: items)
{
item.age();
} |
for _, item in pairs(items)
do
item.age()
end |
You can also use an empty line to structure logical groups.
C++ | Lua | |
✔️ |
auto minutes = 3;
auto seconds = minutes * 60;
for (auto &item: items) {
for (auto i = 0; i < 5; ++i) {
item.age(seconds);
}
} |
local minutes = 3
local seconds = minutes * 60
for _, item in pairs(items) do
for i = 1, 5 do
item.age(seconds)
end
end |
❌ |
auto minutes = 3;
auto seconds = minutes * 60;
for (auto &item: items) {
for (auto i = 0; i < 5; ++i) {
item.age(seconds);
}
} |
local minutes = 3
local seconds = minutes * 60
for _, item in pairs(items) do
for i = 1, 5 do
item.age(seconds)
end
end |
Local code blocks have to be indented by four spaces. Do not use tabulators.
C++ | Lua | |
✔️ |
{
auto result = database.query();
result.validate();
} |
do
local npc = getNPC()
npc:talk("Hello!")
end |
❌ |
{
auto result = database.query();
result.validate();
} |
do
local npc = getNPC()
npc:talk("Hello!")
end |
Each delimiting character has to be followed by exactly one space:
C++ | Lua | |
✔️ |
for (const auto number: {42, 0, 1}) {
cout << number;
} |
for number in {1, 2, 3} do
print(number)
end |
❌ |
for (const auto number:{42, 0, 1}) {
cout << number;
} |
for number in {1,2,3} do
print(number)
end |
All binary operators have to be pre- and succeeded by one space:
C++ | Lua | |
✔️ |
cout << 1;
auto i = 0;
i += 2;
auto b = 3 * i; |
print("a" .. "b")
local i = 0
i = i + 2
local b = 3 * i |
❌ |
cout<<1;
auto i=0;
i +=2;
auto b =3* i; |
print("a".."b")
local i =0
i =i+ 2
local b= 3 * i |
This speaks for itself:
C++ | Lua | |
✔️ |
std::move(object); |
print(1) |
❌ |
std::move (object); |
print (1) |
White space at the end of a line will produce strange diffs, so make sure you do not insert any.
Lower camel case means that the first letter of the first word is written in lower case while consecutive words are directly appended but start with upper case:
C++ | Lua | |
✔️ |
int playerCounter;
Player destroyerOfWorlds;
void fooBar(); |
local playerCounter
local robertOppenheimer
function fooBar()
end |
❌ |
int playercounter;
Player destroyer_Of_Worlds;
void foo_bar(); |
local playercounter
local robert_Oppenheimer
function foo_bar()
end |
Upper camel case means that a name consists of possibly multiple directly concatenated words, each starting with a capital letter:
C++ | Lua | |
✔️ |
class PlayerManager
struct Position;
class SelectionDialog; |
PlayerCraft = {
products = {},
...
} |
❌ |
class playerManager
struct position;
class selection_dialog; |
player_craft = {
products = {},
...
} |
It is important to use proper language as well as the proper language. Others will have to read what you write. Once we decided to use British English as official staff language, so this decision also applies to all written code. Small mistakes might and will happen, but that is no excuse to be lazy about it. Use a dictionary if you are not sure.
Using block comments for license headers at the beginning of each file allows editors to fold them, reducing the noise for you while you are working on actual code.
Pick meaningful names. This is one of the most important aspects of good code. Do not use strange abbreviations. Variable names should describe their content, not an underlying data structure or implementation detail. Function names should describe an action for most functions and a question for boolean functions:
C++ | Lua | |
✔️ |
int playerCounter;
std::vector<Player> players;
for (auto i = 0; i < 10; ++i); // i is a common counter name
void savePlayers();
bool isYellow();
int getStrength(); |
local playerCounter
for i = 1, 10 do -- i is a common counter name
end
function useItem(user, item)
end |
❌ |
int plCtr;
std::vector<Player> playerVector;
void f(); |
local plyCt
function calculate() -- calculate what?
end |
Express yourself in code instead of comments. Do not be redundant in comments. Do not use comments to rant or be otherwise unprofessional. One example for a good comment would be the description of a complex algorithm. You can cite the paper where you got it from and briefly express what the algorithm does. Further examples would be documenting a function's precondition or a class' invariant.
C++ | Lua | |
❌ |
int a = 0; // counts players
int playerCounter = 0; // set to 0
while (true) {
} // end while
int b = ++a + 2 - 3; // this code is shit!!!
// characterAge: the age of the characters
// returns the number of characters
int numberOfCharacters(int characterAge); |
local agility = 0 -- holds agility
local attribute = agility or 0 -- set attribute to agility or 0 if agility is nil
-- begin setting attributes
agility = 5
-- end setting attributes |
Deprecated code will create work when we decide to use a newer version of some language. Do not use it, there is no excuse. Others will have to replace that code in time. If you come across deprecated code, get rid of it.
You should be aware of what is marked as deprecated in the language version you are currently using.
C++ | Lua | |
✔️ |
std::unique_ptr<Network>;
void save() noexcept;
~Player(); |
local M = {}
...
return M
length = #{4, 3, 2}
log = math.log(1234, 10) |
❌ |
std::auto_ptr<Network>;
void save() throw();
~Player() throw(); |
module("base.common", package.seeall)
length = table.maxn{4, 3, 2}
log = math.log10(1234) |
Do not duplicate code. Copy&paste is bad. You or someone else will change that code in one place later and forget the other occurrences or not even know about them. Use functions if you need a piece of code more than once. Also use functions to clarify. If you can name it, you can put it into a function.
Prefer using language features or language libraries over reinventing the wheel. There is a reason those things exist. Your own code will just be inefficient, more verbose and may even contain bugs.
Keep the scope of variables as narrow as possible. The shorter their life span is, the less error-prone is your design.
Functions should be short and do one thing, specified by their name. If they are long and do things they are not supposed to do, it gets difficult to understand them or reason about them.
Be precise in what you write. Write enough so that the code is clear and meaningful, but do not be verbose. Sometimes it is not enough to give a magic number a name, but you might need to change its type as well to better reflect its content.
C++ | Lua | |
✔️ |
auto taskInterval = std::chrono::seconds(60);
addUnsignedCharToBuffer(((data >> CHAR_BIT) & UCHAR_MAX));
return player; |
activePlayers = 0
local strength = getAttribute(Character.strength)
if attribute == Character.dexterity then
...
end
do
...
end |
❌ |
int taskInterval = 60; // 60 what?
addUnsignedCharToBuffer(((data >> 8) & 255));
return (player); |
a = 0; -- the ; is unnecessary in Lua, don't use it
i = (a * 3) + 2
local strength = getAttribute(5)
if (i == 4) then
...
end
if true
...
end |
A form of the goto control statement exists in most languages. Do not use it.
Every user-written source file which runs on the server needs to be prefixed with the AGPLv3 header. This is true for everything we write in C++, Lua or PHP. Everything which will eventually run on a client machine needs to be prefixed with the GPLv3 header.