Notes on Systems

Refactoring as a Signal

March 27, 2026

The code that needs refactoring most often is rarely the worst code. It is the code sitting on top of a structural assumption that no longer holds.

Refactoring is framed as improvement. You take something that works and make it more modular, more readable, easier to extend. That framing is not wrong. But it hides the more interesting question: why does the code need restructuring in the first place?

The need to refactor is a signal

It tells you something about the gap between what the system was designed for and what it is now being asked to do. That gap is worth understanding before you begin restructuring.

Most refactoring conversations start with the code. The function is too long. The module has too many responsibilities. The naming is inconsistent. These describe the code. They do not explain why it drifted. The deeper question is: what changed? What shift in requirements, team structure, or understanding created the misalignment you are now trying to fix?

When you skip that question, refactoring becomes cosmetic. You improve the shape of the code without addressing the structural mismatch that created the problem. Six months later, the same area needs restructuring again — not because the first refactor was bad, but because it treated the symptom.

The pattern of repeated cleanup

A module gets refactored, stabilizes briefly, then drifts again. Each pass feels productive. Each pass addresses real issues. But the cycle continues because the underlying cause — a shifting domain boundary, an unclear ownership model, a feature that outgrew its original scope — was never confronted.

Repeated cleanup is not a sign of careless engineering. It is a sign that the system's organizational shape no longer matches its purpose. The refactoring is correct at the code level and insufficient at the design level.

Consider a payments module originally designed as a simple pass-through — validate, forward, log. Over two years, it becomes the system's primary orchestration layer. It coordinates retries, manages state, triggers downstream workflows. It gets refactored three times, each time to accommodate a responsibility it was never meant to own. Each refactor improves the code. None questions whether the module should still exist in this form.

Refactoring as avoidance

There is a subtler failure mode. Ambiguity makes refactoring attractive. The team faces a question it cannot cleanly answer — whether a feature belongs in this service at all, how to scope a requirement that keeps shifting, a technical decision with no clear winner. Refactoring offers a productive alternative. You spend a week improving the codebase, and the code is genuinely better afterward. But the hard question remains untouched.

This is not laziness. Refactoring has clear inputs and measurable outputs. The hard question does not. Given a choice between visible progress and uncertain exploration, most teams choose progress. The cost is that the architectural question gets deferred again.

I have seen this play out in systems where the same area gets refactored by successive teams. Each team improves the code according to their understanding. Each refactor is defensible. But the cumulative effect is a module that has been reshaped multiple times without anyone asking whether it should exist in its current form at all.

"This code keeps becoming messy"

The signal worth paying attention to is not "this code is messy." It is "this code keeps becoming messy." The first is a maintenance task. The second is a design problem.

The misalignment has a specific shape. The code is organized around one model of the domain, but actual usage has shifted to another. Features that were once separate now overlap. Boundaries that made sense at one scale create friction at the current one. The refactoring impulse is correct — something is wrong. But the fix is not reorganizing the code within the existing structure. It is questioning the structure itself.

This is harder work. It requires understanding not just the code but the forces acting on it. Why do these two modules keep needing to change together? Why does every new feature touch the same three files? Why does onboarding to this area take twice as long as it should?

These questions do not have clean answers. They require conversations about product direction, team boundaries, and debt that accumulated from decisions that made sense when the team was smaller and the product was simpler.

What a refactor should produce

The most useful thing a refactoring can produce is not cleaner code. It is a clearer understanding of why the code looked the way it did. If you finish a refactor and can articulate what assumption was wrong, you have learned something that will inform the decisions that follow. If you finish a refactor and can only say the code is better now, you have improved one file and learned nothing about the system.

Refactoring is maintenance when it addresses known issues in stable code. It is a signal when it keeps happening in the same places. And it is avoidance when it substitutes for the harder work of confronting what actually needs to change.

The discipline is not in knowing how to refactor. It is in asking what the need to refactor is trying to tell you.

March 18, 2026

Defaults Nobody Revisits

Best practices are useful until they become borrowed conclusions that no longer match the system’s actual constraints.

March 12, 2026

The Carrying Cost of Flexibility

Speculative flexibility often looks responsible at first, then quietly raises the cost of understanding, testing, and changing the system.

March 4, 2026

Time As A Design Constraint

Systems age whether we plan for it or not, and the best design decisions are often the ones that remain legible long after their original context is gone.

← Back to all notes