So train people to write better comments, because although code doesn't lie (questionable as that is since the intent behind the code and what it does doesn't always align) it can be literally the worst implantation of an algorithm imaginable, and so we teach people how to code.
Teaching people how to comment is a skill that's just as important and nothing turns me off a project faster than the "code is its own documentation" mantra.
This is misunderstanding the point. Code cannot lie. Reading code (correctly) gives you a correct understanding of the state of the system, no matter how clear or unclear it is.
Reading comments only tells you what the person who wrote the comment believed. It does not tell you anything in particular about how the system behaved, you have to trust the other human beings (in general a long succession of them) to have understood it correctly. And any one of you can mess that up.
Saying good comments have value is fine. But their ability to lie is unique; code doesn't have that misfeature. You can't make comments lie-free by fiat, for the same reason that you can't train your developers not to write bugs.
Given that, IMHO comments have limited value. Don't be a zealot in either direction, but when debugging hard problems, train yourself to ignore the lying comments.
While code gives you a correct understanding of the state of the system, it doesn't give you any understanding of why the system is in that state. That's the point from the OP. Code tells you what a system is doing, not why it was designed to be that way. Comments that try and explain what a system is doing can absolutely be wrong and therefore problematic, but there's no way for code to convey the context in which it was implemented a particular way, which is what comments are for.
Code without comments only gives half the story. It gives you the what, not the why.
The point is not to fetishize comments. Write a comment if you feel something needs explanation, but don't freak out about "uncommented code" as a smell or whatever, when reading the code should have been your first step anyway.
And to repeat: when debugging, train yourself to ignore the comments. They will absolutely lie to you.
Forcing developers to write comments[1] just to get code merged seems like the best possible way to ensure those comments are lies.
[1] Which, it's important to point out since you used the term "CI", are fundamentally untestable. There's absolutely no way to ensure a comment is correct. A CI smoke test at the very least verifies code builds and doesn't break pre-existing cases. No such validation is even theoretically possible for comments.
This is a good point, and I'm not arguing with it, but I don't think the words "code cannot lie" are the right way to express it.
Code can deceive even if, definitionally, the code states what the computer will do. So code most certainly can lie.
[By analogy, you'd still feel lied to if someone told you something that is technically correct but very misleading: "(Me) It's going to rain tomorrow." → tomorrow comes → "(You) It isn't raining today!" → "(Me) It is raining, in Japan. I didn't say it would rain here."]
I suppose it depends on whether you're considering what the code communicates to the machine or what it communicates to a person.
How about "code does what it does but comments say what it might have once, and possibly still might do?"
You can have your pipeline regression test code, but not comments. Just recently my mentee found some of my code where I had changed the code but not the comment. I'm a horrible human and wasted his time. If that comment hadn't existed the code might have taken a moment to understand but as it was, he wasn't sure which was the intent.
It sounds like we have had similar experiences and have come to similar conclusions.
Learning to code, learning to comment and learning to log are all ways to learn to communicate and represent a means to risk-reduction and importantly, to cost-reduction.
Here's a free method that's twice as much work but produces great results:
immediately after composing, for each step and nested step, write a line or two of what its place in the code is for. Write it as though the code is broken and you're following the imaginary line threading through, explained as if to your rubber duck.
Then, having written out the business logic map, look at each written step and see if they're just a description of the logic "iterates through file, passes hits onto nextFunc" and you can safely delete those. They're just glue, really, holding processes together.
What you'll have left is skeletal comments that are restricted to "we did this because this stackoverflow post gave the solution" as well as those mental maps of the solution in your head, which is really what comments are for, future programmers to grok your state of mind and thus better implement their code changes.
And also, the code can be clear and tell the truth along with the comment, while still being unhelpful:
// Add with adjustment factor
function AddWithAdj(int a, int b) { return a + b + 12345 }
When the function was written, everyone probably knew what the adjustment factor was for and how it was determined, but years later, someone's going to look at that code and have no idea.
>When the function was written, everyone probably knew what the adjustment factor was for and how it was determined, but years later,
>someone's going to look at that code and have no idea
Well, to be fair that adjustment factor could be refactored to use a usefully named constant, with a helpful comment.
static const int ZEN_ADJUSTMENT = 12345; //When I wrote this only myself and God knew what this value meant, now...
int AddWithAdj(int a, int b) { return a + b + ZEND_ADJUSTMENT; }
static const int ZEN_ADJUSTMENT = 12345; //When I wrote this only myself and God knew what this value meant, now...
int AddWithAdj(int a, int b) { return a + b + ZEND_ADJUSTMENT; }
And now the maintainer is going to wonder why the originally programmer defined ZEN_ADJUSTMENT just above this function, but actually used ZEND_ADJUSTMENT (which is apparently defined somewhere else in the code). By design or typo!? :-)
Sure, but I don't think anyone would call this a useful comment - it's exactly the kind of comment which should be avoided.
Comments don't need to describe what the code does, but if there's an unexplicable line which handles an obscure edge case you better add a comment or even you won't remember why that line exists 3 months later.
Because comment is out-of-date. That’s technical debt. The comment is not the problem: the developer not updating it is.
You wouldn’t leave out-of-date code in a system, it would cause bugs. Why would you leave out-of-date comments? Oh, because you don’t like to write. Your strength is math and code, not writing. Now we get to the heart of the matter and not some ruse like “code is self-documenting.”
i mean, this is actually perfectly clear—in the sense it’s clearly and obviously a mistake either with the comment and function name or it’s implementation. the example isn’t great because it’s devoid of context that might indicate what specifically is wrong with it.
there are many standard means that should catch this: code reviews, unit tests, etc.
this stuff can obviously still sneak through, but i don’t think this is a good example of what folks are generally talking about here.
// Do does x, y, and z, in that order.
func Do(x, y, z func()) {
x()
z()
y()
}
Sometimes, as here, the comment correctly describes what the code should do. But the implementation does not agree with the comment. Relying on the comment, a developer can confidently fix the bug, or, at least, notice a discrepancy between the intentions, as described by the comments, and reality, as implemented by the lines of code. This is particularly important in complex code. Think: HTTP keep-alive handling, in which many factors, with varying priorities, have to be taken into account (e.g. client hint in the request header, current server load, overall server configuration, per handler configuration).
It’s also plausible that the function used to do x,y,z in that order, but then a new requirement came to do z before y. The code was changed, maybe a test was even added, but the comment was never fixed.
That’s the problem with comments (and all documentation really) - it gets stale and has no guarantee of being correct.
A better way is to write a test which would break if the order of x,y,z was wrongly changed. This way the order is guaranteed to stay correct forever, regardless of any comments.
I agree with your points regarding writing a test. It is one of the best ways, and the right way, to handle the issue.
That said, I think my toy example wasn't the best way to show some of the points I was getting at. Consider a real example, such as this function from file src/net/http/transport.go in the Go source:
// rewindBody returns a new request with the body rewound.
// It returns req unmodified if the body does not need rewinding.
// rewindBody takes care of closing req.Body when appropriate
// (in all cases except when rewindBody returns req unmodified).
func rewindBody(req *Request) (rewound *Request, err error) {
if req.Body == nil || req.Body == NoBody || (!req.Body.(*readTrackingBody).didRead && !req.Body.(*readTrackingBody).didClose) {
return req, nil // nothing to rewind
}
if !req.Body.(*readTrackingBody).didClose {
req.closeBody()
}
if req.GetBody == nil {
return nil, errCannotRewind
}
body, err := req.GetBody()
if err != nil {
return nil, err
}
newReq := *req
newReq.Body = &readTrackingBody{ReadCloser: body}
return &newReq, nil
}
When you work with code in or around this function, the existence of the comments tell you what the function guarantees, for example, regarding the closing of the request body. If at some point, there is a bug, it is more likely to be noticed because of the discrepancy between the intentions in the comment and the implementation in the code. Without the descriptive comment, an unfamiliar developer working on this code will likely not be able to tell if something is off in the function's implementation.
Also, as a user of code, it's nice to first know descriptively what guarantees a complex internal function promises from its comment. Then, if necessary, be able to verify those guarantees by reading the function body.
Of course, in an ideal scenario, a unit test is the best way to address things. But this this is a 22 line function unexported function in at 2900 line file, and sadly it appears this function isn't unit tested.
So, given how things are in practice, I argue that the presence of a comment, like here, can help with correctness of code.
Comments shouldn't be there if they are not maintained in same way code is, meaning there isn't shared understanding in the team about their importance. And important they will be if you actually maintain your code, or somebody else is. Adding few comments means you show respect for their time (and sanity) so they can focus on delivering changes or fixes instead of just grokking your crap.
Every time I see 0 comment in complex logic I attribute it to laziness of coder. And of course every time excuse is the same - its self-documenting, you see what its doing etc. But why, what are the effects elsewhere in the system and outside, what part of business needs this is covering, what SLA it tries to cover, what are overall goals etc. can be even impossible to grok from just code itself. World is bigger than just code.
I wouldn’t use the word ‘lie’ but plenty of Rails code I’ve seen has mystery effects and hard to follow coupling due to monkey patching and the like. Comments please.
You can't attach a debugger to the comments and figure out why your code is calculating that the circumfrence of a circle with radius 1 is 6 rather than 6.28...
You're proving the point of the person you are responding to.
pi = 3 isn't a lie, it's the truth of the program regardless of what any comment says.
But that is a lie. It's deceptive and misleading. The code states what the computer will do; nobody is contesting that or claiming otherwise. They were pointing out that, although part of what `pi = 3` communicates to a person (that the machine will assign the value 3 to the variable named "pi") is correct, another part of what it communicates (that 3 is close enough to the ratio between a circle's circumference and diameter for the task at hand) may be untrue – and thus a lie.
The programs that people work with in their mind are not the programs (the code) that the machine runs. What the machine does is definitely more important in the moment, but the models that live in the heads of people are more important in the long term. The code that the machine runs is just one implementation, a shadow cast by the real thing from one of many angles.
Code doesn't tell the full story either. And code can contain bugs so one cannot fully trust the code too.
If code/comments are out of sync it is likely that someone changed the code and forgot to update comments (which is possible to verify using VCS logs) or it may be that the comment stated the intention right but the code has a bug. Code+comments like a parity check to me - if they agree it is good, if not - I have to investigate if the comment or the code is correct.
Code can lie too. Example: poor naming of classes, functions and variables. A name can tell you it's doing one thing but it can be doing something different.