SAP高级开发工程师 范德成
2014年10月25日
写这篇文章之前,我所思考的前一个问题是代码的质量。而在编写了好的代码的前提下,代码的注释就成了代码质量的另一部分——它的作用初看时显得并不那么大,但是越到后面越显得重要。当一名勤奋的程序员为了一个大项目,洋洋洒洒地写了数千行代码之后,他转而去做该项目的另一个模块。等到一年后,他回头再来看他之前写的这几千行代码时,如果没有详细有意义的注释,那就得挠头了——因为当初没有写注释。
为什么会出现这种情况呢?我们当今所使用的编程语言,比如Java、C#、python等,不是早就是高级语言了嘛?已经不只是给机器看的代码,这些代码也能让人读懂了嘛?的确,它们是高级语言。但问题却在于,代码本身只写了怎么做一件事,做了这件事的结果如何。至于它做的是什么事,为什么要做这件事,在什么情况下可以用它来做这件事,代码本身是体现不出来的。
因此,注释有其独特的用处。代码本身的质量,来自正确性、安全性、可用性、可读性、可维护性、效率等好几个方面。好的注释,则能够帮助代码提升其可读性和可维护性,并最终为正确性等其他几个方带来正面影响。
那么,在保证代码本身的高质量的前提下,注释应该怎么写,才能有效呢?以下是我个人在多年工作中总结出来的经验教训。它适用于绝大多数命令式(imperative)编程语言:
1. 要为复杂的函数接口写清晰的注释。
2. 注释中要写清楚重要的细节。
3. 注释本身不要有冗余信息。
4. 注释要随时更新。
5. 当遇到复杂的、不直观的实现时,也要为实现写注释。
6. 要为简化、抽象和缩写的变量名或函数名,注释其全称及其含义。
7. 不要为不言自明的代码加注释。
8. 不要为频繁变化的代码写冗余的注释。
第一点,要为复杂的函数接口写清晰的注释。这里的函数接口指的是函数名及其参数、返回值、异常等的规范。更严格地来说,关于函数接口的注释定义了一个函数的契约。虽然我们所使用的编程语言不一定支持面向契约的编程,或者我们不选择这样一种编程模式,但我们仍可以在概念上用注释来表示一个函数的契约。
函数接口的注释该怎么写呢?以C#为例,它的一个函数会有函数名,参数和返回值。同时,参数和返回值又分别有其类型,还会有潜在的异常抛出。如下,是一个假想的做归并排序的函数的接口(该接口明显过于复杂,但其目的是为了演示如何写注释):
bool MergeSort<T>(T[] array, int begin, int len, IComparer<T> comparer)
{
}
C#语言支持XML注释,其功能类似于Javadoc。而且,它的XML注释正好支持我们要注释的契约的所有内容。因此,我们可以用XML注释来写。对于这个函数,我们的注释如下:
/// <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