01 Jan 0001

Here’s the consolidated version of the essay: Software Complexity and Why Being Intentional Matters In software development, we’ve all felt that sinking feeling upon opening a codebase and thinking “WTF is this code doing?” This visceral reaction to unfamiliar, disorganised and often just plain badly designed software isn’t just about aesthetics—it’s about something more fundamental: the absence of intentionality in how we write and organise our code. And this isn’t just an issue of inexperience, in fact it is often the people that have been doing this a while that are biggest culprits. I know this because I am one of these people and I think at some point in their careers most software engineers are too. Modern software design often focuses on the wrong priorities. We all too often meticulously follow architectural patterns and best practices whilst overlooking what matters most: clear communication of intent. And I am not saying that patterns and practices are bad, far from it, but if they are not applied thoughtfully and intentionally they can be more of a hinderance than a benefit.

Here’s the consolidated version of the essay:

Software Complexity and Why Being Intentional Matters

In software development, we’ve all felt that sinking feeling upon opening a codebase and thinking “WTF is this code doing?” This visceral reaction to unfamiliar, disorganised and often just plain badly designed software isn’t just about aesthetics—it’s about something more fundamental: the absence of intentionality in how we write and organise our code.

And this isn’t just an issue of inexperience, in fact it is often the people that have been doing this a while that are biggest culprits. I know this because I am one of these people and I think at some point in their careers most software engineers are too. Modern software design often focuses on the wrong priorities. We all too often meticulously follow architectural patterns and best practices whilst overlooking what matters most: clear communication of intent. And I am not saying that patterns and practices are bad, far from it, but if they are not applied thoughtfully and intentionally they can be more of a hinderance than a benefit.

Code as Literature

Donald Knuth, in his 1984 paper on literate programming1, proposed that software should combine documentation and programming languages to produce comprehensible code. Though his specific approach didn’t gain widespread adoption, his core insight still holds up: code is fundamentally a form of literature.

This isn’t a metaphor, code is literally a way of communicating between programmers that describes concepts and problems, whilst happening to be executable by machines. We spend far more time reading code than writing it (or at least we should). Our code exists in context, has subtext and authorial intent, possesses flow and rhythm, and should have reason and purpose.

If we accept this literary nature of code, we can move beyond the dogmatic rules and focus on that clarity of expression and intentionality. Every character, every line break, every organisational decision should serve a purpose. The whitespace matters, the rhythm matters, the naming matters—all working together to communicate intent to the next reader.

Form and Flow in Practice

Code written without attention to form forces readers to comprehend each line in isolation, building a mental model piece by piece. But when we group related concepts and use whitespace deliberately—just as writers use paragraphs—the code’s intent becomes clearer. Lines mean something, and using them thoughtfully transforms stuttering, staccato instructions into a flowing composition.

This attention to micro-scale design extends to macro decisions. Many modern systems suffer from what could be called “clean architecture syndrome”—following prescriptive patterns that explode complexity whilst claiming to reduce it. We see this in the dogmatic application of “Clean Code”2 principles and Clean Architecture3 patterns, which often degrade over a project’s lifetime to be little more than an elaborate folder structure.

This syndrome exemplifies both Maslow’s Law4 and cargo cult thinking5. When developers learn a particular architectural pattern, they often apply it universally, regardless of whether it fits the problem space. They mimic the superficial forms of successful software without understanding the underlying principles or context that made those forms appropriate. They create elaborate folder structures, implement countless interfaces, and add layers of abstraction and indirection not because these solve real problems, but because “that’s how good software is structured.”

The macro design patterns often overwhelm the actual problem space with the irony being that the very patterns that have been used in the name of maintainability and extensibility are now the source of complexity, making the software harder to understand and thus maintain.

AI and Modern Development

In the age of AI-assisted development, where developers can generate thousands of lines of code a day without even looking at the code, these problems have the potential to become exponentially worse. We’re entering an era of “vibe coding”—developers relying on AI to generate code without understanding, reading or caring about the outputted code.

This challenges the very nature of code. In this new paradigm, code risks becoming just there for the machines to communicate. If that is the case, why have human readable code at all? The logical progression would be to have the machines generate byte code that a runtime executes directly.

AI-generated code often produces shallow abstractions, creating interfaces that expose too much and hide too little, because the AI lacks the contextual understanding to make thoughtful encapsulation decisions. This represents a new form of cargo cult programming—copying patterns from training data without understanding their purpose or appropriateness to the current context.

While AI tools can accelerate development, they too must be used with intentionality. The golden rule remains: never commit code you couldn’t explain to another developer. Whether written by human or machine, code must be understood to be maintained.

