翻訳待ち:On Comments
AI サービスが一時的に利用できないため、復旧後に翻訳を補完します。ソース概要:For the past decade or so, my syntax highlighting configuration has had an odd quirk in it: code comments are bright orange, not the standard faded grey. This wasn't an accident. I wanted to force myself to get better a…
AI サービスが一時的に利用できないため、復旧後に翻訳を補完します。
For the past decade or so, my syntax highlighting configuration has had an odd quirk in it: code comments are bright orange, not the standard faded grey. This wasn't an accident. I wanted to force myself to get better at documenting code, and what better way to do so than to make comments extremely in-your-face so that any extraneous noise becomes painfully visible. It's very easy to write useless comments in code; comments that convey little beyond what can already be surmised from the nearby code, that state obvious facts, or that only make sense to the person who wrote them at the time that they wrote them. Such comments stick out like a sore thumb when reading through code (especially with my configuration). They read like noise, polluting the flow of the code with irrelevant information that causes the brain to lose its thread in the code. This is especially apparent when comments reflect some previous iteration of the code, but is no longer accurate, and thus causes the reader to do a double-take and start doubting their own understanding. And yet, comments can be hugely valuable when used well. When a comment points out why the "obvious" restructuring of a bit of code won't work, explains why a particular configuration option needs to be set in a library, or highlights a corner case that the code doesn't support (and why), it can save the reader hours or days of work and frustration. And remember, that reader could be yourself 12 months from now when you've forgotten all the details about how the code in question works! Often, you won't even realize how much time and effort a single comment might have saved you, as you just read it and move on. It only becomes apparent if you consider what you would have done if you had read the code without the comment, spotted something weird-looking, and decided to see if you could make it better. I'm specifically talking about code comments, not in-code documentation (eg, /// in Rust). Documentation is written for your consumers. Comments are written for your collaborators. And while some of these lessons apply for the former, I only want to talk about the latter (for today at least). Just write more readable code! It's no wonder that many engineers have a complicated relationship with comments. We love them when they're good, hate them when they're noise, and (perhaps subconsciously) worry about writing too many of them in case they're just noise to others. My experience has been that some engineers write fewer and fewer comments as they become more senior, preferring instead to make their code easier to read by breaking up code, using more descriptive function and variable names, leveraging the type system more extensively, etc. Those techniques are all worthwhile and good practice, and they do result in code that's easier to read and understand. But they do not replace comments. Not fully. Improving the readability of the code will, in general, make the "how" clearer, but not the "why". Some limited comment-like code constructs exist, such as Option::expect and unreachable! in Rust, where you can pass in a string explaining why something is definitely Some or unreachable!, but they primarily document invariant assumptions and not much else. Information that was never written down cannot be recovered by reading harder. What kinds of comments are useful? There are a number of types of comments that in my experience are nearly always worthwhile. The main ones are: TODOs You know the code isn't complete, finished, or fully polished, but you also know it's not important that it isn't. So, you leave a note to a future developer about what is outstanding and ship the thing. Risky, perhaps, but the reality of software is that there are a lot of corner cases, and we can often know that some simply aren't important at the moment (ranging from "this could be optimized" to "this corner case is complicated to implement but also highly unlikely to be relevant"). The main thing to remember with TODOs is to actually leave enough information for the reader to understand what you meant. When everything is fresh in our minds, it's easy to just jot down a few keywords, but when the junior engineer is looking at it in six months time and you've left the team, that TODO is probably going to remain just that — a to-do. Multi-line TODOs are totally fine, as are outlines / partial steps for how to resolve, and chances are the person who comes after you (or you yourself!) will be happy you took the time. Examples: haphazard — src/domain.rs noria — server/src/controller/migrate/routing.rs noria — server/src/controller/migrate/assignment.rs haphazard — src/lib.rs Linking a TODO to a ticket can also be a good idea to have a space for discussion and tracking while still keeping the strong connection to the code. I'm personally not too fussed about the exact format of TODOs. They need to contain the letters TODO (in that order), but whether you add a name in there and whether it's always a prefix isn't terribly important. At Helsing, we require that a TODO always includes the author's name, though if you don't go that way you can always recover the name via git blame. References Code that has a strong connection to some external source, such as if it's a (lightly modified) copy of some code that lives elsewhere or if it's an encoding of an algorithm specified in a paper, blog post, or book, usually benefits from that connection being highlighted in a comment. For code and blog posts, this often means a link (specifically, a permalink — press y on GitHub before copying the link), whereas for published resources it may mean title + author + page/chapter number. In addition to the reference pointer, any divergence from the reference should also be documented (and motivated). Examples: noria — noria/src/table.rs noria — noria/src/channel/tcp.rs msql-srv — src/params.rs Correctness arguments Proofs (informal or formal) about why some non-trivial code ends up doing the right thing usually warrant a comment. The code itself represents the steps taken (the how), and tests can verify that the outcome is right (the what), but the reasoning for why those steps reliably lead to that outcome should be written down to enable improvements to the what in the future. Correctness arguments often pair well with assertions (eg, unreachable!) where possible, in particular to validate (and highlight) assumptions of invariants that the code makes. Examples: noria — server/dataflow/src/ops/union.rs noria — server/dataflow/src/domain/mod.rs haphazard — src/domain.rs haphazard — src/lib.rs Pro-tip: don't stop in the middle of a correctness argument when you realise it doesn't hold and then commit the partial proof. I did that. Hard-learned lessons Any time you've spent more than 30 minutes getting something to work, and the fix ended up being some brief but unintuitive magical incantation that just has to be right there, that warrants a comment. The reasoning is pretty straightforward: you yourself did not realize 30(+) minutes ago that it was needed, so it will almost certainly not be intuitive to someone else new to the code why it's needed either! And even if you don't actually know why the incantation is needed, document how you arrived at it, and what happens if it is absent, so that those who come after you can continue from where you left off. Examples: noria — server/src/controller/migrate/sharding.rs haphazard — src/hazard.rs noria — server/dataflow/src/backlog/mod.rs Rationale for constants We've all read (and, let's be honest, written) code like this: const HEARTBEAT_INTERVAL: usize = 5; max_packet_size = 1492 range = (i > 0 && i , facing we decided for to achieve , accepting . The format is strict to ensure that all those parts are considered every time; you can't skip any of the fields, and you are also supposed to write concise sentence fragments into each placeholder. No substituting any of these with a paragraph of text (though you can add paragraphs afterwards to add further context). By having these ADRs right by the code, the decision logic for why that code does what it does is easily discoverable, and it's easy to notice that it needs updating when the code is updated. The cost is that the decisions become invisible from the outside; you can no longer survey what a project has decided without reading all of it. So, at Helsing we've written a small tool to lint such comments, and to easily extract them from the code. We're open-sourcing it today as yadr. yadr walks a source tree and lists every decision it finds: $ yadr ls src/ ==> src/crdts.rs -> 2024-05-06 Removal of .alive tracking ==> src/causal_context.rs -> 2024-04-19 Allocation of identifier bits You can also use yadr show 2024-05-06 to print any one of them in full, and yadr check to assert (eg, in CI) that all such Y-Statements follow the convention. That last one is deliberately strict about punctuation and phrasing, Given this is a new tool, there aren't many public examples of them yet, although you can see some in the dson CRDT crate we've published: dson — src/crdts/orarray.rs dson — src/crdts.rs dson — src/causal_context.rs Comments are technical writing Comments are technical writing, and should be written with the same care as other technical documents. This is not the place to go take a deep-dive into technical writing, but there are three key aspects of good technical writing that are worth highlighting about comments in particular: Bytes are cheap. The byte weight of a source code file is mostly irrelevant today, and especially so the number of bytes spent on comments. There is no reason to be overly terse, especially if it may hinder your tired, undercaffeinated, and in-a-rush self from understanding what you meant a year or two down the line. Use more words if it is helpful, make use of punctuation and full sentences, and avoid all but the most obvious of abbreviations and acronyms. Remember the reader. It's easy to make the mistake of assuming that future readers will have all the same context, insight, and recently-acquired clarity as you have when writing. Words like "of course", "trivial", "obviously", and "just" are good indicators of this attitude. Try reading your comment while suppressing your knowledge of the code (and its invariants) and see if it still makes sense. Precision matters. As in most technical writing, the details can be hugely important. Inaccurate or ambiguous use of punctuation, poor grammar, typos and missing words, and invalid references to variables or functions can at worst lead the reader to the wrong conclusions, and at best cause friction when reading. Read over your comments an extra time to make sure it actually says what you meant. What changed with LLMs? With the adoption of LLMs, a lot of code is now written (and read) by agents. But this does not reduce the importance of carefully considered comments; quite to the contrary! Coding agents arrive at your file without any knowledge of why that retry loop is there, what happened in the design discussion on Teams, and no way to tell which choices embedded in the code were deliberate and which were arbitrary. It's like a new engineer to the team who is fast and confident (and has endless energy), but knows nothing about why your codebase is the way it is. They are precisely the target audience for comments that convey information beyond what the code itself can tell you (ref the earlier argument about the self-documenting code fallacy). Comments also have the benefit of being directly injected into the agent's context window. The agent doesn't need to realize there's an ADR somewhere else, or happen upon that particular historical MR description, commit message, or Jira ticket description. I've also found that LLMs are pretty good at writing these kinds of comments (and, relatedly, git commit messages), as [truncated for AI cost control]