I feel like no one serious uses the uncle Bob style of programming anymore (where each line is extracted into its own method). This was a thing for a while but anyone who's tried to fix bugs in a codebase like that knows exactly what this article is talking about. It's a constant frustration of pressing the "go to definition" key over and over, and going back and forth between separate pieces that run in sequence.
I don't know how that book ever got as big as it did, all you have to do is try it to know that it's very annoying and does not help readability at all.
I can assure you that I am very serious and I do cut things up almost as finely as Uncle Bob suggests. Where others balk at the suggestion that a function or method should never expand past 20 or so lines, I struggle to imagine a way I could ever justify having something that long in my own code.
But I definitely don't go about it the same way. Mr. Martin honestly just doesn't seem very good at implementing his ideas and getting the benefits that he anticipates from them. I think the core of this is that he doesn't appreciate how complex it is to create a class, at all, in the first place. (Especially when it doesn't model anything coherent or intuitive. But as Jeffries' Sudoku experience shows, also when it mistakenly models an object from the problem domain that is not especially relevant to the solution domain.)
The bit about parameters is also nonsense; pulling state from an implicit this-object is clearly worse than having it explicitly passed in, and is only pretending to have reduced dependencies. Similarly, in terms of cleanliness, mutating the this-object's state is worse than mutating a parameter, which of course is worse than returning a value. It's the sort of thing that you do as a concession to optimization, in languages (like, not Haskell family) where you pay a steep cost for repeatedly creating similar objects that have a lot of state information in common but can't actually share it.
As for single-line functions, I've found that usually it's better to inline them on a separate line, and name the result. The name for that value is about as... valuable as a function name would be. But there are always exceptions, I feel.
The Ruby ecosystem was particularly bad about "DRY"(vs WET) and indirection back in the day.
Things were pretty dire until Sandi Metz introduced Ruby developers to the rest of the programming world with "Practical Object-Oriented Design". I think that helped start a movement away from "clever", "artisanal", and "elegant" and towards more practicality that favors the future programmer.
Does anyone remember debugging Ruby code where lines in stack traces don't exist because the code was dynamically generated at run time to reduce boilerplate? Pepperidge Farm remembers.
I agree wholeheartedly - I've fought against refactors like this for the same reason. In the end readability is king, and jumping through 6 methods instead of one decreases readability quite a bit.
Sometimes I want to use comments because I'm doing something vaguely algorithmic, and I know some readers won't follow the code.
I'm trying to think of a good example, maybe something like a pointer window based function (off the top of my head)
(This isn't real code. Don't get hung up on it)
func DedupeStrings(ss []string) []string {
if len(ss) < 2 {
return ss
}
ss = strings.Sort(ss)
u := 1 // index of end of "uniques" set
for i := 1; i < len(ss); i++ {
// Consume until new value
if ss[i] == ss[i-1] {
continue
}
// Put new value in 'uniques' set
ss[u] = sorted[i]
u++
}
// Final state: all unique items are positioned
// left of 'u' index
return ss[:u]
}
People will quibble, but
- I'm not convinced you could change the variable names without harming clarity. Would a name like uniquesEndIndex really be any clearer? It adds noise to the code and still doesn't satisfy a confused reader
- I don't want to use function calls for documentation, eg putInUniques(). I'm doing it this way because I want it to run really quick.
Isn’t the purpose of comments to make code understandable?
If that needs a why it’s a why-comment.
If it needs a what it’s a what-comment. Especially if clever programming tricks are used. 6 month later you already forgot what the trick is and how it works.
These examples could both be much better IMHO with a top comment block that describes the purpose of the functionality and shows good usage examples. Something like this below, and ideally using runnable doc comments to help keep the comment correctly explaining the code.
Replace symbol placeholders in the input string with translated values. Scan the string for symbol placeholders that use the format "$foo". The format uses a dollar sign, then optional ASCII letter, then optional word characters. Each recognized symbol is replaced with its corresponding value.
Symbols are only replaced if the symbol exists i.e. getSymbol(String) returns non-null, and the symbol has not already been replaced in this invocation.
Example:
- input = "Hello $name, welcome to $city!"
- output -> "Hello Alice, welcome to Boston!"
Return the string with symbol placeholders replaced.
I'm a data scientist and a lot of my R code are dplyr-chains a la data |> select(features) |> filter(low_quality) |> mutate(feature=...).
It just saves time to comment on what those chains do instead of having go through them every time I want to change something.
> The Uncle Bob approach of extractNameMatchingTransfersWithinSettlementWindow() doesn't actually help - now I need to know what settlement windows are anyway, and I've lost the context of why 3 days.
When the name is long, it should be more than one function. Finding the transactions within a settlement window seems distinct from matching them up.
The comment about 3 days isn't necessary when "numDays" is a variable in the SettlementWindow class (or other relevant scope). I'd be a lot more comfortable reading code that makes this concept a proper abstraction rather than just seeing a comment about a hard-coded 3 somewhere. Magic values are bad and comments don't demystify anything if they get out of sync with the code.
Should stick a (2017) on there as this is almost a decade old.
Comments should explain. If it’s why, it’s why. If it’s what, it’s what. The point of comments is to explain, to the reader/editor, what the intent of the code is and how you chose to approach it. That’s it. No need to fight over what, why, how, who, etc.
If you have domain knowledge that helps support the code, maybe a link to further reading, by all means add it to comments please!
The article is about comments. But, more generally, I think the issue here is about naming things.
Names capture ideas. Only if we name something can we (or at least I) reason about it. The more clear and descriptive a name for something is, the less cognitive load is required to include the thing in that reasoning.
TFA's example that "weight" is a better variable name than "w" is because "weight" immediately has a meaning while use of "w" requires me to carry around the cumbersome "w is weight" whenever I see or think about "w".
Function names serve the same purpose as variable names but for operations instead of data.
Of course, with naming, context matters and defining functions adds lines of code which adds complexity. As does defining overly verbose variable names: "the_weight_of_the_red_ball" instead of "weight". So, some balance that takes into account the context is needed and perhaps there is some art in finding that balance.
Comments, then, provide a useful intermediate on a spectrum between function-heavy "Uncle Bob" style and function-less "stream of consciousness" style.
the first time you write something, descriptive names are handy, but if youre writing a second or thrid copy, or trying to combine several back down into one, those names are all baggage and come with a full mental model.
an alternative ive seen work well is names that arent descriptive on their own, but are unique and memorable, and can be looked up from a dictionary
I'm all against commenting gatekeeping. IMO this is an anti-pattern.
Even in stupidest cases, like:
// add two and two
let four = two + two
The rationale for this is that if you start to mute developer on case of "this is not comment-worthy" you start to actually losing context for the codebase. Sure, maybe "add two and two" is not contextful enough, but "add same account twice" might be signal for appropriate code.
Maybe that's me, but I rarely saw teams which over-document, under-documenting is usually the case. So if we ever meet in professional environment - comment away. Worst case scenario - I'll skim over it.
Here's a thought, and I'm just spitballin' here: maybe they could do both? Sure, writing an encyclopedic tome about three lines of code is probably a bit out of bounds, but overexplaining is only bad when it's your wife or girlfriend. It might be a good idea to be absolutely clear about things.
While it leads to more things to read, which thus may
take time, I feel that the benefits of using comments
far outweighs the negative sides. This is even valid
when comments are outdated usually. To me, adjusting
and updating comments often was much easier and faster
than describing something de-novo.
In the ruby land this is quite problematic because many
do not use any comments. The result is a horrible code
base. Getting people who wrote that code to use comments
is often too late, as they already abandoned ruby in favour
of another language, so I never buy the cop-out explanation
of "the code is self-explanatory" - not even in ruby it is.
Everyone can write horrible code.
I also find that phrase super misleading. I've been using a different heuristic that seems to work better for me - "comments should add relevant information that is missing." This works against redundant comments but also isn't ambigous about what "why" means.
There might be a better one that also takes into account whether the code does something weird or unexpected for the reader (like the duplicate clear call from the article).
People (and I'm one of them) usually say why instead of what because in general the what can be understood if you can read code. But obviously no hard rules, if I write something twisted because I had no idea how to do it better or had the time, then I'll write what it does. It's not perfect, but nothing is.
Same goes for comments vs commit messages. It's a fact comments get outdated and then you have an even bigger problem whereas a commit message is always correct at the time it was made. But obviously again, no hard rules. Sometimes I feel it's better to write a comment, e.g. if it's something that is really important and won't change a lot.
I have changed my commenting style now, since the rise of LLMs.
I used to comment in a similar way to claude/chatgpt, as in both the what and why. (although the why in LLMs is often lost. ) I used to comment as if it was for someone who didn't know the libraries very well. (ie me in two years time. Documentation at the previous place was utterly shite, so it was effectively a journal of discovery)
However, my commenting style is both what and why. My variable names can be longer than my FANG peers, mainly because I know that English not being a first language means that half arsed abbreviations are difficult to parse.
But effort is placed on not using fancy features, python mostly, so avoiding lambdas and other "syntactic sugar", unless they absolutely make sense. If I do use them, i have a comment saying _why_ I'm doing it.
Some of my colleagues were dead against any kind of commenting. "my code should be readable enough without it". They had the luxury of working on one bit for a few months, then throwing it away.
My favorite genre of comments: it warns you not to touch it, and if you do, you must add to the list under the comment how many hours you wasted and got nowhere.
IMO the example shows exactly that splitting code in smaller pieces is way better than just commenting it.
It makes it easier for dev's brain to parse the code e.g. to understand what code really does, while fattier but commented version makes it harder but tries to replace it with information about original coder's intentions. Which is maybe important too but not as important as code itself.
Not to forget that it's too easy to change code and leave comments obsolete.
I do not agree; I think the example that is not split is clearer and does not need comments to explain it (the variable names are helpful, although even if local variables use only one letter it still seems like clearly enough; for variables that are not local to that function, it does help to have a more descriptive names to understand them better). Comments and splitting can both be helpful in some circumstances, but neither is helpful for this one.
65 comments
[ 2.1 ms ] story [ 85.7 ms ] threadI don't know how that book ever got as big as it did, all you have to do is try it to know that it's very annoying and does not help readability at all.
But I definitely don't go about it the same way. Mr. Martin honestly just doesn't seem very good at implementing his ideas and getting the benefits that he anticipates from them. I think the core of this is that he doesn't appreciate how complex it is to create a class, at all, in the first place. (Especially when it doesn't model anything coherent or intuitive. But as Jeffries' Sudoku experience shows, also when it mistakenly models an object from the problem domain that is not especially relevant to the solution domain.)
The bit about parameters is also nonsense; pulling state from an implicit this-object is clearly worse than having it explicitly passed in, and is only pretending to have reduced dependencies. Similarly, in terms of cleanliness, mutating the this-object's state is worse than mutating a parameter, which of course is worse than returning a value. It's the sort of thing that you do as a concession to optimization, in languages (like, not Haskell family) where you pay a steep cost for repeatedly creating similar objects that have a lot of state information in common but can't actually share it.
As for single-line functions, I've found that usually it's better to inline them on a separate line, and name the result. The name for that value is about as... valuable as a function name would be. But there are always exceptions, I feel.
Things were pretty dire until Sandi Metz introduced Ruby developers to the rest of the programming world with "Practical Object-Oriented Design". I think that helped start a movement away from "clever", "artisanal", and "elegant" and towards more practicality that favors the future programmer.
Does anyone remember debugging Ruby code where lines in stack traces don't exist because the code was dynamically generated at run time to reduce boilerplate? Pepperidge Farm remembers.
I'm trying to think of a good example, maybe something like a pointer window based function (off the top of my head)
(This isn't real code. Don't get hung up on it)
People will quibble, but- I'm not convinced you could change the variable names without harming clarity. Would a name like uniquesEndIndex really be any clearer? It adds noise to the code and still doesn't satisfy a confused reader
- I don't want to use function calls for documentation, eg putInUniques(). I'm doing it this way because I want it to run really quick.
If that needs a why it’s a why-comment.
If it needs a what it’s a what-comment. Especially if clever programming tricks are used. 6 month later you already forgot what the trick is and how it works.
Replace symbol placeholders in the input string with translated values. Scan the string for symbol placeholders that use the format "$foo". The format uses a dollar sign, then optional ASCII letter, then optional word characters. Each recognized symbol is replaced with its corresponding value.
Symbols are only replaced if the symbol exists i.e. getSymbol(String) returns non-null, and the symbol has not already been replaced in this invocation.
Example:
Return the string with symbol placeholders replaced.If the comment was
the comment is more evergreen, until the actual logic changes. If/when the logic changes... update the comment?Sure it does. You've isolated the "what" to the name, and the comments can focus on the "why".
(Although I think some of the XP advocates would go off the rails and try to model a settlement window as an object....)
When the name is long, it should be more than one function. Finding the transactions within a settlement window seems distinct from matching them up.
The comment about 3 days isn't necessary when "numDays" is a variable in the SettlementWindow class (or other relevant scope). I'd be a lot more comfortable reading code that makes this concept a proper abstraction rather than just seeing a comment about a hard-coded 3 somewhere. Magic values are bad and comments don't demystify anything if they get out of sync with the code.
Comments should explain. If it’s why, it’s why. If it’s what, it’s what. The point of comments is to explain, to the reader/editor, what the intent of the code is and how you chose to approach it. That’s it. No need to fight over what, why, how, who, etc.
If you have domain knowledge that helps support the code, maybe a link to further reading, by all means add it to comments please!
Change my mind!
Names capture ideas. Only if we name something can we (or at least I) reason about it. The more clear and descriptive a name for something is, the less cognitive load is required to include the thing in that reasoning.
TFA's example that "weight" is a better variable name than "w" is because "weight" immediately has a meaning while use of "w" requires me to carry around the cumbersome "w is weight" whenever I see or think about "w".
Function names serve the same purpose as variable names but for operations instead of data.
Of course, with naming, context matters and defining functions adds lines of code which adds complexity. As does defining overly verbose variable names: "the_weight_of_the_red_ball" instead of "weight". So, some balance that takes into account the context is needed and perhaps there is some art in finding that balance.
Comments, then, provide a useful intermediate on a spectrum between function-heavy "Uncle Bob" style and function-less "stream of consciousness" style.
an alternative ive seen work well is names that arent descriptive on their own, but are unique and memorable, and can be looked up from a dictionary
Even in stupidest cases, like:
The rationale for this is that if you start to mute developer on case of "this is not comment-worthy" you start to actually losing context for the codebase. Sure, maybe "add two and two" is not contextful enough, but "add same account twice" might be signal for appropriate code.Maybe that's me, but I rarely saw teams which over-document, under-documenting is usually the case. So if we ever meet in professional environment - comment away. Worst case scenario - I'll skim over it.
While it leads to more things to read, which thus may take time, I feel that the benefits of using comments far outweighs the negative sides. This is even valid when comments are outdated usually. To me, adjusting and updating comments often was much easier and faster than describing something de-novo.
In the ruby land this is quite problematic because many do not use any comments. The result is a horrible code base. Getting people who wrote that code to use comments is often too late, as they already abandoned ruby in favour of another language, so I never buy the cop-out explanation of "the code is self-explanatory" - not even in ruby it is. Everyone can write horrible code.
For example, for some reason padding bytes are allowed in variable length integers (LEB128) for reasons I still do not understand:
He starts implementing any module or function by first writing the documentation for it and let's it guide both the functionality and structure.
Makes Redis also extremely easy and enjoyable to read.
Random example: https://github.com/redis/redis/blob/unstable/src/aof.c
There might be a better one that also takes into account whether the code does something weird or unexpected for the reader (like the duplicate clear call from the article).
Same goes for comments vs commit messages. It's a fact comments get outdated and then you have an even bigger problem whereas a commit message is always correct at the time it was made. But obviously again, no hard rules. Sometimes I feel it's better to write a comment, e.g. if it's something that is really important and won't change a lot.
I used to comment in a similar way to claude/chatgpt, as in both the what and why. (although the why in LLMs is often lost. ) I used to comment as if it was for someone who didn't know the libraries very well. (ie me in two years time. Documentation at the previous place was utterly shite, so it was effectively a journal of discovery)
However, my commenting style is both what and why. My variable names can be longer than my FANG peers, mainly because I know that English not being a first language means that half arsed abbreviations are difficult to parse.
But effort is placed on not using fancy features, python mostly, so avoiding lambdas and other "syntactic sugar", unless they absolutely make sense. If I do use them, i have a comment saying _why_ I'm doing it.
Some of my colleagues were dead against any kind of commenting. "my code should be readable enough without it". They had the luxury of working on one bit for a few months, then throwing it away.
It makes it easier for dev's brain to parse the code e.g. to understand what code really does, while fattier but commented version makes it harder but tries to replace it with information about original coder's intentions. Which is maybe important too but not as important as code itself.
Not to forget that it's too easy to change code and leave comments obsolete.