home articles newsletter

Domain complexity vs applicative complexity

For years, code would fall into two categories: easy (good!) and hard (bad!).

Recently, I’ve realized that not every piece of hard code is created equal. Complex code often encompasses two kinds of complexity: domain complexity and applicative complexity.

And I often failed to identify which is which.

Domain complexity

Domain complexity is what we refer to when we talk about “rocket science”. Building actual spaceships, forecasting extreme meteorological events, studying the human genome, etc… You know, hard stuff.

But any domain where programming happens has inbuilt complexity.

I once worked for a real estate company. The regulations were abstruse enough that our codebase reflected their complexity. We had procedural abstractions that would handle dozens of steps sequentially.

Later, I worked for a book-streaming company. Handling the (supposedly) standard formats used by publishers to share their books was the stuff of nightmares.

There’s no way around domain complexity. If you want to solve your users’ pain points, you’ll have to accept the complexity of their business.

Applicative complexity

Applicative complexity, on the other hand, happens from how we solve problems.

Applicative complexity is the code smell, the anti-pattern, and the method with thirty-seven conditionals nested three levels deep.

Applications have phases in their lifecycle. When you start building a company, the application is crucial, but not all that there is. Teams focus on shipping new features, not on following conventions and patterns. Getting the company off the ground is the priority.

This first phase is where you’ll introduce a lot of applicative complexity. And when applicative complexity adds up with domain complexity, you end up with hard code.

Your application starts leaking at the seams. Usage drives new problems: your database can’t sustain a heavier load, and your abstractions do not allow you to introduce new requirements.

Solving applicative complexity: consolidation

Later, when you’re out of the woods, a new phase in the lifecycle begins.

I call this phase the consolidation phase.

Features need preliminary refactoring. Teams finally take a look at their .rubocop_todo.yml. Maybe it’s time to rewrite these seven service-objects nested in one another.

Consolidation often frightens teams. Features take twice as much time to ship. Programmers keep yammering about preliminary clean-ups. However, consolidation is necessary if you wish to grow or to evolve. Not doing the work now will only result in your application grinding to a halt.

I briefly worked with a company that had not shipped a single feature in a year. Why? Their codebase was full of applicative complexity (yet empty of any tests). They had decided against consolidation repeatedly. And now, they were stuck.

When you can’t be sure your changes won’t break the application, you don’t push any.

Life has seasons. Applications have cycles. Be sure to watch for the signs so you don’t sow when you should plow.


Rémi - @remi@ruby.social