Software engineers love to talk about code. They extoll the virtues of their chosen languages and favorite frameworks. They start online holy wars about all kinds of code-related nonsense. They group each other into “real” and apparently fake programmers—sometimes on the basis of where they place their starting curly brace or whether their language uses curly braces at all.

It’s very stupid and very tiresome.

Obviously, this isn’t every developer, but if you’ve worked in the industry you have met no shortage of these types. There’s no escaping them. They’re a frequent and unnecessary part of the fabric of software engineering. You would think people who love talking about inane shit related to code on a general basis would eat up the idea about talking about and writing about their own code. Their bespoke, beautiful, perfect, and idiomatic code. In my experience though, they don’t.

How We Talk About Our Code

I don’t really want to talk about code per se. I want to talk about talking about code which—when I put it that way—sounds even worse. I make no promises either. It might be worse.

Forgetting things like design documents and Jira hell for a moment, there are really three main ways we talk about our code that’s either embedded directly in the code or joined directly at the hip with it through source control. These are, in order of importance:

  1. Documentation: What you write about code when the code is not necessarily part of the viewer’s context.
  2. Comments: What you write about code when the code is part of the viewer’s context.
  3. Commit Messages: What you write about changes to code that are relevant for historical and/or meta purposes.

This is an area that a vast majority of the programmers I have ever worked with… suck. They suck bad. I feel like I cannot express how terrible this suckage is. These aren’t only people I have encountered professionally either. It’s an issue in open source projects that anyone can go look at. I dare you. Have a look.

When I was 16 or so, I sort of read the second edition of Practical C Programming. I learned several things from this book.

I learned that I hate C. If I had to program in C, I would do something else for a living because nothing about programming in that language or at that low level appeals to me at all. I thought it did. I even had aspirations about learning assembly so I could become a real programmer. The problem was no matter how much effort I put into it, I was miserable. (I hate C. Did I mention that?)

Despite the joy that lesser languages brought me—at the time there was a serious distinction between us posers using interpreted languages versus the compiled stuff that would apparently put hair on your chest1—I now understand that on some level, the author, Steve Oualline, cared about me. He was trying to warn me. Through the ether that connects an older generation to a more youthful and optimistically ignorant one, Mr. Oualline was preparing me for the things that he knew would come.2

Even if you loathe C like I do, the book is a good one. It’s good in the way that your parents giving you life advice is when you realize about a decade too late that they weren’t just old and out of touch and trying to ruin your fun. They cared about you and were interested in you avoiding ruining your life the way they had done. It was the first book or source that told me that much of my life as a programmer would involve maintaining someone else’s crap and if I cared about systems at all and their ability to survive that I would prize clarity above pretty much everything else. Even if I ascended to mostly writing the new and fun code, someone would have to maintain the old stuff and I should do them a solid.

The book was full of other warnings from a veteran and, like many 16-year-olds, I would disregard them because despite never authoring a book, teaching a class, coding professionally, or doing anything but mess around with Logo and BASIC on an Apple IIgs, I knew what I was doing! Of course I did. My mom told me I was super smart and I was like… taking honors classes in high school.

Tell me if you’ve heard this one before:

My code is so good and so clear it’s self-evident! Dare I say, it’s self-documenting. It’s beautiful. It’s elegant3. When I have to come back to this in a year I’ll be amazed at my own foresight and brilliance.

While the narrator in your head may not be nearly as high on himself as mine is, I still sense if you program, you have told yourself similar bullshit at least once or twice.

In your first decade plus, you’re going to be disgusted with your work from three months ago. You’re going to want to rewrite it all because you learned a handful of new tricks or, probably worse, learned a new language or framework that solves all of life’s problems and surely introduces no new ones. You’re also going to learn how quickly you forget what was going on three months ago.4

If you have never considered this before, I want you to pause for a moment and think long and hard on something. Sometimes, the poor soul maintaining code you wrote two years ago is future you. Future you is older for sure, hopefully wiser, likely more jaded, and somehow completely at the mercy of pretty much all the same problems present you is dealing with while also being plagued by the laziness and procrastination of present you, the consummate asshole. Even if you’re like me, and generally dislike software engineers, you probably don’t full on hate yourself.

Steve Oualline understands this. He warned me. Through his printed words he told me about comments and clarity and all that. Around 15 years later, when I got my first job that involved inheriting someone else’s god-awful system, I finally understood it and started taking this part of the craft seriously.5

Self-Documenting Code Is Not A Thing

