Where technology meets something or other

Swift Holy War: Comments are not an Anti-pattern

Got into a debate yesterday about this write-up by developer Andrew Warner. His “Beware the Siren Song of Comments” suggests that developers delude themselves because comments detract from code quality:

Comments decay. They aren’t compiled, and they’ll never get executed at runtime. If they become out of date or incorrect, no test is going to fail and no user is going to complain. Programmers work around them out of fear that “somebody might need this comment or it might provide some value in the future”, pushing them along far after they’re useful (if you can even argue that they were useful in the first place).

His recommendation?

[Y]ou don’t have any excuse to write comments. Give these methods a try, and I promise you’ll have a cleaner codebase that’s easier to maintain.

I don’t claim that comments should counterbalance bad design decisions like poor naming or flawed algorithms. I do believe they play an important role in good coding practices — whether or not your code is meant strictly for internal use or to be consumed as APIs.

As I discussed on this blog a few weeks ago, there’s a big difference between the you writing code and “future you”, your team, and anyone else reading code. Here’s what I have to say about commenting in Swift Style:

Comment, comment, comment and while you’re at it, write tests. Tests can save you the whole “What I was doing here?” because you can just look at what is broken and what you expected to work.” It’s better to write less code and make up that time by explaining the code you did write better.

Sure, it helps to leave in a “TODO:” where it counts (“the performance is really bad here”) but while you’re at it, try to leave a few ideas about what exactly is going wrong, and what hypotheses you have rolling around your soon-to-be-extinct neurons. Past you understood things. Future you is clueless.

It always costs less to fix things in the past because you’ve invested in uploading the full design into your brain. Re-upping that design and getting back up to speed involves huge penalties.

Comments can explain the non-obvious, the tricky, or the counter-intuitive to a reader: This is intentional. This thing affects that thing. This data is not validated at this stage. Responsibility for this process now passes to this delegate. Comments enable you to establish what your assumptions are at which points in code and they allow you to comment on design qualities.

Comments create a record of intent. They may mention approaches that were tried and why they were abandoned. They may discuss how design decisions came to be — what paths were explored and why some of those paths weren’t chosen in the end.

Comments allow you to colocate thoughts with the code they refer to. Commit messages are helpful but I don’t think a commit message is the proper place to document workarounds or unexpected behavior. Plus version control history may not travel with source code, if the code is reused in another project.

Comments provide a bread crumb trail of design decisions that aren’t kept in working memory and cannot be intuited just from reading code or scanning tests. Unexpected complexity is one of the things you’ll want to comment about. In fact, commenting on the unexpected is important because the alternative is essentially a bug.

Structured comments add another layer of utility, supporting API consumption. Markup allows these special comments to automatically convert into highly formatted local documentation supporting another set of readers beyond code review, debugging, maintenance, and enhancement.

Comments don’t just paper over bad design and coding. They document a process of making code right, supporting future reading and modification. Good comments reduce the mental effort needed by readers each time they review your source, enabling them to focus more on specific tasks like “how to I add this feature” rather than “what the hell was going on here”. While good code can at times be “self documenting”, that doesn’t mean it always is (or even usually is) self documenting.

As stepping stones to “past you”, good comments document what you were thinking and why you did things the way you did. Your past design decisions should never be a mystery or a burden placed before future readers of your code no matter how brilliant or insightful you expect them to be.

14 Comments

Wooo! Holy war!! 😛

I’ve been perusing Clean Code by Uncle Bob the past couple days, skipping over the boring java bits. I like what he said about comments: “The proper use of comments is to compensate for our failure to express ourselves in code. Note that I used the word failure. I meant it. Comments are always failures. We must have them because we cannot always figure out how to express ourselves without them, but their use is not cause for celebration”

My take on this is that sometimes we just hit a limit on our ability to express our intent through code. And that’s okay, it happens. In these cases, leave some comments. But we shouldn’t celebrate comments, i.e. push a culture that encourages us to leave comments constantly. And since it is a failure to communicate, we should continue to hone our craft so that we can in the future write code that needs less comments.

