我有一位同事坚持认为他的代码不需要注释,它是“自我记录”。

我已经审查了他的代码,虽然它比我见过其他人生成的代码更清晰,但我仍然不同意自我记录代码与经过注释和记录的代码一样完整和有用。

帮助我理解 他的 观点看法。

  • 什么是自记录代码
  • 它真的可以取代注释和文档良好的代码吗
  • 在某些情况下,它比有详细记录和注释的代码更好
  • 是否有代码在没有注释的情况下不可能自我记录的示例

也许这只是我自己的限制,但我不认为这是一个好的做法。

这并不是一个争论 - 请不要提出为什么注释和记录良好的代码具有高优先级的原因 - 有很多资源表明了这一点,但它们对我的同行来说并不令人信服。我相信我需要更充分地理解他的观点才能说服他。如果必须的话,可以开始一个新问题,但不要在这里争论。

哇,反应好快啊!请阅读所有现有答案并对答案提供评论,而不是添加新答案,除非您的答案确实与此处的所有其他答案有很大不同。

另外,对于那些反对自记录代码的人来说,这主要是为了帮助我理解自记录代码传播者的观点(即积极的方面)。如果你不关注主题,我预计其他人会对你投反对票。

有帮助吗?

解决方案

在我看来,任何代码都应该是自记录的。在良好的自记录代码中,您不必解释每一行,因为每个标识符(变量、方法、类)都有一个明确的 语义 姓名。拥有过多的注释实际上会使阅读代码变得更加困难(!),所以如果你的同事

  • 为每个类、成员、类型和方法编写文档注释(Doxygen、JavaDoc、XML 注释等)并且
  • 明确注释代码的任何部分 不是 自记录和
  • 为每个代码块编写注释,解释其意图,或者代码在更高抽象级别上的作用(即, 查找所有大于 10 MB 的文件 代替 循环遍历目录中的所有文件,测试文件大小是否大于10 MB,如果为true则返回)

在我看来,他的代码和文档都很好。请注意,自记录代码确实 不是 意思是不应该有评论,只是说不应该有不必要的评论。然而,事实是,通过阅读代码(包括注释和文档注释)应该可以立即理解代码的作用和原因。如果“自记录”代码比注释代码需要更长的时间来理解,那么它并不是真正的自记录。

其他提示

好吧,既然这是关于注释和代码,那么让我们看一些实际的代码。比较一下这个典型的代码:

float a, b, c; a=9.81; b=5; c= .5*a*(b^2);

对于这个自记录代码,它显示 什么 正在做:

const float gravitationalForce = 9.81;
float timeInSeconds = 5;
float displacement = (1 / 2) * gravitationalForce * (timeInSeconds ^ 2);

然后是这个记录的代码,它更好地解释了 为什么 正在这样做:

/* compute displacement with Newton's equation x = vₒt + ½at² */
const float gravitationalForce = 9.81;
float timeInSeconds = 5;
float displacement = (1 / 2) * gravitationalForce * (timeInSeconds ^ 2);

以及最终版本 代码作为文档 需要零评论:

float computeDisplacement(float timeInSeconds) {
    const float gravitationalForce = 9.81;
    float displacement = (1 / 2) * gravitationalForce * (timeInSeconds ^ 2);
    return displacement;
}

这是一个糟糕的评论风格的例子:

const float a = 9.81; //gravitational force
float b = 5; //time in seconds
float c = (1/2)*a*(b^2) //multiply the time and gravity together to get displacement.

在最后一个例子中,当变量应该被描述性地命名时,就会使用注释,而当我们可以清楚地看到操作是什么时,就会总结操作的结果。与任何一天相比,我更喜欢自记录的第二个示例,也许这就是您的朋友在说自记录代码时所谈论的内容。

我想说这取决于你正在做的事情的背景。对我来说,在这种情况下,自记录的代码可能就足够了,但是详细说明完成背后的方法(在本例中为方程)的注释也很有用。

代码本身始终是代码功能的最新解释,但在我看来,它很难解释 意图, ,这是评论中最重要的方面。如果写得正确的话,我们已经知道了 什么 代码确实如此,我们只需要知道 到底为什么 它做到了!

