Skip to content

Coding style

JR edited this page Jan 26, 2022 · 6 revisions

Due to the number of developers the coding style is generally consistent, changing slightly over time. These are the recommended guidelines.

The closest we can get with dfmt:

$ dfmt --split_operator_at_line_end=true --space_after_cast=false --compact_labeled_statements=false --template_constraint_style=always_newline -i

Example

// functionName (for grepping)
/++
    Brief introduction to what the plugin does.

    Brief explanation how it does it.

    Example:
    ---
    // three dashes before and after
    ---

    Params:
        templateParam = Description as if runtime param. Long lines
            are wrapped and indented.
        variableName = Description, wrap at ~80 columns.
        secondVariable = Description, same.
        thirdVariableIndented = Same.

    Returns:
        A humanly-readable explanation of what the resulting value
        represents, potentially with concrete examples.

    See_Also:
        [adrdoxNativeLink]
        [rarelySeeThis]
 +/
@(IRCEventHandler()
    .onEvent(IRCEvent.Type.ANY)
    .chainable(true)
    .verbose(true)
    .addCommand(
        IRCEventHandler.Command()
            .word("word")
            .policy(PrefixPolicy.prefixed)
    )
)
void functionName(templateParam, T)(T variableName, T secondVariable,
    T thirdVariableIndented) // @safe inferred, only add if we need the guarantee
if (someConstraint!T && ownUnindentedLine!T)
{
    import generally.early : onlyFunctionsBeingUsed, nothingElse;

    if (((a == b) && (c == d)) || (e == f))
    {
        // ...
    }

    if (g == h) return T.init;  // only if it fits on one line

    // Don't do this, vulnerable to OpenSSH mistakes
    /*if (i == j)
        return T.init;
    */

    // This is discouraged but okay, prefer using {} for the main if case. else *always* with {}
    if (k == l) return T.min;
    else
    {
        return T.max;
    }

    // In this case, better yet;
    return (k == l) ? T.min : T.max;

    import stuff.things : functionOnlyUsedHere;
    functionOnlyUsedHere();

    immutable someLocal = cast(double)someInt;  // double inferred

    with (SomeEnum)
    final switch (someEnumValue)
    {
    case firstMember:
        // cases on same indentation as the switch statement
        break;

    case secondMember;
        // ...
        break;

    /*default:
        // In non-final switches, defaults have breaks
        break;*/
    }
    
    immutable someString = getSomeString();
    if (!someString.length) return;  // use .length to test if "" or null

    // someString.writeln;  // Use normal writeln(...) or *at least* .writeln();

    foreach (immutable i; 0..100_000)
    {
        // Prefer this to for (int i; i<100_000; i++)
        // If you really need normal for, use size_t i and ++i
        // immutable index. Underscores where commas would be

        if (!condition) break;
        // Normal path
    }
}

///
unittest
{
    // ...
}

General style and expressions

  • British English, to prove a point.
  • Put { and } on their own lines.
  • Very soft 80 column, hard 120 column wraps.
  • Try to write testable code whenever possible, place a unittest immediately after the function.
  • Always* enclose expressions between parantheses; if (((a == b) && (c == d)) || (e == f)) { /* ... */ }
  • if statements don't need {} brackets iff it can fit on one line, and there is no else case.
  • else always needs {} brackets, always.
  • Names may be long; avoid p or upr except for size_t indexes.
  • No need for g_, m_ and s_ prefixes.
  • functions (and function templates) camelCase.
  • Types, other templates and mixins PascalCase.
  • enums PascalCase, enum members camelCase.
  • Constants camelCase, no C-style UPPERCASE_CONSTANTS;
  • /++ ... +/ for documentation; //, /* */, /+ +/ for comments. /+ can nest /* so may be preferred in some cases.
  • Return early, avoid extra indentation.
  • immutable whenever possible, const when not. Only use mutable types if it's a range that requires auto, or it's a variable that we want to reuse (like string verbs).
  • static immutable and enum whenever possible, except in such cases where the manifest constant-ness of them would cause unecessary allocations.
  • final class preferred whenever possible.
  • goto only between cases in a switch.

Plugins

  • Use UFCS, but only where it makes sense.
  • Keep all state inside the plugin class. This ensures that everything is reset on reconnections.
  • Initialise resources in initResources but generally populate non-JSON arrays after RPL_WELCOME or after MOTD.
  • Use either a timed Fiber/delegate or PING to automate tasks.
  • Take IRCEvents by const ref to minimise the copies made.

Other

  • Regex is convenient but super bad for compile times. Avoid.
  • Avoid using .to!string or .text on large enums (read: IRCEvent.Type) like the plague. Use kameloso.conv.Enum.
  • with statements in great moderation, but freely when it's for enums.
  • Templates are great, but don't be afraid to write normal functions.
  • Don't go overboard with annotations unless it's a library-type function.
  • When a function template is a frontend to another template that does all the work, the former is foo and the latter is fooImpl.