Avoiding Premature Software Abstractions

Jonas Tulstrup on 2021-10-24

How we removed 80% of our code — improving development speed and reducing errors

Files required before and after removing premature abstractions. Based on a real example endpoint updating a piece of data. Each post-it represents a file. Image credit: Author.

Software developers love abstractions. Abstractions are great and absolute key for efficient development. Writing software using solely 1’s and 0’s would be quite the chore after all. The problem comes when abstractions are introduced prematurely, i.e., before they are solving a real non-theoretical problem. Adding abstractions always comes at the cost of complexity and, when done excessively, starts slowing down the speed of development and the ability for people to easily comprehend the codebase.

All problems in computer science can be solved by another layer of abstraction… Except for the problem of too many layers of abstraction. — Butler Lampson

This post illustrates how avoiding common abstractions can lead to a much cleaner codebase with massively reduced complexity as well as increased readability and maintainability. The post is based on a transformation in the way I, and the rest of our team, build microservices now compared to previously. For us, this has reduced the size of a regular feature, such as a new microservice endpoint for updating or reading data, from a total of approximately 25 files down to just 5, that’s an 80% reduction with the majority of code simply having been deleted, all while simultaneously improving code readability.

The points discussed are grounded in principles like Keep it Simple, Stupid (KISS) and You Aren’t Gonna Need It (YAGNI), meaning that we want to keep abstractions to a minimum and only introduce complexity when it provides a significant and real benefit. The points will apply to most kinds of software engineering.

Common Premature Abstractions

Let’s look at some concrete cases of premature abstractions that tend to occur often in practice. These are all based on real examples found in our own codebases.

  1. Responsibilities are abstracted too granularly
  2. Design patterns are used without real benefit
  3. Performance is optimized prematurely