Let me get this out of the way right now, if you think your code is “self-documenting” it tells me two things:

  1. You’re full of shit.
  2. You have absolutely no idea what documentation is for, how to write it, or even why it is written.

Actually, it also tells me a third thing: you think you and your code are far more clever than they really are. You’re not. It’s not. It took me like two decades to realize this about myself because I’m a slow learner and an ego maniac. Listen to the wiser people and save yourself a lot of time and pain.

Self-documenting code does not exist and never will.

Documentation

Documentation is a really funny thing. Software engineers love to complain about the poor quality of documentation everywhere. They complain about what it lacks or that it’s lacking entirely. They complain that it is opaque and overly technical when it is there. It seems, most of the time, the only thing we like about it is that we can blame missing or bad documentation for delays in our own work. This is key, because shifting blame is probably the single most important skill one can have to survive and even thrive in corporate life. (Ask pretty much everyone in your company’s Product department. They know. Or ask your CEO because they have mastered this.)

What software engineers by and large do not love is writing good documentation or any documentation at all if they can convince themselves that their function name and variable names are just oh so self-descriptive. It’s much more fun to poke holes in someone else’s work than it is to tell others to pay no attention to the man behind the curtain as they ask about your documentation.

What Good Documentation Isn’t (Or Shouldn’t Be)

If you’re lucky enough to come across a system with any documentation, there’s a good chance it’s still terrible and driven by some kind of company metric or imposition that doesn’t go any deeper than, “Every function must be documented.” Like many directives from leaders who don’t understand how anything actually works this turns into a lot of eye-rolling from employees who then phone it in.

Employees going through the motions manifests itself like so:

@doc """
Returns the first name.
"""
def first_name(name), do: ...

Uhhh, thanks. I could not have divined that from the function name. Someone who has never programmed could likely figure that out. At least your code linter won’t complain about a function lacking documentation—because isn’t that what it’s all about? Satisfying your linter in the most literal way possible.

This is documentation that is written for metrics or policy. It sucks. It also helps feed the idea that code itself is or even can be “self-documenting.” Someone looking at this is likely to think, “This is stupid and useless and a waste of time.” They’d be right on all counts, but that turns into, “So why bother?” And then they don’t and you end up with huge codebases built on apathy—perhaps the single most powerful force in software development.

How to Think About Documentation

Have you ever worked with someone else’s API where you don’t have access to anything but the public interface? A system where you can’t just open the code in your editor and look at it? Unless you’re an industry neophyte, you almost certainly have.

When you write documentation, even for code where you presume most users can see the code along side the documentation, it’s best to think about and write as if the code is not available, as would be the case if this was an API6. At this point, you instantly lose the crutch of thinking someone can “just look at the code and get it.”

When you’re integrating an API, it’s not enough to know what something does. That may actually be fairly obvious and feed the idea that something is self-documenting. The problem is, most system functionality doesn’t live in isolation. It’s interconnected with other objects and operations. How something works, where it fits into a larger system, and why it does what it does are all meaningful pieces of information.

These things matter when someone is trying to understand a system. Take the first name example above. What qualifies as a first name when given a name? What assumptions are made when it’s extracted from a whole name? What are some oddities or limitations? Why might these be entirely acceptable given the intended use case of this function?

You might think that’s a lot of extra work. You’re right! It’s more work. You know what else is a lot of work? Explaining these details over and over again from memory to some new hire that’s run into the same problem and lack of understanding as every other new hire. Put on your thinking cap and tell me which scenario is more work and which one is its own sort of n+1 problem.

It might make me sound like one of those crunchy granola hippie types, but when you write documentation, it should be holistic in nature. Imagine every function just wants to understand where it fits in the system. It wants to know what it all means! Tell it. Tell us.

We’re All in a Hurry

Think about your day job if you’re in software development. Has the following thought ever crossed your mind:

Gee, my job is really low stress. I develop under a sane schedule with a rational product team. I have the support from my leadership to do things correctly. Isn’t it great? I even have an excellent work-life balance with abundant time for family and hobbies.

If you feel this way about your life please, contact me and show me the way. I also sort of hate you out of pure jealousy.

Seriously though, we’re all in a hurry. We’re all trying to get things done. We all want answers as fast as possible. Most of us are operating under the auspice of an authority figure who understands close to nothing about what is going on or how things are built. It’s why the current crop of programmers just run to ChatGPT every time it takes more than a couple milliseconds to figure something out on their own.

