Software Ages Like Milk Unless You Design for Change
Wine improves in the bottle. Software does not. Left alone, it curdles: assumptions harden, edges tangle, and every new request smells like a rewrite.
Freshness is a design choice. So is spoilage.
Published on January 23, 2026

Table of contents
Why Software Spoils
Software spoils because the world moves and the code remembers the old world too well.
Pricing changes. Roles change. Integrations arrive. Regulations appear. A “temporary” branch of logic becomes the main path. The original model of the business is still buried in conditionals that nobody wants to touch.
Age itself is not the enemy. Rigidity is. A three-year-old system that bends cleanly feels younger than a six-month-old monolith of special cases.
Change Is the Product Environment
If you design as if requirements will stabilize, you are designing for a fantasy environment.
Products live in contact with customers, sales promises, support exceptions, and opportunistic features. The question is not whether change will arrive. It is whether your seams can absorb it without cracking the middle.
Design for change or schedule a rewrite you will pretend is a migration.
That sentence is not cynicism. It is scheduling honesty.
Modularity Without Ceremony
Modularity does not mean microservices by default. It means boundaries with names that match the business.
- Billing logic that does not leak into UI components
- Permission checks owned in one place
- Integration adapters that isolate vendor quirks
- Domain language that appears in code the way it appears in meetings
Ceremony without boundaries is theater. Boundaries without ceremony are often enough: folders, interfaces, and a team agreement about what may import what.
When a change request lands, modularity decides whether you edit a room or demolish a wall.
Extensibility That Survives Reality
Extensibility is frequently overbuilt. People invent plugin systems for a product with one customer.
The useful kind is quieter: configuration where policy will change, data models that allow new attributes without rewrites, workflows that can insert a step without forking the whole flow.
Ask: “What will sales promise next quarter that we have not built yet?” Then leave a seam there — not a framework, a seam.
Extensibility without judgment becomes abstraction soup. Extensibility with judgment is how products stay edible.
Maintainability as a Daily Habit
Maintainability is not a refactor epic. It is what happens on ordinary Tuesdays.
- Naming that still makes sense after the meeting ends
- Tests that protect the risky paths, not the vanity ones
- Deleting dead feature flags instead of collecting them
- Documenting the weird edge in the code that owns it
- Refusing to copy-paste a third special case into existence
Teams that wait for a “cleanup sprint” usually discover the milk already turned. Habit beats heroics.
The Rewrite Temptation
Rewrites feel clean because they erase history. They also erase the hard-won knowledge encoded in scar tissue.
Sometimes a rewrite is honest. More often it is avoidance: avoiding the work of drawing boundaries inside the living system.
Prefer strangler moves. Replace a module. Extract a workflow. Freeze the worst area behind an interface and starve it of new features. That is design for change after the fact — still better than pretending a greenfield will stay green.
A rewrite can still be right when the domain shifted so far that the old nouns lie. Even then, migrate in slices with users on the new path early. Big-bang rewrites spoil in the fridge while you are still cooking.
Signals of Spoilage
You can smell it before the rewrite pitch arrives:
- Every ticket seems to touch the same haunted file
- New hires need oral history to change a simple flow
- Feature flags never die; they form sedimentary layers
- Estimates balloon because “we have to check side effects”
- People build shadow tools in spreadsheets to avoid the core app
Those signals are not a call to panic. They are a call to draw seams and pay down the places where change concentrates. Spoilage accelerates when teams only ship forward and never reshape the path behind them.
Budget a fraction of every cycle for that reshaping. Not as charity. As product survival.
Design for Change on Purpose
Designing for change means accepting that the first model of the business is provisional. Ship it. Then keep the seams soft enough that learning can enter the code without a funeral.
A practical checklist:
- Name modules after durable business concepts.
- Isolate vendors and channels behind adapters.
- Prefer explicit configuration over buried conditionals.
- Budget continuous small cleanups, not mythical big ones.
- Treat every special case as a candidate for a rule or a seam.
Software ages like milk when it cannot change. Extensibility, modularity, and maintainability are how products survive contact with reality. Freshness is a design choice — make it on purpose.
👉 Leave seams where the business will bend. Spoilage is optional.