Skip to content

Style Guide

meihapps edited this page Mar 5, 2024 · 9 revisions

Style Guide

All pull requests have to conform to these styling rules before they can be merged. These rules are not fully complete and will be updated as we get more familiar with the language and whenever any style related problems arise. If anybody has any questions about the style guide or believes that an alteration of the style would be better than what is listed here, feel free to bring that up.

Casing

Taking inspiration from PEP-8 styling:

Classes TitleCase
Variables snake_case
GDScript Files snake_case
Functions snake_case
Enums SCREAMING_SNAKE_CASE
Constants SCREAMING_SNAKE_CASE

Meaningful variable names

Avoid unnecessary confusion by making variable names clear and concise.

Avoid single character variable names entirely, with some leniency for things like "x", "y" and "z" when working with multi-dimensional arrays as those names would be meaningful.

Don't remove letters from words to shorten them, any half-decent editor features auto-complete and clarity is far more important than the number of characters used.

Every variable name should be descriptive and intuitive.

Typing

GDScript is dynamically typed and it is okay to take advantage of this fact, but for clarity and conciseness we should limit variables changing type as much as possible and only do this when the situation does call for it, which will be rare.

All variables and functions should utilise type hinting and those type hints should allow all possible states the variable should be permitted to be in and only those states. If this becomes difficult or complex in any instance, be sure to utilise Enums to make this process easier.

In the instance of a variable name being reused with no relation to the previous use of that variable, the original type hint does not need to reflect its current scope of possibilities but a new type hint should be given and it should be treated as a separate variable in all respects.

Whitespace

Whitespace should be done with tabs, allowing for adjustable tab sizes per user without messing with the file structure.

Classes

GDScript is unfortunately an object oriented programming language instead of a hybrid programming language. To minimise the negative impact of this here are some guidelines to follow:

  • Only use inheritance when interacting with the engine.

Inheritance is generally a very good way to introduce a lot of bloat into your software. In order to keep our code clean and readable, we want to minimise use of inheritance. Each file will include an overarching class that the file as a whole represents and that file will, more likely than not, want to inherit from a base class of the engine itself.

You can also define classes within that overarching class and here is where I’m discouraging the use of inheritance. By avoiding inheritance you can keep the inner classes a lot more standalone and avoid the messy and largely redundant code that object oriented programming tends to enforce.

Pattern Matching

As a general rule, if you find yourself using elif at all, what you’re writing can be written in a much more clean and concise way with a match statement. Unfortunately GDScript doesn’t offer the same safety benefits some other languages do (rust) but they are still definitely preferred.

For Loops

Much like python, for loops do not exist in GDScript. Instead, it offers for-each loops which they refer to as for loops. For-each loops are far more powerful, readable and concise than for loops in most instances. Unfortunately GDScript doesn’t offer most of the functions that make that statement true though. I will be implementing some basic helper functions like enumerate but in the meantime we should follow these principles:

  • Do not use range when accessing elements in a collection unless you need the indexes of the items. When the necessary functions are implemented, enumerate will be preferred over range in this instance
  • Until zip is implemented, when iterating over multiple collections at once, use range. After that prefer using zip

While Loops

Whenever possible, convert while loops to for loops. It’ll be a lot more readable that way as the for loop was always intended to be a shorthand for that subset of while loops.

While you can technically give while loops complex conditions, this is difficult to interpret compared to either simply exiting the while loop early or putting that complex conditional into a separate function. Of these two options, the function approach is better, but gauge it based on the situation. If the condition can’t be evaluated until partway through the while loop for whatever reason, exiting early is a fine approach and for a conditional that isn’t particularly complex it would still be fine to just give the while loop the conditional.

Comments

With effective use of type hinting and well named variables, the need for the majority of comments is largely erased, however comments are still important. Sometimes a less readable approach can be preferred for performance reasons and in these instances it’s important to give clear comments detailing the intention and if possible methodology of the code given. For the sake of teamwork however, I would recommend overuse of comments above underuse (within reason), ultimately any completely unnecessary comments can just be deleted but adding a comment to something that needs one and doesn’t have one is a lot more difficult.

Global Variables

Don’t use global variables for anything other than constants, and even then try not to use them if possible.

Git Commit Messages

Use meaningful commit messages. A short succinct summary title and a detailed list of each change made. If this is not doable, you need to increase the frequency at which you make commits.

Coroutines

Coroutines are incredibly useful. They allow you to go through elements in a sequence or collection one at a time, while only keeping the current one in memory. For large collections or infinite sequences, this is a huge benefit. Try to prefer these whenever a large collection is being used but do bear in mind that coroutines can be exhausted, you can only iterate through a coroutine once and cannot retrieve elements multiple times so don’t use these unless each element can be acted on fully independently.

Multiline Expressions

If an expression is too long to comfortably fit on one line you can split it across multiple to maintain readability. Generally try to find places to break the expression up that allow for a clear pattern. I will provide examples later to make this more clear.

Alignment

There are some scenarios where alignment can be used to increase readability. For example, if you’re defining an Enum and there is a consistent mapping of NAME = integer, consider aligning the equals signs with whitespace to make it easier to read. This will also result in all the integers being aligned. There are many similar scenarios that can benefit from using whitespace to align similar elements together, but sometimes this can worsen readability so judge it based on the situation at hand. As a general rule, I would say so long as the identifiers are close enough in length it will improve readability.

I will amend this to give exact values at some point in the future to make this more clear but for now I will keep it general.

Constructors

When passing variables to a constructor, try to avoid pre-initialising those variables with dummy values. Instead use the format of _name in the class and name in the parameter. E.G.

class Map:

    var _nodes: Array[Node]

    def _init(nodes):
        nodes = map_nodes

Strings

For strings, always prefer " over ' unless " is not possible for the scenario, e.g. format strings if godot supports them like this.