Remember this empty shell of a person who once had hopes and dreams and aspirations of something greater when you’re writing documentation. This could be future you. It could be a co-worker. Whatever. Empathy, strange as it sounds, is an excellent skill to cultivate to make your documentation better.

What do you want other people to know? What do you want to remember in six months when a bug is found or functionality needs to be altered? You can answer these questions preemptively for yourself and future generations of people who make poor career choices.

IDEs, LSPs, and General Developer Experience

Text editors and IDEs with LSP support (or a something like it) really highlight the power of documentation. Even when working in a codebase and having access to the code, hovering a mouse pointer over a function and seeing a description of what it does, what the arguments are, and all the related type data is so much better than seeing… nothing and being forced to jump to the function definition and read the code.

Instant feedback helps reinforce good behavior. In a funny way, it’s incredibly satisfying to see output that looks like it was put together by a professional. Wouldn’t it be nice to feel like a professional instead of an imposter?

The Side Effect of a Good Documentation Process

Writing good documentation does much more than just document code. When you make documentation a core part of your workflow it will improve your code, your planning, and your system.

In my own workflows, depending on a combination of mood and task, I’ll either write my documentation first—this is commonly done for describing something like a module or a class—or I’ll write after I’ve completed some functionality as part of my first major self-review. You don’t even have to write all of this from scratch. As a co-worker taught me, this is a good time to take content from your project management system and put it into the code directly. When combined with reviews and test writing it helps the whole process come together and, over time, has a glorious halo effect on pretty much everything.

You’ll realize… it’s like… all connected man.7

Be Real With Yourself

Many years ago I started seeing a therapist because I am mentally unhealthy. I had always wondered what the big deal with talk therapy was or how it even worked. What I learned in the process is that no one is more likely to be dishonest with us than ourselves. When a good therapist asks you to explain something—something that sounds completely reasonable or brilliant in your own head—it’s amazing how incredibly stupid or ridiculous it sounds once you have to express it or justify it to someone else.

There were many occasions where the utter absurdity of my own mind came into clear focus as the words were leaving my mouth. My therapist, who was a very sagely old man, would often either chuckle or nod because he knew that I knew that I knew.

This is a rather roundabout way of saying that documenting your own designs and processes might be the best time to expose that maybe it all sounded a lot better in your head than in reality.

Pretty Please, With Sugar on Top, Document Your Fucking Code

This is all to say, document your code. It’s like eating vegetables or exercise or any other responsible thing you should do that will improve your life and the lives of those around you. Make it a habit and then just do it. It won’t be long before you move faster when you do it—even if it’s slower at first—and that the end result is of greater quality and more sustainable.

If you have a manager or other higher-up that doesn’t see or understand the value, tell that person I gave you permission to explain to them that they’re entirely ignorant and should probably become more educated about software before attempting to impose their poor judgement. In fact, you can tell them that I suggest they resign and find a new profession because they’re unsuited to their current position. If they protest, tell them it’s really for the good of the company. C-suite types really understand that one.

Comments

Depending on your language and/or documentation framework of choice, there’s a chance that comments and documentation are mechanically the same thing. We’re not talking about mechanics though. We’re talking about audience and purpose.

Comments exist to add clarity and additional context to code that doesn’t matter outside the code itself. This isn’t for an integrator. This is for the poor bastard modifying or fixing something from the inside.

I know your code is beautiful and self-documenting and all those other things it probably isn’t, but even then that doesn’t always matter. Sometimes you have to do things that don’t make a lot of obvious sense because someone else did something that doesn’t make a lot of obvious sense in a system where you can’t make changes.

Years ago, I was integrating with an API that was itself nothing but a really poorly written layer between me and an instance of Zimbra (the 2009 version). Everything seemed fine at first, but every time I tried adding a user with any sort of accent in their name—let’s say José for instance—I would not get José at the other end.

After messing around, I figured out that something was decoding UTF-8… twice in the process. I was working in Ruby at the time and had to force re-encode UTF-8 strings into UTF-8 again in my payloads and, lo and behold, I’d see José in the remote system.

This is not something an integrator needs to know. This is not something someone just using the function needs to know. This is nothing but a pesky implementation detail. This only matters when someone is looking at the actual code doing this.

# Hey! If you're looking at this function wondering why I'm using Iconv to force
# encode twice, it's because the geniuses who created this terrible API have a
# bug that they refuse to fix. If they ever bother to fix it, this will
# break and you'll need to remove this terrible hack.

