Recently a subset of our dev team sat down and had an open discussion about what challenges we faced while developing, what areas of our codebase were hard to work in, and how we could mitigate them. We came up with a list of existing software development principles that we felt were important to us, and they are listed below:
- Universal Principle - Minimize complexity
- DRY (when it reduces complexity)
- YAGNI/KISS (don’t pre-bake)
- Minimize Leakiness
- Universal Principle - Code should be easy and readable
- Be explicit
- Provide context and prevent ambiguity with variable names
- Put conditionals into variables
- One method should solve one problem
- Maintain the same level of abstraction
- Write inline documentation if code is not easy or does something unexpected
We organized them by defining two Universal Principles, which are the overarching ideas that lead to better code - which are supported by the principles below them.
Universal Principle - Minimize Complexity
This is the most important principle while developing software, and most other software development principles are to simply ways achieve this. The most common mistake I see developers making is applying principles (e.g., DRY) without considering if they are actually reducing complexity.
Don’t repeat yourself unless it makes sense to. It’s important to note that there are times when your code does become less complex by repeating yourself.
YAGNI / KISS
A common mistake I see software developers making is pre-baking (prematurely optimizing) their solutions to reduce complexity without considering the age of the feature. In product terms, if a feature is early in its development (and therefore is subject to change), it doesn’t always make sense to over-architect the solution.
There are patterns that ‘bake’ solutions more than others, and it’s important to consider that when deciding which patterns to implement.
All abstractions are leaky to an extent, but we should try to be aware and minimize the leakiness as much as possible. Abstractions lose a lot of their value and can increase instead of decrease complexity when they are too leaky.
Universal Principle - Code should be easy and readable
Code should be optimized for reading and understanding. It generally gets written once and read many times by other people. If your code relies on a contextual understanding that others may not have or you may forget, you’re shooting yourself in the foot.
It often isn’t valuable to take shortcuts when naming classes, functions, or variables. It also doesn’t often pay to write a clever, shorter solution over a longer more explicit one.
Provide context and prevent ambiguity when naming variables
y = calc(x1, x2)
Is less effective than:
area = calculate_area(width, height)
Put conditionals into variables
Oftentimes even when not reusing them, it can make code more readable when you place conditional statements into a variable.
if (height > 10 && width < 7 && (depth < 10 || depth > 10)) # ...elsif (height > 10 && width < 7) # ...end# vshas_valid_area = height > 10 && width < 7has_valid_depth = depth < 10 || depth > 10if (has_valid_area && has_valid_depth) # ...elsif has_valid_area # ...end
One method should solve one problem
Use methods as a single conceptual unit. Instead of saying that methods should be limited N lines, it’s better to break logic up into conceptual blocks.
Maintain the same level of abstraction in a method
Methods usually involve calling several other methods, and they become easier to read if they only call methods one level of abstraction lower. Think of it like a pyramid.