曾经有人说过

1)只为难以理解的代码编写注释。
2)尽量不要编写难以理解的代码。

“自文档化”代码背后的想法是,代码中的实际程序逻辑非常清晰,可以向任何阅读代码的人解释,不仅代码在做什么,而且还解释为什么这样做。

在我看来,真正的自文档化代码的想法是一个神话。代码可以告诉你正在发生的事情背后的逻辑,但它无法解释 为什么 它是以某种方式完成的,特别是当解决问题的方法不止一种时。仅仅因为这个原因,它永远无法取代 好评如潮 代码。

我认为质疑某行特定代码是否是自文档化是相关的,但最终如果您不理解一段代码的结构和功能,那么大多数时候注释不会有帮助。以 amdfan 的“正确注释”代码为例:

/* compute displacement with Newton's equation x = v0t + ½at^2 */
const float gravitationalForce = 9.81;
float timeInSeconds = 5;
float displacement = (1 / 2) * gravitationalForce * (timeInSeconds ^ 2);

这段代码很好,但在大多数现代软件系统中,以下内容同样提供了丰富的信息,并且明确认识到使用牛顿计算是一种选择,如果其他一些物理范式更合适,则可以更改该选择:

const float accelerationDueToGravity = 9.81;
float timeInSeconds = 5;
float displacement = NewtonianPhysics.CalculateDisplacement(accelerationDueToGravity, timeInSeconds);

根据我个人的经验,很少有“正常”的编码情况是绝对需要注释的。例如,您多久会推出自己的算法?基本上,其他一切都是构建系统的问题,以便编码人员可以理解正在使用的结构以及驱动系统使用这些特定结构的选择。

我忘了我从哪里得到这个,但是:

节目中的每一条评论都像是对读者的道歉。“很抱歉,我的代码太不透明了,你无法通过查看它来理解它”。我们只需要接受自己并不完美,但努力做到完美,并在需要时立即道歉。

自记录代码是“DRY”(不要重复自己)的一个很好的例子。不要在代码本身中或可能存在的注释中重复信息。

与其解释变量的用途,不如重命名变量。

不要解释一小段代码的作用,而是将其提取到一个方法中并为其指定一个描述性名称(可能是注释文本的缩短版本)。

与其解释复杂测试的作用,不如将其提取到一个方法中并给它一个好名字。

ETC。

在此之后,您最终会得到不需要太多解释的代码,它会自行解释,因此您应该删除仅重复代码中信息的注释。

这并不意味着您根本没有注释,有些信息您无法放入代码中,例如有关意图的信息(“为什么”)。在理想情况下,代码和注释相互补充,各自添加独特的解释价值,而不会重复对方的信息。

自记录代码是一种很好的做法,如果做得正确,可以轻松传达代码的含义,而无需阅读太多注释。尤其是在团队中每个人都充分理解该领域的情况下。

话虽如此,注释对于新手或测试人员或生成文档/帮助文件非常有帮助。

自记录代码+必要的注释对于帮助跨团队的人员大有帮助。

首先,很高兴听到您同事的代码实际上比您见过的其他代码更清晰。这意味着他可能不会使用“自我记录”作为懒得评论他的代码的借口。

自记录代码是不需要自由文本注释的代码,知情的读者就可以理解它在做什么。例如,这段代码是自记录的:

print "Hello, World!"

这也是:

factorial n = product [1..n]

这也是:

from BeautifulSoup import BeautifulSoup, Tag

def replace_a_href_with_span(soup):
    links = soup.findAll("a")
    for link in links:
        tag = Tag(soup, "span", [("class", "looksLikeLink")])
        tag.contents = link.contents
        link.replaceWith(tag)

现在,“知情读者”的想法是非常主观和情境化的。如果您或任何其他人在遵循您同事的代码时遇到困难,那么他最好重新评估他的知情读者的想法。为了调用代码自文档化,必须假设对所使用的语言和库有一定程度的熟悉。

我见过的关于编写“自文档代码”的最佳论点是,它避免了自由文本注释与编写的代码不一致的问题。最好的批评是,虽然代码可以描述 什么如何 它是自己做的,它无法解释 为什么 某事正在以某种方式进行。

