Skip to main content

In Software, One Thing is Better Than Two Things

· 3 min read

I recently took over a software project that had, from both an engineering perspective and a usability perspective, outright failed. The code was a teetering tower that had collapsed in on itself into a pool of leaked abstractions and interdependent logic.

I've spent many hours wading through the mess, and it's become almost a meditation on what can go wrong in software. The issues with this specific code aren't that interesting; what feels important here are the core values that a developer has to have for all the little microdecisions flow out of.

I think it sits at the bottom of "don't repeat yourself" or "keep it simple stupid" or all the other quippy platitudes. Even "single source of truth" is putting it too narrowly. Never have 2 things when you can have 1. I think this has to be so deeply rooted that it's more an ever present gut feeling than it is some explicitly reasoned rule. Like if you stand on a precarious ledge you don't need to be told to feel anxious.

Here's some shallow examples to try and illustrate the deeper point:

  1. If a SQL table needs a date, it should have a date. Not a Unix timestamp and an ISO 8601 and a colloquially-styled date string and so on. Have one date column and make sure it's correct. Use a functional transform if you need to display something else.

  2. When possible, avoid cache layers. A cache means you have data in two places and now have the non-trivial problem of keeping it in sync. If you can't avoid a cache layer, you at least want to make it feel like one thing. For example, tracking changes to the underlying data and pushing them into the cache layer is vastly better than relying on a timer to expire the cache. If you can avoid states where the cache decouples from the data underneath, you should.

  3. You should prefer composition over inheritance. That's not advice; that's an observation. If you've ever spent time working with a deep inheritance tree, having to implement the same behaviour in multiple child classes or seeing logic sprinkled across a 5 layer deep inheritance chain should feel wrong. Composition is better because it more tightly defines the "one thing" of a behaviour, rather than letting that behaviour become diffused into different places or even outright duplicated.

  4. If data needs to be validated, it should be validated once, at the point it enters the system. If your code is constantly having to call if is_valid(some_data) {...}, then there's no clear contract for what the application can trust. There's "fuzziness" in the system, and that's never good.

For each of these examples, it's easy to think of "hey what about scenario X" or "but there's also consideration Y", and that's totally valid. There are reasons to duplicate data or behaviors, both pragmatic and conceptual. The point is that as you feel the fundamental tension between differing concerns in software design, the principle of "prefer one thing" should pull pretty hard.

The project failed because this principle was missing. Multiple overlapping cache layers made data in -> data out a broken relationship. Repeated code with small permutations meant something was always missed when things were added or changed. Layered, partial checks for error conditions meant errors propagated deep into the code were only sometimes caught. It wasn't some singular critical flaw; it was small compounding errors that multiplied until the whole thing fell under the event horizon.