Comments are an ideal place to explain workarounds, regrettable optimizations that bypass best practices when necessary, or just provide some extra guidance that makes the code easier to follow. Warn future developers, “I know you think you can do this some other way but we made this decision for a reason that probably isn’t even sort of obvious just by looking at the code.” Explain the reason while you’re at it because you’re likely to forget.

Whenever commenting, always take a moment to consider whether what you have to say belongs in the documentation or if renaming some variables or function names alleviates the need for them entirely.

How Not To Comment

I don’t think this is necessary for explaining good comments, but I have a compulsion and I have to point this out because it really, really pisses me off when I see it.

Have you ever been knee-deep in a codebase—code that was clearly written by someone who hates you personally and wants to punish you for some past sins—looking desperately for any sort of sign or clue about what the hell anything does only to find a barren wasteland of modules and classes and code without so much as a drop of documentation? There’s not even some really out-of-date document left to rot in Confluence or Google Drive containing archeological clues about the original intent or purpose of what’s been written. Nothing.

In all of this spaghetti, with no context and no help, you come across a most cruel signpost:

counter++; // increments the counter

Why!? You leave me with one comment per 10,000 lines of code and it’s this?

Either the author is trolling you or decided to try their hand at this whole “documentation thing” once, didn’t like it, gave up, but for reasons that defy explanation left this jewel for posterity.

I’ve seen this too many times over the past quarter century to believe it’s an isolated issue. Never do this. Ever. There is a very specific demon in a very specific deep plane of hell who exists entirely to torture the souls of those who do this.

Commit Messages

This isn’t about how to write a good commit message. Many, many guides8 already exist on that topic. My only suggestion is that you find something you like or your team can agree on and stick to a standard.

This is about why we write commit messages and what should go into them.

Okay, why?

Think about the times you actually look at someone else’s commit messages. I can’t speak for everyone, but if I’m looking at a specific commit message it’s for one of two reasons:

  1. I’m doing a code review and trying to get an idea of the order and process used for developing a feature.
  2. I’m dealing with a regression bug and trying to figure out what happened.

The first scenario belongs in the nice to have bucket and heavily influences my own commit process. The second, and far more common situation, is where good commit messages are essential.

(I’m keeping this code-centric. Commit messages done correctly also make things like writing change logs easier and help communicate a lot of metadata to other systems and non-engineers. They’re also an excellent place to complain about management.)

So, what then?

I have seen some wild discussions about commit messages. There’s a whole thread where I work about the adoption and enforcement of Conventional Commits9 that goes into very silly places. In an effort to sound like the smartest person in the room, many co-workers took the time to opine on the ideal commit message in ways that are… frankly, completely bananas.

The largest bananas in this bunch were providing massive examples that crammed in everything but the kitchen sink. As I read the thread, I was expecting someone to suggest keeping a copy of the code itself in the message because they were running out of ways to pack more and more into commit messages in an effort to outdo the other engineers.10

Do not overcomplicate this. Commit messages should cover three things:

  1. What the commit is for or does. It doesn’t need to be super detailed either.
  2. Information that is of historical relevance. This means, if someone were to look at the code as it is now and never see the commit message, their understanding of the code in would not be impacted in any negative way.
  3. Meta information for connecting history to other systems. For instance, associating a commit with something in your project management system is extremely nice for both regression tracking and auditing.

Commit Messages Are Not Made in Isolation

Commit messages are bundled with code. The changes are literally part of a commit. There is no need to paraphrase documentation or comments that should already be in the code. There is no reason to explain or justify your changes outside of their historical relevance.

Anything relevant to the current state of the code that would be useful to someone looking at it belongs in the documentation or the comments. Don’t send fellow programmers off into the commit log for essential information.

Things relevant only to the actual commit or that simply don’t belong in the documentation or comments should go into the commit message and not clutter up the frontline documentation.

Commit messages should be entirely additive to the code changes included in the commit itself.

Can AI help?

No.

I don’t just say that because of my deep hatred for LLMs and AI coding tools. Even if they worked as the hype-meisters claim, I would still argue this is one of the most detrimental uses of AI that I have seen to date.

The only documentation AI can produce is the kind that’s already a useless afterthought. It’s probably good in some organizations for checking boxes and pumping up those pointless metrics that the very worst leaders love so much, but none of it is actually helping fucking programmers fucking program (i.e. do their job).

