"This change should be easy"
"We built a clone on a hackathon"
"We kind of forgot how to ship stuff fast"
"We have no time for refactoring"
Managing codebases = Thinking in and managing of several dimensions:
- the file / component (what a it does)
- the system interconnection (how stuff fits together)
- the product usecases (how users actually use it / domain logic)
- the changes over time (legacy / pivots etc)
Note: technically there can be more (infinite) dimensions but i usually stick to those 4 as mental framework)
As your app grows you scale on all of those 4 axes.
Your individual components become more complex.
Your components interlink more (eg to DRY your code).
Your product usecases become wider to a bigger range of diverse customers.
And most important: You make changes over time.
Often radical changes: Pivots. Either in domain logic or technical approach.
Very quickly your codebase becomes complex and hard to manage.
Thinking of your codebase as a 4d cube
For some people it's hard to imagine 4d objects (gee… i wonder why…)
For the sake of this argument it might be easier to visualize a radar chart.
Example used: Wine (dont judge)
While you add to your code you move on those axes and increase the area and thus make the overall area under management bigger.
If you pay attention the shape of your 4d cube stays smooth and the volume small.
But if you "ain't got the time" your cube will have hacks and weird surface areas which, by itself, will create new problems in future.
Managing a codebase means managing how far you move on those axes.
You can usually walk through a codebase with the same mental model
- how well are components structured?
- how well are systems isolated and boundries defined?
- how well are the product usecases defined and isolated/expressed in code?
- how well are changes over time managed? (acknowledged tech debt, isolation, comments, etc)
"This change should have been easy…"
If you are working on a fresh codebase you have no pre-existing surface area to manage. No needless moves on directions you currently don't care about. You can pretty much go straight to the point you want to get to.
Already established codebases are different.
A change in a component might not be easy if you have to move on the "time" axis by fixing other parts of the system. Aka "refactor this legacy first".
A change in a product usecase won't be easy if your system has a lot of interconnections with badly defined boundries.
A fix in legacy systems might not be easy if systems are highly interconnected / tightly coupled.
"…but it wasn't easy"
The next time you feel bad about how long a change needed think about how much space you moved on each of those axes to get anything done.
Also appreciate how much complexity on all of those axes you improved when you touched those parts of your codebase. And be sure that also the next person will appreciate it.