为了:

  • 自记录代码是向读者清楚表达其意图的代码。
  • 不是完全。评论总是对评论有帮助 为什么 选择了特定的策略。然而,评论解释 什么 正在执行的代码部分表明代码的自记录性不够,可以使用一些重构。
  • 评论会撒谎并变得过时。代码 总是告诉 更有可能说实话。
  • 我从未见过这样的案例 什么 如果没有注释,代码就无法足够清晰;然而,正如我之前所说,有时有必要/有帮助地包括对 为什么.

然而,值得注意的是,真正的自我记录代码需要大量的自律和团队纪律。你必须学会​​更加声明式地编程,并且你必须非常谦虚,避免使用“聪明”的代码,而选择那些显而易见的代码,似乎任何人都可以编写它。

其一,请考虑以下代码片段:

/**
 * Sets the value of foobar.
 *
 * @foobar is the new vaue of foobar.
 */
 public void setFoobar(Object foobar) {
     this.foobar = foobar;
 }

在此示例中,每 3 行代码有 5 行注释。更糟糕的是,注释没有添加任何您通过阅读代码看不到的内容。如果您有 10 个这样的方法,您可能会出现“评论盲目性”,而不会注意到偏离模式的一种方法。

当然,更好的版本应该是:

/**
 * The serialization of the foobar object is used to synchronize the qux task.
 * The default value is unique instance, override if needed.
 */
 public void setFoobar(Object foobar) {
     this.foobar = foobar;
 }

不过,对于琐碎的代码,我更喜欢没有注释。意图和整体组织在代码之外的单独文档中得到了更好的解释。

当您阅读“自记录代码”时, 你看到它在做什么, 但你不能总是猜到它为什么会以这种特定的方式做。

有大量的非编程约束 如业务逻辑、安全性、用户需求等。

当你进行维护时,这些背景信息就变得非常重要。

只是我的一小撮盐...

您可能希望向您的同事指出的一件事是,无论他的代码如何自我记录,如果考虑并放弃其他替代方法,则该信息将丢失,除非他用该信息注释代码。有时,了解是否考虑过替代方案以及为什么决定反对它也同样重要,并且代码注释最有可能随着时间的推移而保留下来。

您听说过 Donald Knuth 的“WEB”项目来实现他的 文学编程 概念?它不仅仅是自记录代码;它更像是可以作为代码编译和执行的文档。但我不知道今天它的使用量是多少。

区别在于“什么”和“如何”。

  • 您应该记录例程“做什么”。
  • 你不应该记录它“如何”做到这一点,除非特殊情况(例如具体算法参见论文)。这应该是自我记录的。

在我工作的一家公司,一位程序员的显示器顶部贴着以下内容。

“记录你的代码,就像维护它的人是一个知道你住在哪里的杀人狂一样。”

代码是自我记录的观点让我发疯。特定的代码行或子算法可能确实是自我记录的,但它在更大的图景中的目的根本不是。

一两个月前,我对此感到非常沮丧,我写了一篇完整的博客文章来描述我的观点。 在这里发帖.

自记录代码通常使用与代码正在执行的操作完全匹配的变量名称,以便很容易理解正在发生的事情

然而,这种“自记录代码”永远不会取代注释。有时代码太复杂,自文档化代码还不够,尤其是在可维护性方面。

我曾经有一位教授,他坚信这个理论 事实上,我记得他说的最好的一句话是“评论是给娘娘腔的”
起初,我们所有人都感到惊讶,但这是有道理的。
然而,情况是,即使您可能能够理解代码中发生了什么,但经验不足的人可能会落后于您并且不明白发生了什么。这时候评论就变得很重要了。我知道很多时候我们并不认为它们很重要,但很少有评论是不必要的。

我很惊讶没有人带来“文学编程”,这项技术由 Donald E. 于 1981 年开发。Knuth 以 TeX 和《计算机编程的艺术》而闻名。

前提很简单:由于代码必须由人类理解,并且注释会被编译器丢弃,为什么不为每个人提供他们需要的东西 - 代码意图的完整文本描述,不受编程语言要求的限制,供人类读者使用和编译器的纯代码。