I would amend this slightly: The need for comments represents a failure to represent intent in code to the necessary audiences in the particular language that you are using.

In a large code base, there will be engineers with a wide range of expertise, and clear intent for more sophisticated uses may not be the same for an intermediate engineer and advanced engineer reading the same code.

My view: comments-puritanism is amateur-hour. It’s a silly sport pursued mostly by people who don’t have to maintain real-world products across generations of operating systems, SDKS, compilers, languages.

In the real world, comments are both necessary and desirable. If someone doesn’t comment their complex algorithms, gotchas, workarounds, cunning tricks, hacks and to-dos, then I don’t really want to be reading that code, let alone work on it.

Programming is a means to an end. It’s not performance art. The goal is to MAKE SOMETHING, and keep it working, in a cost-effective, time-effective way.

Maybe you should be avoiding complex algorithms, gotchas, workarounds, cunning tricks, hacks and to-dos. If your code is filled with these, then there is a bigger problem.

I comment very frugally with the exception when I’m writing the documentation comments in a public API. Otherwise, a comment is there to point out a deliberate break in my normal coding conventions—something I rarely do.

If you don’t have gotchas, workarounds, cunning tricks etc. when working with iOS, then your app probably isn’t doing very much. And I doubt we’re here to talk about the most basic, beginner aspects of iOS/Mac development.

The real world of professional iOS development is full of complexity, complications, workarounds, frustration. I invite you to look at Stack Overflow some day. :p

Just try and write a complex, automatically resizing, adaptive layout using Auto Layout, Size Classes, Container Views and embedded navigation. Throw in a few Stack Views. If your code doesn’t have comments explaining all the myriad gotchas and issues across iOS 9 and iOS 10, then it’s not professional code, IMO. It’s certainly not maintainable.

I certainly don’t want to spend hours reading your code and reverse-engineering your decision-making, when you simply could have TOLD me WHY you are performing some action in such a way. e.g. explaining your workaround to make that Stack View lay itself out properly in iOS 9, and why it’s not needed in iOS 10.

At the very least, professional code needs to explain the failure conditions: what can go wrong, and why, and what assumptions are made.

Example: if you are making network connections to specific types of servers, your comments had better mention the various non-programming requirements (such as supported TLS versions, plist attributes, the differences between those attributes in iOS 9 and 10). Otherwise, your code is effectively useless to anyone else, and probably useless to you in two years time when Apple breaks something again, like they did in iOS 10.

Another example: the keychain system broke in iOS 10. I hope for your sake, and that of your customers, that you properly document the workarounds required, including the additional plist settings, and the reasons why your code is doing extra work to reconstruct legacy data in to a new format.

You already accept that some comments are needed: that’s why you use (hopefully) descriptive names for your classes, functions, variables etc. You are using type naming as comments. But of course, that’s a very limited form of communication. It’s VERY hard to signal intent through an API alone, and even harder to signal the complications.

Trying to tame complexity, or are pretending complexity doesn’t exist by reducing or eliminating the commentary, doesn’t reduce the complexity. It just makes it much, much harder to deal with that complexity.

That approach may make your code look superficially neat and clean, but it doesn’t actually help anyone, and it doesn’t make for better products, IMO.

I’m confident in my bonafides as a professional software developer, having done so for over 20 years, and being responsible for the maintenance of codebases that both lives and livelihoods depend upon.

In the case of having to special case code for different operating systems. I use a workaround code block that would be of the following form (did not check if this compiles)

Having said that, looking through the apps I write under my own name, I am switching on the operating system version a few times per app or framework. It just isn't this massive problem on iOS where things get broken from version to version in such a way that the workaround doesn't work OK on the preceding version. For instance, NSBackgroundColorAttributeName stopped doing the right thing with attributed strings back in iOS7, so I changed how I rendered, but I didn't have two code paths for pre iOS7 and post iOS7. Part of this is I remove workaround code when I drop old OS versions. Regardless, I don't use comments to explain workarounds, I use properly compiled code blocks that will let me know when I can drop the workaround.

