Do programmers need to write code comments?

Keywords: Programmer Code Style

"Don't comment on bad code - rewrite it." - Brian W. Kernighan and P. J. Plaugher

Nothing is more useful than well placed comments. Nothing is more capable of messing up a module than messy comments. Nothing is more destructive than old, misinformed comments.

Notes are not like Schindler's list. They are not "pure good". In fact, annotation is at most a necessary evil. If programming languages are expressive enough, or if we are good at expressing intentions in these languages, we don't need annotations at all - maybe not at all.

The proper use of comments is to make up for the failure we encounter when expressing our intentions in code. Note that I used the word "failure". I mean it. Annotation is always a failure. We can't find a way to express ourselves without notes, so we always have notes, which is not worth celebrating.

If you find that you need to write comments, think again to see if there is a way to turn over and express it in code. Every time you express it in code, you should praise yourself. Every time you write a note, you should make a face and feel your failure in expression.

Why should I try to belittle comments? Because notes lie. It's not always or intentionally, but it happens too often. The longer an annotation exists, the farther it is from the code it describes and the more it becomes completely wrong. The reason is simple. Programmers cannot insist on maintaining comments.

The code is changing and evolving. Move from here to there. They are separated from each other, rebuilt and United. Unfortunately, annotations don't always change - they can't always follow. Comments are often separated from the code they describe, and become more and more inaccurate. For example, look at the following comment and what the line of code it was intended to describe has become:

MockRequest request;
private final String HTTP_DATE_REGEXP = 
 "[SMTWF][a-z]{2}\\,\\s[0-9]{2}\\s[JFMASOND][a-z]{2}\\s"+
 "[0-9]{4}\\s[0-9]{2}\\:[0-9]{2}\\:[0-9]{2}\\sGMT";
private Response response;
private FitNesseContext context;
private FileResponder responder;
private Locale saveLocale;
// Example: "Tue, 02 Apr 2003 22:18:49 GMT"

In http_ DATE_ It is possible to insert other entity variables between regexp constants and their comments.

Programmers should be responsible for keeping annotations maintainable, relevant, and accurate. I agree. But I prefer to use my strength to write clear code and directly ensure that there is no need to write comments.

Inaccurate comments are much worse than no comments. They are full of nonsense. What they expect will never come true. They set old rules that need not and should no longer be followed.

There is only one real place: code. Only code can faithfully tell you what it does. That's the only really accurate source of information. Therefore, although sometimes comments are needed, we should also pay more attention to reducing the amount of comments as much as possible.

Comments do not beautify bad code

One of the common motivations for writing comments is the existence of bad code. We wrote a module and found it disturbing and messy. We know, it sucks. We tell ourselves, "Oh, you'd better write some notes!" no! It's best to clean the code!

Neat and expressive code with a small number of comments is much more decent than piecemeal and complex code with a large number of comments. Instead of spending time writing comments that explain the bad code you make, spend time cleaning that pile of bad code.

2 use code to explain

Sometimes, the code itself is not enough to explain its behavior. Unfortunately, many programmers think that very little code, if any, can explain well. This view is purely wrong. You want to see this:

// Check to see if the employee is eligible for full benefits 
if ((employee.flags & HOURLY_FLAG) && 
   (employee.age > 65))

Or this?

if (employee.isEligibleForFullBenefits())

Just think for a few seconds and you can explain most of your intentions in code. Many times, it's as simple as creating a function that describes the same thing as the annotation.

3 good notes

Some comments are necessary and beneficial. Let's take a look at some notes that I think are worth writing. But remember, the only really good comment is the one you try not to write.

3.1 legal information

Sometimes, company code specifications require legal notes. For example, copyright and copyright notices are content that must and has reason to be placed at the beginning of each source file.

The following example is the standard comment we placed at the beginning of each source file of the FitNesse project. I'm happy to say that the IDE automatically rolls up these comments so that they don't appear messy.

// Copyright (C) 2003,2004,2005 by Object Mentor, Inc. All rights reserved.
// Released under the terms of the GNU General Public License version 2 or later.

Such notes should not be contracts or codes. Whenever possible, point to a standard license or other external document instead of putting all the terms in a comment.

3.2 notes to provide information

Sometimes it's useful to use annotations to provide basic information. For example, the following comments explain the return value of an abstract method:

// Returns an instance of the Responder being tested.
protected abstract Responder responderInstance();

Such annotations sometimes work, but it's better to use the function name as much as possible to convey information. For example, in this case, as long as you rename the function responderBeingTested, the comments are redundant.

The following example is slightly better:

// format matched kk:mm:ss EEE, MMM dd, yyyy
Pattern timeMatcher = Pattern.compile(
 "\\d*:\\d*:\\d* \\w*, \\w* \\d*, \\d*");

In this example, the annotation indicates that the regular expression is intended to match a time and date formatted with a specific format string via the SimpleDateFormat.format function. Similarly, if you move this code to a class that converts date and time formats, it will be better and clearer, and comments will become superfluous.

3.3 interpretation of intention

Sometimes, annotations provide not only useful information about the implementation, but also the intention behind a decision. In the following example, we see an interesting decision reflected in the comments. When comparing two objects, the author decides to place his class higher than anything else.

public int compareTo(Object o)
{
 if(o instanceof WikiPagePath)
 {
  WikiPagePath p = (WikiPagePath) o;
  String compressedName = StringUtil.join(names, "");
  String compressedArgumentName = StringUtil.join(p.names, "");
  return compressedName.compareTo(compressedArgumentName);
 }
 return 1; // we are greater because we are the right type.
}

The following example is even better. You may disagree with the programmer's solution to this problem, but at least you know what he wants to do.

public void testConcurrentAddWidgets() throws Exception {
 WidgetBuilder widgetBuilder = 
  new WidgetBuilder(new Class[]{BoldWidget.class});
 String text = "'''bold text'''";
 ParentWidget parent = 
  new BoldWidget(new MockWidgetRoot(), "'''bold text'''");
 AtomicBoolean failFlag = new AtomicBoolean();
 failFlag.set(false);

 //This is our best attempt to get a race condition 
 //by creating large number of threads.
 for (int i = 0; i < 25000; i++) {
  WidgetBuilderThread widgetBuilderThread = 
   new WidgetBuilderThread(widgetBuilder, text, parent, failFlag);
  Thread thread = new Thread(widgetBuilderThread);
  thread.start();
 }
 assertEquals(false, failFlag.get());
}

3.4 interpretation

Sometimes, annotations can also be useful to translate the meaning of some obscure parameters or return values into some readable form. In general, it is better to make the parameter or return value itself clear as much as possible; But if a parameter or return value is part of a standard library, or code that you cannot modify, code that helps explain its meaning will be useful.

public void testCompareTo() throws Exception
{
 WikiPagePath a = PathParser.parse("PageA");
 WikiPagePath ab = PathParser.parse("PageA.PageB");
 WikiPagePath b = PathParser.parse("PageB");
 WikiPagePath aa = PathParser.parse("PageA.PageA");
 WikiPagePath bb = PathParser.parse("PageB.PageB");
 WikiPagePath ba = PathParser.parse("PageB.PageA");

 assertTrue(a.compareTo(a) == 0);  // a == a
 assertTrue(a.compareTo(b) != 0);  // a != b
 assertTrue(ab.compareTo(ab) == 0); // ab == ab
 assertTrue(a.compareTo(b) == -1);  // a < b
 assertTrue(aa.compareTo(ab) == -1); // aa < ab
 assertTrue(ba.compareTo(bb) == -1); // ba < bb
 assertTrue(b.compareTo(a) == 1);  // b > a
 assertTrue(ab.compareTo(aa) == 1); // ab > aa
 assertTrue(bb.compareTo(ba) == 1); // bb > ba
}

Of course, this also runs the risk that explanatory notes themselves are incorrect. Looking back at the above example, you will find how difficult it is to confirm the correctness of comments. On the one hand, it shows how necessary interpretation is, on the other hand, it also shows that it has risks. Therefore, before writing such comments, consider whether there is a better way, and then double carefully confirm the correctness of the comments.

3.5 warning

Sometimes, comments that warn other programmers of certain consequences are also useful. For example, the following comments explain why a particular test case should be closed:

// Don't run unless you
// have some time to kill. 
public void _testWithReallyBigFile()
{
 writeLinesToFile(10000000);

 response.setBody(testFile);
 response.readyToSend(this);
 String responseString = output.toString();
 assertSubString("Content-Length: 1000000000", responseString);
 assertTrue(bytesSent > 1000000000);
}

