
Do not write code comments!
Introduction
Good day, the guard. I want to make a reservation right away that the name does not mean that I will urge not to write comments ever, any extreme in this world is most likely flawed. I just want to say that the desire to write a comment in any place almost always indicates a more important problem in the code, having dealt with which the need for commenting disappears.
Before starting, I want to say that I will give examples using Java, and a small piece of code (with a small addition) is taken from the project described here .
To understand the problem, turn to Wiki, and then move on to the examples:
Comments - explanations of the source code of the program, located directly inside the commented code.
Code Comment Example
public static void main(String[] args) throws IOException {
// "ua.in.link.rest.server" - name of package with classes for Jersey server
ResourceConfig rc = new PackagesResourceConfig("ua.in.link.rest.server");
// creating httpServer for url "http://localhost:8080"
HttpServer httpServer = GrizzlyServerFactory.createHttpServer("http://localhost:8080", rc);
InetSocketAddress socketAddres = httpServer.getAddress();
doSomethisWithSocket(socketAddres); // waiting for Enter before stoping the server
System.in.read();
httpServer.stop();
}
Constants
Let's take a look at this example. Let's start with these lines:
// "ua.in.link.rest.server" - name of package with classes for Jersey server
ResourceConfig rc = new PackagesResourceConfig("ua.in.link.rest.server");
For a person who has not worked with the local Jersey server, it may be really not obvious what kind of string is passed in the constructor (“ua.in.link.rest.server”). And the author, for sure, wanted to make this part more understandable. However, now, if for some reason the package name in the constructor is changed, there is a chance that the comment will remain old, and, as you know, an outdated comment is worse than none. Moreover, as I said earlier, the desire to insert a comment in the code indicates (almost always) about problems with the code. In this example, I think it is clear that the problem is the "hardcoded" line of the package ("ua.in.link.rest.server"). And if we put it into a separate entity, we will be forced to name it to associate it with this code. For example, we rendered it as a constant:
private static final String JERSEY_CLASSES_PACKAGE_NAME = "ua.in.link.rest.server";
public static void main(String[] args) throws IOException {
ResourceConfig rc = new PackagesResourceConfig(JERSEY_CLASSES_PACKAGE_NAME);
// creating httpServer for url "http://localhost:8080"
HttpServer httpServer = GrizzlyServerFactory.createHttpServer("http://localhost:8080", rc);
InetSocketAddress socketAddres = httpServer.getAddress();
doSomethisWithSocket(socketAddres);
// waiting for Enter before stoping the server
System.in.read();
httpServer.stop();
}
With such a change, there is no longer any need to add a comment. Information from it completely migrated to the link between the string itself and the code where it is used - to the name of the constant JERSEY_CLASSES_PACKAGE_NAME. Accordingly, the time spent writing comments could be spent on a little refactoring, after which the need for the comment itself disappeared.
Divide and rule
We go further ... Pay attention to these lines:
// creating httpServer for url "http://localhost:8080"
HttpServer httpServer = GrizzlyServerFactory.createHttpServer("http://localhost:8080", rc);
InetSocketAddress socketAddres = httpServer.getAddress();
doSomethisWithSocket(socketAddres);
And here the comment does irreparable benefit =). It seems that it’s clear that I’m doing the lines, the comment has been added, since the method in this example solves more than one problem and you need to delimit the code, very often it is done with comments, like here. What we ultimately have. Suppose that the server starts in some other place and that the server’s start must be removed from this place. We need to know exactly which code is responsible for its start. And here is the problem. Only this line is responsible for starting:
HttpServer httpServer = GrizzlyServerFactory.createHttpServer("http://localhost:8080", rc);
And the other two were added only because for the correctness of the program you need to execute a couple more lines of code, or a working server can be obtained only and exclusively by these three lines of code:
HttpServer httpServer = GrizzlyServerFactory.createHttpServer("http://localhost:8080", rc);
InetSocketAddress socketAddres = httpServer.getAddress();
doSomethisWithSocket(socketAddres);
It is not clear which lines need to be removed in case the server starts somewhere on the side. Moreover, when modifying the code, it is very easy to leave a line:
ResourceConfig rc = new PackagesResourceConfig(JERSEY_CLASSES_PACKAGE_NAME);
Not noticing that it also refers to the server creation block.
Now let's refactor this code.
private static final String JERSEY_CLASSES_PACKAGE_NAME = "ua.in.link.rest.server";
private static final String SERVER_URL = "http://localhost:8080";
public static void main(String[] args) throws IOException {
HttpServer httpServer = startServer();
InetSocketAddress socketAddres = httpServer.getAddress();
prepareSocket(socketAddres);
// waiting for Enter before stoping the server
System.in.read();
httpServer.stop();
}
private static HttpServer startServer() {
ResourceConfig rc = new PackagesResourceConfig(JERSEY_CLASSES_PACKAGE_NAME);
return GrizzlyServerFactory.createHttpServer(SERVER_URL, rc);
}
private static void prepareSocket(InetSocketAddress socketAddres){
doSomethisWithSocket(socketAddres);
}
Now it’s obvious that operations with Socket do not concern the fact of starting the server, otherwise they would be executed in the server start method, something like this:
private static HttpServer startServer() {
ResourceConfig rc = new PackagesResourceConfig(JERSEY_CLASSES_PACKAGE_NAME);
HttpServer httpServer = GrizzlyServerFactory.createHttpServer(SERVER_URL, rc);
InetSocketAddress socketAddres = httpServer.getAddress();
prepareSocket(socketAddres);
return httpServer;
}
Now it’s clear what exactly is responsible for starting the server. Moreover, if you can more accurately imagine what code is responsible for what, then more simply localize the bug.
In this case, it is vital that the user stops the server. This is fine, only if the user should stop, then the user needs to talk about it, not the programmer. Let's execute the last code modification:
private static final String JERSEY_CLASSES_PACKAGE_NAME = "ua.in.link.rest.server";
private static final String SERVER_URL = "http://localhost:8080";
private static final String PRESS_ENTER__TO_STOP_STRING = "Press Enter to stop server";
public static void main(String[] args) throws IOException {
HttpServer httpServer = startServer();
InetSocketAddress socketAddres = httpServer.getAddress();
prepareSocket(socketAddres);
userStopsServer(httpServer);
}
private static HttpServer startServer() {
ResourceConfig rc = new PackagesResourceConfig(JERSEY_CLASSES_PACKAGE_NAME);
return GrizzlyServerFactory.createHttpServer(SERVER_URL, rc);
}
private static void prepareSocket(InetSocketAddress socketAddres){
doSomethisWithSocket(socketAddres);
}
private static void userStopsServer(HttpServer httpServer){
System.out.println(PRESS_ENTER__TO_STOP_STRING + httpServer.toString());
System.in.read();
httpServer.stop();
}
And where are the comments?
Very well about the tests said in the Wiki, which I quote:
Most experts agree that comments should explain the intentions of the programmer, not the code; what can be expressed in a programming language should not be commented out - in particular, you need to use the spelling names of variables, functions, classes, methods, etc., break up the program into parts that are easy to understand, and strive to ensure that the class structure and structure databases were as clear and transparent as possible, etc. There is even an opinion (it is adhered to in extreme programming and some other flexible programming methodologies) that if comments are required to understand a program, then it poorly written.
This example is taken from one Unit test. The tests contained a class (which was taken as an example) that started the server, after which it was possible to run all Unit tests. Accordingly, in this class I would like to describe my intentions, why we expect the server to be turned off by the user, etc. But remembering what I said (if you want to put a comment, most likely a problem in the code), the programmer will understand that starting the server should be more tightly integrated into the tests and not start by itself. Actually, after refactoring, all this code crawled into the base class, from which all test classes that test the Web part inherited. Even in an indication of an intention, the rule indicated earlier very often works.
And yet, sometimes there is no other way but to resort to a non-obvious solution. An example of this is the use of an older version of the library or @Depricated methods. But all these cases in practice are much less common than they seem.
Javoc
Many projects, already at the pre-release stage, abound with standard comments of the form:
/**
* Имя или краткое описание объекта
*
* Развернутое описание
*
* @имя_дескриптора значение
* @return тип_данных
*/
Creating such a “junk” text often only clutters the project, creating some illusion that JavaDoc is already at least a little, but ready. On the contrary, such junk only confuses some programs analyzers of code coverage by documentation. Such comments should be added only when making changes to this branch is minimal and there is no need to constantly fear for the relevance of comments due to an active change in the code. Very often this is done last and places that need to be refactored due to the fact that they are written in an unobvious manner are identified.
Instead of conclusions
Of course, comments are a very holistic topic. There are many who can get into open projects, which I manage and, pointing to some code, say - “without comment it is not obvious” and will be absolutely right. However, if I have time to deal with this part of the code, I will spend this time refactoring and not writing comments, and only being cornered and not having figured out how to apply the KISS principle, I dutifully write a comment, but not earlier. I would also like to say that young programmers often remind about comments, who actually have a trivial lack of practice to see a well-known pattern, approach or ignorance of the work of any libraries in a particular code. But this is not a problem (and certainly not a necessity) of comments, but simply the lack of proper experience.