I’m with Dave on this one. I can pretty much guarantee that my understanding of language X is different to yours. Nothing is worse than coming blind to a piece of code and then figuring out both what it is doing and why it was done that way. Even the briefest of comments can save oodles of time and guesswork. Do bad comments exist? Yes, of course but in my book no comments are just as unhelpful.

Since Bob Martin has been writing production code for decades, “amateur-hour” is perhaps over the top. Tags like “puritanism” and “real world” … these are amusing and evocative … but are they helpful? Perhaps not.

It turns out that many comments, and in some code perhaps most, are there to clarify what the code does. It is often possible to let the code better speak for itself by making it more expressive. One particular advantage to this is that code is unambiguous and it’s quite difficult to write English (or your human language of choice) unambiguously.

Now Bob writes in a more commanding moralistic tone than I prefer. I like to say that I was taught that a comment is the code’s way of asking to be made more clear. When I feel the need to write a comment, I try to make my first reaction be to see how to make the code better. Often, when I do that, the need for the comment disappears.

Other times, when I’ve made the code more clear, the need for the comment changes. It’s usually quite easy to make the code say what it’s doing quite clearly. It can be more difficult to make the code way why it’s doing it. To pick an example, imagine that for some reason we write a sort function (probably no one would do that today but go with me) and we decide to use QuickSort. (I’d never do that, I find it hard to get QuickSort right. Yay, libraries. But I digress …)

So we write this function sort(anArray) and we put a comment on it:

// QuickSort

function sort(array, span) {
...
}

We think, is that comment asking me to make the code more clear? We see that it is. So we rename the function to get:

// QuickSort

function quicksort(array, span) {
...
}

And it becomes pretty clear that we don’t need that comment. We’d be tempted to remove it: certainly as written it doesn’t help any more. Or … is there something else that we want to communicate? Perhaps we’d like to put a reference to the article where we got the code? Perhaps we’d like to say why we chose QuickSort over other alternatives? Those ideas, whatever they are, are difficult to express in code, so it can make sense to leave them as comments.

My conclusion on this topic, for now, having spent years thinking about it and planning to think about it again today, is:

When I feel the need for a comment, I first try to express the comment’s idea directly in the code. I can usually do this, and it usually improves the code to the point where the comment isn’t needed. I emphasize improves. If it made it worse, I wouldn’t do it.

Then, since I’m conveniently thinking about this code, I think about what else I might want to tell someone about it. Whatever those ideas are, if I can express them in code, I do so. If there are still things I’d like to say that I don’t see how to say in code, those are the things I might put in a comment.

One more thing … when discussing this topic, it is much much easier to reach understanding and agreement when we look at actual code and actual comments. Otherwise, we’re just waving our flags and not working toward common understanding.

Code should explain _what_ it’s doing with minimal or no commenting. If you feel the need to add a “what” comment, first look to better naming, formatting, and code organization. Use comments only for what code cannot explain.

// Here item is a Foo, despite the inconvenience of converting from Bar, because
// in lib X sends us a Foo is situation Y, and the Bar conversion has significant
// materialization costs. See https://further.explanation.here
func process(item: Foo)

If found with Obj-C I wrote fewer comments as the language favoured longer terms. Now with Swift I find myself writing more comments again. But it’s mostly to explain architecture; where and why a particular class/struct would be used.

You can go overboard with comments, yes, but in all my (beard-greying) years while I saw plenty of uncommented code that was written 50/50 by those who cared about good software and those who didn’t, I never saw well commented code by someone who didn’t care.

The virtue of a comment depends on context: who will be reading it, what the code is for (research, throwaway, production, mars-rover), etc. Let the punishment fit the crime. Programming can often be both Art and Enigneering.