Why You Have Spaghetti Code

Last updated Mar 24th, 2024
Code that gets worse instead of better over time results from too much divergence & little convergence.

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 & 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:

Acceptance Test

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.

Zig Zag Test-Code

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)
  • ...


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


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.


Liked this? Sing it loud and proud 👨‍🎤.

Stay in touch!

About the author

Khalil Stemmler,
Software Essentialist ⚡

I'm Khalil. I turn code-first developers into confident crafters without having to buy, read & digest hundreds of complex programming books. Using Software Essentialism, my philosophy of software design, I coach developers through boredom, impostor syndrome, and a lack of direction to master software design and architecture. Mastery though, is not the end goal. It is merely a step towards your Inward Pull.

View more in Design

You may also enjoy...

A few more related articles

Reality → Perception → Definition → Action (Why Language Is Vital As a Developer)
As developers, we are primarily abstractionists and problem decomposers. Our task is to use language to decompose problems, turnin...
The Code-First Developer
As you improve as a developer, you tend to move through the 5 Phases of Craftship. In this article, we'll discuss the first phase:...
Object Stereotypes
The six object stereotypes act as building blocks - stereotypical elements - of any design.
Non-Functional Requirements (with Examples)
Non-functional requirements are quality attributes that describe how the system should be. They judge the system as a whole based ...