Writing system software: code comments
antirez.com
antirez.com
I tend to organize my files into paragraphs or even sections of paragraphs. I often use "section headers" to distinguish these. It is essentially a weaker version of splitting a file across multiple files.
For example, I am working on a very simple pre-processor, and I have split it into 4 sections. The first is unmarked and presents the public interface. After that is a section marked simply by the comment "Lexer", after that is a section marked by the comment "Parser", and finally there is a section marked "TURN BLOCK BACK INTO TEXT". That last one should probably be changed to something like "Process result of parsing".
Sometime, my 'paragraphs' of code also get a short line comment explaining what the entire paragraph does. I guess that would generally fall under the 'guide comment' category.
Moreover, it would make it more difficult to understand the whole thing. The lexer and parser are closely coupled (by the data type for the tokens) and the parser and 'processor' are closely coupled by the data type of the syntax tree.
I could still see it happen that it does get split, and there are certainly coding styles that would have split this without question.
Obviously if yin gave 9 functions that belong in 3 groups of 3, you can argue that they should have a header but for most code I’d argue that more smaller things wins. I’d rather split it into 3 types and document the types even if the purpose of the split was mostly to get something to attach the comment to. The biggest issue with “free floating” comments (that aren’t for a statement, method, type or other code element) is that they tend to rot in the worst ways. Moving the code with refactoring tools won’t move the associated headers. When adding new code it’s easy to add under the wrong heading etc.
//== wrappers for inherited functions
where the equals work like in markdown headers, and the region continues until the end of scope or a new region.It's a chore and and half to plow through code in which the author has, for whatever misguided reasons, shortened variable names to inscrutable abbreviations, acronyms, and single letter head-scratchers.
Context-switching between comments and code may be easy for the author, but isn't good for the reader.
More often than not, the short-variable tendency points to an even worse problem: the author hasn't though very deeply about the software at a high level and therefore hasn't developed a good vocabulary or conceptual framework around the problem space.
Just because a developer thinks their code is a gift from the gods and understandable by all doesn’t make it true.
Code is the what. Comments are the why. You can’t expect the developer’s intent to come out through the code alone.
Until you have a better language or library, write comments but you should be striving to find better tools.
Being able to easily create types as needed gives not only compiler guarantees but also communicates exactly what sort of “id” or “name” you’re dealing with.
Generics separate orthogonal parts of logic, e.g. a List vs domain object. I remember the days when the domain objects would contain the “next” pointer. More generally any ability to separate aspects is hugely helpful. Eg: list<lazy<person>> is a lot easier to read than baking it all that in one object. Once done, you don’t have to have a section of code inside person that says “instantiate these fields lazily for performance”.
Better question: “What feature am I missing from my current PL or API that would mean I didn’t need this comment/documentation?”
Then strive to get that built.
For example a type system removes the need for an entire kind of method comment.
DSLs are excellent at this. Lombok removes the need for an entire kind of class comment.
Convention over configuration is great at this. Rails routes removes the need for an entire kind of comment.
Moving optimization’s into the compiler are good for this. Removing the need for an entire kind of comment.
Rust’s borrow checker removes the need for an entire kind of documentation around concurrency.
Every time a PL or library gets more advanced/ergonomic it removes the need for an entire kind of documentation.
Why shouldn’t we strive to take this to the limit, making better and better PLs and libraries till we need 0 documentation?
#1 If anything in a pull request isn't blindingly obvious, ask a "why are we doing this here?" style question.
#2 Encourage all developers to do this on all pull requests.
#3 Get developers to answer those questions in the form of a pushed code comment rather than (or as well as) answering the comment. Make that an iron rule.
IMHO this gives the best comments. I'd usually discourage comments that don't come about like this.
In general I'd prefer to see 'why' attached to behavioral tests.
When I am asking "why was it written this way?" 8 times out of 10 it's the actual desired behavior that I don't get, maybe 2 time out of 10 it's "why was it implemented this way" (and most of the time those are apologizing for hacks).
For small tools that I have written myself I prefer the guide comments because I usually know the general design of the program but need some help to remember the order of the things done inside a function/procedure/method. But the more complex the application, the more higher-level comments are needed.
Note that the guide comments can also appear in the form of function comments. For example, you see sometimes the networking function written like this:
...
logLinkDisconnectionWithReplica();
freeQueryBuffer();
deallocateBlockingStructures();
...
In that case the the function comments of the called functions are actually the guide comments (edit). I have the impression that this style of programming is popular among smalltalkers (please correct me if I am wrong).It is also interesting to see how the comments depend on the expressiveness of the programming language. For example, you can see a lot of "trivial" comments in assembly code because even simple things (like incrementing a counter) sometimes need code that is not easily readable. In Java, you see less comments like "This method throws an IllegalArgumentException if ..." because the programmer thinks that the list of checked exceptions in the method defnition are sufficient. In a language that I just made up for this comment, a procedure with signature
getPerson : (name : String not empty) -> List(Person) not empty | Nothing
needs less function comments than struct list *getPerson(char* name) typedef struct list *person_data_list;
person_data_list getPerson(const char const *name);
Yes it still does not provide all the necessary information, but it already does provide a bit more.A problem with code comments is that it very easily becomes outdated. And if the same method keep changing for different reasons, the code comments might become rather long.
I believe a good practice is to avoid code comments and document the why in the commit messages and PR descriptions. This way is easy enough to git blame and go to the "why" whenever a code is not self-explanatory
/* Unlink the client: this will close the socket, remove the I/O
* handlers, and remove references of the client from different
* places where active clients may be referenced. */
unlinkClient(c);
I wouldn't trust a comment as technically detailed as this one to stay in sync with code changes to unlinkClient.That's a red flag for code reviews being absent. (Or badly performed)
I sometimes write in (zSeries) assembly and I use guide comments a lot, on almost every line, to say what is the value that the resulting register will hold.
I also sometimes use backup comments for code that I wrote at some point, but then it wasn't strictly needed, but I still believe it might become useful in the future. You could have it in Git, but then the information that this code is already in Git is lost.
Not sure if is a separate category or not, but I often do Quirks type comments especially when dealing with APIs of libraries with links to relevant documentation ( even if though those links may go dead at some point, I'll capture the essence of the quirk in the comment and leave the link )
One specific type of comment that I've found valuable is a sort of BDD in my tests.
testMethod() {
// GIVEN a <specific type of thing>
<setup code>
// WHEN the <specific operation is done>
<do the thing code>
// EXPECT <specific effect>
<assertion code for the effect>
}
These are like "guide comments" but with more structure. I find that they help write tests and also make reading tests transparent.One unacknowledged facet: Code with different purposes requires different levels of commenting. If you're (say) implementing a UI, you can often avoid commenting through good naming: `saveDocumentButtonClicked()`. But if you are implementing the C++11 spec, you should provide liberal references to that spec in the comments.
Comments become more important as the code becomes more forced and less natural.
Kudos! I wish more "content creators" realized this. If you have something useful to say, it is much easier for your audience to read it at their own pace, instead of having to listen to someone talking, sometimes with a weird accent.
If your topic is about something that need the visual presentation, by all means, video is cool (although often a bunch of screenshots, graphs, or other imagery is better), but if you just want to talk about something, it's a waste of everyone's time and resources.
But I guess monetization is better on youtube, huh? rolls eyes
What I really like are "This is fine, but with effort it could be done better like this" comments, which one might also call 'debt comments'. For example, if you have a brute-force search on something that could be done by bisection.
Re. Guide comments - I usually try to make a smaller function with an appropriate name and then call the function. So long function become a series of invocations to smaller functions. This eliminates most guide comments IMO.
I've often wondered why my code style is moderate inline blocks with guide comments when I know about the benefits of good naming and self-documenting code. In the end, I find it more effective. By encoding in function names, you've (technically) eliminated comments but now buried what it actually does somewhere else meaning the reader must navigate to verify it does exacty and only what each reader infers from the name. I much prefer having a function of a small-medium logical size that I can hold in my head all at once and see relevant details without navigating away from where I'm reading/editing.
My experience is that less depth and fewer single-use functions aids overall comprehension similarly to how flattening as in list comprehensions or filter/map pipelines buys more headroom. I can imagine building/using DSL where everything is well-named and free of guide comments but I have never encountered such a thing where there are multiple committers using a procedural language.
the docstring is my goal ... still waiting for time on the commute to dig in but thoughts welcome
What is this "self-documenting" code and where can you read it?
And I'd go as far as to say they're almost mandatory for any math related code with a lot of complicated conditions. It's 100x easier just reading the guide comments. Not that they should repeat the if statement exactly, that's pointless. But describing what's happening (the bigger picture that is) really helps imo, and adding an example is even better. I try to keep them short though, single sentence, usually 1 comment for every 5-10 lines depending on the complexity of the code. For example, I was writing a sort of panel/window manager, it had a ton of conditions that looked like:
if (area_left < current_left) {/*...*/}
if (area_right > current_left && area_right < current_right) {/*...*/}
You could probably figure out what was going on if you saw it was inside the resize handler for a panel (current_*) and looping through all the other panels/areas, but something like "// finds any areas that will limit our movement" right above the ~10 line section is so much easier to understand. For more complex situations I might have added a visual description as well: "// e.g. given panels [1][2][3] - when dragging a side of #2, find any limiting panels to the right (#3) and left (#1)", though usually I keep the examples to function comments.I'm torn about debt comments though, especially for protoypes/beta projects where they're the most common. On the one hand, if they're in the code, it makes finding where changes for certain features should be added easier if you add them consistently. On the other hand, it's then hard to see an overview of them all. Putting them in their own file makes it easier to manage them and helps me remember what I was working on, but then I have to go hunt down all the places the functionality needs to be added to. Maybe some combination would be better. Like naming them something specific: "todo - feature - somefeature" then having the details of somefeature in the todo file. Or another solution might be to use headers as someone else has described, then refer to the filename/headername/s that need to be looked at in the todo list.
Usually imo, the more comments the better, so long as none of them are outdated (working alone though I haven't really found this to be a problem). You can always remove comments, it's much harder to add them in months after you wrote some code.
Any place that takes an attitude of "code should be self-documenting" I just walk away from. That is a type of dysfunction you're not gonna be able to solve. Also places that say, "we're always evaluating new ways to improve documentation practices..." begs further questions to get the specifics out of them.
For most jobs, I know I'm going to have to deal with bad legacy code. But if there is also a culture of not documenting things, arguing against comments or formatted API and function documentation, and it results in documentation spaghetti of Google docs, Markdown files, spreadsheets, emails, confluence pages, etc., then there's no point. I can't help you.
It’s also not about whether your code “needs” documentation or comments. The code should be clean and no more complex than required, always, and refactoring is to help do that over time.
That has almost nothing to do with providing comments or documentation for future people who could be inexperienced, unfamiliar with that programming language, unfamiliar with that type of algorithm, coming to the source file many years after the last time it was edited, etc.
The reasons to need documentation are many, and even if the code “is so good it doesn’t need comments” that usually does not matter at all to the person reading the code, and that person still does need comments.
Thinking of comments as an indicator of inferior code is a dangerous code golf mentality that ignores the fact that you write comments _for people_, especially people coming to the code in a way that breaks all the assumptions the code author was thinking when they decided not to write comments.
a = 4 //assign value 4 to a
Of course that is exaggeration but usually they restate obvious things and think it is good comment.
Also I don't trust comments, I have to see commit history, and if I see there was piece of code changed where comments are, without stepping through and debugging I don't trust code is doing what comment is saying.
I do not know how other people are writing code, but I have to run code locally, ideally in unit test or click through in the interface with test data. Maybe others just can run code in their heads and account for what is written in comments. But unfortunately I am not human compiler/runtime/processor.
When I see people arguing against comments, I see this strawman again and again and again.
In my multiple decades of programming I have never seen anybody write a blatantly redundant comment like that. It's not "most of the time" that people comment like that, in my experience it's never.
Programmers have a job to do, and in my experience generally do not waste their time using comments to "restate obvious things and think it's a good comment". Rather, they comment when necessary to explain the why, or to provide a concise 2-sentence description that would be too long for a function name but lets you skip reading the next 30 lines of code which would take much longer.
I really wish people would stop using this strawman of "most comments are redundant/obvious anyways, therefore don't comment". I mean, maybe you personally did have the terrible luck to work with a codebase that was actually like that... but I personally have never seen it.
I have.
That said, I think we are on the same position on the issue. When I've seen such comments, it's when someone that didn't understand the value if comments is responding to a request to ask them.
Thus is not necessarily because they are being deliberately obtuse, but because they never really experienced helpful comments. To me, 90% of the time a good comment wont say what's happening, instead it will say WHY it is happening. If good coding practised makes the code itself clear, good coding practice makes the context the code is written in clear.
But for those new, and/or those too use to a particular codebase or just never exposed to good comments, they wont get it, and when asked to do one (either as a general request or in a code review) the strawman is what you get (usually not as ridiculous as this example, but equally useless).
I see it as a cycle when learn to code: first we find comments useless so we skip them where we can. Then thus becomes normal, so we are used to seeing no or only useless comments. Then one day we look at code that we know we were confused on before but have forgotten why. On the N-th time of this, we leave the one line comment that says the secret. On the N plus one-th time, we read that comment and are enlightened. We then spend years trying to figure out the magic behind a useful and a useless comment, and arguing with those who think only useless comments exist.
Juniors think that they should write comments and then they waste time writing epic descriptions of trivial things. They waste time writing whole story of creating simple piece of code in one commit message. They waste time deliberating if tabs are better than spaces.
This job made me hate inline comments. If I were to ever create a new language, I'd insist in there being no way to write comments in the same line as executable code.
To be clear, I agree with you about how comments should be used.
While I share your dislike for this counterargument, it's certainly not never for me. Not even close. In my last job, I was dealing with code that had been in development for 15-20 years. We started a project to clean up the codebase. I would randomly browse people's commits. A fair amount of cleanup involved removing exactly those types of comments.
>Programmers have a job to do, and in my experience generally do not waste their time using comments to "restate obvious things and think it's a good comment".
Try working on a code base developed by engineers who are not trained in programming.
You're simply insanely lucky!
As another commenter pointed out, we use comments for why. Sometimes the commit history can help this, but a well-placed comment can shrink that effort.
I get a feeling we may not pass OP’s litmus test, but I’d be curious if we would. We use the term self-documenting code, but that’s primarily because our tests are part of our code, and those tests are indeed our primary documentation.
Good code comments are important. Good unit tests are important too. But both are necessary.
That only works if the commit message is informative. A commit message that says "updated tests" is just as informative as a comment that says "assign value to a" (following your code comment example).
Commit messages are as, if not more important, than code comments (given VCS tools like git blame that can associate code changes with a comment explaining what was done and why).
Code should mostly be self-documenting in terms of the "how", and coding style, idioms, and guidelines should be enforced to ensure that the code actually achieves this goal. Importantly, a lot of implicit intent by the programmer can be made explicit through coding style alone. I prefer this method when possible because inferring programmer intent is one of the single biggest headaches for legacy code, and when intent is made explicit by the code itself you don't have to worry about the documentation diverging from the code nearly as much. I say "mostly self-documenting" because there are occasional cases where the "how" is not obvious even if the code is e.g. a specific result is guaranteed as a side effect of non-local state.
The "why", on the other hand needs significant documentation to exist inline with the code, and you typically need two types of "why"s. First, for some things you need to explain why a specific implementation was chosen versus other possible implementations. Sometimes an implementation is arbitrary -- and I document this too, so people know it can be swapped out with minimal consequence -- but other times it was chosen to effect specific optimizations based on specific assumptions, both of which need to be documented so that a programmer can determine if the assumptions are still true. The other type of "why" is, of course, why does a specific bit of code need to exist at all? I've seen extremely innocuous code that fixed a subtle bug or edge case that would almost certainly be refactored away by a programmer some day if it wasn't explicitly documented that a tiny bit of logic was doing something very important.
From an "80/20 Rule" perspective, I find that code styles the explicitly capture programmer intent, plus some well-written inline "why" documentation does most of the heavy lifting for code maintenance while minimizing the volume of documentation that has to be written and maintained by the programmers separate from code. There should also be some high-level documentation on design assumptions and scope, but programmers are less involved in that on a day to day basis.
- good commit messages
- references to persistent requirements artifacts (eg Trello cards, Jira tickets, whatever)
YMMV... but really doing these two things makes a huge difference.
So those things are both good also, but I consider them to be a fallback for cases where the code isn't commented like it should be.
Writing good comments requires a lot of vigilance: not only do you need to know which decisions to explain, but good documentation often entails explaining why you didn’t do something.
“We don’t need a memory barrier here because ____.”
“There is a subtle race between X and Y here, but it’s benign because ____.”
Of course, if your colleagues are not as disciplined as you, even the best comments can fall victim to comment rot over time.
Most of the comments I see are useless, outdated, or worse. Code, on the other hand, doesn't lie. Well commented code is nice, but there is no guarantee that comments match the code, it is better if code can be understood without the comments.
This is a common problem in companies with coding rules that require a minimum comment/code ratio. People will write comments for the sake of writing comments. There will probably be also formal specification and design documents, user manuals, etc... That's great on paper, and the dev doing that will probably answer correctly to your interview questions. However, chances are they are all full of lies. Because, doing all that and keeping it up to date is really time consuming, and there are deadlines to meet.
To put things simply, I've never worked on a well documented project. By well documented I mean that the documentation is better than grep. Some big open source project are good but I still sometimes end up with grep. The only part where people can be serious is in public API documentation.
It makes much more sense to centralize the "why does this behaviour exist" there where the specific behaviour is being tested rather than in the code.