Comments from programmers

Source: Internet
Author: User

Fan decheng, SAP senior Development Engineer

October 25, 2014

 

Before writing this article, I was thinking about the quality of code. On the premise of writing a good code, the comments of the Code become another part of the code quality-its role is not that big at first glance, but the more important it becomes to the end. When a diligent programmer writes thousands of lines of code for a large project, he turns to another module of the project. One year later, when he looked back and looked at the thousands of lines of code he had previously written, if there were no meaningful comments in detail, he had to scratch his head-because he didn't write comments at the beginning.

Why is this happening? The programming languages we use today, such as Java, C #, and python, are advanced languages too early? I don't just want to show the code to the machine. Can this code be easily understood? Indeed, they are advanced languages. But the problem is that the Code itself only writes how to do one thing, and how to do it. As for what it does, why it does, and under what circumstances it can be used to do it, the Code itself cannot be reflected.

Therefore, annotations are of unique use. The quality of the Code comes from correctness, security, availability, readability, maintainability, and efficiency. Good comments can help the Code improve its readability and maintainability, and ultimately bring a positive impact to other parties such as correctness.

So, how can I write comments to ensure the high quality of the code? The following are my lessons learned from my years of work. It applies to most imperative programming languages:

1. Write clear comments for complex function interfaces.
2. note important details.
3. Annotations do not have redundant information.
4. Notes should be updated at any time.
5. When complex and intuitive implementations are encountered, you should also write comments for implementations.
6. comment out the full name and its meaning for the simplified, abstract, and abbreviated variable or function name.
7. Do not add comments to self-explanatory code.
8. Do not write redundant comments for frequently changing codes.

First, you need to write clear comments for complex function interfaces. The function interface here refers to the function name and its parameters, return values, exceptions, and other specifications. More strictly speaking, comments on function interfaces define a function contract. Although the programming language we use does not necessarily support contract-oriented programming, or we do not select such a programming mode, we can still express a function contract with comments on the concept.

How do I write comments for function interfaces? Take C # as an example. A function has a function name, parameter, and return value. At the same time, parameters and return values have their own types, and potential exceptions are thrown. The following is an imaginary interface for merging and sorting functions (this interface is obviously too complex, but its purpose is to demonstrate how to write comments ):

bool MergeSort<T>(T[] array, int begin, int len, IComparer<T> comparer){}

C # supports XML annotations, and its functions are similar to javadoc. In addition, its XML annotation supports all the contents of the contract to be annotated. Therefore, we can use XML annotations to write data. For this function, our comments are as follows:

/// <summary>/// Performs merge sort on a part of an array with a comparer./// </summary>/// <typeparam name="T">the type of members in the array to be sorted</param>/// <param name="array">the array to sort</param>/// <param name="begin">the beginning index of the part in the array to be sorted</param>/// <param name="len">the length of the part in the array to be sorted</param>/// <param name="comparer">the comparer used to compare elements in the array; see documentation on <see cref="System.Collections.Generic.IComparer<T>">IComparer</see> for more information</param>/// <returns>/// Whether the part of the array is already sorted./// </returns>/// <remarks>/// <para>This method checks whether the specified part of the source array is already sorted. If it is already sorted, the method returns true directly without changing the array. Otherwise, it sorts the part and returns false.</para>/// <para>This method performs stable sort on the specified part of the array.</para>/// <para>After calling this method, the range of the array from position <paramref name="begin" /> and of length <paramref name="len" /> is sorted.</para>/// <para>The time complexity of this method is O(n log n), where n is the length of the part being sorted.</para>/// </remarks>/// <exception cref="System.IndexOutOfRangeException">/// An IndexOutOfRangeException exception is thrown if the indices <paramref name="begin" /> and <paramref name="len" /> are out of range./// </exception>bool MergeSort<T>(T[] array, int begin, int len, IComparer<T> comparer)

Chinese Translation:

/// <summary>/// 利用一个比较器,对一个数组中的一段内容执行归并排序。/// </summary>/// <typeparam name="T">被排序数组的元素类型</param>/// <param name="array">要排序的数组</param>/// <param name="begin">数组中要排序的段的起始下标</param>/// <param name="len">数组中要排序的段的长度</param>/// <param name="comparer">用于比较数组中元素大小的比较器;详细信息请参见<see cref="System.Collections.Generic.IComparer<T>">IComparer</see>的文档。</param>/// <returns>/// 该数组段是否本来就是有序的。/// </returns>/// <remarks>/// <para>本方法将检查源数组的指定段是否已经处于有序状态。如果是这样,本方法将不修改数组内容,直接返回true。否则,本方法对指定段进行排序并返回false。</para>/// <para>本方法对数组的指定段执行的排序是稳定排序。</para>/// <para>在调用本方法之后,数组中从<paramref name="begin" />开始,长度为<paramref name="len" />的段将被排序。</para>/// <para>本函数的时间复杂度为O(n log n),其中n是被排序部分的长度。</para>/// </remarks>/// <exception cref="System.IndexOutOfRangeException">/// 若下标<paramref name="begin" />和<paramref name="len" />的范围溢出了,则抛出一个IndexOutOfRangeException异常。/// </exception>bool MergeSort<T>(T[] array, int begin, int len, IComparer<T> comparer)

