We've recently put our application through a restructuring exercise to try and ease some pain points when the next major set of features come down the line.
Two observations have come out of this process:
1) The file structure of the application informs the decisions a developer makes about how to organize their logic.
2) If the structure reflects poor organization, or categorization without intent, the code written in that structure will be poorly organized and categorized without intent.
Consider the separation of logic and model. Where should that decision be made? At an application level or a feature level? Should it be made at all?
Maybe we separate them at an application level. When building a feature we end up with a directory featureName in both the logic directory and the model directory. We have duplicate naming, but the directories map 1-to-1 with each other and we can make logic changes separate from the model.
But what about the things we haven’t considered? The lines between system api, business domain, framework, and presentation? If those lines don’t exist in the file structure they won’t exist in the code.
This is how logic grows to couple disparate system concerns. This is a painful place.
The file structure should prescribe, at least at a high level, separations of functionality that move in one direction.
Things that run the app → things that run the business domain → things that present the business domain
In this organization, things that deal with the domain do not know about things that present it and vice versa, and they both use things that run the app.
Let’s consider two scenarios, one with a type categorized file structure and one with a separation prescriptive file structure.
A developer is tasked with building a feature that has a slim and full version depending on the device spec. The developer (because they’re good at their job) abstracts out the system concern of device detection, then sets about building this dual type feature.
device detection → feature model → feature logic(arg: deviceType)
Then business comes a month later and says “we want a medium spec version of this feature”. The developer is now in a position to either:
a) untangle the logic for feature version selection from the logic for running the feature
b) add more if statements
If they opt for A, the system will be more robust but business will ask why it takes so long. If they opt for B, they will have added more technical debt to burn them when anything else in that area changes but business will be happy with the turn around time.
You could argue that if the developer really was a good developer they would have seen this coming. Ok. What if they develop 100 features? What’s the acceptable error rate? Why add the cognitive overhead of more problem anticipation when you can bake this one into the structure?
In this case there is no protection from future change without a prescriptive file structure.
Again a developer is tasked with building a feature that has a slim and full version depending on the device spec. The developer looks at the file structure of system -> domain | presentation and can organize the logic accordingly:
device detection → feature version functionalities - type[full, slim] → feature presentation(type[index])
Here the structure has forced the distinction between running selected logic and defining selectable business logic to run.
Then when business asks for the medium version of the feature, the solution is straightforward to implement.
device detection → feature version functionalities - type[full, slim, medium] → feature presentation(type[index])
It’s easy to miss all the ways our environment influences our productivity, and I’m not talking about open plan offices 😒, I’m talking about the code bases we work in.
Think of the times you’ve spent too long building mental context of a feature just to add a small tweak, or fix a bug. Or when business asks why something is taking so long and you respond with dissertation on quantum mechanics just to establish a starting point to describe how fucked everything is.
A well organized code base can ease that pain.
Designing and building software isn’t just about constructing things in a way that makes sense, it’s also about constructing things in a way that puts the developer in stronger position to handle what’s coming next.