If you have read the above it should be clear that so much about documentation, comments, and commit messages is there to provide information outside of the context provided by the code itself. Unless your LLM somehow gets connected to the Psychic Friends Network—and that’s presuming the technology actually worked the way also-ran grifter Dario Amodei11 wants you to believe it does—these are problems it cannot solve nor understand. It would be no different than the poor technical writer12 your boss thought was going to just solve the documentation problem by sifting through your hot garbage unassisted.

LLMs don’t know why your Product department decided to build a bridge to nowhere or that your executives make their decisions primarily on the basis of FOMO. They don’t get why your code is bypassing system conventions legitimately in some cases. They don’t have any way of understanding nor expressing your intent nor the way you want the code you have written to be used.

Whenever I see some demonstration or article about AI being used to write documentation from code it makes me even angrier than watching some buffoon generate another todo app using something like Claude. At least with code generation, in theory, you’re presenting context to the agent in the form of your prompts and then it’s carrying that out.

Documentation, on the other hand, represents that very context. It’s the thing we miserable meat bags are still expected—and need—to provide. If LLMs ever get good at writing code, the irony is that we’ll all have to become better documentation writers anyway.

You cannot write code first and then command some AI tool to just add documentation and commit messages that fulfill their purpose. This isn’t even an AI-specific problem. It’s a very basic order of operations problem.

Just Do It

Do not treat documentation like one of those “boring things” that you hope some magic tool can solve for you. If you do, your ceiling as a software engineer will remain quite low. Writing clear metadata about your code—while it tends to get Rodney Dangerfield levels of respect—will set you apart as a good engineer more quickly and more often than any sort of design pattern or whiz-bang algorithm. It will definitely put you well ahead of every wank who thinks Cursor is turning them into Superman.

Don’t be like me. Don’t wait ten or fifteen years to listen to industry veterans like Steve Oualline who have already seen enough to know what they’re talking about. I wish I could remember precisely when and where I heard the saying, but:

Your predecessor is never as dumb as you think they were and you’re never as clever as you think you are.13

Write documentation and metadata with intent and purpose. Don’t skip. Don’t skimp. Just fucking do.

Before you know it, you’ll be a spark of change for the engineers around you.


  1. What’s old is new. These days it’s static-typing that really makes chest hairs come in especially thick and solves all your problems. Or maybe it’s AI tools. Honestly, I can’t even keep up anymore. 

  2. I read this for the first time today. Not only is it a significantly condensed version of this essay and my previous one, he ends with the same quote my non-programmer father taught me. If only I had listened 30 years ago! 

  3. Dear 20-something-year-old me, as someone who has seen my wife in a legit evening gown, let me tell you, “elegant” is not a word that should ever be used to describe any code ever. Stop using that word. You sound like a tool. 

  4. This gets worse with age, I assure you. 

  5. And it took me another six to seven years before it was something I’d even consider passable by current standards. 

  6. Since it fucking is

  7. I know this sounds silly. In fact, I read this in the voice of The Dude from The Big Lebowski. It also happens to be the single most important and central point of this essay. 

  8. I don’t like everything about this guide but I do think it’s an really good starting template. 

  9. I’m mixed on these. This has become the standard where I work mainly because there was no standard. The more I use them the more I mostly like them. I just hate feat. I always think of actual feet and that makes things weird. (Which, I understand, is my own cross to bear.) That’s not a problem of the style so much as the most common implementation. 

  10. An unfortunate byproduct of allowing engineers to weigh in on something in public is that you’re invariably going to find yourself in the middle of a lot of dick-measuring which in most instances is considered extremely awkward in public.

    Have I ever mentioned how much I generally detest software engineers? 

  11. If you want to understand how completely insane our world is, your CEO is almost certainly going to believe whatever comes out of Dario Amodei’s mouth over someone like Steve Oualline. This is because Andoei is an entrepreneur and a visionary who also has no idea how his own company works rather than some nerd who actually builds things. This is, unfortunately, the problem at the very heart of our society.

    The demon waiting in hell for Dario Amodei is even worse than the one waiting to punish programmers for their poor comments. 

  12. I love technical writers. Please don’t get the impression that I have anything but the highest respect for the good ones. Like other valuable—and entirely thankless—professionals, what they do and how they work isn’t really understood by anyone at the executive level. I have worked with at least three CEOs who believed that we can solve our documentation problems by “[hiring] a technical writer when we have the budget.”

    My guy, who do you think is going to have to guide and explain all this crap to the technical writer? 

  13. If anyone knows the origin of this saying, let me know. I vaguely recall there is a version with an official name.