The Aspiration of Intentionality

There is an ideal we should strive towards in software design: the complexity of your application should approach, as closely as possible, the inherent complexity of the problem space it inhabits, and no greater. This isn’t a rule we can measure precisely—problem spaces resist quantification, and the boundary between essential and accidental complexity6 constantly shifts. Yet it should serve as a north star, guiding us away from over-engineering.

This principle works best as a smell test. When you find yourself writing more boilerplate than business logic, needing extensive documentation to explain your architecture, or spending more time on infrastructure than features, you’re likely drifting from this ideal.

Modern software often fails this test rather spectacularly. We lose ourselves in infrastructure, build systems, and grand architectural visions without focusing on the correct form to express the current problem. We’ve forgotten how to build small, beautiful pieces of code that solve discrete problems elegantly.

Abstractions and Cognitive Load

At its core, programming involves creating abstractions and encapsulations. Good abstractions, like file system APIs, vastly reduce cognitive load by hiding complexity behind simple interfaces. The best modules are “deep”7—offering significant benefits through narrow, simple interfaces.

Conversely, shallow modules with broad interfaces force consuming code to understand and choose amongst numerous options, exploding the possibility space and increasing cognitive load. Pass-through methods, data types that expose their internals, and boilerplate-heavy code all represent thoughtless abstractions that add complexity without value.

Design as Trade-offs

No design principle, when taken to its logical extreme, remains sensible. Good design emerges from understanding and balancing trade-offs. Should you write 200 lines of custom code or pull in a dependency? Should you split functionality into microservices or keep it together? The answer, invariably, is “it depends.”

The key is understanding the true sources of complexity in your system. Sometimes maintaining 200 lines of your own code is genuinely simpler than managing external dependencies. Every line you write is a burden, but so is every line you adopt—or let AI generate without understanding.

We must acknowledge that some accidental complexity is inevitable. The goal isn’t perfection but conscious choice—understanding when we’re adding complexity and ensuring it delivers proportional value. Avoiding Maslow’s hammer means maintaining a diverse toolkit and, more importantly, the wisdom to know which tool fits which problem.

Tests for Good Design

Rather than rigid rules, we need heuristics to evaluate design quality:

Could this be done with fewer moving parts? Additional components should only be added when their total maintenance cost is lower than a localised solution.

Is this operable in production? Software built with maintenance and observability in mind is fundamentally better.

Is this easy to change? Since change is the only constant, designs that facilitate modification trump prematurely “extensible” designs.

Does the complexity match the team? A system’s complexity should align not just with the problem but with the team’s size and expertise.

Are we solving real problems or imagined ones? Guard against the second-system effect by questioning whether each abstraction addresses an actual, current need rather than a hypothetical future requirement.

The Path Forward

Writing intentional code requires a shift in mindset. After writing code, read it again and again. Question every decision. If you can’t justify your choices to yourself in quiet contemplation, how can you expect others to understand them?

Different problem spaces require different aesthetic viewpoints. Not everything needs to be a multi-layered MVC application or a collection of microservices. Embrace the variety of forms available to express solutions appropriately. Resist the temptation to treat every problem as a nail just because you’ve mastered a particular hammer.

The complexity of modern software isn’t inevitable—it’s a choice, often made thoughtlessly. By approaching code as literature, focusing on intentionality, and treating simplicity as an aspiration rather than an absolute, we can create software that communicates its purpose clearly.

Write your software with intention and purpose, as if writing for someone you truly care about. Because ultimately, that’s what good code is: an act of consideration for the reader, making the complex comprehensible and the abstract tangible. In an era of AI-assisted development, this becomes not just important, but essential for the future of software quality.

References


  1. Knuth, D. E. (1984). “Literate Programming.” The Computer Journal, 27(2), 97-111. ↩︎

  2. Martin, R. C. (2008). Clean Code: A Handbook of Agile Software Craftsmanship. Prentice Hall. ↩︎

  3. Martin, R. C. (2017). Clean Architecture: A Craftsman’s Guide to Software Structure and Design. Prentice Hall. ↩︎

  4. Maslow, A. H. (1966). The Psychology of Science: A Reconnaissance. Harper & Row. ↩︎

  5. Feynman, R. P. (1974). “Cargo Cult Science.” Engineering and Science, 37(7), 10-13. ↩︎

  6. Brooks, F. P. (1987). “No Silver Bullet: Essence and Accidents of Software Engineering.” IEEE Computer, 20(4), 10-19. ↩︎

  7. Ousterhout, J. (2018). A Philosophy of Software Design. Yaknyam Press. ↩︎