I’m on the same page. Just look at the given example. Does it look wasteful to say that you’re adding a vertical bar when you have the following line of code?:
// Add a vertical scroll bar
vScrollBar = new JScrollBar(JScrollBar.VERTICAL);
add(vScrollBar, BorderLayout.EAST);
Perhaps. But the comment is making a lot more than simply describing what’s below of it. The comment is doing the following things:
- It’s creating a visual boundary that aids to the readability of the code. It adds context within a large set of instructions.
- It’s providing context that might not be there for the person who is reading the code. Just because you think the name of that function and what you’re passing into it clearly describes the instruction, doesn’t mean that everyone else does. At least not in a snap. The comment helps to reduce the cognitive load of reading the code because it’s explaining the instruction in plain english.
- The comment itself could be part of a larger narrative that is trying to explain what that file is doing. It’s not there to make the obvious more obvious. It’s there to make the whole file obvious which is evidently important to write readable code.
Look. I know there are purists that get offended with obvious code comments. But you cannot expect everyone to have the same proficiency you do. Writing good code, also means writing code that a newbie can at least comprehend. And sometimes that means explaining in plain english what could be already obvious to you.
People like to think they are writing code like prose that is delightful to the reader, but many times are just writing reader hostile code.
Write code focusing on clarity. Not elegance by obscurity. Make it clear, add structure, add visual boundaries. Ask yourself if what you think is obvious is obvious to everyone. If in doubt explain it with a comment. I’ll rather see an obvious comment and say “well that’s obvious” than spending valuable time trying to understand what certain piece of code is doing.
Yeah, I like the "visual boundaries" way of framing it. A line of whitespace is a visual boundary, of course, but I find the // comment above it acts as a heading. Like a bold heading above a couple of paragraphs of text in a document.
I wouldn't add a heading above every paragraph, but I would might above every few paragraphs. In code, this translates to every 5-15 lines of code. Here's some code I wrote recently that shows this (https://github.com/canonical/pebble/blob/b152ff448bbe7d08c39...):
func writeFile(item writeFilesItem, source io.Reader) error {
if !pathpkg.IsAbs(item.Path) {
return nonAbsolutePathError(item.Path)
}
// Create parent directory if needed
if item.MakeDirs {
err := os.MkdirAll(pathpkg.Dir(item.Path), 0o755)
if err != nil {
return fmt.Errorf("cannot create directory: %w", err)
}
}
// Atomically write file content to destination.
perm, err := parsePermissions(item.Permissions, 0o644)
if err != nil {
return err
}
uid, gid, err := normalizeUidGid(item.UserID, item.GroupID, item.User, item.Group)
if err != nil {
return fmt.Errorf("cannot look up user and group: %w", err)
}
sysUid, sysGid := sys.UserID(osutil.NoChown), sys.GroupID(osutil.NoChown)
if uid != nil && gid != nil {
sysUid, sysGid = sys.UserID(*uid), sys.GroupID(*gid)
}
return atomicWriteChown(item.Path, source, perm, 0, sysUid, sysGid)
}
I know this style by the name "coding in commented paragraphs". I learned about it from Damian Conway early in my career, and the idea resonated with me so strongly that I immediately adopted it and have used it every since.
I especially appreciate how under syntax highlighting the comments provide a visually offset natural language outline or summary of the code.
i use this method too. my flow is typically to write the comments first then implement them. this makes it easy to find gaps in reasoning before i write code and then easy to extract functions after if things get mor involved than expected.
FWIW, I don't really do that. Sometimes I write the comments as I go; there is almost always a pass I make at the end as I'm preparing the material for review where I revise the existing comments and add missing ones.
I mention this because I think it's great that the commenting style works with both our workflows and doesn't really imply rigid adherence to either one of them. I suspect we would find it comfortable to maintain each others' code despite the divergence in approach.
I tried to find more information on this by searching for those terms and "damian conway" but came up empty. Would you mind sharing a link, if you have one?
"Coding in paragraphs" is described in Damian Conway's book "Perl best practices" [1]
Myself I like coding in paragraphs very much, and giving the paragraphs one-liner headings (comments) definitely makes code for me more readable and easier to navigate.
I usually don't give a heading to a "paragraph" which is just one line, unless that line does something subtle.
(Conway provides a definition of "subtle": if you need to think more than 10 seconds or consult the fine manual to figure out what the line does).
I found a page which excerpts 10 of Conway's recommendations from Perl Best Practices (which I no longer have around). Point 7 is "Code in Commented Paragraphs", and the reasoning is about what I remember — it might even be taken from the book verbatim:
There are other recommendations on that page and in Conway's highly opinionated book which I disagree with (it's notorious for recommending Class::Std), but that point has stuck with me.
Note that Conway's example even includes three single-code-line paragraphs, all of which are commented. Perhaps Osterhout would not approve, or perhaps he would — each of those lines includes some subtlety, and each of the comments describes the "why" not the "what".
FWIW I don't always add a comment above single line paragraphs if the comment adds nothing. But neither does Conway comment "unpack arguments" above the first line in his example, nor say anything about the return statement — which to my mind illustrates that there's some flexibility to be expected in how you apply this technique.
I like this a lot and I also use this. Comments as a narrative - you may quickly scan the function and read only 'headlines'-type of comments and if they are good, not too verbose, not too sparse, you have all the information needed. Combine it with a bit more descriptive summary at the beginning and and it's a pleasure to read and you immediately know where are the parts of interest. Reading pure code, even with the crazy enterprise style verbose function names is much harder and takes more time you need to waste on parts that in the end don't matter for the case you're looking for
Yup. I love this style of using code to create a "paragraph" of code with a section heading. You don't always want to separate out a function, but you do want the advantage of "titling" a section of code.
> But you cannot expect everyone to have the same proficiency you do. Writing good code, also means writing code that a newbie can at least comprehend.
If you're writing example code or documentation, by all means, add this kind of comment.
Otherwise, if someone can't comprehend these particular two lines without reading the comment, they need to get back to the drawing board. You can't have noisy code like that in production at the off-chance that somebody with zero competence needs to read it. The expected case is that a competent developer needs to find "the thing", which means they need to be able to scan the code quickly without having to read everything twice. That's what you should optimize for.
The DRY principle also applies, this sort of error is not uncommon:
// Add a vertical scroll bar
hScrollBar = new JScrollBar(JScrollBar.HORIZONTAL);
That said, I agree with both OP and GP. I personally prefer clarity take precedence over whatever code-commenting or code-formatting guidelines one might have in place. It is all about common sense, I guess.
If a sense is discussed by camps with opposing opinions, it is far from common by definition.
A better analogy I think would be legal contracts with no headers and no numbered/bullet points. Headers in bold text have no value in a court, why not simply have a few pages of plain text that is self-explanatory? Also applies to comments to laws and religious texts.
I personally prefer clarity should take precedence over
Many programming interfaces are designed with no clarity in mind, sadly. Also many processes are not so straightforward and contain intermixed logic, like (0) get a raw request (1.a) get this, (2.a) get that, (3) log raw event with half-assed data for monitoring or debugging, ((1,2).b) check both and throw, (4) combine into a task, (5) log task, (6) proceed further. This creates zigzags in the code flow that you can’t reduce by abstracting things away, because it would blow up a complexity to insane levels.
I agree that on one hand micro commenting is dumb, but if your blocks are 3-4-line long it’s usually a sign of good structure, which is unrelated to commenting itself. A good structure is self-explanatory, but its flow may be complex (ask Brooks). Human attention-switching helpers seem useful to me, because they help seeing the flow. A short comment on what’s going on is like an inlined IIFE without accompanying visual/scope clutter.
I can't think of a single benefit of a function over a comment for a piece of code that's only used once. The downsides of the function:
- Unless the function is extremely clear like max() or min(), you will have to read the function and jump around the code. I feel people underestimate the cognitive overhead required for this. This gets worse the more arguments are required to pass around. Linear code is much easier to read.
- More lines of code, a comment adds 1 line while a function or method adds at least 4. Also, code that has to be separated over multiple functions is practically always longer than linear code. Doing this to the extreme can have quite an effect.
- Naming issues, it can be hard to think of a good name for a function and it almost never describes it perfectly. Misnaming things leads to bad assumptions and leaky abstractions. Comments have the luxury of potentially conveying more information and are also less important to get right.
If you use a lot of vertical scrollbars then by all means go ahead and abstract it, but I'd generally pick the comment.
I think the most important drawback of the function for used once code is that its definition doesn't really map well to linear files.
There isn't really a proper place to put such function.
In this example, you would like to define add_vertical_scrollbar() right where it is used as this is the proper context where it makes sense to read it. But if you define it where it's used it no longer adds any clarity.
> I can’t think of a single benefit of a function over a comment for a piece of code that’s only used once.
A function makes the higher-level flow more clear and uncluttered than without either the function or comment, a comment may make it more clear but also makes it more cluttered.
> Unless the function is extremely clear like max() or min(), you will have to read the function and jump around the code. I feel people underestimate the cognitive overhead required for this. This gets worse the more arguments are required to pass around. Linear code is much easier to read.
Yes, function names should be extremely clear. But less code is easier to read than more code, so unless the contents (rather than the purpose) of the block of code is likely to be important every time you read the containing bit of code, you make the containing bit of code more readable by extracting the block.
> more lines of code, a comment adds 1 line while a function or method adds at least 4.
Depends on the language. In JS, for instance, a function definition can be a single line, so definition + call adds a minimum of 1 line, just like a comment. But a comment adds 1 line to the parent block of code, abstracting out a function at worst simplifies a single line, and usually reduces the lines of code in the parent block. So where the parent but not the content of the abstracted bit is important, it reduces code read, whereas the comment always increases it.
> Naming issues, it can be hard to think of a good name for a function and it almost never describes it perfectly.
Whether you can think of a clear name for the function is, IMO, part of the test for whether it is a logically coherent unit of work to abstract out to a function in the first place.
> Comments have the luxury of potentially conveying more information and are also less important to get right.
Comments are a pure addition of clutter to the code they are attached to; if they aren’t gotten right they are not just visual noise but misleading visual noise. Comments are no less important to get right than naming of functions.
> A function makes the higher-level flow more clear and uncluttered
It is subjective and depends on a given piece of code. In my experience functions (abstraction in general) often obfuscate code, because you cannot really see what they do without jumping to another place and breaking a flow of reading.
There are of course cases where factoring a piece of code into a function would make in more readable, but I cannot say this about any group of commented lines. And a function requires a comment too.
> In my experience functions (abstraction in general) often obfuscate code, because you cannot really see what they do without jumping to another place and breaking a flow of reading.
Already discussed in the post you are responding to: “unless the contents (rather than the purpose) of the block of code is likely to be important every time you read the containing bit of code, you make the containing bit of code more readable by extracting the block.”
> There are of course cases where factoring a piece of code into a function would make in more readable, but I cannot say this about any group of commented lines.
Also already addressed, e.g.: “Whether you can think of a clear name for the function is, IMO, part of the test for whether it is a logically coherent unit of work to abstract out to a function in the first place.”
> And a function requires a comment too.
Not necessarily. A function in the public interface of a module probably needs a doc comment or docstring, but we’re discussinf abstracting out a piece of functionality used once, so presumably this is to a private function in the same module, not part of the public interface of the module.
What border layout does that function use? Hardcoded BorderLayout.EAST, as in the code it replaces? Now the function is much more specific than the name suggests. We have a function that looks like it could be used in other parts of the code too, yet was only intended to replace one specific piece of code in one specific function. This is my main problem with extracting even small pieces of code into separate functions, especially in languages that don't have nested functions or similar facilities.
When reading the calling function, add_vertical_scrollbar() makes perfect sense. But when you read the code and you encounter add_vertical_scrollbar(), it is not at all clear where it fits in the grand scheme of things. You could of course add a comment "Meant to be called from this-or-that-function", but that kinda defeats the purpose.
We could make the function more general, which is not always as simple as in this case, but then we're doing more than just extracting some code. Or we could call the function add_vertical_scrollbar_east() instead, but that gets unwieldy pretty fast if there are more tunables.
Don't get me wrong, I do see value in extracting code into functions, and I often do exactly that. But in doing so I notice some drawbacks of that approach that IMO are not generally addressed by small-function-advocates.
In fact, coding in paragraphs and factoring chunks of code into separate functions are not mutually exclusive or contradictory; coding in paragraphs facilitates such factoring.
It's very easy to generate code that "jumbles" tasks. You set "x" coordinate of your point in one place, then do some calculations for "y" coordinate, then set it, then do something unrelated, and then set the color of your point. Such code does not make it obvious that you could create a "set_point(y, y, color)" function.
When coding in paragraphs, you organize your code into chunks so that each one "does one thing and does it well". It's natural then to consider whether a particular chunk could be a separate function.
Often it is, but sometimes it is not. First, creating a separate function has costs: choosing an appropriate name, designing an interface, etc. If the name and/or interface are not well-designed, a separate function can decrease readability: consider a function called "fix" that has 10 positional arguments...
Second, especially if your chunk is within a loop or two, the code may be too tightly coupled with the state within your code. You would need additional and perhaps modifiable "state" parameters to your function, making the resulting code more complicated and less comprehensible than the original one.
And of course, if your hypothetical function cannot be inlined (perhaps because your language does not even have the facility, or for other reasons), you would pay the price of a function call, which may matter in a tight loop.
In general it's always good to consider factoring chunks of code as a separate function, but sometimes upon this consideration you should reject it.
In my opinion there are two approaches to the idea of a function
Some use it as a method to separate code into independent, concise blocks that represent a singe step in a larger algorithm. Do one thing and do it well.
The other is code reuse, if a function is not reused in other places why create it at all? That's the reason for preferring comments rather than fragmenting code into add_vertical_scrollbar() functions. It's easier to read from top to bottom instead of jumping all over the place
It triggers half of HN because taken at extreme (abusing) this advice will render code unreadable again.
It is never clear cut. But if you never reuse the same code, or it is targeting the wrong abstraction layer (you can't reuse the function without introducing another set of arguments) and you are merely encapsulating two already very readable lines of code, you might end up with a too much redirection and an increase in mental load.
What I'm trying to say is: It might be even harder for the outsider to follow/read the code, just simpler on first glance.
If the lines of code were already very readable, they wouldn't require a comment. Comments are for interfaces or complex or inobvious code that can't easily be made more obvious simply through an expression of code IMO.
The code example of the OP is excellent because there are literally so many more avenues for code readability that are all superior to comments. First of all, the same code can literally be reused in the next line:
addScrollbar(HORIZONTAL);
addScrollbar(VERTICAL);
Do you frequently or always need to add both scrollbars to the UI? Another chance for reducing repetition and error-proneness when refactoring:
addScrollbars();
There's no increase in mental load because chances are you'll never need to navigate into these functions to know what they are doing at all, you can read over them as you would normally when you read the "This adds a horizontal scrollbar" comment, and trust that the implementation does what it says on the tin.
It seems you did not try to understand my comment:
I said "taken to the extreme". Put differently: If you are going to wrap every pair of lines into separate functions as a way to structure your code, you code will end up becoming functions calling functions calling functions - and whenever you try to read the code, you are required to jump through all these redirections.
Now instead of a bunch of prepared and labeled dishes you have an API border in the middle of your kitchen, which of course will greatly help at slight changes in the menu. When in doubt, just pepper it with more enums and boolean flags. /s
I'm all in on prettifying... If it's done automatically. I find it such a good tradeoff to not have to think about it that I'm happy to accept the times when I think my discretion would do it better.
That said, I've started to develop strong views on some eslint rules. Usually around things being errors that should be warnings in development, like logging messages or debugger or unused variables. I like to run in CI with max warnings being zero to catch them then.
The problem with this approach comes down to when the functionality of the code changes.
Consider the scenario that a second developer has to change your code in the future, for example the decision is made that a vertical scrollbar isn't required anymore and a horizontal one is required instead. It is very possible you end up a comment that completely contradicts what the code is actually doing.
// Add a vertical scroll bar
hScrollBar = new JScrollBar(JScrollBar.HORIZTONAL);
add(hScrollBar, BorderLayout.EAST);
Which suddenly ends up being far worse than having no comment at all. You could say that the second developer should be diligent and ensure they fix these, but given they don't have to change them to meet the requirements you are inviting the possibility that it could happen.
I never understood this line of reasoning. If you're changing a line of code you should at least look at the whole function that contains it, and update the comments according to the change.
> given they don't have to change them to meet the requirements
What are the requirements? Code review is standard practice, so I see clear code as part of the requirements.
Developers and code reviewers make mistakes. If a change involves looking at code numerous trivial comments, the probability that both the developer and the reviewers misses at least one increases.
This is particularly problematic for this sort of issue because it's impossible for a static analysis tool to pick up.
I disagree that it "ends being far worse than having no comments at all". I think it's still helpful:
1) still provides the visual boundary for the following chunk of code
2) still tells me that the code has something to do with scroll bars
As they say, "Documentation is like sex: when it is good, it is very, very good; and when it is bad, it is better than nothing" :-)
Yeah, it could trick me, but this is no different to any other types of sloppiness.
Should we stop giving meaningful names to variables and functions, because some developer can change what a function does, or variable represents, but fail to update the names to reflect the fact?
Most of the time when I have to look at the code with out of date comments, after paying some initial "tax" of confusion and lost time, it becomes clear that the comment is out of date(git helps with this, too). So I take time and fix the comment. Or in rare cases when I still don't understand what the comment should say, I put a "FIXME: out of date comment" above the offending line.
One thing I give you: when reading code, I find comments explaining why the code does X, more useful than comments stating the the code does X...
I do this all the time as well ever since I knew about this technique.
For me, another giant benefit is that I can scan through the function and understand what it does just by reading the comment. The energy saved by those comments can be spent on writing more code :)
I tend to agree that sections headings are OK (although it might be better to extract the sections to separate functions) but some people take it further than that.
> Does it look wasteful to say that you’re adding a vertical bar when you have the following line of code?
It is, if the alternative is to just refactor your code to extract a method so that it reads like:
addVerticalScrollBar();
No comment, cleaner code, smaller functions/methods, everything is easier to read and follow and reason about.
The main problem with your suggestion is that you're wasting precious space to say the same thing twice.
More importantly, your comments adds nothing important or relevant to those reading the code, such as why did you felt the need to add a vertical scrollbars to begin with. That's the sort of thing that's relevant to those reading the code.
Then these two lines of code move away from the context of the surrounding function. There may also be something specific about how the scrollbar is added in this instance, requiring a very complicated name if you want to convey that.
As long as the surrounding function is not overly complex, I don't see the advantage of having to jump back and forth from mini function to mini function to understand what the code is doing.
Yes, this is bad. If it was not clear enough that this created a VScrollBar, then create a function CreateVerticalScrollBar(). Why? Well this does exactly the same as commenting the code, except that this can easily be renamed with your IDE if the code base changes. Comments will always be outdated, functions not.
- It’s creating a visual boundary that aids to the readability of the code. It adds context within a large set of instructions.
- It’s providing context that might not be there for the person who is reading the code. Just because you think the name of that function and what you’re passing into it clearly describes the instruction, doesn’t mean that everyone else does. At least not in a snap. The comment helps to reduce the cognitive load of reading the code because it’s explaining the instruction in plain english.
- The comment itself could be part of a larger narrative that is trying to explain what that file is doing. It’s not there to make the obvious more obvious. It’s there to make the whole file obvious which is evidently important to write readable code.
Look. I know there are purists that get offended with obvious code comments. But you cannot expect everyone to have the same proficiency you do. Writing good code, also means writing code that a newbie can at least comprehend. And sometimes that means explaining in plain english what could be already obvious to you.
People like to think they are writing code like prose that is delightful to the reader, but many times are just writing reader hostile code.
Write code focusing on clarity. Not elegance by obscurity. Make it clear, add structure, add visual boundaries. Ask yourself if what you think is obvious is obvious to everyone. If in doubt explain it with a comment. I’ll rather see an obvious comment and say “well that’s obvious” than spending valuable time trying to understand what certain piece of code is doing.