文学编程工具通过为文档提供特殊标记来做到这一点,告诉工具哪些部分应该是源代码,哪些是文本。该程序随后从文档中提取源代码部分并组装一个代码文件。

我在网上找到了一个例子: http://moonflare.com/code/select/select.nw 或 HTML 版本 http://moonflare.com/code/select/select.html

如果你能在图书馆找到 Knuth 的这本书(Donald E.Knuth,文学编程,斯坦福大学,加利福尼亚州:语言与信息研究中心,1992,CSLI 讲义,编号。27.) 你应该阅读它。

那是 自记录代码,完整的推理等等。甚至制作了一份不错的文档, 其他一切都写得很好:-)

我的观点写在这篇文章里:

记录代码的唯一技巧。

摘抄:

而不是写很多评论 来解释 你的程序,为什么不重组你的 逻辑,使它们不言而喻?而不是记录什么方法 正在做,为什么不选择一个明确的名字 对于那个方法?而不是标记 你的代码来指示未完成的工作, 为什么不直接扔一个 NotImplementedException()?而不是 担心您的评论听起来是否合理 对你的老板足够有礼貌,你的 同事或任何阅读代码的人, 为什么不停止担心 写它们吗?

代码越清晰,越容易 它是维护它,扩展它,以 在将来的版本中对其进行处理。这 你的代码越不普通,就越少 需要评论它。越多 评论越多,越高 维护成本。

我想对众多有效答案提供另一种视角:

什么是源代码?什么是编程语言?

这些机器不需要源代码。他们很高兴运行装配。编程语言是为了我们的利益。我们不想写汇编。我们需要理解我们所写的内容。编程就是编写代码。

你应该能够阅读你写的东西吗?

源代码不是用人类语言编写的。它已经被尝试过(例如FORTRAN),但还没有完全成功。

源代码不能有歧义。这就是为什么我们必须在其中添加比文本更多的结构。文本仅适用于上下文,当我们使用文本时,我们认为这是理所当然的。源代码中的上下文始终是明确的。想想 C# 中的“使用”。

大多数编程语言都有冗余,这样编译器就可以在我们不一致时发现我们。其他语言使用更多的推理并试图消除这种冗余。

计算机不需要类型名称、方法名称和变量名称。它们供我们参考。编译器不理解语义,那是供我们使用的。

编程语言是人与机器之间的语言桥梁。它必须对我们来说是可写的,对他们来说是可读的。第二个要求是它应该对我们来说是可读的。如果我们在允许的情况下擅长语义并且擅长构建代码,那么源代码即使对我们来说也应该很容易阅读。最好的代码不需要注释。

但每个项目都潜藏着复杂性,你总是必须决定将复杂性放在哪里,以及吞下哪只骆驼。这些是使用注释的地方。

自记录代码很容易解决这个问题,随着时间的推移,代码、注释和文档会出现分歧。编写清晰的代码是一个纪律因素(如果你对自己那么严格的话)。

对我来说,这些是我尝试遵循的规则:

  • 代码应该简单明了 尽可能阅读。
  • 评论应说明理由 我做出的设计决策,比如:为什么 我是否使用此算法,或者 代码的局限性,例如:确实如此 不工作时......(这应该是 在协定中处理/断言 代码)(通常在函数/过程中)。
  • 文档应列出用法 (呼叫 converntions),侧面 effects,可能的返回值。它 可以使用 jDoc 或 xmlDoc 等工具。它 因此通常不在 函数/过程,但接近 它描述的代码。

这意味着记录代码的所有三种方法都紧密结合在一起,因此当代码更改时更有可能发生更改,但它们所表达的内容不会重叠。

所谓的自文档化代码的真正问题在于它传达了它实际所做的事情。虽然一些注释可能有助于某人更好地理解代码(例如,算法步骤等),但它在某种程度上是多余的,我怀疑您能否说服您的同行。

然而,文档中真正重要的是那些从代码中无法直接看出的内容:潜在意图、假设、影响、限制等。

通过快速浏览就能确定代码执行 X 操作比确定代码不执行 Y 操作要容易得多。他必须记录 Y...

例如,您可以向他展示一个代码示例,该示例看起来不错、很明显,但实际上并未涵盖输入的所有基础,然后看看他是否找到了它。

