by Cesare Rocchi

Undesigning abstractions


Undesigning sucks. Disassembling an architecture that I built months ago following the requirements provided at the time is never a nice experience. There’s a bit of a sense of failure, mixed with a feeling of time waste. But requirements and user needs change over time, and software should follow along.

To overcome situations like these I try to avoid getting in bed with a too specific architecture. I prefer to work bottom up. I write specific code first and then generalize along the way, without going overboard.

After all:

Designs are always playing catch up to changing real world requirements. So even if we found a perfect abstraction by miracle, it comes tagged with an expiry date because #1 — The House wins in the end. The best quality of a Design today is how well it can be undesigned.

RDX source

I tend to apply the following tactics to avoid over-engineering:

  • favor composition instead of inheritance
  • write code that, when crashing, it crashes as early as possible
  • create helper functions/objects to group common functionalities but make them very simple
  • write an example of an API before creating it (trick by Krzysztof Zabłocki)
  • avoid smart code
  • refactor in micro steps
  • celebrate when I delete code

Reusable software is a great concept until you sacrifice too much flexibility. And when requirements change often, or when you work on a product that needs quick iterations, flexibility is key.