Why You Have Spaghetti Code
Before we begin: Doors open for the latest version of The Software Essentialist on March 31st. Join the waitlist for perks, bonuses & 55% off.
If I had to call out the definitive Code-First experience, it's that feeling when you realize you're writing code that gets worse instead of better all the time. Code where no one understands it, no one wants to touch it because they're afraid they'll break something...
And as a result, you get this pretty unfulfilling working your codebase.
It feels disheartening knowing that you tried your best, yet still: spaghetti.
Been there.
I talk about this in the Phases of Craftship - that Code-First developers need to go through this experience at least once (or twice) to realize that code isn't enough. Only then when you experience this do you typically realize it's time to look for better practices.
But why does this happen in the first place?
What's at the heart of this un-maintainability problem?
I believe the answer lies in the ideas of divergence and convergence, two fundamental laws of abstraction and creation (of anything).
Divergence & convergence
To create anything in the world relies upon these two creative forces: divergence and convergence.
Divergence is about:
- expansion
- emergence
Where convergence is about:
- contraction
- contracts
For well-designed creative works, we're going to need to use a mixture of these two sorts of forces: these two forms of design (upfront design & emergent design).
Divergence involves making a mess
For example, when I'm in the initial brainstorming phase of writing newsletters and course modules, I fully expect a mess. You'll see messy whiteboards, papers, voice notes - all sorts of things. I don't give too much focus to anything other than getting the ideas out & following the trains of thought.
Or when I sit down to write a song. Sometimes I'll beatbox the drums, make noise, add parts randomly, use whatever I have available, or ramble the lyrics. I don't give too much focus to anything other that following what's interesting to me.
This is all an act of divergence. It's un-boundaried, flowing, and it can basically go forever (if you let it).
Divergence is a crucial part of any creative process, but any time we write code without boundaries, this is where the spaghetti comes from.
(Well, not where REAL spaghetti comes from 🍝 - that's from Italy, but you know what I mean).
Convergence involves using boundaries
If divergence is all about expanding and building up, the convergence is all about bringing things to a completion.
In the writing process, this is where I look at the brainstorm and select the goals, write the headers & the summary before writing the content.
In the music production process, this is where I'd select the instrumentation, take it to the studio and get notes from the producer before re-recording it.
Convergence is all about starting from the end. It's all about defining exactly what success will look like, before we even start, and then every action we take next is to bring the work to a close.
It's a focus on outcomes, not steps.
And it's something we contractualize -- typically, in programming using acceptance tests, like so:
You need both
Acceptance tests are contracts that help us converge. They give us the absolute bare minimum set of things our code has to do.
And when we configure an acceptance testing rig, we can actually balance a mixture of test-code-test-code-test-code moving inwards into the codebase, writing as minimal code as possible.
Play the zig-zag
It's incredible. Every creative process involves this zig zag between the two.
Writing a newsletter
- Brainstorm (divergence)
- Select goals (convergence)
- Write summary (divergence)
- Write the headers (convergence)
- ...
Making a song
- Recording an idea (divergence)
- Selecting the arrangement (convergence)
- Recording the demo (divergence)
- Producer helps suggest revisions for the next recording (convergence)
- ...
FAQ
What about refactoring?
Refactoring is a form of convergence. You're cutting down on what was divergently created.
But consider the problem with refactoring after having performed way too many divergence cycles in a row.
It's like asking a painter to completely change a painting to a new one.
At that point, it'd just be way easier to create a new painting.
And this is precisely what Code-First developers often feel called to do.
Ah yes, The Classic Rewrite™️.
The costly and expensive rewrites.
Better solution is to avoid this altogether, start with valuable tests and zig-zag your features to fruition.
How can I fix my spaghetti code?
OK, so what to do if you're facing spaghetti right now?
(Eat it. 🍝 Spaghetti is delicious. Ha, kidding.)
For real though, in this situation, the experts suggest you focus on SAFELY refactoring your code.
And the first step to SAFELY refactor your code is to characterize it.
What do I mean by this?
Functionally, a characterization test is the same thing as an acceptance test; meaning: it documents the feature and the acceptance criteria, but from a high level.
So after you've already made a mess (or you're getting ready to clean up some legacy code), start with convergence.
Write a characterization test before you do anything else.
Just like how a tailor sets pins in place before cutting any fabric, you want to clarify the Vertical Slice of functionality - the feature that you're about to adjust - and you want to do this before you do anything else.
This can vary in terms of challenge and complexity.
For example, your features are all coupled together, you might have to characterize 'em all upfront, otherwise you could break one by fixing another.
I know developers love the phrase "it depends". That's sarcasm.
But really, it does depend.
It depends primarily on how coupled your features are to each other AND how easy it is to work with or mock out any connected infrastructure to test infrastructure.
I'll share more on this in future letters.
In summary
What's the main takeaway of all this?
When you write a test, you're converging.
When you write code, you're diverging.
When you only write code, it'll continue to diverge and diverge until it's extremely hard to continue to stack new features and functionality on top.
The solution is to play the zig-zag of divergence and convergence. Cutting and adding.
And finally, when you start with a valuable test, you actually set up a vector of success. You get a guardrail which helps you avoid accidental complexity, and helps you converge only on what matters. On what's valuable.
So learn to do 'em both. Convergence & divergence. Tests & production code.
This is how we get out of Code-First spaghetti territory.
That's all for this letter, folks.
Enjoy the rest of your weekend.
As always, To Mastery
Khalil
PS: A reminder that the waitlist for TWSE 55% opens March 31st at 8:00AM EST. We've already reached 300 waitlist submissions with only 200 spots. Get notified early when doors open here.
Stay in touch!
Join 20000+ value-creating Software Essentialists getting actionable advice on how to master what matters each week. 🖖
View more in Design