textlize pricing account
Why Can't We Make Simple Software? - Peter van Hardenberg
Cover

00:41:33

Why Can't We Make Simple Software? Unpacking the Inevitability of Complexity

In a world demanding more features and scale, software complexity seems inevitable. Peter van Hardenberg explores why this happens and how we can better manage it, rather than fight a losing battle.

Key Insights

  • Complexity is not merely difficulty or size; it arises from unpredictable interactions within a system.
  • Better tools alone cannot eliminate complexity; it is a natural byproduct of evolving requirements and scale.
  • Organizations and individuals have a "complexity homeostasis"—a set point of complexity they unconsciously maintain.
  • Strategic approaches include starting fresh, drastically cutting scope, or deliberately isolating complexity.

Defining the Beast: What Actually Is Complexity?

Peter van Hardenberg begins by clarifying a crucial distinction. Complexity is not synonymous with difficulty. Mastering calculus is difficult, but its core principles are elegant and simple. Complexity is also not about sheer size—a massive box of Lego bricks isn't inherently complex.

True complexity emerges from the interactions between components within a system. It becomes a problem when these interactions make the system unreasonable—not in a casual sense, but in the literal sense that you can no longer reason about it or predict its behavior. This unpredictability is what makes fixing bugs or adding features so challenging. However, complexity can also be generative, leading to surprising and powerful emergent behaviors if harnessed correctly.

The Many Faces of Software Complexity

Software complexity doesn't have a single source. It sneaks in through various doors as projects evolve.

1. Handling the Real World: From Happy Path to Edge Cases

The first version of any code is often beautiful. It's the "happy path," where everything works perfectly in an idealized world. Imagine a simple web endpoint that fetches a user by ID and says hello. But what if the requested user doesn't exist? What if the database is down? What if the input is malicious?

Soon, the elegant core logic is buried under layers of validation, error handling, and security checks. This defensive coding is necessary, but it creates what some call "shotgun parsing"—the same validation logic is scattered unpredictably across the codebase, creating potential security holes if any spot is missed. Vigilance is not a strategy. The solution is to architect systems that minimize these failures, for instance, by using type systems to handle nulls automatically.

2. The Scale Trap: When Growth Changes the Problem

Scale doesn't just make things slower; it changes the very nature of the problems you're solving. An admin panel that lists all users is trivial with 10 users. With 10,000 users, you need pagination. With 1 million, simple SQL `OFFSET` queries become performance nightmares. With 100 million, the challenge is no longer technical—it's about legal compliance, ethical moderation, and abuse detection.

The complexity here is that the problem itself morphs. Building for a scale you don't yet have is as harmful as being unprepared for growth. The key is to be scale-appropriate and evolve your tools as your needs change.

3. The Lie of Simple Abstractions: Leaky Abstractions

Abstractions promise simplicity by hiding complexity. But when they leak, the underlying complexity bursts through. A classic example is the C programming language's `strcpy()` function. The interface is simple, but the modern implementation is hundreds of lines of complex assembly code to handle issues like memory alignment on modern CPUs.

This creates a dilemma. The simple API is a lie that undermines understanding, but it also provides a consistent interface that has worked for decades. The trade-off between simplicity and power is a constant architectural struggle.

4. The Model-Reality Gap: When Your Assumptions Break

Often, complexity arises from a gap between our mental model of a problem and reality. A common example is modeling names with "first_name" and "last_name" fields. This model breaks for many cultures globally—not everyone has a surname, and the order of names varies widely.

When reality contradicts your model, you must bridge the gap. You can fix the model (a costly rewrite), hack around it (e.g., forcing a single "full name" into a first_name field), or ignore the problem. These gaps are especially prevalent in distributed systems, where timing, failures, and network partitions introduce realities your initial model likely didn't consider.

5. The Multiplicative Effect: When Problems Compound

Complexity explodes when multiple variables interact. Supporting your app across different browsers, operating systems, screen sizes, and network speeds creates a combinatorial explosion of possible states. Testing and ensuring consistency across all these environments becomes nearly impossible.

This is why many large companies opt for solutions like Electron—it's simpler to manage one codebase for one "platform" (the web) than to coordinate multiple native teams. The complexity of the technology is sometimes less than the complexity of coordinating human teams.

Complexity Homeostasis: Why You Can't Win

Perhaps the most fascinating idea presented is complexity homeostasis, inspired by the concept of risk homeostasis from traffic safety studies. When seat belts made cars safer, people didn't die less; they drove more recklessly, maintaining their personal level of acceptable risk.

Similarly, software systems and organizations seem to maintain a set level of complexity. If a refactoring or a new tool simplifies the codebase, that "saved" complexity budget is quickly spent on new features, more scale, or other demands until the feeling of complexity returns to its previous level.

This set point is subjective. Brilliant engineers might comfortably hold vast complexity in their heads, while others feel overwhelmed. Well-funded projects can simply hire more people to manage the complexity, as seen in annual game releases like EA Sports titles. The complexity is tolerated because the product is profitable.

This suggests that the degree of complexity is tied less to the tools and more to the people, the organization, and the problem domain over time. Buying back complexity with better tools just means you'll spend it again.

Coping Strategies: Living with Complexity

If complexity is inevitable, how do we cope? We can't eliminate it, but we can manage it strategically.

1. The Strategic Reset: Starting Over

One effective way to manage complexity is to start fresh. New programming languages, new frameworks, and new projects reset the clock. There's no legacy code, no accumulated debt—just a clean slate. This is part of why greenfield projects are so appealing. The lesson from id Software's early days was to become experts at starting over quickly, learning from previous projects, and building a better version each time.

2. The Power of Less: Cutting Scope and Dependencies

Another approach is radical simplification through amputation. The Playdate handheld is a perfect example: one hardware spec, a tiny B&W screen, limited inputs. This constrained scope allows developers to focus their complexity budget on polish and gameplay rather than compatibility.

The Berkeley DB team emphasized that software architecture degrades without constant effort. A motto from the Excel team was "find your dependencies and eliminate them." They took this to an extreme by building their own C compiler to avoid relying on other Microsoft teams. Cutting dependencies is a powerful way to isolate and reduce complexity.

3. Isolating Complexity: The Local-First Approach

Peter discusses his work at Ink & Switch on "local-first software," which aims to give users ownership and agency over their tools. By building software that runs locally and syncs collaboratively only when needed, they cut out the immense complexity of the entire cloud-native stack. This is simplification by removing a whole class of problems—servers, scaling, and cloud outages.

Conclusion: It Never Gets Easier, You Just Go Faster

The quest for simple software is not about finding a magic bullet that kills complexity. Complexity is a natural consequence of successful, evolving systems interacting with the real world.

The goal is not to win a battle against complexity but to learn to manage it better. We can be more conscious of how we spend our complexity budget, isolate it, and make strategic choices about when to start over or cut scope. We can choose to harness complexity for generative effects, like the emergent gameplay in The Legend of Zelda: Breath of the Wild.

In the end, the sentiment is best captured by cyclist Greg LeMond's famous quote: "It never gets easier, you just go faster." Software development doesn't get simpler, but we can get better at navigating its inherent complexity.

© 2025 textlize.com. all rights reserved. terms of services privacy policy