OpenAutonomy Code Style Explanation
While many projects maintain a set of strict style rules in order to maintain a consistent appearance, it has been our experience that this approach has a few problems:
- it removes the ability to learn from your mistakes (since some coding idioms are dangerous, yet popular - styling for consistency often preserves these weaknesses)
- it wastes time in useless code churn, often leaving useless version history in the process
- it encourages developers to think small thoughts instead of large thoughts : no longer does it matter if the code is correct or designed with the right concerns in mind but that it looks a certain way
So, to this end, this guide explains why the OpenAutonomy code is written as it is. These concerns are either abstract ideas to keep in mind or specific defensive idioms.
Philosophical ideas:
- The type system is there to help you so work with it, not against it (especially true in languages which don't expose types since usage is the only way to infer meaning)
- Be concise, but not terse: using a small number of characters to express your meaning isn't the point - size should be small due to structuring the code into blocks which are re-usable but have obvious purpose
- Keep a small scope of understanding: more important than a small code size is a small logical size so try not to rely on hidden side-effects or global state (using instance variables instead of parameters is just a smaller version of the global state problem)
- Avoid pre-optimization: Just write the code that seems most obvious and worry about performance once a problem is measured (the hot spots are rarely where someone thinks they will be, at first)
- Avoid short-cuts or special rules which are specific to one language unless absolutely required. A good idea should be sufficiently high-level that it applies in related environments: what works in C should work in PHP, should work in Java, etc.
- Favour unidirectional class dependencies. This makes each component testable since it never assumes the type of its caller. Use interfaces and object instances to avoid this problem.
- Maintain a symmetric control flow: one path should enter and one path should exist a given block (obvious exception is in cases like a PHP foreach, which cannot be given multiple conditions). This means that early returns, breaks, and continues should not be used in place of else branching or proper use of loop conditions. The reason for this is that it represents a hidden assumption in the control flow which is very brittle to future changes and complicates the process of debugging and refactoring (since both require understanding how you did, or didn't, get to a given line of code).
Common patterns seen in the code:
- Use assertions to document code: documentation becomes out of date and incorrect because the compiler doesn't test it (note that none of this code is in Eiffel). An assertion is effectively testable documentation but it also limits the exposure of bugs to the underlying state. It is always best to crash early, and in a way which can be later debugged and fixed, than to allow subtle bugs to persist, creating unexpected later behaviour or corrupting data to be used in later calls.
- Always compare types correctly: While Java enforces type-safe comparison, PHP does not and has non-obvious (and often misleading) type coercion rules so always use as strict a comparator as possible ( !== and === ) against the correct expected type. 0, '', null, false, and '0' are not the same things so be wary of cases where they may be treated as such.
- Avoid l-values on the left of comparisons: While Java will protect you from this bug, PHP will think that "if ($foo = '')" is correct while "if ($foo == '')" is what you meant and these are only one typo apart. While this isn't always possible, it is preferable to see this as a hard error instead of a subtle mis-behaviour: "if ('' = $foo)"
- Prefix private variables and methods with "_" so that it is always obvious who can access them and what the public interface is. This is especially meaningful in languages which do not distinguish enforce these access rules
- Line length is irrelevant but long comments should be wrapped to avoid scrolling when reading several sentences (at a reasonable width) and code should be wrapped if it makes an idiom more easily identified (constructing a list, map, or checking a list of cases, etc)
- Initialize all variables with a "default" or "error" value. Java will require this but PHP will intermittently emit runtime errors if a variable isn't declared in all traversed paths