Of course, most of us now use the @ Ignore attribute with an appropriate explanatory string to close test cases. For example, @ Ignore("Takes too long to run [2] "). But in the days before JUnit 4, it was customary to underline the method name. If the notes are persuasive enough, they will be very useful.

Here is a more troublesome example:

public static SimpleDateFormat makeStandardHttpDateFormat()
{
 //SimpleDateFormat is not thread safe, 
 //so we need to create each instance independently.
 SimpleDateFormat df = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z");
 df.setTimeZone(TimeZone.getTimeZone("GMT"));
 return df;
}

You may complain that there will be better solutions. I would probably agree. However, the above comment is absolutely reasonable. It can prevent an eager programmer from using static initializers in the name of efficiency.

3.6 TODO notes

Sometimes it makes sense to put a to-do list in the source code in the form of / / TODO. In the following example, the TODO annotation explains why the implementation part of the function does nothing and what it should be in the future.

//TODO-MdM these are not needed
// We expect this to go away when we do the checkout model
protected VersionInfo makeVersion() throws Exception
{
return null;
}

TODO is a kind of work that programmers think should be done but haven't done yet for some reasons. It may be to remind to delete an unnecessary feature, or to ask others to pay attention to a problem. It may be asking for a good name, or suggesting changes to events that depend on a plan. Whatever the purpose of TODO, it is not an excuse to leave bad code in the system.

Nowadays, most good ides provide special means to locate all TODO annotations, which seem to be indelible. You don't want the code to become a pile of garbage due to the existence of TODO, so you should check it regularly and delete what you no longer need.

3.7 magnification

Annotations can be used to amplify the importance of something that seems unreasonable.

String listItemContent = match.group(3).trim();
// the trim is real important. It removes the starting 
// spaces that could cause the item to be recognized
// as another list.
new ListItemWidget(this, listItemContent, this.level + 1);
return buildList(text.substring(match.end()));

3.8 Javadoc in public API

Nothing is more useful and satisfying than a well described public API. Javadoc in the standard Java library is an example. Without them, writing Java programs becomes difficult.

If you are writing a public API, you should write a good Javadoc for it. However, keep in mind the other recommendations in this chapter. Like other comments, Javadoc can be misleading, inapplicable, or provide error information.

4 bad notes

Most comments fall into this category. Usually, bad comments are the support or excuse for bad code, or the correction of wrong decisions, which is basically the programmer's own words.

It is recommended that you read the way to code neatness, which is explained in detail in Chapter 4.

This book can be roughly divided into three parts. The previous chapters introduced the principles, patterns, and practices of writing clean code. There are quite a lot of sample code in this part, which is quite challenging to read. After reading these chapters, you are ready to read Part 2. If you stop here, I can only wish you good luck!

Part 2 takes the most time. This section includes several case studies of increasing complexity. Clean up some code in each case -- turn problematic code into less problematic code. This part is extremely detailed. Your mind should jump between explanation and code snippets. You have to analyze and understand the code and figure out the context of each modification.

Your hard work will be rewarded in part 3. There is only one chapter in this part, which lists the enlightenment and inspiration from the above case study. As we scan and clean up the code in the case, we record each operation reason as an inspiration or inspiration. We try to understand our response to reading and modifying code, and try to understand why we feel like this and why we act like this. Results a set of knowledge base describing the way of thinking when writing, reading and cleaning code was obtained.

If you don't study hard when reading the case study in part 2, this knowledge base may not be worth much to you. In these case studies, each revision is carefully marked with the label of relevant enlightenment. These labels are marked in square brackets, such as: [H22]. From this, you can see in which environment these revelations are applied and written. Revelation itself is not worth money, but the relationship between revelation and the specific decision to clean up the code in the case study is valuable.

If you skip the case study section and only read parts 1 and 3, you're just reading another "feel good" book about writing good software. However, if you are willing to spend time thinking about those cases and follow them step by step - from the author's point of view and force yourself to consider problems in the author's thinking path, you can have a deeper understanding of these principles, models, practices and enlightenment. In this way, it's like a person who has mastered the technology of cycling, and the bicycle is like an extension of his body; For you, the principles, patterns, practices and Enlightenment of clean code introduced in this book become your own skills, not "feel good" knowledge.

Posted by pdn on Sun, 31 Oct 2021 23:30:07 -0700