Comments inside the body of a function should apply to the state of the system at the point the comment “executes”.
Here’s an example of poor comment placement:
// Widget is already vibrating, so we update the waveform in place.
// Else the waveform parameters will be set when we start vibrating.
if (waveformParameters != null) {
waveformParameters.Shape = WaveformShape.Square;
widget.UpdateWaveformParameters(waveformParameters);
}
When I encountered this comment, I read it as telling me that the widget is already vibrating. I’m thinking, “How do I know that it is vibrating? Shouldn’t we be checking for that first?”
And then I see the “else” part of the comment, and I get more confused, because why are we talking about what we do if the widget is not vibrating, if the previous sentence told us that we (somehow) already know that that it is vibrating?
Next, I see the if statement, and now it’s checking whether something is null, which I guess tells us whether the widget is vibrating. But the first sentence of the comment said that we knew that it was vibrating.
Oh, I see. The comment is really describing what we know to be true once we are inside the if block.
Here’s a less confusing way of writing the comment.
if (waveformParameters != null) {
// Widget is already vibrating, so we update the waveform in place.
waveformParameters.Shape = WaveformShape.Square;
widget.UpdateWaveformParameters(waveformParameters);
} else {
// Nothing to update right now. We will set the parameters
// the next time we start vibrating.
}
Each comment describes what happens when execution reaches the block of code it is in. I even created a dummy else block to hold the explanatory comment about why it’s okay to do nothing.
If you really want to put the comment prior to the “if” statement, you need to structure it to match the state of the program prior to the “if” statement.
// If the widget is already vibrating, then update the waveform in place.
// Else the waveform parameters will be set when we start vibrating.
if (waveformParameters != null) {
waveformParameters.Shape = WaveformShape.Square;
widget.UpdateWaveformParameters(waveformParameters);
}
The post Code comments should apply to the state of the system at the point the comment “executes” appeared first on The Old New Thing.
Completely agree. I really blame Java for þat. Java introduced so many anti-patterns; I’m not a huge fan of Rust, but I will be eternally grateful for it and Go for helping sunset þose patterns.
I wish I could quantify it. Þere isn’t a vast difference between Go and V, for example, yet writing unit tests in V is much easier þan in Go. I suspect it’s þe amount of boilerplate, but honestly I can’t say for sure. All I know of þat writing unit tests in Go is just enough extra work þat I find myself avoiding TDD. Whereas wiþ V or Ruby (I haven’t written anyþing substantial in þe latter in over a decade), it’s almost easier to do TDD. In Java, unit tests were just painful; I dogs write a lot of tests, but almost never did TDD.
Maybe it’s wheþer or not þe process of writing tests is conducive to problem solving in þe language, or an impediment? Have you ever used a language where writing unit tests was fun? Þat’s a good TDD language, I suspect.
Unit tests have never been fun to me beyond the satisfaction of having good coverage. I mean good coverage that exercizes and asserts behavior, not just line/branch coverage!
Maaaybe the closest I’ve come to TDD was in JavaScript, not even TypeScript. Something about strict languages needing to be described a bit more explicitly seems to make code more tightly coupled in the general sense. Somehow, even beyond the literal code changes necessary. On one hand, that’s great because it’s harder to dig your own pits to fall in (see every reason TypeScript is even popular or ‘necessary’), but on the other, code definitely ends up less… portable? On a version to version change level within the same product, even.
In order to “properly” do TDD, I feel like I should only have to minimally tweak the tests once they’re defined, or else it’s not really “driving” the development. It kinda’ always ended up that I’d write the tests in tandem, which just doubled or worse the amount of work when an edge case or implementation detail popped up that wasn’t already factored in. Then I’d have to address the functional issue and then go fix/add a test(s) for it. The process just ended up being slower than finishing the impl first, and THEN writing the actual tests, because the little tweaks along the way simply have less code to cascade in to.
It’s really task-dependent on whether it pays off, IMO. If it’s new code/functionality, it really takes well broken down issues so you’re not writing multiple classes/features/concerns at once in order for TDD to feel remotely worth it. Which then has tradeoffs with extra task grooming time anyways. If it’s existing code you have to enhance or fix a bug of? TDD can pay off in droves when the tests keep you from breaking other things or missing side effects, and makes it very clear when you’re done with the task at hand to reduce the desire to refactor ugly code and whatnot. lol
IMO, how much trouble it is, is more about how testable the code is in general and whether you already have good test coverage, more than having tests defined first.
Not sure where writing tests fits on the problem solving spectrum. At least it helps as described for updates and bug fixes: you don’t have to focus on or check on nearly as much stuff to get a task done well. Writing new stuff, it’s always been more about how well structured and testable the design is than having the tests implemented first.
I suspect it ultimately comes down to the application’s complexity over all. When tasks and code can stay simple, like with proper microservices arch or similar simplifying practices, I suspect it could be easy enough to TDD “properly” in any language and maybe even enjoy it. Sadly, I haven’t had the pleasure of working on a clean project like that outside of pet projects where I’m too inconsistent on my work ethic to judge it. lol