DEV Community

Cover image for "Do not comment on your code, it should be self-documented". Well... I don't agree.
SnoopyDev
SnoopyDev

Posted on • Updated on

"Do not comment on your code, it should be self-documented". Well... I don't agree.

Since I started write my first block of code, I heard from many developers that commenting is useless and it's a type of apology to write a bad code, but after working in a big projects with a big team, the only thing I can say is: Not commenting your code is narcisist and excludes beginners, by the way, who said your code is so good and obvious as you think it is? Yeah, your mind.

During your work, probably you faced a function that you asked yourself: "-What the hell is even that?". Many of us experienced this situation, even with our own code after some weeks far from the project. Now, imagine if instead of you have to waste your time, searching through the houndreds of files for the right function you need to put hands on, you could have just comment your code telling the function purpose, its params and what it should return. Life could be a dream, right?

Also, we cannot asume the everybody think like us and we are being so obvious. People have different ways to analyze things, we have people that are less experienced or even have mental health conditions like, anxiety and ADDH, and that makes the process of understand some pieces of code even harder. Should we just exclude them because we can't use one single minute to comment our complexity? I think we shouldn't.

The question is not about if you should comment your code or not, but is what you need to comment in your code and how it should be done.

Write a clean and easily readable code is unegotiable, and you get better on this with experience, but you can also write clean and good comments, so it can be used as a reference for you and the others, and it do not make you a bad programmer, on the contrary!, It makes you a better professional,your code will be easily maintainable, plus, you're ensuring that no matter the level of who enters on your team, they'll get it faster and start working on the project, and if you have to leave your job, the devs that comes after you will be grateful and thanks you everyday before they going to bed. (okay, I'm not so sure about this last part).

Image description

β€œPrograms must be written for people to read and only incidentally for machines to execute.” - Hal Abelson - MIT Professor.

Recommended reads:
Best practices for writing code comments

What's the best way to document JavaScript?

Top comments (104)

Collapse
 
jonrandy profile image
Jon Randy πŸŽ–οΈ • Edited

It's never 'useless', but it can be overkill.

If code is written well (good variable & function names, clear logic), then it should be fairly obvious from reading it what it does. In cases where the logic is a little hard to follow then some comments can be very helpful. It can be a tricky balance - you don't want absolutely no comments ever, but at the same time, commenting absolutely everything just to try and cater for every possible skill level is also not a good idea.

If code is written well, and is uncommented - then it is probably the assumption of the team (as presumably the code has passed code review) that it is already understandable enough. If a new developer comes to this code and does not understand it, the best solution would be to consult a team member who does, and get them to explain it. This will have the dual benefit of increasing the junior developer's understanding, and making the team aware that there may be an issue with the code being too impenetrable in places.

Collapse
 
klausdonnert profile image
Klaus Donnert

And that's just it: Everybody thinks their code is well written. But most of the time it isn't near as good as the writer thinks it is. When I look at code that I wrote just 6 months ago I can see that it's not as good as the code I write today. I'm always learning improving.

Collapse
 
chasm profile image
Charles F. Munat

If they can't write clean and readable code, why would you think that they can write clear and understandable comments?

Thread Thread
 
klausdonnert profile image
Klaus Donnert

I wish people wrote clean and readable code. Most of the code I read is crap. I must admit I don't have much experience reading comments.

Thread Thread
 
chasm profile image
Charles F. Munat • Edited

Sadly, most devs aren't very good. Just like most doctors, lawyers, bricklayers, actors, barbers, politicians. Most people are mediocre at what they do. By definition, actually. Mediocre means average. Average isn't very good, usually.

But if a person can't even write decent code, I find it unlikely that they are going to write understandable comments in a natural language (much harder -- ask any writer), or, more importantly, that they are going to be diligent enough to keep that comment in sync with the code.

And my experience -- closing in on three decades -- bears that out. If you think code is bad, read the comments. They are almost always awful.

What's needed, really, is not comments but good code reviews by talented leaders who ensure that code is clean and readable before it goes into production. Which would also help to teach coders to write readable code in the first place.

But that might take time, right? We never have time to do it right. We only have time to write comments that we wouldn't have needed if we'd done it right, and then pay a bigger penalty down the road when the comments and code are incompatible and everything is a mess. Ever seen any code like that?

/*
 * Adds two arguments together. It will be gooder if the arguments
 * are numbers. You should put in two numbers. Then you will get
 * back the number you get when you take two numbers and add
 * them together. That's what will come out.
 */
const f = (arg1, arg2) => arg1 * arg2
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
klausdonnert profile image
Klaus Donnert

You made that up. Nobody is that bad. I did get a chuckle out of it.

Thread Thread
 
chasm profile image
Charles F. Munat

It was a joke. But it's not as far off as you might think. :-)

Thread Thread
 
klausdonnert profile image
Klaus Donnert

I consider comments as documentation. If a programmer does not document their code... I don't have energy to finish that sentence.

Thread Thread
 
chasm profile image
Charles F. Munat

Comments are a shit way to document code. The best way is in the code itself. So if your code is self-documenting, then you have documented your code. The only excuse for comments is that you had to do something in the code that you can't figure out how to make clear without a comment.

Maybe it's a workaround. Maybe you're just not that good. That's why we have teams -- so they can show you how to write better code.

In short, comments are generally where mediocre coders document their failure to write understandable code. It's either that, or the comments are redundant and probably just get out of sync.

So one could say that the more you comment your code, the more you're willing to admit that you don't write very good code. And if that's the case, then I guess comments are better than nothing. But why not learn to be a better coder or take up a different profession?

But hey, black and white condemnations like yours are all the rage these days. Maybe see a doctor, though, about your anemia?

It is always surprising how many devs confuse their personal preferences and pet peeves as scientific arguments and absolute judgements. And then boast about it.

Thread Thread
 
klausdonnert profile image
Klaus Donnert

Apologies. Sir, I do not condemn self documenting code. Most of the code I write is self documenting. We agree that bad code is bad code. You make a solid argument that comments don't improve bad code.
I generally use comments in a couple of ways. One is when I am writing a function or method, I write the steps out in plain English as comments before I implement them. Then I remove redundant comments. Some comments I leave because some things are nuanced and not obvious.
The second way is explaining, usually to my future self, why I did something a particular way, not what the code is doing, because well, I'm not as smart as I think I am. Even then, most of those comments are from my future self to my future future self to save time the next time I have to modify it.
Third, is explaining somebody else's code for instance, where they used a variable or parameter named "id" where id could be the column of one of 6 tables, and refactoring is not an option because it's spaghetti code in a huge legacy codebase.

I'm interested in your work flow. You're a teacher. I'm a student. How do you do it?

Thread Thread
 
chasm profile image
Charles F. Munat

Sorry, I misread your last comment as saying that all code had to be commented. I rarely comment my code.

Instead I:

  • Keep functions short -- 10 to 20 lines max, if possible
  • Limit functions to doing one thing only (though that thing might involve conditional outputs)
  • Name the function by what it does; name length is much less important than clarity
  • Keep my functions pure and referentially transparent
    • Hence, a function with zero parameters is a constant (e.g., function returnFalse() { return false }
  • Use options parameters
    • If the number of parameters will exceed 3
    • If I want to avoid positional parameters
  • Return tuples or objects if I need to return more than one thing
  • Always use a return statement (unless an arrow function)
  • Prefer named functions; arrow functions are for:
    • Passing an anonymous function in the parentheses of a function call
    • Controlling the this, typically in callback functions (anywhere you might use const self = this)
  • Use TypeScript (waiting for a better type system) for static typing
    • No any
    • type not interface -- interfaces are mutable
    • Avoid unknown in production code

Also:

  • I never use classes: I use modules (ESM)
  • One function (or component) per file
  • Name of the function or component (camelCase or PascalCase, respectively) is the name of the folder
  • Filename is either index.ts(x) or mod.ts(x)
  • All files associated with that function/component/module go in that folder, e.g.:
StringField/
  index.module.css
  index.stories.tsx
  index.tsx
  index.test.tsx
  README.md   -> the exception
utilities/
  not/
    index.test.ts
    index.ts
Enter fullscreen mode Exit fullscreen mode
  • I typically use module aliases so I can import from the top down: import not from "~utilities/not"
  • I don't use the bang (!) because it is too easy to miss; instead, I always write a generic not function and use that
  • I use double quotations because they are easier to see (I'm writing TypeScript)
  • I don't use semicolons because they just add noise -- in a decade of leaving them out, ASI has never once let me down, and now with the TSC compiler and using deno_lint to lint...
  • I use tabs for indentation
    • They use fewer characters
    • Everyone can set their own damn tab width
    • I'm using Dprint anyway
  • I prefer and use almost exclusively the default export option (exceptions: types, constants)
  • I always use import type for type imports, it's cleaner
  • I like one import per line where possible and I use an import sorter
  • I use VSCode currently, so I can easily collapse the imports
  • I put the default export function at the top of the file just below the imports so it is immediately visible
  • Using named functions means I can put helper functions below the default function as they're hoisted
    • But I generally put helper functions in their own folders
  • If a type declaration might be reused, I add it to a types file at the top of the module with like types, otherwise I keep it below the function/component but export it as a named export just in case
  • I deeply nest folders (remember: all names are on folders, not files)
    • At the top of the app (I do front end apps), I generally have:
      • An apps folder
      • A utilities folder (for generic utilities such as not, pipe, identity)
      • A services folder for any bespoke services (i.e., not in an npm or deno module) such as authentication
      • A modules folder for any reusable modules (e.g., generic components, icons)
  • IMPORTANT: If I have a function used only in one folder, then its folder goes in that folder (as a child folder), but if I then begin using that function in some other branch of the folder hierarchy, then I move its folder to the node where those two branches meet
    • In short, imports can go up the folder hierarchy, but they don't go back down.
    • And, often, I just do all imports not in a descendent folder from the top down, e.g., import doIt from "~apps/TicTacToe/utilities/doIt" rather than import doIt from "../../utilities/doIt"

In the apps folder, I have "micro-apps". These are standalone apps. Everything bespoke to that app is in that folder. Everything outside of that folder is generic and reusable.

So, for example, I may have a services/useGraphQL folder that provides a hook for using GraphQL, but it takes a config and returns query and mutation functions. So the actual URL, query, variables, etc. are provided where they are used. None of this is hard coded in the useGraphQL hook. (And I don't bother with Apollo -- a simple POST request returning JSON works fine.)

Inside the apps folder, I might have a micro-app called, I dunno, TicTacToe. The hierarchy of that folder and its subcomponents would follow the hierarchy of the components, for example:

apps/
  TicTacToe/
    Board/
      Square/
        index.tsx
      index.tsx
    index.tsx
Enter fullscreen mode Exit fullscreen mode

The benefit of this is that:

  • I can close up folders and see clearly the structure of the app
  • The structure of the folders matches that of the app, reducing cognitive load
  • PascalCase or camelCase tells me immediately what is a component and what is a function, repectively
  • The .tsx ending tells me it is using JSX (I am forced to use React usually, though I prefer SolidJS or even plain vanilla TS -- deno gives me JSX for free)
  • Most importantly, if I want to remove TicTacToe, I just delete this folder (or move it elsewhere) and remove the typically one reference to it (<TicTacToe />) elsewhere in the app
  • The TicTacToe micro-app can import from any of the other apps (using aliases), but other apps do not import from the micro-app (except to use it as an app, e.g., in a route)
  • I keep all the business logic and bespoke bits in the micro-app

I do not care how short a file is. Why would I? For some reason, many devs fear files. I don't get it. Why would I make a 1000-line file full of named exports when I could make a well organized folder tree with maybe 20 files, each of which contains a single function? If I need to view multiple functions at once, I can open them in side-by-side tabs.

Here is an example of the not function I mentioned, from production code:

export default function not<T>(value: T): boolean {
  return !value
}
Enter fullscreen mode Exit fullscreen mode

That is the entire file! Three lines. Here is a somewhat longer one:

export default function concatenateCssClasses(classNames: {
  [className: string]: boolean | undefined
}): string {
  return Object.entries(classNames)
    .reduce(
      (classList: Array<string>, [className, include]) => [...classList, ...(include ? [className] : [])],
      [],
    )
    .join(" ")
}
Enter fullscreen mode Exit fullscreen mode

That replaces an entire dependency (on "classnames")! You can see pretty easily, I think, that it takes an object with CSS class names as the keys and booleans as the values, and then includes only those that are true, concatenating them into a space-separated string. And if that's not enough to be clear, then in the very same folder is the test:

import { expect, test } from "vitest"
import concatenateCssClasses from "./"

const classNames = {
  red: true,
  green: false,
  blue: true,
  "burnt-sienna": true,
}

test("[concatenateCssClasses] creates a space-separated string of class names for className keys with truthy values", () => {
  expect(concatenateCssClasses(classNames)).toBe("red blue burnt-sienna")
})
Enter fullscreen mode Exit fullscreen mode

Other than a few polyfills for Intl and Temporal, fetch, and uuid, my app uses only React, XState, and, sadly, Auth0.js (not my choice).

"dependencies": {
  "@formatjs/intl-getcanonicallocales": "^1.9.2",
  "@formatjs/intl-listformat": "^6.5.3",
  "@formatjs/intl-locale": "^2.4.47",
  "@js-temporal/polyfill": "^0.4.1",
  "@xstate/react": "^3.0.0",
  "auth0-js": "^9.19.0",
  "cross-fetch": "^3.1.5",
  "react": "^18.0.0",
  "react-dom": "^18.0.0",
  "uuid": "^8.3.2",
  "xstate": "^4.31.0"
}
Enter fullscreen mode Exit fullscreen mode

I write my own code, including utility functions, and reuse it from app to app, improving it as I get better (and the language improves). So yes, I write my own pipe function, and often map, filter, find, reduce and more (wrapping the JS methods where appropriate).

That means that I know practically my whole code base (an argument for vanilla TS). It means that to the greatest extent possible, no one else has code in my code base, which means better security, better reliability, etc.

It means that when things break, I know where they broke and why they broke, and I can fix them rather than waiting for the owners to get around to fixing them and releasing a patch.

It means that I am highly motivated to keep my code base simple, not to bulk it up with unnecessary and bloated dependencies.

It means that most of my files can be seen in full without scrolling.

And if my code needs further documentation, I try to put it in those README.md files right in the folder (README means GitHub will automatically display them).

That's just a start, but I hope it answers your question at least a little.

Lots of devs violently disagree with one or another of the above, and I have done all of these things differently over the years, but this is the methodology that has stood the test of time. I'm sure it can be improved still further, and significant changes in language, framework, or library might make adaptations necessary, but I can say that of the many people I've taught this too, none have gone back to their old ways. It's simple, and it works.

YMMV.

Thread Thread
 
klausdonnert profile image
Klaus Donnert

Good answer. That's more than I expected. It will take me some time to digest it all.
I struggle keeping my functions short. 50 - 100 lines is not unusual for me. Three line functions always make me second guess myself, "Should I inline this"?
Earlier, I was thinking to myself "I bet he uses readme files".
Your use of utilities is fascinating. I have some functions that I seem to redefine over and over in different projects. This a good way to organize then and not redefine them.
I've used the folder/index.* naming convention in one project. It confuses me a bit and make my tabs too wide when I get several files open at once.
The temporal polyfill is interesting.
I like writing vanilla js for much the same reasons you use vanilla TS.

Good Night

Thread Thread
 
flutterclutter profile image
flutter-clutter

@chasm
Just wanted to say that I agree with almost every statement. I have made very similar experiences during my time as a software developer and have come to very similar conclusions. Most of the things are in line with Uncle Bob's Clean Code. Thank you for the detailed elaboration!

Collapse
 
ana1337 profile image
Ana

+1 I couldn't have said it better myself

Collapse
 
oribellove profile image
Love Oribel

thanks

Collapse
 
tmchuynh profile image
Tina Huynh

I've never in my years of education been told (except from that student who doesn't want to comment their code) that commenting/documenting code is "useless". Oh my! Documenting your code teaches beginners what you're doing. Not documenting at all excludes them because....how are they supposed to understand if you don't explain it in plain english? I bet the developer himself/herself won't even remember what the program he or she wrote does five or six months down the line without documentation. Who is it harming? True productivity, team work, and efficiency? Or just their ego and arrogance?

Collapse
 
rafo profile image
Rafael Osipov

If you keep naming your classes, methods, parameters and variables consistently and their names express their purpose, your code without comments is easy to understand even for beginners.

Sometimes comments are necessary to describe and point to code that does not work in an expected way, and you have implemented strange looking workaround. For example some libraries, used in your project may have side-effects and wrong behavior and to overcome these problems, you have to write strange-looking code. And in this case comment helps to understand what is going on.

Collapse
 
dinhanhx profile image
dinhanhx • Edited

Oh my! Documenting your code teaches beginners what you're doing

I totally agree on this. I write and read mostly deep learning Python code. Therefore, I understand the pain when reading undocumented inputs. Documenting these code not only teaches the beginners SOTA but also save time of running the code again just to determine what is the correct shape of a Tensor.

Collapse
 
curiousdev profile image
CuriousDev

But if you have beginners in a project, you can not rely a decision on this, which affects how the whole project and code is build. Again, just to provide an example, you would need to maintain the comments, if you do not, these will rather harm the process of understanding.
It is possibly a better approach to on-board and support beginners well. Allow them to contact and ask you whenever they need help. Be their mentor and teach them to work with the code like an experienced developer, do not create "code for the beginners" with comments all over the place.

Collapse
 
maxwebbnz profile image
Max Webb

Yep fully agree. It isn't harming anyone, but more so creating an inclusion to new people and beginners.

Collapse
 
paratron profile image
Christian Engel • Edited

My golden rules:

Add a summary at the top of a function about what it does.

This way I do not have to read and mentally parse your functions code to understand what it does.
This does not apply to very simple functions where the function name can describe everything like removeLastCharacter(). But calculateSingularityProbability() might win by some description.

Add comments to reduce mental work

If some line(s) are really complicated, add a short comment above describing what it does.

Add comments to explain hidden knowledge

Why are you doing array.pop() twice here without any appearant reason? Well, because I do know that the array always contains two empty entries at the end, which we don't want.
If you write the code, you have that knowledge at hand. Your team member might not. And you, looking at that code in 2 month wont remember either.

Collapse
 
flutterclutter profile image
flutter-clutter

I have to say, I disagree.

This does not apply to very simple functions where the function name can describe everything like removeLastCharacter(). But calculateSingularityProbability() might win by some description.

calculateSingularityProbability() is already a pretty good summary, isn't it? It shouldn't be necessary to describe formally what the function does exactly because, well, that's exactly what code is: a formal description of behavior.
If you write code that you think is hard to read and needs a comment then why don't you change the code instead of adding a comment? This is like creating a product with a crappy UX and then writing a descriptive manual instead of fixing the UX.

Why are you doing array.pop() twice here without any appearant reason? Well, because I do know that the array always contains two empty entries at the end, which we don't want.

Why don't you wrap the double array.pop() in a function named removeEmptyArraysAtTheEnd()? Shorter functions, single responsibility maintained and description inside the function title. No risk of "changing the function but forgetting to change the comment".

In my opinion, writing comments is the last resort and should almost never be done. Instead, keep functions very short (10 -30 LOC) and parameter count low. I recommend reading Uncle Bob's "Clean Code".

Collapse
 
paratron profile image
Christian Engel

I prefer reading one line of comment in human language than having to read 10 - 30 lines of machine language and parse it in my head to figure out whats going on.

I read that book. Also many others. πŸ‘

Thread Thread
 
flutterclutter profile image
flutter-clutter

Don't you prefer to read one function title that describes on a high level in human readable language what's inside the function? Basically the same that would be in the one comment line?

Thread Thread
 
paratron profile image
Christian Engel

We went full circle to my initial comment. Yes, there are simple function where all they do fits into the function name.

Since functions are composed from other functions no matter how much you break things up, you will end up with something more complex. And the case will be even worse since I now have to scan all over the place nd go down rabbit holes to understand whats going on inside.

I also mentioned that its depending on the cases. You cannot generalize the topic.

Thread Thread
 
flutterclutter profile image
flutter-clutter

I understand what you're saying. I would argue, though, that only because you add a layer of abstraction to something, it doesn't mean that you need to understand every detail of the layer below to understand the abstraction. I would even say that's the purpose of abstraction.

So when you compose 5 functions in a new function, you don't need to read the code of the 5 child functions if they have a descriptive name.

If I wrap five HTTP requests in a repository, I don't need to understand the HTTP request logic to refactor the repository. I can stay in this layer of abstraction because I separated all the logic into smaller pieces.

I would argue that if a function does more than fits in the function name, the function does too much. If it's only one responsibility, it can be usually described in a short title.

But we may have made different experiences and we will possibly not come together and agree here and this is fine :).

Collapse
 
eslammahgoub profile image
eslam mahgoub

Agreed big, and so few developers know that I always hear those words and I can't argue.
Best line ever:
"Not commenting on your code is narcissist and excludes beginners, btw."

"Who said your code is as good and obvious as you think it is? Yeah, your mind." Yes Yes Yes a lot of developers say it and yes it's only good in your mind even the seniors will struggle with undocumented code.

Collapse
 
polterguy profile image
Thomas Hansen

Although I do see your point, and I myself tend to "over comment" my own code, there is a valid argument to the code being self documented. Interestingly, with a "meta programming" language, such as our Hyperlambda, the code literally is self documented, due to the abilities of the programming language to extract meta data from the code, being able to intelligently understand what each snippet of code does, to the point where you can write stuff that's logically similar to the following (pseudo code).

select c.filename from codebase c where c.code invokes(log)
Enter fullscreen mode Exit fullscreen mode

Of course the above is pseudo code, but still a perfectly example of something easily achieved with a "meta programming language", resulting in that your "comments" literally becomes its code, and information about what the code does can be dynamically extracted using automated processes, allowing you to easily understand everything your code actually does, without having a single comment in your code base.

Still, kind of out of fear from possibly being wrong, I tend to document my code (too much) ... :/

Collapse
 
jeremyf profile image
Jeremy Friesen

I love self-documenting "What does the code do" but I have yet to see self-documenting "Why does it do it?"

Collapse
 
flutterclutter profile image
flutter-clutter • Edited

"Why" is a question that has nothing to do with the implementation. It has something to do with the requirements. These come from the stake holders. They should document the requirements somewhere else, not in the code. If the code is the single source of truth for the requirements of your software, then you're doing something wrong.

Thread Thread
 
jeremyf profile image
Jeremy Friesen

"Why did I choose to use this mapping method? Because in exploring the options this was the most performant."

That's never something you'll see in requirements somewhere and is ideally situated near the code you wrote.

Thread Thread
 
flutterclutter profile image
flutter-clutter

If it's really a matter of performance, then I agree. However, in today's web applications, performance on such a low level is almost never a concern. From what I've learnt, it almost always boils down to IO in a loop or other nested loops with cubic complexity.

Apart from that: readability > performance. Unless you're working in game industry or doing other low level stuff.

Thread Thread
 
jeremyf profile image
Jeremy Friesen

I've spent a long-time in open source. The one thing that invariably remains…the source code (and it's git history). All other things rot and decay far faster.

So, include whatever comments can help provide contextual support of the local "state" of the code.

Thread Thread
 
jeremyf profile image
Jeremy Friesen

In addition, one must consider that placing way finding comments in a code-base are of high value.

There have been a few cases, where past decisions/specs were lost but we still had code. I don't want to go and "backfill" those specs, so I'll write a note saying "This is my understanding given what I've been able to piece together."

Thread Thread
 
flutterclutter profile image
flutter-clutter

Okay, maybe I am viewing it too much from a business perspective. It seems like there are a lot more specs from a non-technical view there. This somehow eliminates the need for specs inside code.

Thread Thread
 
flutterclutter profile image
flutter-clutter

If you use comments as way to communicate with other developers working on the same code base and have no real communication channel outside of that, then I can better understand the necessity!

Thread Thread
 
jeremyf profile image
Jeremy Friesen

We're both looking at the same "elephant" but from different perspectives. The code is the most accurate representation of the product. It says exactly what the product is. There will invariably be cases where the intention of the code will be hidden in a private Slack channel, a lost email, or even an unrecorded Zoom meeting.

The code is the product and provides the most reliable place to "pin" a way finding comment/annotation.

Thread Thread
 
jeremyf profile image
Jeremy Friesen

Specs inside code are…treacherous. A URL in the code to that spec? Gold!

Collapse
 
polterguy profile image
Thomas Hansen

Good point, but I was speaking of the ability to runtime extract semantic data about what the code does, not reading the code itself ...

But you've got a very good point ...

Collapse
 
mindstormer619 profile image
Siddarth Iyer

I intend to write a full post about this sometime, but here are my thoughts in summary.

Self-documenting code doesn't exist because the purpose of documentation is different from what clean code gives you. Cleanly written code makes it trivial to understand how the code functions β€” nothing surprises you, the code isn't hard to follow or constructed of spaghetti, chunks of it fit neatly in your memory and it doesn't require you to go back-and-forth too often. You understand both the implementation and the abstraction quickly and cleanly. It exposes all of the how and most of the what, but what it doesn't necessarily do is explain all of the why.

Sure, well-written clean code with properly named functions and properties etc. can help expose the why, but it still requires you to do several iterative readings in any sizeable codebase to grasp the original business intent β€” namely, why does this code exist at all? What purpose does it serve?

That's where documentation steps in. Documentation should expose as much of the why as possible, and some of the what, without focusing at all on the how, since how it is implemented is quite literally implementation detail, and is subject to change even without the original business intent changing. The why changes very rarely, and also requires the least comprehensive documentation (the type of documentation that Agile tries to avoid) which is quick to read and grasp.

In my experience, pretty much every programmer I've met who has clamored for "don't write documentation, write self-documenting code" has parroted this statement because they didn't want to spend the time it takes to write documentation in the first place, not because they genuinely believe trawling through the code trying to tenuously grasp the intent of its writing is better than reading a short document about it.

Collapse
 
jackmellis profile image
Jack

I've always found this to be a pretty egotist attitude. "My code is so good you shouldn't need help to understand it".
I've learnt there's a balance in commenting. There are really 3 use cases:

  • you need to explain the "why" of a function or a piece of code. Not the "how" or the "what", which should be clear from the code
  • a function has very complex or business-specific logic and it's 100x easier to talk the reader through what is happening than to just hope they can follow all the branches and call chains
  • you need to explain/excuse something stupid of illogical with how a 3rd party library or api works. If I find myself in this situation I often consider whether I'm using the best library for the job...
Collapse
 
flutterclutter profile image
flutter-clutter

And what is not egoist about thinking that a comment is so good that every reader should get what the corresponding code does? If you know by yourself that your code might be hard to read, then why don't you refactor it instead of adding an explanation? Reminds me of products with manuals that nobody reads. Always asked myself why they don't make the product of intuitive usage instead of writing a manual. Apple was the first big company to understand this.

  • The "why" is an artefact from the business domain and not from the implementation domain. The stake holder should know why he wants things this way. This is nothing that should be explained in the code.
  • Is it, though? If a business-specific logic is complex then it needs to be documented outside of the code. If the code itself is well-written but describes a complicated and ugly business-logic then why would you describe the code?
  • If a library has a complex logic then you need to wrap the library calls in your own class and make the wrapper as understandable as possible. You should also make an interface from it so you can easily switch to a different library when you get the hang of it. The caller of the interface shouldn't care about ugly 3rd party libs.
Collapse
 
moopet profile image
Ben Sinclair

During your work, probably you faced a function that you asked yourself: "-What the hell is even that?"

Yes, but that's not so much because I didn't comment the code, as that I didn't write clean, readable code in the first place. If the bit of my wetware that needed to light up at the time to tell me to comment it had actually been doing its job I'd have written it better anyway!

Mostly, I'm on your side in this. I like commenting things. I like having a standard doc comment at the top of every function, even if it's "obvious".

However, I've had colleagues who don't, and their arguments are usually something like, "now you have to update two things", i.e. whenever you make a code change you need to make sure the comments and documentation match, and it's way too easy to forget. In fact, how many times have you seen the same comment repeated because someone's copied a component from one file to another to use as a kind of boilerplate, even though it has an entirely new purpose now?

Collapse
 
snoopydev profile image
SnoopyDev

I'm so happy to see so rich discussion here in my article! Thank you so much devs.

Collapse
 
pinotattari profile image
Riccardo Bernardini • Edited

I guess that it is necessary some context, but to make it short:

  • High-level comments explaining what (not how) a specific function/package/class does is useful since allows the reader to orient itself in the code. Also, that is the kind of information that does not change frequently, so the probability that the comment gets stale is minimal
  • Comments to explain a non-trivial algorithm (e.g., the Euclidean GCD) can be useful, also to keep track of the conditions that are true at every point. Here assertions, loop invariant and similar stuff are your friend since they allow you to document what is going on in a way that the compiler can check, minimizing the probability of getting stale
  • Comments that explain why something is done. This is quite uncommon, in my experience, but it happens every now and then. Maybe there is a counter-intuitive way to do the things or maybe there is a strange check that seems useless and you would be tempted to "optimize it away," but it actually takes care of some borderline case that it is not easy to imagine.
  • The kind of comments that are wrong are maybe the most common ones that try to clarify what would had been clear if the code were written nicely. For example, instead of writing this
int d;
int c;      /* total cost */
int e[7];  /* daily expense */

for (d=0; d<7; d++) // Loop over the week
{ c = c + e[d];}
Enter fullscreen mode Exit fullscreen mode

better this

int week_day;
int total_cost;
int daily_expense[7];

for (week_day=0; week_day < 7; week_day++)
{  total_cost = total_cost + daily_expense[week_day]; }
Enter fullscreen mode Exit fullscreen mode
Collapse
 
tqbit profile image
tq-bit • Edited

I don't think leaving out comments excludes beginners. Quite the opposite. Writing too much waste into your code sets a bad example. And I've seen more redundant comments than useful ones in codebases I worked on (including my own).

My favourite to this day is:

// Initial request to vendor API
// ... 3 uninformative lines explaining what's going on
// TODO: If fails, figure something out. First request should never fail
function getStuffFirstTime() { ... }
Enter fullscreen mode Exit fullscreen mode

Does this look beginner friendly to you?

If you do want to comment stuff, please write proper JavaDoc / JSDoc / whatever-Doc. That's what it's there for.

  • Use @desc, @property/s and @returns.
  • Give a 2-liner about what your code does if you must
  • If you must use a comment inline, you will probably be better off refactoring your function or method anyway

And if you want to go bonkers, at least be so kind and do so in your automated test suites. You can even use @see in your production code base. And everybody wins.

Collapse
 
adrvnc profile image
Adrian Carter

Great article! Writing comments about what your code does can be helpful. But, I've learned that writing too many comments can be excessive.

Collapse
 
lexlohr profile image
Alex Lohr • Edited

I use comments in the code very sparingly and only to tell the story about the reasoning behind the code if that should not be obvious even to a beginner.

I'm very liberal with elaborate JSDoc blocks above my API interfaces, though.

Collapse
 
darthbob88 profile image
Raymond Price

The line I always take with students I mentor is that I don't need a comment to tell me what the code is doing, because I can read the code just fine, and most of that can be encoded in variable and function names, like frobnicate_the_input_array() and input_array_to_frobnicate. I need comments to tell me why it's doing that, and particularly why you're not doing it a different way.

"But requirements and statements of purpose don't belong in the code! They should be in other requirements documents." As a developer, I have ready access to the code I'm working on, not the requirements docs, and I especially don't have anything to connect frobnicate_the_input_array to a requirements doc saying that the input array needs to be frobnicated, or to a later decision saying that it needs to be frobbed in reverse order. That's what I need a comment for.

Collapse
 
chasm profile image
Charles F. Munat

This is a straw man argument. I don't know a single developer who never writes a comment. The argument is over whether you should comment everything, or just when a comment is needed, which is inversely proportional to the amount of clean, readable, and easily understandable code you write. The argument is not that you should never comment your code.

If you have to make up a straw man argument, then your credibility as an authority is undermined.