Back to archive

Engineering

Software Engineering Is Programming With Time as a Dependency

A principal-engineer frame for code that must survive future people, traffic, policies, and failures.

Software Engineering Is Programming With Time as a Dependency

Programming can solve the problem in front of the keyboard. Software engineering has to account for the fact that the code will meet different people, different traffic, different policies, different failures, and different budgets after the author has moved on.

The difference is time.

The thesis

Software engineering is programming with time as an explicit dependency.

Today's code is not evaluated only by whether it runs today. It is evaluated by how it changes, how it is understood, how it fails, how it is migrated, and how much coordination it demands while the world around it keeps moving.

This is why principal engineering is less about cleverness than preservation of future options. The job is to make important systems easier to change without requiring everyone to remember the original conversation.

The production pattern

The production pattern is familiar. A feature begins as a direct implementation. The first version is reasonable. It solves the immediate need. Then usage grows. A second team integrates. A policy changes. Old data remains. A new region appears. A dependency changes behavior. The original owner rotates away. The code is now carrying obligations that were not visible when it was written.

None of this means the original code was bad. It means the lifetime of the code exceeded the horizon of the decision.

I have learned to be suspicious of reviews that ask only whether the code is clean today. Clean for whom? Clean under what traffic? Clean with which owners? Clean before or after the next migration? A readable implementation can still create a compatibility tail that lasts for years. A slightly boring abstraction can save a future team from rediscovering a policy edge case during an incident.

Time turns local choices into organizational costs.

The trap

The trap is treating maintainability as aesthetics. Naming, structure, and formatting matter, but they are not the whole problem. The harder maintainability questions are about rate of change, ownership, compatibility, and memory.

Another trap is applying production governance to everything. If every prototype needs a migration plan, an on-call model, a long-term API contract, and a full decision record, the organization will move slowly and still produce bad systems. Time as a dependency does not mean every line deserves ceremony. It means the expected lifetime should influence the design.

The deepest trap is pretending that future readers will share today's context. They will not know which deadline forced a shortcut, which requirement was rejected, which invariant came from policy, or which integration depends on a strange field. If the system needs that memory to stay safe, the memory has to be encoded somewhere more durable than a chat thread.

The model

I use a five-part time model when reviewing systems.

Time horizon asks how long the code is expected to live. A throwaway experiment, a pilot, a product surface, a platform API, and a data schema deserve different levels of care. The mistake is not writing short-lived code. The mistake is letting short-lived code become permanent without changing the contract.

Change rate asks how often the surrounding requirements are likely to move. A stable calculation can be simple. A policy-heavy workflow may need explicit state, decision tables, or extension points because future change is not a possibility. It is the main workload.

Ownership decay asks who will understand the code later. Ownership always decays unless replenished. People change roles, teams reorganize, and memory fades. Systems that require a specific person to explain them are borrowing against the future.

Compatibility tail asks what old clients, old data, old jobs, old events, or old assumptions will continue to exist after the new version ships. Most systems are not upgraded atomically. The past keeps sending traffic.

Institutional memory asks where decisions live. Tests, schemas, comments near unusual invariants, runbooks, decision records, dashboards, and migration logs are all memory systems. The goal is not documentation volume. The goal is to preserve the facts future work depends on.

The model is horizon, rate, ownership, tail, memory.

Where this model breaks

The counterpoint is that some code should be disposable. Prototypes should not carry production governance. Exploration has a different failure mode: too much structure can make learning expensive and slow. When the purpose is to discover whether something should exist, the right move may be a small, direct implementation with an explicit expiration date.

The model also breaks when teams use "future-proofing" to justify abstraction without evidence. Time-aware engineering is not a license to build frameworks for imaginary requirements. It asks for the specific future pressure the design is responding to: more owners, more traffic, stricter policy, longer compatibility, or harder recovery.

Another limit is that time horizon is often uncertain. A hack can become important. A platform can be deprecated. That uncertainty is exactly why I like explicit classification. It creates moments to revisit the decision instead of letting the first version silently become the architecture.

What I do now

I ask teams to classify work by lifetime: experiment, pilot, product path, platform contract, or durable record. The classification does not need bureaucracy. It just changes the review questions.

For experiments, I ask for containment and a removal date. For pilots, I ask what would need to change before broader use. For product paths, I ask about tests, observability, and ownership. For platform contracts, I ask about compatibility and migration. For durable records, I ask about schema evolution, retention, and replay.

I look for time bombs in code review: hidden global state, ambiguous ownership, data without versioning, APIs without deprecation paths, migrations without rollback thinking, and comments that explain what should be enforced by tests or types.

I also try to leave memory close to the decision. A short note near an unusual invariant can prevent a future cleanup from becoming a regression. A decision record can prevent a settled tradeoff from being relitigated every quarter. A test can preserve behavior more reliably than a paragraph.

The principal-engineer lens is sequencing. Do not pay every future cost today, but do not pretend future cost is zero. The craft is choosing which future changes must remain cheap and which risks can be consciously deferred.

Closing takeaway

Before approving important code, ask: what will still need to be true when the people, load, policies, and failures around this code have changed?