Skip to content

Programming Standards

Colin Basnett edited this page Oct 3, 2023 · 13 revisions

Spaces vs. Tabs

"Soft tabs" consisting of 4 spaces should be used instead of a hard tab character.

The reason for this is that different programs and platforms handle and display tabs differently, meaning a space is always a space, but a tab is not always a tab!

Indentation

Keeping indentation consistent, while syntactically irrelevant, is highly important for maintaining readability.

Braces

Braces should always appear around any scope change, with each brace being on a separate line.

if (Foo())
{
    for (i = 0; i < 10; ++i)
    {
        Bar();
    }
}
else
{
    i = Goo();

    switch (i)
    {
        case 0:
            Car();
            break;
        default:
            break;
    }
}

NOTE: switch cases do not need separate bracing since separate cases don't generate nested scopes.

Naming

Consistent naming of variables, classes and functions is incredibly important.

Borrowing from existing standards, all names must employ CamelCase for consistency.

Avoid using abbreviations whenever possible, as it can make code more difficult to understand for newcomers and can obfuscate meaning.

Classes

All Darkest Hour: Europe '44-'45 classes must be prefixed with DH. For example:

class DHPlayerReplicationInfo

Currently, as in the past, there is a bit of a hodge-podge of naming conventions depending on what package you're in. A while back, we made an effort to try and convert all of the hundreds of classes to use a new naming convention, but we soon realized that in order to maintain backwards compatibility, we could only do this in half-measures.

Going forward, when making new classes, simply try to stick to the naming conventions established in the particular package you're working in.

bool variables and functions

All bool variable and function names should be in the form of a question. Rather than:

bDirty
bCleanRoom
Protected()

we use:

bIsDirty
bShouldCleanRoom
IsProtected()

Additionally, there exists a convention in UnrealScript to prefix all bool variables with a b character (eg. bIsFoobar). While I personally dislike the practice, there are already mountains of code in use that adheres to this convention; we therefore adopt it as a de-facto standard.

Keywords

All UnrealScript keywords such as if, for, string, quat, none etc. should be lower case so they are differentiated from named symbols.

Legacy Code

There is a ton of terrible, awful, nasty, despicable, dirty, wretched pre-existing code in Darkest Hour and Red Orchestra, there's no question about it. However, for the most part that code works, which means we should treat it carefully and with a certain level of begrudging respect.

Re-factoring is encouraged, but only when the changes are within the scope of code that is already being rigorously tested. Major re-factoring simply for the sake of doing so is highly discouraged, as it has a high likelihood of creating knock-on bugs that may get into a live build without ever having been identified.

Defensive Programming

When writing code, employ defensive programming techniques to ensure that likely and unlikely errors are guarded against and handled gracefully. Keeping on the defensive will ensure robust and high quality code.

Array bounds

Never assume that an array index will be valid. For example:

local array<int> A;
local int j;

j = Foo();

Bar(A[j]);

In this case, if Foo returns an integer that is not a valid index into the A array, an error will be thrown.

To avoid this situation, this following method is preferred:

j = Foo();

if (j >= 0 && j < A.Length)
{
    Bar(A[j]);
}

Casting

When casting an object, always ensure that the result of a cast is not used before being checked against none. For example:

if (DHPawn(Owner).IsSpawnProtected())
{
    DHPawn(Owner).Foo();
    DHPawn(Owner).Bar = 42;
}

In this case, if Owner is not a DHPawn or the underlying reference is none, the cast will fail, returning none. The script will attempt to run the IsSpawnProtected on a null instance of DHPawn, resulting in an accessed none error.

To avoid this problem, check that result of the cast to ensure that it succeeded:

local DHPawn P;

P = DHPawn(Owner);

if (P != none && P.IsSpawnProtected())
{
    P.Foo();
    P.Bar = 42;
} 

It is also encouraged to avoid casting duplication (ie. casting the same object to another type multiple times within the same scope). If you have to use a casted value more than once, you should simply store the result of the cast in a local variable and reference that variable instead.

Headers

All official Darkest Hour: Europe '44-'45 *.uc files should contain the following header:

//==============================================================================
// Darkest Hour: Europe '44-'45
// Darklight Games (c) 2008-2023
//==============================================================================

Comments

Ideally, code you write should be self-documenting, however in practice comments are needed to aid the maintainer in understanding the implementation and design of code.

Clone this wiki locally