Chapter 12: Comments for Ghosts
“The past is never dead. It’s not even past.”
— William Faulkner
Every codebase is haunted.
The names in the Git blame belong to people who left years ago. The functions were written by versions of yourself you no longer remember. The decisions were made in meetings that weren’t recorded, for reasons that made sense at the time, by teams that have since scattered. The code remains. The context evaporates.
Comments are how we speak to ghosts.
Not the ghosts of the dead, necessarily. The ghosts of the absent. The future maintainer who will inherit this code without inheriting the conversations that shaped it. The new hire who will open this file in three years and wonder why it works this way. The you-of-next-year who will have forgotten why this decision made sense.
And now, there’s another ghost in the machine. One that reads your code without attending your standups. One that needs to understand your reasoning but can’t tap anyone on the shoulder. One that will make choices based on what you left behind.
Comments are messages across time. The question is whether they’re worth sending.
What Code Can Say
Code is precise about mechanism. It tells you what happens, step by step, in unambiguous detail. A function named calculateInvoiceTotal that sums line items and applies tax is already explaining itself. The what is right there. The how is right there. The when and where are traceable through the call stack.
The best code, the kind we talked about in Chapters 8 and 11, makes these answers obvious. You read it and you understand it. The names are clear. The structure is predictable. The flow is followable. Comments that explain what the code does are redundant at best, dangerous at worst. They add noise. They drift out of sync. They become lies that future readers trust.
If you find yourself writing a comment that explains what the code is doing, stop. That’s a smell. The code is telling you it’s not clear enough. Refactor until the comment becomes unnecessary.
But there’s one question code cannot answer, no matter how clear it is.
Why.
The Question Code Can’t Answer
Why does this function exist? Why is it structured this way? Why do we handle this edge case but not that one? Why is there a three-second delay here? Why does this validation happen twice?
The code shows you what the system does. It can’t show you the constraints that shaped it. The business requirement that seemed insane until you heard the customer story. The deadline that forced a compromise. The bug in a third-party library that required a workaround. The meeting where the product team explained that yes, this really is how invoices work in Germany.
These are the ghosts that haunt the code. The reasons that made those decisions reasonable. Without them, the code looks arbitrary, or worse, stupid. With them, the code becomes inevitable.
A “why” comment doesn’t explain the code. It explains the world the code lives in. It says: I know this looks odd, but here’s what you need to understand. It provides the context that lets a reader trust the code instead of second-guessing it.
// German tax law requires invoices to show line-item tax separately,
// even when the tax rate is 0%. This differs from our default behavior.
if (country === 'DE') {
invoice.showZeroTaxLines = true;
}
Without the comment, this looks like a bug or a hack. With the comment, it’s obviously correct.
Here’s another:
// We intentionally allow duplicate submissions here.
// The upstream payment gateway retries with different request IDs during failover,
// and deduping caused legitimate payments to be dropped. See incident #4471.
function processPayment(request) {
...
}
This one explains a production reality that contradicts best practice. Without the comment, a well-meaning developer would “fix” it by adding deduplication. With the comment, they understand why that “fix” would break things.
The code tells you what. The comment tells you why. Together, they form a complete message.
Messages from the Departed
I’ve encountered comments that felt like messages from ghosts.
Sometimes they’re apologies. “Sorry, I know this isn’t great, but the deadline didn’t leave time to do it right.” Sometimes they’re warnings. “This logic handles a race condition in the payment gateway that took us three weeks to diagnose.” Sometimes they’re confessions. “We tried three other approaches. This is the one that worked.”
These comments bring the human back into the code. They remind you that the developers who came before had constraints too. They had roadmaps that didn’t leave time. They had bugs that took weeks to find. They had pressure and deadlines and imperfect information, just like you do now.
When I read a comment like that, something shifts. The code stops being an artifact to judge and becomes a conversation to continue. The previous developer isn’t a faceless author of technical debt. They’re a colleague I never met, leaving me a note about what they were dealing with.
I’ve written those comments too. The ones that say: I know this looks wrong. Here’s why it’s right. Or: I know this should be refactored. Here’s why we couldn’t. They’re not excuses. They’re context. They’re a note left in the stairwell, hoping the next person doesn’t fall.
When Dragons Are a Smell
Early in my career, I built a system that analyzed code for security vulnerabilities. We parsed multiple languages into ASTs, then ran heuristic algorithms to detect known malware signatures. It was written in Java with ANTLR, and it worked.
It was also a tangled mess. Events fired other events. A shared global state object accumulated results as the events talked to each other. The flow was impossible to follow without running it. When I finally got it working, I left a comment: “Here be dragons. Don’t change this unless you really understand it.”
I thought I was being helpful. A warning to future maintainers. A breadcrumb from someone who’d been through the maze.
Years later, I realized what that comment actually was: evidence that the approach was wrong.
If you have to warn people not to touch something because it’s too complex to understand, the complexity is the problem. The comment wasn’t wisdom. It was a confession. I thought I was leaving a breadcrumb. I was leaving fear.
Now when I’m tempted to write a “here be dragons” comment, I stop. That urge is a signal. It means something is too complicated, too fragile, too entangled. The right response isn’t to warn future readers. It’s to fix the code.
Sometimes you can’t fix it. The deadline has passed. The priorities have shifted. You have to ship and move on. In those cases, leave the warning, but know what it is: an apology, not a solution. A marker for the debt, not permission to ignore it.
I left that company before I could clean up the dragon. I still think about it sometimes.
The Ghost in the Machine
There’s a new reader now. A ghost that never lived.
It doesn’t attend your standups or hear your hallway conversations. It has no memory of the meeting where the decision was made, no context of the company culture, no sense of the pressures that shaped this code. It’s reading the code cold, with none of the story.
AI reads your code. It makes decisions based on what it sees. And without comments, it sees only the what. Not the why.
I’ve watched AI misunderstand code that lacked context. It made choices I wouldn’t have made, changes that violated the reasoning behind the original design. Not because it was stupid, but because it didn’t know. The information lived in the team’s shared mental model, in meetings and water cooler chats. The AI had no access to any of that.
The fix was simple. Roll back. Add comments explaining the reasoning. Run it again. With the context in place, the AI made better choices. It respected the intent because it could finally see the intent.
Comments have always let you encode reasoning for future readers. Those readers used to be human, with human escape hatches. They could search old tickets. They could check the Git blame and send a message. They could ask around until they found someone who remembered.
AI doesn’t have those options. The comments are its only view into why something works the way it does. They’re its access to the context that humans get just by being on a team. Without them, it’s guessing. With them, it’s collaborating.
In this way, comments become a form of engineering guardrail. They don’t just help humans understand. They help AI respect your vision. They encode the constraints that the code itself cannot express.
The Lie That Lasts
Comments can lie. Code can only behave.
Code does what it does. It might do the wrong thing, but it doesn’t persuade you otherwise. A function that deletes records doesn’t claim to be read-only. A loop that runs forever doesn’t say it terminates.
Comments make promises that drift. Someone changes the code, forgets to update the comment, and now the comment describes something that no longer exists. The next reader trusts the comment, misunderstands the code, and makes a mistake.
This is why “what” comments are dangerous. They describe behavior that can change. “Why” comments are more durable. The reason a function exists, the constraint it addresses, the business rule it implements: these usually outlast the implementation details. The how might change. The why remains.
But even “why” comments can become lies if the world changes. The German tax law gets updated. The bug in the third-party library gets fixed. The business requirement that drove the decision gets reversed. When that happens, the comment becomes an artifact of a past that no longer applies.
The discipline is maintenance. When you change code, check its comments. When you encounter a comment that doesn’t match reality, fix it or delete it. A missing comment is neutral. A lying comment is sabotage.
The Conversation Through Time
Writing comments is an act of faith.
You’re speaking to someone you’ll never meet, about a context they don’t share, hoping they’ll understand. You’re trying to compress everything you know about this moment, this constraint, this decision, into a few lines of text that will outlast your memory of writing them.
Most of those messages will never be read. The code will be deleted. The feature will be deprecated. The system will be rewritten. The comment will vanish along with everything it explained.
But some of them will land. Some future developer, human or otherwise, will open a file and find a note you left. They’ll understand something they wouldn’t have understood otherwise. They’ll trust the code because you gave them a reason to trust it. They’ll make a better decision because you shared your constraints.
You won’t be there to see it. You won’t know it happened. That’s what makes it faith.
Write comments for the ghosts. The developers who came before you were ghosts too, once. They left messages hoping someone would read them. Some of those messages reached you. Some of them helped.
The code says what. The comments say why. Together, they’re a conversation that outlasts everyone who participates in it.
Leave something worth knowing.