9 ms·
Maybe comments should explain 'what' (2017)
- awesan 10mo agoI 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.
- ngruhn 10mo agoI know plenty of Java/C# developers who still suffer from this mind virus ;P
- embedding-shape 10mo ago> where each line is extracted into its own method Never heard of "that style of programming" before, and I certainly know that Uncle Bob never adviced people to break down their programs so each line has it's own method/function. Are you perhaps mixing this with someone else?
- troupo 10mo ago> I certainly know that Uncle Bob never adviced people to break down their programs so each line has it's own method/function There's a literal link to a literal Uncle Bob post by the literal Uncle Bob from which the code has been taken verbatim.
- eterm 10mo agoThis is from page 37 of Clean Code: > Even a switch statement with only two cases is larger than I'd like a single block or function to be. His advice that follows, to leverage polymorphism to avoid switch statements isn't bad per-se, but his reasoning, that 6 lines is too long, was a reflection of his desire to get every function as short as possible. In his own words, ( page 34 ): > [functions] should be small. They should be smaller than that. That is not an assertion I can justify. He then advocates for functions to be 2-3 lines each.
- embedding-shape 10mo ago> to leverage polymorphism to avoid switch statements [...] was a reflection of his desire to get every function as short as possible. That's both true, but long way away from "every line should have it's own method", but I guess parent exaggerated for effect and I misunderstood them, I took it literally when I shouldn't.
- eterm 10mo agoI've edited my comment to add more context to that quote. He absolutely advocated for the most minimal of function lengths, beyond what is reasonable.
- zephen 10mo agoHe has expressed admiration for lisp, and he comes from a time before IDEs. These may color his desired level of complexity.
- xlii 10mo agoHaskell enters the chat Haskell (and OCaml I suppose two) are outliers though as one is supposed to have a small functions for single case. It's also super easy to find them and haskell-language-server can even suggest which functions you want based on signatures you have. But in other languages I agree - it's abomination and actually hurt developers with lower working memory (e.g. neuroatypical ones).
- danielscrubs 10mo agoIt’s because maths are the ultimate abstraction. It’s timeless and corner cases (almost) fully understood. Ok maybe not, but at least relative to whatever JavaScript developers are reinventing for the thousand time.
- Scarblac 10mo agoTurns out writing a book and getting it published with the title "Clean Code" is great marketing. I have had so many discussions about that style where I tried to argue it wasn't actually simpler and the other side just pointed at the book.
- __s 10mo agoCult think loves a tome
- hamdingers 10mo ago> and the other side just pointed at the book One of the most infuriating categories of engineers to work with is the one who's always citing books in code review. It's effectively effort amplification as a defense mechanism, now instead of having a discussion with you I have to go read a book first. No thanks. I do not give a shit that this practice is in a book written by some well respected whoever, if you can't explain why you think it applies here then I'm not going to approve your PR.
- rasmus-kirk 10mo agoYeah, and any of these philosophies are always terrible when you take them to their limit. The ideas are always good in principle and built on a nugget of truth, it's when people take it as gospel I have a problem. If they just read the book and drew inspiration for alternative, possibly better, coding styles and could argue their case that's unequivocally good.
- bluecalm 10mo agoIt's like with goto. Goto is useful and readable in quite a few situations but people will write arrow like if/else tree with 8 levels of indentation just to avoid it because someone somewhere said goto is evil.
- Scarblac 10mo agoA colleague recently added a linter rule against nested ternary statements. OK, I can see how those can be confusing, and there's probably a reason why that rule is an option. Then replaced a pretty simple one with an anonymous immediately invoked function that contained a switch statement with a return for each case. Um, can I have a linter rule against that?
- zimpenfish 10mo ago> I feel like no one serious uses the uncle Bob style of programming anymore (where each line is extracted into its own method) Alas, there's a lot of Go people who enjoy that kind of thing (flashback to when I was looking at an interface calling an interface calling an interface calling an interface through 8 files ... which ended up in basically "set this cipher key" and y'know, it could just have been at the top.)
- mannykannot 10mo agoHardcore proponents of this style often incant 'DRY' and talk about reuse, but in most cases, this reuse seems to be much more made available in principle than found useful in practice.
- zimpenfish 10mo agoThere's also the "it makes testing easier because you can just swap in another interface and you don't need mocks" argument - sure but half of the stuff I find like this doesn't even have tests and you still tend to need mocks for a whole bunch of other cases anyway.
- falcor84 10mo agoI wonder how hard it would be to build an IDE "lens" extension that would automatically show you a recursively inlined version of the function you're hovering over when feasible and e.g. shorter than 20 lines.
- falcor84 10mo agoI wonder how hard it would be to have an IDE extension that would automatically show you a recursively inlined version of the function you're hovering over when feasible and e.g. shorter than 20 lines.
- WillAdams 10mo agoFor an example of what happens when he runs into a real programmer see: https://github.com/johnousterhout/aposd-vs-clean-code https://github.com/johnousterhout/aposd-vs-clean-code _A Philosophy of Software Design_ is an amazing and under-rated book: https://www.goodreads.com/en/book/show/39996759-a-philosophy-of-software-design https://www.goodreads.com/en/book/show/39996759-a-philosophy... and one which I highly recommend and which markedly improved my code --- the other book made me question my boss's competence when it showed up on his desk, but then it was placed under a monitor as a riser which reflected his opinion of it....
- onionisafruit 10mo agoEach page in that book serves its purpose. That purpose is raising the monitor 0.1mm.
- hyperman1 10mo agoThat's a really interesting read. I felt myself being closer to John about the small method part, but closer to UB for the TDD part, even if in both cases I was somewhere inbetween. At the very least, you convinced me to add John's book to my ever-growing reading list.
- jcranmer 10mo agoThat entire conversation on comments is just wildly insane. Uncle Bob outright admits that he couldn't understand the code he had written when he looked back on it for the discussion, which should be an automatic failure. But he tries to justify the failure as merely the algorithm just being sooooo complex there's no way it can be done simply. (Which, compared to the numerics routines I've been staring out, no, this is among the easiest kind of algorithm to understand)
- tptacek 10mo agoThe whole thing is really uncomfortable; it's as if, after attempting the sudoku solver, Ron Jeffries sat down for a discussion with Peter Norvig, who was not especially diplomatic about the outcome of the experiment. The section before that, where they're talking about the "decomposition" of Knuth's simple primality tester, is brutal. "Looping over odd numbers is one concern; determining primality is another".
- lucketone 10mo agoIt was written in different times, different audiences. (When variable names t,p,lu were the norm) It was useful for me and many others, though I never took such (any?) advice literally (even if the author meant it) Based on other books, discussions, advice and experience, I choose to remember (tell colleagues) it as “long(e.g. multipage) functions are bad”. I assume CS graduates know better now, because it became common knowledge in the field.
- ekjhgkejhgk 10mo agoGreat, that's exactly how I feel with any style that demands "each class in its own file" or "each function in its own file" or whatever. I'd rather have everything I need in front of my eyes as much as possible, rather than have it all over the place just to conform with an arbitrary requirement. I said this at a company I worked and got made fun of because "it's so much more organized". My take away is that the average person has zero ability to think critically.
- frozenlettuce 10mo agoIf those demands made any sense they would be enforced by the languages themselves. It's mostly a way of claiming to be productive by renaming constants and moving code around.
- TZubiri 10mo agoIt's an extremism to get a strong reaction, but the takeaway is that you should aim when possible to make the code understandable without comments, and that a good programmer can make code more understandable than a newbie with comments. But of course understandeable code with comments simply has much more bandwidth of expression so it will get the best of both worlds. I see writing commentless code like practicing playing piano only with your left hand, it's a showoff and you can get fascinatingly close to the original piece (See Godowsky's Chopin adaptations for the left hand), but of course when you are done showing off, you will play with both hands.
- jffhn 10mo ago>where each line is extracted into its own method As John Carmack said: "if a lot of operations are supposed to happen in a sequential fashion, their code should follow sequentially" (https://cbarrete.com/carmack.html https://cbarrete.com/carmack.html). A single method with a few lines is easy to read, like the processor reading a single cache line, while having to jump around between methods is distracting and slow, like the processor having to read various RAM locations. Depending on the language you can also have very good reasons to have many lines, for example in Java a method can't return multiple primitive values, so if you want to stick to primitives for performances you inline it and use curly braces to limit the scope of its internals.
- zahlman 10mo agoI 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.
- Rapzid 10mo agoThe 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.
- rokkamokka 10mo agoI 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.
- eterm 10mo agoI wouldn't take the examples from Bob Martin as gospel, see also: "Don't refactor like Uncle Bob": https://theaxolot.wordpress.com/2024/05/08/dont-refactor-like-uncle-bob-please/ https://theaxolot.wordpress.com/2024/05/08/dont-refactor-lik...
- JanNash 10mo agoThank you! Taking that refactoring advice at face value when I still was quite junior led to me writing an immensely over-abstracted framework that bit me in the butt for years afterwards when trying to debug or add a new feature. Wasn't easy to unlearn...
- rasmus-kirk 10mo agoThank God you did. I hear of seniors who still do this shit with no real sense of style because they just assume that over-abstracting everything is always the correct thing to do. You're learning, not unlearning!
- zero_shift 10mo agoSometimes 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.
- toxik 10mo agoI'll be honest, this code is easier to read for me without the comments. Also sorting feels like it's going to be slower than having some kind of set structure? You don't need ordering, just collocation of duplicates. If not or if it's a wash, that is also a good thing to comment. Also I'm not sure about the semantics of Go but it seems this mutates the argument AND returns a value, something I consider dangerous. Otherwise I agree, people have a weird hang up about short variable names. Somehow not a problem in mathematics...
- zero_shift 10mo agoThere's probably a better example. The point is sometimes the What needs explanation, and finding a better What isn't practical. I have slightly unorthodox opinions about short variables. I used to hate them. Then I posted a question on one of the PL design forums - it might have been Reddit r/programminglanguages - why is there are history of single letter names for type variables? ie T, U, etc for generics. The answer I got back, was, sometimes you want code to focus on structure rather than identities. That stuck with me, because it helped me understand why so much C code (including Linux) code uses similar naming practices. Names can lie, and sometimes expressing the structure is the absolute critical thing.
- croes 10mo agoIsn’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.
- zero_shift 10mo agoYes, but some people have a Jihad against "what" comments - believing these should be elevated into the structure of the code itself, eg veryVerbosePainfullySpelledOutVariableNames or thisFunctionNameExplainsAWholeDomainConcept()
- croes 10mo agoI would like to see their version of https://en.wikipedia.org/wiki/Fast_inverse_square_root https://en.wikipedia.org/wiki/Fast_inverse_square_root
- Scarblac 10mo agoYes, but there are other ways to make it more understandable (like good names, idiomatic code, units that are neither too long nor too short) that are often preferable, because comments always have a danger of going out of sync with the code. The "why" is the part of the explanation that can't be deduced from the code.
- croes 10mo agoSometimes good names etc. don’t help. Think of this https://en.wikipedia.org/wiki/Fast_inverse_square_root https://en.wikipedia.org/wiki/Fast_inverse_square_root That definitely needs a what comment.
- Scarblac 10mo agoYes of course, sometimes there is no understandable way to write the code and comments are the best way. But there's also the case to be made that the comments that particular code needs are "why" comments. I can see what happens, but why does it work?
- jph 10mo agoThese 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.
- Signez 10mo agoI think the argument against this kind of top comments is that it makes easier to forget to update them if you change the code it refers to. A single line comment is easy to parse, read and spot as having to be changed when you patch something.
- aquafox 10mo agoI'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.
- jackfranklyn 10mo ago[flagged]
- coffeebeqn 10mo agoI also do that but I’d argue that business rules/quirks count as a “why”
- troupo 10mo agoThey are often a "why" in the form of "what the hell" :)
- gbin 10mo agoI agree it is about "what the code is technically doing" vs why which is "what external factor you need to understand to read this"
- mashally 10mo agoI agree with that as well, the "why" explains the business logic and that makes much more sense to me. Otherwise, I might end up explaining the algorithm (the "what") instead of the business behind it.
- bostik 10mo agoWhile I agree, I think that's still incomplete. To me good comments have always been about "what is being done AND why". Or to put it another way: to provide the necessary context for figuring out why a particular piece of code is written the way it's done. (Or what it's supposed to do.)
- wizzwizz4 10mo ago> and that's the tolerance banks allow for settlement delays This is missing from your comment. While I'd probably be able to pick that up from context, I'd have to hold "this is an assumption I'm making" in my head, and test that assumption against all parts of the codebase I encounter until I become certain: this'd slow me down. I'd recommend including the "why" explicitly.
- 10mo ago
- reactordev 10mo agoShould 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!
- meindnoch 10mo ago"Uncle Bob" is a bad programmer, simple as. Change my mind!
- michaelcampbell 10mo agoDid he actually ever write anything substantial, other than that book?
- meindnoch 10mo agoNone which I'm aware of.
- xorcist 10mo agoHe was very successful in establishing that nickname.
- frumiousirc 10mo agoThe 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.
- 8note 10mo agothe 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
- xlii 10mo agoI'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.
- panstromek 10mo ago> Maybe that's me, but I rarely saw teams which over-document, under-documenting is usually the case. This is a good point, although this recently changed with LLMs, which often spit out a ton of redundant comments by default.
- chuckadams 10mo agoClaude Code in particular seems to use very few redundant comments. That or it's just better at obeying the standing instruction I give it to not create them, something other assistants seem to blithely ignore.
- drivingmenuts 10mo agoHere'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.
- mgaunard 10mo agobug workarounds don't need to be justified if instead of working around the bug you just fix it instead.
- panstromek 10mo agoSure, but you can't always fix the bug if it's not in your system.
- mgaunard 10mo agoFork it, you should have ownership of your whole stack. If you have the spare time, you can try and submit your patches upstream; in the meantime, you just maintain your own version.
- panstromek 10mo agoNo, you can't always do that. We have workarounds for platform bugs that were even fixed, because we get users with old devices that can't upgrade. You cannot fork a phone of a random person on the other side of the world. Once a platform bug is out, it can stay out in the wild for a very long time.
- mgaunard 10mo agoDeploy your own platform -- if need be built on top of other (unreliable) platforms.
- panstromek 10mo agoOur website codebase contains a workaround for a bug in native Android file picker in Samsung One UI. How are you supposed to solve this by "deploying your own platform?"
- mgaunard 10mo agoBy decoupling your application logic from your UI toolkit.
- oweiler 10mo agoHis first "what" example actually explains "why" clear is called two times. The code perfectly captures the "what".
- Scarblac 10mo agoNowadays my rule is "the comment should contain enough about the why and the how so that the LLM generates the next few lines mostly correctly."
- shevy-java 10mo agoI use a lot of comments, for many reasons. 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.
- willtemperley 10mo agoI like to see comments for external information that can't be derived from the code or API documentation. For example, for some reason padding bytes are allowed in variable length integers (LEB128) for reasons I still do not understand: // Allow padding bytes that do not affect the value let expectedBits: UInt8 = (result < 0) ? 0x7F : 0x00
- epolanski 10mo agoI like antirez' style of comments. 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 https://github.com/redis/redis/blob/unstable/src/aof.c
- boredemployee 10mo agoReally nice, we would have a better world if comments like that were the standard.
- CuriouslyC 10mo agoThis is a great practice. I used to do it when writing complex algorithms, I'd do pseudo-code comments to outline functionality, then basically implement the comments line by line. Now I sometimes use this practice when working with agents, if I need something done just a certain way. It's time consuming, but it produces good results.
- panstromek 10mo agoI 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).
- matt_kantor 10mo agoI like this framing, but might add to it: "comments should add relevant information that is missing and which can't easily be added by refactoring the code".
- rossant 10mo agoI like to use comments extensively, even if it’s just to visually separate blocks of code in my IDE. A bit more spacing simply feels cleaner to me.
- matt_kantor 10mo agoWhy not use blank lines? (I suspect the answer might be "my code formatter deletes them", which is a damn shame.)
- bonesss 10mo ago.Net languages have ‘regions’ that can collapse & nest, providing a high level narrative of file organization (and, in practice, display most files in a clean collapsed view).
- zahlman 10mo agoI used to do that (and it's part of why I put a double line between functions and classes). But over time I started to feel like those were the points where I should just refactor. (If the function can't be cleanly cut at those joints, in turn, I take that as a sign that the logic needs to be disentangled first.)
- rossant 10mo agoI do it, but I also like to use headers.
- locallost 10mo agoPeople (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.
- KaiserPro 10mo agoI 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.
- DiabloD3 10mo agoMy 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.
- broken-kebab 10mo agoIMO 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.
- BoorishBears 10mo agoSplitting example is way too much indirection, but capturing what the code does in the code itself is a preference for me. In any high level language don't know why the middleground wasn't explored: var hasSymbol = getSymbol(symbolName) != null var replacementPending = !alreadyReplaced.contains(symbolName) if(hasSymbol && replacementPending){ alreadyReplaced.add(symbolName); stringToReplace = stringToReplace.replace("$" + symbolName, translate(symbolName)); } Technically this performs worse because you lose short-circuiting, but in performance-sensitive contexts code styling is less a concern anyways. And I also wouldn't rely on short-cutting alone to avoid a really expensive operation or side-effect: at some point someone will fail to notice it.
- 1718627440 10mo agoAs for the comments, I would probably write it like this: /* Symbol actually exists */ if ((NULL != getSymbol (symbolName) /* and still to be added */ && (!alreadyReplaced.contains (symbolName)) { ... Although in this specific case the comments seem like noise to me. > Technically this performs worse because you lose short-circuiting Not really, because optimizing compilers are a thing, when this thing is parsed into SSA, there won't be a difference.
- BoorishBears 10mo agoThe compiler would have to determine that these are pure calls which I wouldn't rely on if performance actually matters I just tested a recent gcc at -O2 with a contrived example using strings in an unordered_set: a look-up always occurs if not relying on short-circuiting
- mattacular 10mo agoExplain "why not what" is good general advice. My further advice for comments is: even bad comments can be useful (unless they're from LLM output maybe...) therefore when in doubt, write a comment. Write it in your own words. Had to add the last sentence for the circa 2020s developer experience. LLM comments are almost never useful since they're supposed to convey meaningful information to another human coder, anything your human brain can think of will probably be more helpful context.
- falcor84 10mo agoI strongly disagree. If you're using something like Claude Code to generate the code, it has significant context about the task, which from my experience provides very useful (albeit overly verbose) comments. I sometimes edit/rewrite its comments (as I might with the code itself), but I would never ask it to generate uncommented code.
- 3371 10mo agoI always think LLM comments are more about helping themselves to stay on track.
- yen223 10mo agoSame goes for human comments tbf
- CuriouslyC 10mo agoAI comments are fine for high level summaries of the what/how. They fail at the why, which is where we come in.
- bonesss 10mo ago> even bad comments can be useful Bad comments aren’t just less helpful than possible, they’re often harmful. Once you’ve hit a couple misleading comments in a code base (ie not updated, flatly wrong, or deeply confusing due to term misuse), the calculus swings to actively ignoring those lying lies and reading the code directly. And the kind of mind resorting to prose when struggling to clarify programming frequently struggles with both maintenance and clarity of prose.
- onion2k 10mo agoIf you need a comment to explain 'w' means 'weight' then you should remove the comment and rename the variable 'weight'.
- StellarScience 10mo agoIgnoring the git commit message strawman (those can include "what/why the change", not "what/why the code") and the Uncle Bob strawman, the final code block looks fine. But notice: // translate will replace all instances; only need to run it once This is a "why". // Replace all symbols This is a "what". It's better conveyed by improving the function name rather than by a comment: String replaceAllSymbols() { Ultimately this article buttresses conventional wisdom about comments.
- jarek83 10mo agoI think the benefits of either clean code (sepearate methods) and commenting "what" are of different kind: - clean code one, for me it just reads easier, specific bits of the larger operation are not surrounded with noise coming from other bits like it is in the commenting "what" one. I can focus more on each step, and that can make it easier to spot a bug or to refactor/adjust/extend, as now I'm more like on a given page of Lego set instructions - the commenting of "what" one - yeah, this obviously makes it quicker (but probably not easier) to see the larger picture. It has benefits, but I believe this helps more hacker/scripter kind of people than programmers
- CuriouslyC 10mo agoComments should explain everything, but via links. Large comments cause context rot, so keep your comments tight and focused, and provide links to details as needed.
- chrisweekly 10mo agoAgreed... IFF you're linking to something immutable (Jira tickets probably qualify in many enterprise scenarios). Otherwise linkrot is at least as big a problem. Meaningful commit messages surfaced in-IDE via git blame can serve a similar purpose.
- okonomiyaki3000 10mo agoWe need to differentiate between a "What this is" comment and a "What this does" comment. "What this does" is a lot closer to "Why" than it is to "What this is". I would hope "What this is" is rarely needed but "What this does" can certainly be helpful pretty often.
- huflungdung 10mo ago[dead]
- akabalanza 10mo agoI'm glad LLMs will make these conversations obsolete, just like linters did to tab-vs-spaces
- matt_kantor 10mo agoCare to elaborate? It's not obvious to me why/how that would happen. In fact, my experience so far tells me that code with tons of "why" comments (and possibly some "what"s as well) makes LLMs less likely to break stuff.
- maximgeorge 10mo ago[dead]
- d-lisp 10mo agoAs I am involved in more low level stuff, I prefer to read the source than the man pages, and I am very happy with people overcommenting their code as a user of e.g. a lib. On the other hand, it is unbearable to me to see comments on a codebase I am working on. Fortunately, emacs show/hide comments exists, so I find myself overcommenting things.
- antirez 10mo agoI made a point here https://antirez.com/news/124 https://antirez.com/news/124 that comments are needed at the same time for different reasons, and different comments have differente semantical properties that can be classified in classes you very easily find again and again, even in very different code bases.
- commandersaki 10mo agoThis is a great post and meshes with how I like to comment as well. I like to break the so called rules and get a bit dirty when it comes to writing code and comments. My opinion which you state, is to remove the effort from the reader in needing to figure things out a second, third, or n-th time. Here is one I wrote just to talk about iterating a loop in reverse: /* * We iterate the v6 prefixes in reverse from longest prefix length * to shortest. This is because the ipv6 address is a sequence of bytes, * and we want to perturb the address iteratively to get the corresponding * network address without making a copy for each perturbation as that * would be expensive. * * For example take address: abcd:abcd:abcd:abcd:abcd:abcd:abcd:abcd. * * With masks /112, /64, /8 we want to create the following network addresses * to lookup as follows: * * Lookup abcd:abcd:abcd:abcd:abcd:abcd:abcd:0000 in /112 bucket * Lookup abcd:abcd:abcd:abcd:0000:0000:0000:0000 in /64 bucket * Lookup abcd:0000:0000:0000:0000:0000:0000:0000 in /8 bucket * * In any other order aside from most specific to least, we'd have * to create copies of the original address and apply the mask each * time to get each network address; whereas in this case we can take * the same address and clear lower bits to higher bits as we go from * most specific to least specific masks without incurring any copies. */ for (auto it = m_v6_prefixes.crbegin(); it != m_v6_prefixes.crend(); ++it) Or here is another for masking a v4 address, but also explaining why a uint64 is used (this is calculated in a hot loop [same as the previous comment example], so I felt it was imperative to explain what is going on as there is very little room otherwise to optimise): for (const auto & [ mask_len, bucket ] : m_v4_prefixes) { /* * Example: * * netmask 255.255.128.0 (/17) or 0xffff8000: * * 0xffffffff ffffffff << (32-17) * -------------------------------- * 0xffffffff ffff8000 (shifted left 15) * * Applying against address 192.168.85.146 or 0xc0a85592: * * (converted to uint64_t due to implicit integer promotion) * * 0x00000000 c0a85592 * & * 0xffffffff ffff8000 * ------------------- * 0x00000000 c0a80000 * ------------------- * 0xc0a80000 (after conversion to uint32_t causing * lower 32-bit truncation) */ std::uint64_t mask = (~0ULL) << (32 - mask_len); std::uint32_t network_addr = addr & mask; const auto itt = bucket.find(network_addr);
- ivanjermakov 10mo agoI noticed that when I write code that is not trivial to understand I tend to extract intermediate values into variables with meaningful names. applyDrag(): void { const { quad: quadConfig } = settings const quad = this.getRigidBody() const quadVel = vec3ToTwgl(quad.linvel()) const dragMag = aerodynamicDrag(quadConfig.dragCoefficient, v3.length(quadVel), quadConfig.frontalArea) const dragDir = v3.negate(v3.normalize(quadVel)) const dragForce = v3.mulScalar(dragDir, dragMag) const dragImpulse = v3.mulScalar(dragForce, dt) quad.applyImpulse(vec3TwglToRapier(dragImpulse), true) } This way code gets more natural language anchors which helps understanding what it does.
- necrotic_comp 10mo agoI appreciate this way of programming - also, if I may, in the age of auto-complete I think it's okay to have verbose variable naming. Imho, it's perfectly fine to have quad, quadVelocity, dragMagnitude, etc. I see this a lot in the wild, though - as an honest question (not trolling!) why do people still shorten their variable names in place of having a terse descriptor ?
- ivanjermakov 10mo agoI tend to have shorter names for function scoped variables. Wider the scope - more descriptive the name. Short names are good to have more concise code and more logic fitting into a line width.
- codemonkey-zeta 10mo agoI find that Lisps encourage this behavior more than other languages. Many (most?) of the lisp functions I read have the exact same structure: (fn some-function [args] (let [binding (transform args)] (an-expression binding))) I find that this makes skimming lisp code much easier, because I can usually skip reading the bindings and just read the function name and ultimate expression and usually get the gist very quickly. You might wonder how this is different than the example you provided, and the answer is because you could sneakily intersperse anything you wanted between your imperative bindings (like a conditional return statement), so I actually have to read every line of your code, vs in a lisp `let` I know for a fact there is nothing sneaky going on.
- filoeleven 10mo agoThe bigger point I take from this is that the purpose of comments and good names are all attempts to help the developer grasp "the context of this code". The article uses "context switch" repeatedly, and in fact never uses the word "context" any other way. Since the author acknowledged they're starting a friendly flame war, I'll go ahead and add that the biggest problem with the example code is that it's object-oriented and mutable, which forces a sprawling context on the developers working with it. When I read the replace() method, I was immediately confused because it has no arguments. stringToReplace and alreadyReplaced are properties of the class, so you must look elsewhere (meaning, outside of the function) for their definitions. You also don't know what other bits of the class are doing with those properties when you're not looking. Both of these facts inflate the context you have to carry around in your head. Why is this a class? In the replace() method, there is a call to translate(symbolName). Why isn't there also a SymbolNameTranslator class with a translate() method? Who decided one was simple enough to use a function while the other warrants a class? SymbolReplacer surely could also be done with a function. I understand that this is illustration code, so the usage and purpose is not clear (and the original Bob Martin article does not help). Is there a reason we want these half-replaced strings made available every time we call SymbolReplacer.replace()? If there is, we can get the same data by using a reduce function and returning all of the iterations in a list. A plain, immutable function necessarily contains within it the entire scope of its behavior. It accepts and returns plain data that has no baggage attached to it about the data's purpose. It does one thing only.
- commandersaki 10mo agoThe translation from function to class Martin Bob style makes it become literal spaghetti code.
- scottlamb 10mo agoThis is tangential to the article's point, but that `replace` function is a complete WTF in a way both authors completely ignore. Because it replaces things in the entire string in a loop, it will translate symbols recursively or not depending on ordering. Imagine you have the following dictionary: a=$b b=oops if your input string just has one of these, it will just be translated once as the programmer was probably expecting: input: foo $a bar output: foo $b bar but if your input string first references $b later, then it will recursively translate $a. input: foo $a bar $b output: foo oops bar oops Sometimes translating recursively is a bizarre behavior and possibly a security hole. The sane thing would be to loop through building the output string, adding the replacement for each symbol as you go. Using String.replace and the alreadyReplaced map is just a bad idea. Also inefficient, as it and throws away strings and does a redundant search on each loop iteration. Feels typical of this whole '90s-era culture of arguing over refactoring with Java design patterns and ornate styles without ever thinking about if the algorithm is any good. Edit: also, consider $foo $foobar. It doesn't properly tokenize on replacement, so this will also be wrong.
- shiandow 10mo agoThe insane thing to do would be to implement a variant of loeb so it works regardless of order.
- scottlamb 10mo agoThat'd still be less surprising!
- scottlamb 10mo ago> The sane thing would be to loop through building the output string, adding the replacement for each symbol as you go. As follows: // SYMBOL_REF should be a class-level static final to avoid recompiling on each call. int pos = 0; // input[..pos] has been processed. StringBuilder out = new StringBuilder(); // could also guess at length here. Matcher m = SYMBOL_REF.matcher(input); while (m.find()) { String replacement = symbols.get(m.group(1)); if (replacement == null) { continue; // no such symbol; keep literal `$foo`. } out.append(input, pos, m.start()); out.append(replacement); pos = m.end(); } out.append(input, pos, input.length()); (Apparently there's also now a Matcher.replaceAll one could use, but it's arguably cheating to outsource the loop to a method that probably didn't exist when the Uncle Bob version was written, and it's slightly less efficient in the "no such symbol" case.) Coding style must serve the purpose of aiding understanding. If you have strong opinions about the coding style of `replace` but those opinions don't lead to recognition that the implementation was incorrect and inefficient, your opinions are bad and you should feel bad. Stop writing garbage books and blog posts! </rant>
- shiandow 10mo agoIf we're doing hot takes then I propose the following guideline taken from mathematics. Only comments should explain what, variable names should only hint The first example is perfectly fine, nobody has the time to derive or read a verbose formula involving the words 'weight', 'radius' and 'price'.
- tangotaylor 10mo ago“What” comments can be quite nice to quickly parse through code. But I don’t think they’re worth it. My issue with “what” comments is they’re brittle and can easily go out of sync with the code they’re describing. There’s no automation like type checking or unit tests that can enforce that comments stay accurate to the code they describe. Maybe LLMs can check this but in my experience they miss a lot of things. When “what” comments go out of sync with the code, they spread misinformation and confusion. This is worse than no comments at all so I don’t think they’re worth it. “Why” comments tend to be more stable and orthogonal to the implementation.
- TZubiri 10mo agoThe maybe is the strongest part of this article. In my early days I read a lot of "you should" "this is wrong". But code is an expressive medium, you can do things one way, or do it the other, you can write "amountOfEmployees" or you can write "ne" with an "#amount of employees" comment, either way is absolutely fine and you can use whichever depending on your priorities, tradeoffs or even your mood. Also I used to obsess over code, (and there's a lot of material that obsesses about code), but after you become profficient at it, there's a cap on the returns of investing time into your codebase, and you start to focus on the product itself. There's not much difference between a good codebase and a marvelously polished codebase, so you might as well use the extra focuse on going from a bad UX to a neutral UX or whatever improvement you can make to the product.
- drob518 10mo agoI agree with the OP, but, unfortunately, posts like this generate more heat than light. Like a lot of things, what and how to comment code comes down to “do the right thing,” not a list of rules, where everyone will always find counter examples. Do whatever you need to communicate effectively with “the next guy,” who could very well be you.
- kayo_20211030 10mo agoLove it. Particularly the "correct" refactoring at the end. The future maintainer will thank you. (BTW: that future maintainer might be you). To a first approximation one should write "local" code i.e. the bits that are important should be right in front of you in the editor. It shouldn't need too much navigation, with all the overhead of context switches, to understand what you're looking at. Of course, be sensible and use good judgement :-) But, that's what we're paid for, right?
- ImPleadThe5th 10mo agoI really do not like working with uncle bob hardliners There have been so many times where I have commented _why_ I think some uncle bob-ism made the code unclear and the response is always: > *Sends link to Clean Code, maybe you don't know about this? No, I do, and I am allowed to disagree with it. To which they always clutch their pearls "How do you think you know better than uncle bob!?", this is a Well Established Pattern TM. I don't think I know better than Uncle Bob, but I don't think Uncle Bob works on this codebase nearly as much as you or I.
- zephen 10mo agoThere are five canonical questions: What, When, Where, Why, and How. "When" is occasionally a good question to answer in a comment, e.g. for an interrupt handler, and "Where" is also occasionally a good thing to answer, e.g. "this code is only executed on ARM systems." The other three questions typically form a hierarchy: Why -> What -> How. A simplistic google shows that "code comments" are next to "what" and "how" at about the same frequency as they are next to "why" and "what." This makes some amount of sense, when you consider the usual context. "Why" is often an (assumed) obvious unstated business reason, "What" is a thing done in support of that reason, and "How" is the mechanics of doing the thing. But with multiple levels of abstraction, _maybe_ the "What" inside the hierarchy remains a "What" to the level above it, but becomes a "Why" to the next level of "What" in the hierarchy. Or maybe the "How" at the end of the hierarchy remains a "How" to the level above it but becomes a "What" to a new "How" level below it. Is it: Why -> What/Why -> What/Why -> What/Why -> What -> How or Why -> What -> How/What -> How/What -> How/What -> How In many cases the intermediate nodes in this graph could be legitimately viewed as either What/Why or as How/What, depending on your viewpoint, which could partly depend on which code you read first. In any case, there are a few hierarchies with final "Hows" that absolutely beg for comments (Carmack's famous inverse square root comes to mind) but in most problem domains that don't involve knowledge across system boundaries (e.g. cache optimization, atomic operations, etc.), the final "How" is almost always adequately explained by the code, _if_ the reader understands the immediately preceding "What." If I see a function "BackupDatabase()" then I'm pretty sure I already know both "Why" and "What" at the highest levels. "How" I backup the database might be obvious once I am reading inside the function, or the code might be opaque enough that a particular set of lines requires explanation. You could view that explanation as part of "How" the database is backed up, or you could view that explanation as "What" the next few lines of code are doing. Again, this viewpoint might even partly depend on where you started. If you are dumped inside the function by a debugger, your question might be "What the heck is this code doing here?" but if you are reading the code semi-linearly in an editor, you might wonder "How is BackupDatabase() implemented?"
- nooee 10mo agoAlso note, that after Uncle Bob's refactoring, you now have six additional functions floating around, which may or may not make sense outside of the context of their original caller. This can make the API surface of your class (even if it's just the internal one) harder to grasp and it invites other devs to reuse code that was never intended for reuse, creating unintended couplings. That's mainly a problem for languages that don't allow nested functions though. But in Java, I am sceptical of excessive function creation for the sake of self-documenting code, unless maybe it's in the context of a command pattern, where the whole class only has one obvious function anyway.
- xg15 10mo agoThe "don't explain variable definitions with comments because people won't see the comments at usage sites" argument also seems obsolete in the face of modern(?) IDEs. If I look through code and see a variable I don't know, I want to see its definition anyway, so I know the type, scope, initial value, etc. And it's trivially possible to do that with everything that has a "jump to definition" command.
- programmertote 10mo agoOne thing I learned from programming since the early 2000s, there is no such thing as one size fits all advice. You do what is best for future folks--as I like to call the unfortunate folks who would have to maintain the code I wrote--by providing them helpful hints (be it business rules, assumptions related to code/tech) along with as simply and clearly written code as possible (how do I know if my code is simple and easy to understand? Have a junior teammate review my code and have her/him leave comments wherever she has to spend more than 10-15 mins reading an area in the code). I hope not of a lot of the future folks hate me for leaving them with ample context and clear/dead simple code.
- FrameworkFred 10mo agoI agree with this soo much. In a very real way, a codebase is a conversation among developers. There's every chance the next guy will be less familiar with the code than you, who just did a deep dive and arrived at some critical, poorly documented line of code. There's an even better chance that person will be you, 6 months from now. Use opportunities to communicate.
- scoofy 10mo agoI feel like a complete weirdo when it comes to comments and variable names. I've never worked professionally as a coder, but I've been working with python and a bit of js for like 15 years now. I strongly believe that variable names should be long, and explain what they are, and that comments should be long, and explain what's happening. I have no idea why people want to "save time" to write short comments and short variable names. I just CTRL+C, CTRL+V my variable name anyway. They compound on each other, and that ends up adding an unnecessary level of complexity, and the possibility for errors. When I come back to a piece of complex code after a year or two, I'm very, very happy that I've used a variable name like "three_tuple_of_weight_radius_price" instead of "tuple" or "t".
- rasmus-kirk 10mo agoI think it's a leftover before proper LSP's, if you had to type/paste everything, that would be fairly tedious, but now-a-days everyone uses LSP's with autocomplete that have all the variables in scope. I do think very long names can hurt readability though so it's still a balancing act, but variables like "t" (often used in Haskell, ugh) is awful in my opinion.
- deleted 10mo ago[deleted]
- gavmor 10mo ago"tuple" is entirely redundant with the types, though.
- scoofy 10mo agoI’m not sure what you mean. You’re not necessarily going to know the type of a variable just by reading a random section of code… especially in Python. I absolutely going to add the type to the variable name if it’s a complex function. It’s just clearer.
- zahlman 10mo agoThe point is to not care about the type. If you see `weight, radius, price = ...`, then it generally isn't going to matter whether `...` is a tuple or a list (or more exotic possibilities). What matters is that you can iterate over it, and iterating over it gives exactly three results, and the first result represents a weight, etc. If your weights need to be, say, a floating-point number of kilograms, then you establish that convention, and only mark intentional deviations from the convention (e.g. because you're doing an explicit conversion to format output). (Or you use a library like Pint to give more formality to it.)
- Thorrez 10mo ago> // translate will replace all instances; only need to run it once That comment is not helpful, because it's wrong. The translate() function just is some sort of lookup. It doesn't replace anything. It's the stringToReplace.replace() call that replaces all instances.
- stravant 10mo agoOne way I like to write comments is describing what would break if the block / line of code weren't there. I find that ends up being succinct and useful in the future.
- nvader 10mo agoI will take the bait and selectively flame the use of "f. ex" for "for example", when the standard e.g. is available. Thought-provoking article, nonetheless!
- ToolboxPro 10mo ago[dead]
- ryandv 10mo agoComments exist to provide information beyond primitive, domain-agnostic types (String, Int, etc.), but without the overhead of more elaborate modelling into domain-specific types (ChequingAccounts, Widgets, and so forth). I may want to communicate further information about the inhabitants or values of a particular type, without introducing extraneous or superfluous types: -- given a path to a PEM encoded PKCS#8 formatted RSA private key and a -- JWT Claims Set (RFC 7519) represented as a strict ByteString, -- return a strict ByteString representing a base64 encoded RSA256-signed JWS. generateJWT :: FilePath -> B.ByteString -> IO (Maybe B.ByteString) generateJWT fp claims = (fmap unJwt <$>) . (maybe (return Nothing) (fmap eitherToMaybe . encodeClaims) =<<) $ fromPKCS8 fp where eitherToMaybe = preview _Right encodeClaims = (flip $ rsaEncode RS256) claims Arguably newtype wrappers could (or even should) be introduced in place of the more primitive FilePaths and ByteStrings - but even if they were, the type names would either be prohibitively long, or fail to communicate the full depth of information of the comment.