C # xml annotations also support more labels, such as example (example. But in our daily programming process, these labels are only used as needed and occasionally. These are frequently used. Let's take a look. First, we have an introduction to this function (see the summary section ). Then, we will describe each parameter and the return value of the function. Typical exceptions should be explained, but not every exception is necessary. For complex functions, there is no way to summarize what is interesting in one sentence in the introduction. You need to write an annotation (remarks ).

The introduction of the function aims to clarify what the function should do in one sentence (at most two sentences. The comments of parameters and return values should explain their meanings and their special values. The example of a special value is a different value from the value passed in peacetime. For example, for some optional parameters, if null is input, this parameter is ignored. Such a value is a special value, which must be described. Some details of the function contract must be added to the annotation section. For example, the preconditions (the conditions that need to be met before a function call) and the backend conditions (the data changes after a function call, for example, the sorted status here is the backend condition) time-Space complexity (if needed), typical application scenarios, and special application scenarios (this is important for some business APIs that need to be executed in specific contexts.

Here, I would like to explain in particular the comments on exceptions. In each language, exceptions have different syntax requirements. C # does not support checked exception. Its designer Anders hejlsberg does not recommend that we use checked exception. in Java, all exceptions except program bugs must be used as checked exception. The so-called checked exception is such an exception type. When they are thrown by the current function, the current function must declare these exceptions in the prototype (that is, the function interface. The advantage of doing so is that the caller knows what exceptions will be received. The disadvantage is that it is inconvenient for applications to expand: when a new exception class needs to be added from the underlying layer, you must either capture these exceptions when calling these APIs in the application function body, you must declare these exceptions in the application's function prototype. Otherwise, compilation errors may occur. For some libraries, it is required that they derive all their exception classes from a base class for the sake of the application, so that the application only needs to declare that base class. For this reason, we write comments only for typical exceptions (exceptions that are easy to encounter in actual scenarios. Exceptions that are hard to come up with or even theoretically impossible do not need to be written. Besides, when necessary, although the checked exception statement may be a base class, our annotations must reflect the specific occurrence of a subclass exception.

Second, the important details should be clearly written in the annotations. In the previous example, we have brought this point: the time complexity and Stability of sorting algorithms are such important details.

Third, Annotations do not have redundant information. Annotations are used to explain programs. During annotation writing, for the sake of annotation integrity, its content may overlap with the meaning of the program code, that is, redundant information. This is hard to avoid. However, the redundancy between different parts in the comment can be avoided. There is no way to avoid such redundancy. It is mainly to read the duplicate content several times during the process of writing comments. In some cases, you can use the reference method to avoid repeated comments.

Fourth, annotations should be updated at any time. This is what we need to do to have high-quality comments. Each time the expected functions, interfaces, and contracts of a function change, the comments should also be updated accordingly. Of course, in actual software engineering, it is difficult to guarantee the update at an ideal time, but at least it is necessary to try to make new comments for large changes. Moreover, when reading the program code, if we find that the comments are incorrect, we can also investigate the program behavior and update the comments accordingly.

Fifth, when complicated and inintuitive implementations are encountered, you should also write comments for implementation. Sometimes, a function is very short and its implementation is self-evident. However, sometimes the implementation of a function is complicated. This may be due to complicated algorithms, complex business logic, and other reasons. In this case, it is very convenient to annotate the execution steps. The following Topology Sorting function demonstrates this point:

def topological_sort(graph, output_func):    # Time complexity is O(n ^ 2)    while len(graph) > 0:        # Output dependency-free nodes        to_pop_node_name = []        for node_name in graph:            # Remove and output all nodes without dependencies            if len(graph[node_name].dependencies) == 0:                output_func(node_name)                to_pop_node_name.append(node_name)        # Remove the nodes        for node_name in to_pop_node_name:            graph.pop(node_name)        to_pop_node_name = None # finished using        # Remove dependency links        for node_name in graph:            current_node = graph[node_name]            to_pop_node_name = []            for child_node_name in current_node.dependencies:                if child_node_name not in graph:                    to_pop_node_name.append(child_node_name)            for child_node_name in to_pop_node_name:                current_node.dependencies.pop(child_node_name)            to_pop_node_name = None # finished using

Among them, output dependency-free nodes and remove the nodes are comments to a code block. Such Annotations indicate the steps of the program. This annotation can span a wider range. The trick is to use braces or the words begin and end to indicate its range. As follows:

int i;int max = -1;int sum = 0;// Do the first thing {for (i = 0; i < arr.Length; i++) {    if (max < 0 || arr[i] > max) {        max = arr[i];    }}// }// Do the second thing {for (i = 0; i < arr.Length; i++) {    sum += arr[i];}// }

For situations where bosses do not like to see braces in comments, use begin or end instead:

// Begin of "Do the second thing"for (i = 0; i < arr.Length; i++) {    sum += arr[i];}// End of "Do the second thing"

The advantage of accurately using braces, begin, and end in comments is that when a large piece of code contains nested comments, a Range can still be clearly expressed. In addition, my personal principle is not to add the words "Step 1", "Step 1.1", and "Step 2" to annotations. The reason is very simple. Once a new step is inserted in the original step, it is very troublesome to adjust the numbers of all steps after this step.

In addition, for complex algorithms or business needs, you also need to add annotations to avoid looking back at this code several years later and forgetting why it was written like this. For an algorithm, you need to know what the algorithm needs, how it is designed, what input needs to be processed, and what the processing principle is. For business logic, you need to describe the input to be supported (including global, static variables, database and other environment data) the meaning of all processing steps in the business process, the impact on the data and business status after the processing is completed, and so on. Take an algorithm as an example. The following example shows how to check whether the graph has a loop before Topology Sorting:

def detect_loop(graph, o_loop):    """    detect_loop:        Detects any loop in a graph.    Parameters:        graph - the graph to test        o_loop - a list to receive the looping nodes    Return value:        True if a loop has been detected. False otherwise.    """    # We are using depth-first search to find loops.    #    # If we did not implement recursive calls, we could use trace-back in a    # non-recursive manner; python default recursion limit is about 900, which    # is in general enough here, as the dependency we analyze is usually less    # than 100.    #    # Due to the fact that if we traverse a non-tree directed acyclic graph    # (DAG), we may end up in a time complexity of O(2 ^ n), we make a deep    # copy of the graph first, and make it into a tree. During the process, we    # can detect loops. The time complexity is O(n ^ 2) where n is the number    # of nodes.    result = False    copied_graph = GraphNode.deep_copy_graph(graph)    # Cases:    # Root 1 leads to a loop--will be detected and the function will return.    #     The loop will have a link pointing back to an ancestor node or the    #     current node itself.    # Root 1 leads to a DAG--any link to a visited node (cannot be an ancestor    #     node or the current node itself) will be detected and removed, and    #     made into a tree    # Root 1 leads to a DAG (call it DAG1), root 2 links to DAG1--no loop can    #     involve DAG1. Reason: suppose there is a loop involving DAG1, then    #     from a node that is a part of the intersection, we can go back to    #     it through the links, thus making DAG1 not a DAG--contradiction.    # So if root 2 leads to a loop--the loop will be detected by checking the    #     DFS traversal stack    # If all of root 1..n-1 lead to DAGs, and root n leads to a loop, it will    #     be detected only there    #    # accessed: used to mark accessed nodes in the DAG. Its members are the    # names of accessed nodes. When an accessed node is met through a link,    # the traversal returns and the link is removed, because the link should    # not be added to the tree.    accessed = set() # of node name (string)    # traversal_stack: is used to record the loop to show to the user    traversal_stack = []    for node_name in copied_graph:        result = detect_loop_rec(copied_graph, node_name, accessed, traversal_stack)        if result:            # Loop detected            o_loop.extend(traversal_stack)            break    return result

Sixth, comment out the full name and its meaning for the simplified, abstract, and abbreviated variable or function name. For example, when you use winnt4wks to represent Microsoft Windows NT Workstation 4 i386 multiprocessor free, you should write the full name on the right (or above) as a comment ):

// winnt4wks: Microsoft Windows NT Workstation 4 i386 Multiprocessor Freeobject winnt4wks;

Note: In the above example, if the comment is written above the variable name, first use this variable name as the forerunner, then add a colon, and then the next step is the explanation.

7. Do not add comments to self-explanatory code. This is natural. For example, if everyone knows what a piece of code is, there is no need to write comments. If you write comments, it will interfere with the audio and video, making it difficult to maintain the code in the future, or forget to maintain it, which may mislead the Later users. For example, the following code annotations are completely unnecessary in the production code:

// Loop and print every element of the arrayfor (i = 0; i < arr.Length; i++) {    Console.WriteLine(arr[i]);}

Eighth, do not write redundant comments for frequently changing codes. As mentioned above, the meaning of comments and codes may overlap. At this point, if a code segment is often changed, it is basically certain that the code owner knows what the code is, because it has just been changed recently. Therefore, necessary comments must be added, but redundant information that is duplicated with the code meaning is unnecessary and can be omitted.

As a programmer, after mastering the above points, he can write good comments to make his code easier to read and maintain.

Comments from programmers

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.