Frontpage Projects Game Development Posts

Opinionated Style Guide

This is my personal style guide and opinions.

General rules

  • Check early: If it is possible to misconfigure a component, such that it will always fail due to that misconfiguration during usage, then it should fail during configuration; usually in the constructor (insofar as the issue is easy to diagnose at configuration time.) The usual suspects are: fields that should never be null, and fields with out of range values.
  • Use Dependency Injection, as it makes it easy to test components, by just replacing sub-components.
  • Avoid Dependency Injection frameworks. These are often a bit too smart, making it difficult to determine which components use which other components. Manual Dependency Injection is simpler and easier to keep track of.

Specific Rules

All languages:

  • In languages without option types: Perform null/nil/None check on all constructor arguments for those fields that must not be null. Really seems obvious, doesn't it? (Specific case of Check early)
  • Always use Path types instead of String when working with paths. Especially when you are writing a library.
  • Mark as much as possible using static or const. This is a small and very cheap optimization (it only costs a keyword.) For fields it will ensure that the code is only executed on startup, and can often enable compile-time/JIT optimizations. For functions it can remove invocation indirect.
  • Do not assign fields to locals when it doesn't add any more information. bytesLength := bytes.length is an useless redirection that detaches from the code.
  • Do not create objects to then just access a single field afterwards. For example: new Point3d(1,2,3).x
  • Related: Do not create a new class or record just for wrapping some arguments in a function call. (This is only appropriate in the rare languages where it improves readability.)

Java:

  • Do not use URL, and especially do not do equality on URLs, as they will result in DNS lookups. Prefer URI instead.
  • All classes should either be final or abstract.
  • Avoid abstract classes; prefer interfaces instead.
  • Use liberal amounts of final for local variables.
  • Use var with moderation.
  • Error prone: All classes should have @CheckReturnValue. Annotate methods with optional return values with @IgnoreReturnValue.
  • Structure your methods like so:
    1. Validate input: By placing all your assumptions at the start, you make it easier for readers to determine what holds for your program.
    2. Perform operations.
    3. Return output: Can be mixed with 2 for complex algorithms.
  • Structure your canonical constructors like so:
    1. Validate complicated input: These are more complicated formula, like range checks, and
    2. Null checks: Simple Objects.requireNonNull checks should be done after the complex checks, as it allows you to test cover the complex input validation without having to create non-null values for the uncomplicated fields. The complex validation will itself often have some implicit null checks (field and method lookup).
    3. Field assignments. Placing these last ensures that you never assign an invalid field to a value; this can be combined with the null checks.

Bash:

  • Always start your script with set -e.
  • Use curl --fail to ensure that curl produces an error message in case of an unexpected status code.

Testing

Tests can either be specific or parameterised/properties. Specific tests tests the behaviour for a specific set of inputs, while parameterised tests the behaviour for a class of inputs.

  • Specific tests should not use variables/constants for the expected value.
  • Specific tests must not perform formatting when asserting error messages. The error message should occur directly within the code as a literal.
  • Templating can be used in and is most likely needed for parameterised tests.

Naming

  • Variables should be named after their purpose, not their type, nor the process they've been created from. A variable typed BigInteger, should not be named bigInteger, and a list created from a filtering process should not be named filteredList.
  • Methods that check some condition, and panic or fail if the condition is false should be prefixed with assert.
  • Absolutely do not name your variables i, j or x. If you are looping over something, name it after what you are looping over. E.g. dogIndex.
  • An index is not the same as an identifier.
  • Name test utilities correctly

Documentation

  • Treat documentation of functions and classes like a sales pitch: Why should anybody call or use this component? What can this component do for you?
  • Structure your dokumentation with the Five Ws:
    • What: First sentence should be a short description of what this function, method or class does or is. There is often specific support in the language's documentation tools for these first sentences. Make it short and snappy.
    • Who/When/Where/Why does this thing do what it does? This adds context for how this component fits into the larger program. You can often leave out the these for internal components.
    • How: How does this thing do what it does? Here you can add your implementation details. These should always be last in your description. The how is mainly only relevant for protocols and highly complex code:
  • Documentation should rarely be a recitation of the implementation in human language. "Foos and then bars, followed by a foobar of the result of the foo and the bar". Disregard when documenting an protocol that the component is implementing, and that users are expected to implement.
  • Use your documentation tool's annotations, and avoid double documentation. In Java it is redundant to write the summary "Sends HTTP request and returns HTTP response", when annotating with @return Server's HTTP Response. Similarly for @param.
  • It is better to say nothing with no words, that to say nothing with lots of words.
  • Do not start your documentation with any of the following. We already know that it's a function or a class, it's right there in the code! Instead describe what it does:
    • "This"
    • "For"
    • "Function/Class that"
    • "Represents" or "Representation of"
    • "Stores", "Stores information about/on"
  • Avoid weasel words. Some specific examples:
    • Someone/Something: There is almost always a specific kind of someone. Is this someone a developer, a client (software), a server, an end user?
    • Correct(ly), Valid, etc: We don't need to specify that our code is doing something correctly (why would we write incorrect code?)
    • Determine: Vague. Prefer find, compute, get.
    • Handles, Handler: Vague; how does it handle it?
  • Avoid filler words like Actual. In tests prefer result instead.
  • Another filler word current: When talking about the current state of the system, there is rarely a need to specify that this is the current state of the system.

The rule to rule all the rules

  • This list is not for you to slavishly follow. It exists to get you to think. I've avoided adding exceptions to each rule, even though they could be a plenty, because I'm sure you can identify them yourself. The worst code on this planet is that which was made without care, and without thought. If you find a rule too oppressive, then good! Hold that opinion close to your heart, break the rule and be ready to defend it. That will make you and your code better.