This post was originally posted on medium. Here I paste it and give it a fresh look in a few places.
Was anyone else ever under impression that principles of software development are a mess?
Take a look at the following list of arbitrarily chosen, but — I believe — a fair list of software development principles. I’m pretty sure most programmers come across them sooner than later.
- Make it work, make it right, make it fast
- Be agile
- Write clean code
- Use design patterns
- Be consistent
- Principle of least surprise
- Boy scout rule
11 rules (15 if SOLID counts for 5).
Each of those is very popular. Each seems very reasonable. However, it’s not like I challenge my code every second against each and every principle. Do you?
Yes, sometimes I immerse into a single one — I code with a strong resolution that this code will be clean af, Uncle, you’ll be proud. Or: this stuff is going to be almost like that one — let’s not repeat ourselves, extract that stuff, extract it, now. Some of them naturally take me over.
Following a relatively small group of the principles — fine. But following all of them constantly — I doubt if anyone does that. And yet we are reminded to follow this rule and that rule, eventually ending up with such a big pile.
And after I thought about it, I discovered that there’s a worse problem than the quantity of the principles.
They all make perfect sense in isolation from the others:
- After learning SOLID I almost despised people that don’t follow it. No excuses.
- DRY – come on, why would you want to repeat yourself? Are you stupid or something?
- And being consistent — perfect. Nobody likes inconsistent code. All the styleguides agree — you better turn a blind eye on our rules if your team decides otherwise.
- …and so on
Now the scary part — in reality, some principles can stand in contradiction to each other:
- YAGNI vs SOLID. I’ve seen many times pull requests that consisted of solid but unnecessary code. Which is better? Speculative solid code or no code at all?
- Boy scout rule vs be consistent. When the code is a mess, but somehow “organized” mess, consistent across all the system — do you stick to the current way, or clean it up?
- KISS vs design patterns. Design patterns always add complexity, don’t they?
I’ve been facing this kind of dilemmas many times. Principle A on one side, Principle B on the other side. You can’t have both. I’ve been looking for a sweet spot between two opposite virtues along the common axis. I’ve been trying to find a good defense line against accusations like “this solution isn’t consistent”.
In such situations, I was forced to chose one principle over another. Apparently, some principles are more important than others.
Unfortunately, none of them compare its importance to the others (make it work, make it right, make it fast is close, though). That’s a bummer. Here is my proposition.
I took them 11 principles (well, almost) and laid them on a pyramid, so that the ones below are the foundations for the ones above them — kind of like Maslow’s one.
The idea behind the pyramid is that you shouldn’t undermine the lower layers at the expense of higher layers.
I would like to stress that every single principle is close to my heart. However, as given previously, conflicts happen. Ideally, each is implemented, but in a scope that doesn’t interfere with the ones below. I will use the terms “more important” and “less important”, by which I explicitly mean “when they are at odds”.
In other words, it’s good to let a rule go, if another one is:
- lower on the pyramid
- already in place
- in conflict
You can think of the pyramid as presenting the right order of which rules to follow. When you follow it bottom-up, you can’t be wrong.
Please keep in mind that this is only a skeleton. A lot of circumstances can influence the positions. Yet I still think this reflects pretty well my current understanding of what’s important, and maybe start some interesting discussions.
Let’s elaborate the principles bottom-up (“more important” to “least important”):
- Make it work. This is the first most important principle in your everyday job. This may be obvious, but it’s good to remember that all the follow-up principles work best on code that does the job.
- YAGNI. The second most important thing to assure is to remove unnecessary code. Because we first made it work, all the logic is already in the code – we won’t need to add any code that brings business value. YAGNI on top of that guarantees that we don’t have unnecessary code as well.
- Principle of least surprise. Now let’s make sure that the remaining code surprises other people as little as possible. If this happens to be the last rule you apply, your code could be unclean or have other flaws — but at least it’s not surprising to anyone. This trait – predictability – encourages other people to improve it, if necessary.
- KISS. Only after that, it’s time to keep it simple. I believe it’s less important than being not surprising. I would rather work with complex, but not surprising code, than the other way around. It’s easier to simplify code if you can get the idea behind it, and the latter is greatly easier if the code is not surprising.
- Be consistent. Now it’s time to care for consistency. I would rather work with inconsistent, but simple code than the other way around. It’s easier to make code consistent than to make it less complex preserving consistency. This means for example that I would drop Ruby idioms or Rails magic, despite being consistent with the whole ecosystem, if it undermines code simplicity.
- DRY. I guess this one being so high could surprise a lot of people, but I think DRY is somewhat overrated. Maybe because it’s brain-dead simple to trot out and blindly follow, but hard to spot the danger of it. For example, abstracting a KISS-less code can lead to wrong abstractions. Repetitive code isn’t the end of the world — if consistent, simple and not surprising.
When I think about it, I’ve always been intuitively looking for an additional reason to stop repeating myself than the repetition itself. If found, it was usually in previous, more vital principles.
- Clean code. Now when we don’t repeat ourselves, it’s time for meaningful names, short methods, etc. This one should be already partially applied by DRY anyway. As much as I’m a fan of clean code, I believe that prior to this point there were more urgent issues to fix. For example, clean but overly complex code seems like caring more about the form than the content.
- SOLID. In short, this seems to me as the natural continuation of the previous step for design level— “clean design”, so to speak. Clean code is higher because it makes keeping clean design easier.
- Design patterns (if necessary). This step I would explain as “add complexity to areas where you already identified common issues”.
- Agile practices. In order to have adaptive software, we might want to add some patterns and abstractions to it. However, unlike design patterns, there’s always the factor of speciation to it, because we’re talking “adapting to speculative future”. I picture this phase as “adding complexity”, but rather to areas where you predict to identify common issues. This could be fine, but please make sure you don’t neglect present problems.
- Up to this point (except for the first step) it’s making it right.
- Boy scout rule. It’s nice to clean your borough, but not before making your code right. This principle is kind of meta to me: it doesn’t tell what to do with your code, but rather when (always) and how much (not too much).
- And finally — make it fast. This one is normally applied after noticeable time, after measurements — no need to worry in advance.
Here’s how the pyramid would help with my previously mentioned struggles:
- YAGNI vs SOLID. Of course, let’s be SOLID, but only in code that we are sure we need in the current iteration.
- Boy scout rule vs be consistent. First, make sure the code is working and simple; then stay consistent, apply SOLID, etc. Only after that, if the structure of the code still seems messy to you, and you can fix it within a relatively small amount of time — feel free to clean it up. If you find it messy, but can’t fix it quickly — don’t do it this time.
And thank the pyramid for holding you back from optimisation in advance 😉
- KISS vs design patterns. Design patterns are fine, but please make sure they are applied in places where clean, simple code reflects the pain that a pattern is supposed to fix.
What do you think? Does it even make sense to arrange the principles in such an order?
If not, how else do you deal with code review accusations? How would you judge the value of particular principles?
If yes, does my pyramid speaks to you?