我认为自记录代码是注释的一个很好的替代品。如果您需要注释来解释代码是如何或为什么是这样的,那么您应该修改函数或变量名称以使其更具解释性。不过,这取决于编码人员是否会通过注释来弥补不足,或者重命名一些变量和函数并重构代码。

但它并不能真正取代您的文档,因为文档是您向其他人提供的用于解释如何使用您的系统的内容,而不是它如何工作的内容。

编辑:我(可能还有其他人)可能应该规定数字信号处理 (DSP) 应用程序应该得到很好的评论。这主要是因为 DSP 应用程序本质上是 2 个 for 循环,其中包含值数组,并对所述值进行相加/相乘等...要更改程序,您可以更改数组之一中的值...需要一些评论来说明您在这种情况下正在做什么;)

在编写数学代码时,我有时发现编写长篇文章式的注释、解释数学、代码使用的符号约定以及它们如何组合在一起很有用。我们在这里谈论数百行文档。

我尝试让我的代码尽可能地自我记录,但是当我几个月后回来处理它时,我确实需要阅读解释以避免对其进行哈希处理。

当然,在大多数情况下,这种极端措施是不必要的。我认为这个故事的寓意是:不同的代码需要不同数量的文档。有些代码可以写得很清楚,不需要注释——所以写得清楚,不要在那里使用注释!

但是很多代码确实需要注释才能有意义,因此尽可能清晰地编写代码,然后根据需要使用尽可能多的注释......

我认为——正如你们中的许多人一样——为了真正实现自我记录,代码需要表现出某种形式的意图。但令我惊讶的是还没有人提到 BDD - 行为驱动开发. 。这个想法的一部分是你有自动化测试(代码)来解释你的代码的意图,否则很难变得明显。

Good domain modeling 
+ good names (variabes, methods, classes) 
+ code examples (unit tests from use cases) 
= self documenting software 

除了代码之外,额外的注释可能会更清晰,原因如下:

  • 您正在查看的代码是自动生成的,因此对代码的任何编辑都可能在下次编译项目时被破坏
  • 不太直接的实现是为了获得性能增益(展开循环、为昂贵的计算创建查找表等)

这将完全取决于团队在文档中所重视的内容。我建议记录原因/意图而不是如何重要,并且这并不总是在自记录代码中捕获。get/set 没有,这些是显而易见的 - 但计算、检索等一些原因应该表达出来。

如果您来自不同的国家,还要注意您的团队的差异。措辞上的差异可能会影响到方法的命名:

二分搜索

二分查找

二进制斩波

这三种方法由在 3 个不同大陆接受过培训的开发人员贡献,其作用相同。只有通过阅读描述该算法的注释,我们才能识别库中的重复项。

对我来说,阅读需要注释的代码就像阅读我不知道的语言的文本。我看到声明,但不明白它的作用或原因 - 我必须查看评论。我读到一个短语,我需要查字典才能理解它的含义。

编写自我记录其功能的代码通常很容易。为了告诉你为什么这样做,注释更合适,但即使这里的代码也可以更好。如果您在每个抽象级别上了解您的系统,您应该尝试像这样组织代码

public Result whatYouWantToDo(){
  howYouDoItStep1();
  howYouDoItStep2();
  return resultOfWhatYouHavDone;
}

方法名称反映了您的意图,方法主体说明了您如何实现目标。无论如何,你无法在书名中讲述整本书,因此仍然需要记录系统的主要抽象,以及复杂的算法、重要的方法契约和工件。

如果你的同事生产的代码确实是自我记录的 - 你和他都很幸运。如果您认为您同事的代码需要注释——它需要。只要打开其中最不平凡的地方,读一遍,看看你是否理解了所有内容。如果代码是自我记录的 - 那么你应该这样做。如果没有 - 向您的同事询问一个问题,在他给您答案后询问为什么该答案没有事先记录在注释或代码中。他可以声称代码对于像他这样聪明的人来说是自文档的,但他无论如何都必须尊重其他团队成员 - 如果你的任务需要理解他的代码并且他的代码没有向你解释你需要理解的所有内容 - 它需要评论。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top