| 8min read | 1467 words | Photo by Simon Goetz on Unsplash

The one mistakes that keeps you from building great software

Working on your own code base is easy. But what about other people's code? Avoid this one mistake to make onboarding into an existing code base a breeze.

Share

Do you know this feeling when you work your way through a new code base, but have no idea what is about? If the only thing you see are folders named ‘db’, ‘utils’, ‘frontend’, ‘src’, and it overwhelms you, then I can tell you why.

TL;DR: Split your code by feature domain instead of technological domain to make it easier to understand and maintain.

1 The problem with prescriptions

When you read about software architecture on the internet, you will get an insight into what technical people care about: The technicalities. What is the best pattern for structuring our code? Is it MVC? What do other developers do?

This is great, because the goal of software patterns was to have standardized solutions for reoccuring problems. After collectively thinking about this for years, we have a grand pool of tools and patterns at our disposal.

But there’s a catch:

Having great tools can also instill this false sense of confidence. That ‘if I just use this pattern all my problems will be solved’.

I hope you are having a different experience but I have been in a lot of projects where certain architecture styles were chosen just because they were deemed ‘best practice’ at the time.

Yet it completely disregards what a good architecture can do for us, if chosen with your goals in mind and not just based on what some random guy on the internet said. Good software architecture is a form of communication. It communicates to the reader what the code really is about, what its goals are, and what concepts are important to understand first.

2 Where People are Wrong

In the case of Clean Architecture you can go online and find most articles talking about it being divided into layers, going from the base ‘Entity’ layer without dependencies up to the ‘Driver’ layer, where our ‘dirty’ code lives. So if you didn’t read the book you will take this advice and roll with it, ending in a module structure that will probably look something like this:

# ! DONT DO THIS
app
|--src
|  |--entities
|  |  |--cats
|  |  |--fighterjets
|  |--useCases
|  |  |--pettingZoo
|  |  |--fighterjetSimulator
|  |--database
|  |  |--catsRepo
|  |  |--fighterjetsRepo

Trust me when I tell you that of the dozens of projects I have seen in my career so far, almost all of them chose a structure like this.

So, guessing by the file names, can you see what the code is doing? It’s immediately clear, isn’t it? Don’t you see how cats are part of the petting zoo? Of course fighter jets are not In case you don’t: I was being sarcastic.

2.1 The Common Closure Principle

Gather into components those classes that change for the same reasons and at the same times.

Separate into different components those classes that change at different times for different reasons.

The Common Closure Principle1

The mistake with the structure shown above is that files are grouped with respect to the layer they are in. Cats and Fighterjets are both entities within the software. But why should you care? Considering that you have coded before you probably already knew that an application needs some type of entity to hold data, right?. Additionally, the information that both are entities means very little to us.

What actually matters is that once I start working on the Cat entity, I want to know if there is other classes that depend on Cat that need changing as well. Classes/files/modules that depend on each other will also be touched with each other. Everytime I change an entity I want to know if there associated Repos, Use Cases, Helpers, a.s.o. In the example above I basically have to scan the whole codebase to make out which classes belong together.

On top of that comes the pain once you try to remove or exchange a feature from the code base. In this case you have to:

3 The Modular Slices Architecture

To solve this violation of the Common Closure Principle, let me show you a different approach:

# ! DONT DO THIS
app
|--src
|  |--cats
|  |  |--catModel
|  |  |--catsRepo
|  |  |--pettingZoo
|  |--fighterjets
|  |  |--jetModel
|  |  |--fighterjetsRepo
|  |  |--fighterjetSimulator

Already looks a lot better, doesn’t it? You can clearly see which files belong togther. As a new developer on the project, you will also see that there are two things that you need to be aware of to understand the system: Cats and Figterjets. If you want to change something about how Cats work, you have one folder to concentrate on.

Note how:

This organisational structure comes with literally zero costs and side effects and makes your code base a lot easier to understand and work with. The key is to cut your code base into slices of the problem domain, not layers of implementation.

This is something other people have already talked about. The Primeagen calls it Locality of Behavior. Some people call it Vertical Architecture or Sliced Architecture. Different names, maybe different concepts, but always the same basic idea:

If you need to cut your code into smaller pieces, cut it into slices, not layers.

4 Common Closure in the wild

You might ask if this is one of those things in development we talk about but then forget because it’s just a minor change that doesn’t make that much of a difference in the long run. And I’m happy to contradict. Look at code patterns where this philosophy of ‘slicing’ makes the difference and decide for yourself.

4.1 React

What set React apart when it came out wasn’t its performance. It was the way you wrote JS in it. By integrating HTML, JS via JSX. Gone were the days where the whole site was one document with a script containing a never-ending stream of functions. Now you would divide the site into components. The markup AND the logic of the component will now reside in the same space.

Want to delete a component? Just delete one file and all the logic, styles and markup are gone. Gone like the days where you had to remove one jQuery plugin here, some markup there, and lines 12378-12413 in your style sheet.

4.2 Microservices

Microservices have helped me think about this a lot. Instead of having a Monolith you have one service per ‘thing’ that needs to be done. Some services need a database. Some might not. In any case each service can autonomously decide on its own implementation.

Same goes for Microfrontends.

4.3 Plugin Systems

Plugin systems are peak software architecture imho. The plugin cannot be colocated with the core code, thus forcing the core and each plugin to be independent and self-contained. Plugins thus can (usually) bring their own dependencies as a means of integrating with additional services. This style of software makes the core code small and understandable while making it easy for a large amount of developers to add features.

5 Conclusion

I hope to have spread some awareness about why it matters how you structure your code.

If you are working on a new project, make sure to split your code into modules based on feature domain instead of technological domain.

If you are taking on an existing code base and the amount of stuff done just overwhelms you, here is an idea of mine: Refactor the existing code according to the Common Closure Principle and see if it clears things up for you.

Footnotes

Footnotes

  1. The above quote is excerpted from Clean Architecture p.105 by Robert C. Martin

Share