很好的代码注释技巧


Good code commenting tips

当我开始编写应用程序时,我没有非常仔细地注释我的代码。后来我当然后悔了。这种情况已经发生了很多次,我正在寻找一些关于如何在我仍然理解我所写的代码的情况下对代码进行有效注释的技巧。我不想以这样的结局结束

 //When I wrote this code only my and god understood what it did... now only god do

Clean Code一书描述了一种处理注释的好方法(在我看来)。

该section的tl;dr为:

  • 更倾向于重构方法/变量/类,这样他们就能更好地描述他们在做什么
  • 少即是多,如果有太多的评论,人们不太可能阅读它们
  • 注释可能会过时,代码不会
  • 作为最后的手段,写一个注释来描述代码
  • 的意图

在关于不知道自己在做什么的注释的特定示例中,您应该重构该代码。如果你甚至不能描述你写的代码是做什么的,那么这肯定是糟糕的代码。一个好的注释并不能弥补写错的代码。

文档为什么你决定采用某些设计。通常,在软件工程和编程中,有几十种解决方案来实现特定的任务,重要的是要知道权衡利弊,最终决定使用解决方案x。

如果您的设计偏离了您的编程语言中已建立的标准习语,或者当您似乎重新实现了轮子,但有很好的理由这样做(遗留系统,特定客户端系统的极端条件,由于某些截止日期造成的时间压力,与某些库或框架的兼容性,等等)时,这一点尤其重要。

不同的注释风格适合不同的场景和项目。语言随着时间的推移而改变,注释风格也是如此。

首先要记住的是,注释只是让代码易于理解的众多因素之一。在大公司和大型项目中,规格说明、测试计划等文件是将所有东西粘在一起的管道胶带。在许多现代脚本语言中,编码风格(命名约定、接口选择等)的重要性不亚于你对代码片段功能的评论。在某些语言中采用良好的编码风格几乎可以使代码自解释。

我的第二个建议是在开始编码之前进行注释。这就像你开始画画之前的草图,它可以帮助你澄清自己的意图。如果你能用语言描述你想做什么以及你将如何去做,那么你肯定可以用代码来完成。

最后,不要过度注释,让每个字都有意义,这对你和其他维护者都有好处。

我在自己的代码中很少写注释。我依靠:

  1. 函数名称
  2. 一致且良好的变量名称。
  3. 使用枚举和其他符号名作为"常量"。
  4. 使用类型声明来解释类型的用途。

注意,"好名字"是上下文相关的。在短代码段中重复多次的长而复杂的名称不一定比短名称好。关键是名称应该是变量的作用的一个很好的线索。称一个变量为outerLoopVariable并不比称它为i好多少。调用用于相同目的的参数,在不同但密切相关的函数上使用相同的名称绝对是一个好主意。这样,你就知道下一个函数是一样的。

同样,试图弄清楚为什么一个数字在一段代码中是117有时不是特别容易,如果它被称为NumberOfElementsColorGreen就更有意义了。

如果函数的形参的类型和名称不明显,那么可能需要在函数之前加一点注释来解释输入是什么,以及它们应该是什么。

类型在以下方面也很重要:1. 确保将正确的东西传递给函数。2. 再提示一下"这个东西是做什么的"。

例如

char *buffer;不像typedef char* PixelBuffer; PixelBuffer *buffer;那样清楚,尽管指向完全相同的类型,后一个版本解释说buffer实际上不是文本字符串,文件缓冲区或其他东西,而是包含像素的缓冲区。

另一个经常被发现的"缺陷"是缺乏遵循"一个函数应该做一件事"的规则——如果你有一个函数做很多事情,那么很难确定这个函数是否可以被重用。

坚持使用小函数,并且使用许多小函数,这通常是一个好主意。如果现代编译器认为内联是有益的,它会很好地进行内联(如果您不到处重复内容,那么无论哪种方式,您都可能获得更高效的代码)。

  1. 在代码语义不清楚的时候解释算法(人类可读的变量和方法名,最好不要注释它们!!)。
  2. 从变量/参数/函数名(例如int distance_in_meters;, int dist_cm;, int calc_dist_cm(point a, point b);)来看,文档指标并不清楚
  3. 解释接口及其行为(在类级别上)。
  4. 解释你必须实现的技巧,以处理笨拙的第三方或操作系统界面。

代码中的其他内容应该通过正确命名类、(成员)变量/函数和使用标准算法来表示。

的例子:

// represents a point (x,y value) in a cartesian coordinate system
class point {
public:
    point(double x, double y);
    // Copy constructor for point
    point(const point& rhs);
    double x() const; // get x value
    void x(double value): // set x value
    double y() const; // get y value
    void y(double value): // set y value
    // Calculates the distance from an 'other' point in cartesian units
    double getDistance(const point& other) const;      
private:
    double x_;
    double y_;
}

依赖注释不是一件好事,因为它会随着时间的推移而腐烂。有效注释代码的更好方法是:

  • 选择好名称(变量、方法、类)
  • 尽可能使用assert,特别是作为前置和后设条件
  • 使用单元测试。单元测试解释方法的行为。
  • KISS KISS KISS KISS KISS KISS KISS KISS KISS KISS KISS KISS KISS KISS KISS KISS KISS KISS KISS

有些人喜欢在代码中写一些todo或fix。相反,我建议您使用失败的单元测试。记住,你很少需要在你的代码中写注释,你应该尽量避免它。

我建议您阅读《Clean Code: A Handbook of Agile Software Craftsmanship》以获得更多信息。