Skip to content

Instantly share code, notes, and snippets.

@TopchetoEU
Last active December 17, 2023 13:51
Show Gist options
  • Save TopchetoEU/0d7c411bc983ba6ef7b61d3753aa34bb to your computer and use it in GitHub Desktop.
Save TopchetoEU/0d7c411bc983ba6ef7b61d3753aa34bb to your computer and use it in GitHub Desktop.
The code style that I use in my code and my projects

TopchetoEU's coding style

This is a short document describing my coding style. Coding style is very personal, and I won’t force my views on anybody, unless you're contributing to any of my projects. In that case, this code style has to be strictly followed.

I. Syntax and formatting

Here are outlined rules, that you must follow so that your PRs get accepted by yours truly (there are some exceptions, but generally, this is how I code, and this is how you should code if you want to contribute to my projects).

1) Indentation size

This is a controversial topic, but I've picked 4 space-wide indentations as an appropriate value, since it's not too big (8), and not too small (2). Indentations shouldn't be the tab character ('\t'), unless the language explicitly requires it (thanks, Makefile).

2) Block statements

All contents of a block statement ({...}) must be indented one level, the opening brace should be left on the same line as the previous statement, and the closing brace should be at the same indentation level as the opening brace. The opening brace, if preceded by anything, should be separated from it with a space. Here's an example of how you SHOULD indent your code:

if (stack.empty()) {
    cout << "Stack is empty :(";
}

And here's an example of what will get you in the deepest pits of hell:

// This is neutral
while (locked)
{
    sleep(1);
}
// This is passive-aggressive
while (locked)
    {
    sleep(1);
    }
// You're drunk (GNU), go home
while (locked)
  {
    sleep(1);
  }

All statements in a block must be put on a new line.

3) Spacing

3.1) Comma lists

Anywhere, where a comma list appears in your code, it should be formatted in this way: commas should have a space after them, but not before them. A comma list must be on the same line as the paradigm it's included in. Even if the language allows it, you shouldn't put a trailing comma, except if it's an object literal in Javascript.

If a comma list is too long to fit in one line, the first element should be put on a new line, and all elements should be grouped in logical groups on single lines (according to context). The closing paradigm should be put on a new line, on the same level as the statement itself. Only the list should be indented, just one level:

auto mat = create_matrix(
    1, 0, 0, 0,
    0, 1, 0, 0,
    0, 0, 1, 0,
    0, 0, 0, 1,
);

3.2) Parentheses

Under any circumstances, parentheses shouldn't have spaces inside them, but spaces around them (unless specified otherwise)

3.2) Expressions

In an expression, operators should be surrounded by spaces. Here are the exceptions to this rule:

  • After sizeof, typeof, nameof, alignof, and any other operator, that resembles a function call
  • After a prefix unary operator
  • Before a suffix unary operator
  • Before or after a member access operator (., -> or ::)
  • After a cast

4) Labels

You should avoid using labels and the goto statement at all costs, however, if you somehow find yourself using them, the preferred way of indenting them is on a single line (no other code should go on the line of the label), and on the same level as the enclosing code block:

void scuffed_loop(int n) {
    int i = 0;
start:
    if (i >= n) goto end;
    cout << i << endl;
    i++;
    goto start;
end:
    return;
}

void infinite_loop() {
    while (false) {
    weird_jump:
        cout << "AAA";
    }
    goto weird_jump;
}

5) Switch statements

In the case you're using labels for switch statements, the case statement should be indented one level deeper than the switch statement:

switch (c) {
    case 'n':
        return '\n';
    case 't':
        return '\t';
    case 'b':
        return '\b';
    case 'r':
        return '\r';
    default:
        return c;
}

This is NOT the way to write switch statements:

switch (c) {
case 'n':
    return '\n';
case 't':
    return '\t';
case 'b':
    return '\b';
case 'r':
    return '\r';
default:
    return c;
}

Nested switch statements are not allowed. If a case in a switch consists of a single statement, excluding the break statement, the case may be written on one line:

switch (5) {
    case 1: cout << "It's 1\n"; break;
    case 2: cout << "It's 1\n"; break;
    case 3: cout << "It's 1\n"; break;
    case 4: cout << "It's 1\n"; break;
    case 5: cout << "It's 1\n"; break;

6) Ifs, elses, loops and switches

Such statements consist of three parts: the name of the statement, the condition (or something else) in parentheses, and the body. The condition must be in parentheses, separated with a single whitespace:

if (locked) {
    cout << "We're locked lol";
}

In the case of ifs and elses, you can write the statement on the same line of the if statement, but never on the next line:

// ok
if (locked) cout << "We're locked lol";
// bad
if (locked)
    cout << "We're locked lol";

This is used mainly with if-else chains, but you may use it with loops, sparingly. Note that this will make debugging harder for some languages (*cough* Java *cough*).

In the case of if-elses and do-whiles, the else/while counterpart should be put on a new line:

if (!strcmp(argv[2] == "output")) {
    printf("%s", project.output);
}
else if (argv[2] == "deps") {
    printf("%s", project.deps);
}
else {
    fprintf(stderr, "Invalid command given. Available commands: output, deps.");
}

This is bad:

if (!strcmp(argv[2] == "output")) {
    printf("%s", project.output);
} else if (argv[2] == "deps") {
    printf("%s", project.deps);
} else {
    fprintf(stderr, "Invalid command given. Available commands: output, deps.");
}

7) Calls

A call should have no space between the name and the opening parenthesis. All other rules apply.

printf("%d", 10);

8) Pointer/reference specifiers

In C and C++, pointer and reference specifiers should be written next to the following identifier, because the variable is the pointer, not the type itself. However, if in the language the type itself is the pointer, not the variable, the pointer/reference specifier must be written next to the type. Example:

// C/C++
int *a;
// C#
int* a;

In C/C++, we have different modifiers for pointers, the most common of which is const. When we want, for example, to have a pointer to a const pointer to a pointer to an int. Because this is a very complex syntax, you can use spacing ad-lib.

9) Function signatures

No matter where a function signature appears, it should be formatted the same way. All parts of the signature must be on the same line, unless it's too long. Then, the rules for breaking up a comma list apply.

First are the function modifiers (public, static, private, protected, override, etc.). They should be ordered in the following manner:

  • public, protected, private
  • internal, export
  • static
  • virtual, abstract, override
  • inline

Next, is the function's return type. It should be written as if it's a variable type.

After that, the remaining signature should be written like a call: no space between the name and the opening parenthesis.

If the language has any modifiers, that go after the argument list (for example, const and override) in C++, they should be written (in no specific order), separated with a space from the parameter list.

If the signature is followed by a body, the body's opening brace should be written on the same line as the function. If the language allows putting just one statement without curly braces, you should do that (for example, C#'s => syntax).

10) Naming

Names of the public API (anything that is exposed for the world to see) must be descriptive and should make clear what the function/struct/class/property/etc. does. Naming should follow the language's naming conventions. If two languages should interact, ideally, the two languages should use their own naming conventions, otherwise one of the naming conventions should be picked and used consistently, where necessary.

Names of unexposed parts of the program are more relaxed, but still you have to follow the appropriate casing. Local variables are the most relaxed. You can use single letter names, abbreviations, and meaningless names (foo, temp, i). Common names are:

  • i, j, k, smthI, smth_i - counters
  • n, smthN, smth_n - static counts (the amount of something
  • res, smthRes, smth_res - a variable that will keep the result of the function
  • val, tmp - used when you don't know how to name a variable

In C/C++, all names should be in snake_case, with the following exceptions:

  • Types should be suffixed with _t
  • Constants and #define names should be in UPPER_CASE
  • Names of template parameters should be in PascalCase, suffixed by T
  • Names of local variables may be flatcase, but snake_case is preferred

II. Good practices

This section contains more guidelines than rules, but its still important to follow these guidelines, so that your code is good.

1) Don't make stuff too complex

