The Codebase Doesn’t Know You Quit
In nearly twenty years of writing .NET, I have left more companies than I currently remember the names of.
The companies have, in roughly equal measure, forgotten me. My Slack accounts are deactivated, my email aliases bounce, the wiki pages I wrote have been archived or quietly deleted by someone reorganizing the space. The architectural decisions I argued for in rooms that no longer exist are now policy, or have been reversed, or have become folklore.
What hasn’t forgotten me is the code.
git log still has my name on it. Methods I wrote in 2014 are still running at companies I left years ago. A junior engineer somewhere is, right now, opening a file and seeing Author: Martin Stühmer next to a line they’re trying to understand. They can’t ask me anything.
The first four parts of this series (one, two, three, four) treated legacy as a relationship between me and a future version of myself. That framing is incomplete. The most consequential version of Future Self is often not me at all. It’s a stranger, at a company I no longer work for, reading the code I left behind.
The Continuity Illusion
The earlier parts share a hidden assumption: that you’re still around. Past Self left the mess; Future Self cleans it up; both are you, separated by time. That’s legacy inside one employment. It’s the smaller version of the problem.
The larger version: you won’t be around. You’ll have moved teams, moved companies. Your access is revoked. The Slack thread where you explained the design has aged out of retention. The colleague who could have answered on your behalf left a year after you did.
What remains is a binary fact: the code, and a stranger, in a room together, with no intermediary.
Every company I’ve worked for has had at least one piece of code I wrote that survived three rounds of personnel turnover after I left. The people who reviewed it are gone. The product manager who set the requirements is gone. The reason the method exists is, in many cases, no longer reconstructable from anything except the code itself. Useful code survives: that’s the whole point of writing it. Surviving means outlasting the context.
I once got a LinkedIn message from someone I’d never met, working at a company I’d left in 2018, asking what a particular TimeoutPolicy was supposed to do. The class had my name on the original commit. I had no memory of writing it, no access to the repository to read the surrounding code, and no useful answer. He had inherited a maintenance question I could no longer help with. Six years and three jobs after I’d left it for him. That exchange is the one I think about when I write anything I expect to outlive my involvement.
What the Codebase Remembers
The artifacts a company actually preserves across years and reorganizations are a short list:
- The source code.
- The commit history (until someone rewrites it for “cleanliness,” which is its own crime).
- A small number of documents someone deliberately curated.
- The data, sometimes.
Slack ages out. Wiki pages rot. Confluence reorganizations bury entire hierarchies. Tickets get archived when the tracker is replaced.
Notice what’s not on the list: the why. The why was almost always in the medium that didn’t survive: the meeting, the Slack thread, the architect’s verbal walkthrough at the offsite, the principal engineer’s pre-review feedback in a 1:1 that was never written down.
If you want the why to survive, encode it in something that survives: the structure of the code, comments that explain motivation rather than mechanism, commit messages that say more than fix bug, ADRs versioned in the repository, tests that document expected behavior. All of these live in the repository. The repository is the only artifact with the same survival profile as the code, because it is the code.
The Handoff That Never Happens
There’s a polite fiction that knowledge transfer happens when someone leaves. The departing engineer writes a document, presents it, answers questions for two weeks, the team continues with full understanding.
It does not work that way.
The departing engineer captures perhaps 30% of what they know, because much is unconscious: patterns recognized on sight, constraints internalized so long ago they no longer notice them. The team skims the document, files it. Six months later, when they meet a problem the document would have addressed, nobody remembers it exists. A year later, the document is in an archived space nobody has access to.
The reliable channel is the code itself. Not because it’s a good narrative medium, but because it’s the only artifact guaranteed to be in front of the next maintainer when they need it. If the line contains its own justification, the justification reaches them. If it lives in a document they don’t know about, it doesn’t.
What I Write Into the Code Now
These aren’t best practices in the abstract. They’re habits I formed after watching enough code outlive its team.
Commit messages as the durable narrative. A commit message is the only writing about a piece of code guaranteed to travel with it forever. Not the PR description (lives in a tool that may not exist in five years). Not the ticket. The commit message. I write them as if the only reader is a stranger trying to understand why the change happened.
# Bad: tells you what the diff already shows
Fix timeout handling in report service
# Better: tells you why this exists
Raise report timeout to 30s to match legacy ReportService SLA
The legacy ReportService can take up to 28s under load; the previous
10s timeout caused intermittent failures during EOM batch runs.
30s is deliberate, not arbitrary. Revisit when ReportService is
replaced (tracked in #1247).
Architecture Decision Records in the repository. Not in the wiki. Not in Notion. In docs/adr/, versioned with the code, surviving every reorganization. ADRs are letters from the team that existed in 2024 to the team that exists in 2028. The 2028 team won’t have access to the meetings. They will have git checkout.
Comments that explain the constraint, not the mechanism.
// Bad: explains what the next line already says
public long Next()
{
_value++; // increment the counter
return _value; // return the value
}
// Good: explains why this shape was chosen
public long Next()
{
// Pre-increment is required: downstream subscribers index events by the
// post-increment value (see docs/adr/0014-event-sequence-numbers.md).
// Switching to post-increment silently corrupts replay across the cluster.
return Interlocked.Increment(ref _value);
}
Test names that are sentences. A test name is documentation the build refuses to let you forget. It survives every refactor of the implementation, every renaming, every change of test framework:
public sealed class TaxCalculatorTests
{
[Fact]
public async Task CalculateTax_OnRefundedOrder_ShouldNotProduceNegativeTotal()
{
var order = new Order(Total: 100m, IsRefunded: true);
var sut = new TaxCalculator(TaxRate: 0.19m);
var result = await sut.CalculateAsync(order, CancellationToken.None);
result.Should().BeGreaterThanOrEqualTo(0m);
}
}
When someone tries to “simplify” the calculator years later, this test fails with a name that names the violated constraint.
Deliberate naming over generic naming. OrderService tells the reader nothing. RetryingOrderSubmitter tells them where the design effort went:
public sealed class RetryingOrderSubmitter
{
private readonly IOrderGateway _gateway;
private readonly IAsyncPolicy _retryPolicy;
private readonly ILogger<RetryingOrderSubmitter> _logger;
public Task SubmitAsync(Order order, CancellationToken cancellationToken) =>
_retryPolicy.ExecuteAsync(ct => _gateway.SubmitAsync(order, ct), cancellationToken);
}
Names are the cheapest form of documentation, and the one that survives every refactor short of renaming.
README.md next to the code that needs it. Not at the repository root, where it gets stale within months. Next to the subsystem it describes: a README.md in the folder of the projection engine, the message dispatcher, the retry layer. Short. Three paragraphs. What this code is for, the one or two constraints that aren’t obvious, the decision record that explains the shape. Engineers who never read the top-level README will read the one that lives in the folder they just opened, because it’s already on their screen.
The Question I’ve Started Asking
Before I commit anything substantial, I ask:
If I stopped working on this tomorrow, with no warning, no handoff: what would the next person need from this code to keep it working?
It’s not morbid. It’s planning. The probability that I stop working on any given codebase tomorrow is low for any specific tomorrow but approaches one over a long enough horizon.
The question reframes a lot of decisions. The // TODO without a ticket: would the next person know what it meant? No. The magic constant: would they know why this number? No. The test I would have skipped: would they know which edge cases were considered? Not without it.
The shape of a method changes once you write for someone who can’t ask you anything:
// Before: works for me, today, with the context I have
public async Task<decimal> Calculate(Order o)
{
var rate = 0.19m;
return o.Total * rate;
}
// After: works for a stranger, in 2030, with no one to ask
/// <summary>
/// Calculates VAT for a single order using the rate active on the order's
/// <see cref="Order.PlacedAt"/> date. Historical rates are intentional:
/// invoices issued before a rate change must reproduce the original amount
/// (see docs/adr/0021-historical-vat-rates.md).
/// </summary>
public async Task<decimal> CalculateVatAsync(Order order, CancellationToken cancellationToken)
{
var rate = await _vatRates.GetRateForAsync(order.PlacedAt, cancellationToken);
return decimal.Round(order.Total * rate, 2, MidpointRounding.ToEven);
}
The functional difference is small. The legacy difference is enormous. The second version answers every question a stranger could reasonably have: what is calculated, against which rate, with what rounding, justified by which decision record, cancellable when the caller gives up. The first produces the same number on the happy path and a multi-hour archaeology project the first time it doesn’t.
Careful, in the end, mostly means: legible to someone who doesn’t get to ask you anything.
The Larger Legacy
Across a career, the code I write will outlast me at every employer I had. Some will outlast the employers themselves; some will be inherited by acquirers, ported to new languages, embedded in libraries someone else maintains. The chain is longer than I imagine while writing.
I am not famous. I am not writing a foundational library. I write the unglamorous middle layer of enterprise applications. And even so, the chain is long.
The legacy isn’t theoretical. It’s just what code does. It survives. It accumulates strangers. It either repays them for the time they spend reading it, or it taxes them.
The motto from part one, with one final addition:
The code you create is a valuable legacy.
And, after part four:
The code you accept is a valuable legacy.
The version that finishes the thought:
The code you leave behind is a valuable legacy: to people you will never meet, building on work you no longer remember, at companies you no longer work for.
That’s the population I’m writing for, in the long run. They don’t know me. They never will. The least I can do is write something they can read.
This is part five of the Code as Legacy series. Part one is the manifesto. Part two introduces Past Self. Part three is about empty promises. Part four is about the AI in the room.

Comments