Okay so. Story time. Grab a coffee; this one's got a body count.
A few months back I got assigned a "small fix" on a payment reconciliation servic...
For further actions, you may consider blocking this person and/or reporting abuse
Actually, with AI, none of this is a problem anymore.
We have new tools for dealing with the problem, but this is the kind of situation where the general lack of breadcrumbs (and I imagine testing issues) leads to unclear production expectations where you can never really know what to prod at.
Alongside everything @gamya_m laid out I'd immediately (with AI tools) start writing a lot of tests and building in a more robust pipeline in that regard — but you can't solve it all in one fell swoop because you can never be certain about true expected behavior or side effects which are now "just how things work" in prod.
This is exactly the tension that made the whole experience so uncomfortable — you can't write confident tests for a system whose expected behavior you don't fully trust yet, because the tests themselves would just be encoding your current assumptions, which might be wrong in exactly the ways that matter. 😊
The "just how things work in prod" framing is so accurate. Some of what I was nervous to delete wasn't code I thought was correct — it was code that had survived long enough that production had quietly built expectations around it, and removing it correctly required understanding not just what it did but what depended on it doing that. AI tools help with the archaeology, but that last mile of "is this actually what should happen" still requires a human who can ask someone, check a ticket, or make a judgment call about what the intent was. Really appreciate you adding this angle!
I'd push back on this a bit — AI is great at summarizing what code does, but it still can't tell you why someone made a tradeoff three years ago, especially when the reasoning lived in a Slack thread or a deadline conversation that never made it into the repo. It can speed up the archaeology, sure, but it's still working from the same evidence I was: the code itself, with no access to the missing context. If anything, I've seen it confidently guess at intent that turned out to be wrong, which is arguably worse than an honest "I don't know" — at least a stuck human knows they're stuck.
I disagree. The problem just shifted the burden. You still have to know the prompts or have a SKILL.md and have a process to deal with this.
Exactly — it's less "solved" and more "moved." Instead of manually digging through git blame and old tickets, now you have to know how to prompt well, structure a SKILL.md, or set up the right workflow just to get the AI to do the digging effectively. That's still a skill someone has to build and maintain, it's just a different shaped one. The burden of "understanding legacy code" didn't disappear, it turned into "understanding how to get an AI to understand legacy code" — which honestly might be a harder skill to teach, because at least git blame was consistent. Prompting and process quality vary wildly by person.
my favourite was a variable called data2 in a 4000-line file. no data or data1 anywhere. just... 2. the git history had one commit: 'fix'. from three years ago.
data2 with no data in sight is genuinely haunting — whoever wrote it either deleted the original and forgot to rename, or just started at 2 out of pure chaos energy and never looked back. And "fix" as the only commit message is the cherry on top. Three years of nobody asking questions is its own kind of documentation. 😂
chaos energy is the only explanation that holds. three years of silence means everyone else made the same peace with it and moved on.
"Made the same peace with it and moved on" is the lifecycle of every mysterious variable honestly 😄 First person sees it, thinks about asking, decides it's probably fine, moves on. Second person sees it, assumes the first person understood it, moves on. By year three it has tenure and nobody remembers there was ever a question. The silence isn't consensus, it's just accumulated avoidance wearing the costume of consensus.
silence as consensus is one of those things that becomes load-bearing. the first person to actually ask the question does not just risk being wrong — they risk unraveling three years of collective avoidance, which is scarier.
That's exactly why nobody asks — it's not really about being wrong about the variable, it's about being the person who surfaced that nobody actually knew what it did for three years. That's a much more uncomfortable thing to discover out loud than just quietly continuing to avoid it. The silence isn't protecting the code; it's protecting everyone from having to admit the code was never understood in the first place.
The handlePayment / handlePayment2 co-parenting situation is funny because it's universal, but the real problem isn't the missing comment â it's the missing decision record.
// TODO fix this from 2022 isn't documentation failure. It's architecture failure. The function name handlePayment2 doesn't tell you whether it replaces, extends, or diverges from handlePayment. The useNewLogic flag doesn't tell you what "new" means, what "old" did, or why both paths still exist. These aren't comment gaps. They're naming failures that embed ambiguity into the code structure itself.
When the original dev leaves, you lose two Slack threads and one hallway conversation. That context is gone. But if the code had been structured so that the decision lived in the type system â an enum that explicitly models PaymentStrategy::Legacy vs PaymentStrategy::Revised, with each variant carrying its own handler â the architecture would preserve the decision even when the person who made it has ascended.
Comments decay. Names decay slower. Type structures decay slowest. The fix for inherited-code trauma isn't more comments. It's making architectural decisions explicit enough that the code can tell you what happened without any human around to explain it.
What's the oldest dead-flag you've found that turned out to be load-bearing?
"Comments decay. Names decay slower. Type structures decay slowest." — that's going in my mental model permanently. It reframes the whole problem in a way that makes the fix feel structural rather than just disciplinary, which is why "write more comments" never quite solves it even when people try.
The PaymentStrategy enum example is exactly the kind of thing that would have saved me two hours on that service — not because it explains the decision in prose, but because the shape of the code forces you to acknowledge that two strategies exist and are different. You can't accidentally pretend handlePayment and handlePayment2 are the same thing when the type system is explicitly modeling them as distinct variants.
As for oldest dead flag that turned out to be load-bearing — I haven't found the absolute oldest, but I've definitely met the cousin: a boolean that looked like it defaulted to false and had been false in every environment I could check, so I assumed it was safe to remove. It was not safe to remove. One very specific production path that I hadn't thought to check disagreed strongly. The flag is still there. We do not speak of it.