Its pretty much self-explanatory, but I'd like to elaborate, since it is vital to understand this. Your mission as a programmer is not to solve the world, neatly describing it in struct and functions. Your mission is to have a clear idea of what you want your program to do and write the least complicated piece of code to achieve that. You can forget about some patterns, because they just overcomplicate your code. Don't be afraid of tools that the language gives you (like inheritance, static classes, goto statements, etc.), just because someone said they're bad. Use every tool the language gives you with caution, but use all the tools you can and that are applicable to the case. In general, this will help you write better code.

2) Use your language

In a lot of forums and social media you can see the advice "Don't use X". For example: "don't use C#'s static classes", or "don't use multiple inheritence in C++". Although in 90% of the cases those can be good advices, there are some problems that are solved much easier with the tools your language provides you. Don't restrict yourself to use a subset of the syntactical tools that the language gives you, but use the tools responsibly.

3) Breaking up your functions

Let's face it: we don't really need functions, since we can just write all our code in main (and sometimes, for some very simple programs, this is advised). Still, functions generally tend to make our code just that much readable. Still, you shouldn't make a function for every statement in your code. Use functions with moderation, but don't forget about their existence. A good rule of thumb is that if you need to put and or or in your function's name, the function probably needs to get broken up. If you happen to repeat the same code, extract that into a function. Generally, a function needs to be about a screen (20-40 lines) of code. Still, if the algorithm used is complicated or you're microoptimizing something and there are tangible positive differences, it is not a problem to break this rule.

4) Making structures

Deciding when to make a structure for something is a complicated question, and doesn't have a single answer. Still, generally, you create structures, when you have a group of functions that operate with similar kinds of data (for example, functions for vector operations). In such cases, you can group the functions under one whole by creating a structure and putting the functions in it. BE EXTREMELY CAUTIOUS to not create a typeseption. Sometimes, programmers create too many structs, when they can make just one. A rule of thumb is that a struct must be responsible for one system/object/API/etc. and do its job outstandingly well. You might be tempted to create something, called a god class/function/file/module. A god class for example is a class which contains a significant portion of your logic. DON'T DO THIS. God classes can be a pain to mainain and read, and is technical debt in its purest form.

5) Interfaces

This doesn't apply to C++/C, but in more modern language, we have the interface construct, that allows us to have a level of abstraction, and just specify our requirements, without baring the weight of the implementation details. Still, interfaces should be used with caution. Sometimes, its appropriate to just skip over the interfaces and go directly to classes (despite what some programming gurus might say). Interfaces should be used only when you have two vastly different implementations of the same behaviors, that need to be interchangeable. In such cases, interfaces are perfectly acceptable. Generally, an interface is used where interchangability of behavior is needed.

6) Decoupling your architecture

This is pretty self-explanatory, but don't make your achitecture one big heap of lasagna. Think about your architecture in advance and more importantly: what are its separate parts and how are they going to communicate. Make those parts as decoupled as possible. It is fine to have high coupling inside the architectural parts themselves, but you should keep track of how big these parts are getting, and you should try to split up parts of your architecture that are getting too big to be maintainable.

7) Commenting

Use comments sparingly. Comments should be used to bring the code reader's attention to something that might not be noticed at first. Comments should be concise and to the point. Don't overcomment. After all, you're not writting a novel, you're writting code. If you write bad code, at least have the courtesy to explain what in the world your code does.

8) Documenting your code

It is best to use the language's integrated tools for documentation, so that you help the IDE give more information to yourself and other fellow programmers. If you're coding something more complicated, create a documentation folder/repo to go alongside your project, in which you explain in detail how your program works and how the user may interact with it. When writing a documentation, you should write it unambiguously - whoever's reading the documentation should know exactly what you meant, even if that is at the expence of being wordier.

9) Be literate

Self explanatory; Please, don't make stupid spelling or grammar mistakes, after all, we'd hope our operations are at least semi-professional. If english is not your strength, please install a spellchecker (they don't take up too much space, don't worry)

10) Error messages

This depends on personal taste, but I will deny pull requests just because of this. Please, write your error messages like a sentence: with a first capital letter and ending with a period.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment