"自我记录代码"理论存在一个悲剧性的缺陷.是的,阅读代码将告诉您它到底在做什么.但是,代码无法告诉您它应该做什么.
我认为可以肯定地说,当代码没有做它应该做的事情时,所有的错误都会引起:).因此,如果我们添加一些关键注释来为维护者提供足够的信息来了解一段代码应该做什么,那么我们就已经让他们能够修复大量的bug.
这就留下了我们要提出多少评论的问题.如果你提出太多评论,那么维护这些事情会变得乏味,并且评论将不可避免地与代码过时.如果你投入太少,那么它们并不是特别有用.
我发现定期评论在以下地方最有用:
1).h或.cpp文件顶部的简要说明,用于解释课程目的的课程.这有助于维护人员快速浏览,而无需筛选所有代码.
2)在执行非平凡函数之前的注释块,解释其目的并详细说明其预期输入,潜在输出以及调用函数时预期的任何奇怪之处.这使得未来的维护者不必破译整个功能来解决这些问题.
除此之外,我倾向于评论任何可能对某人感到困惑或奇怪的事情.例如:"由于等等,这个数组是基于1而不是基于0".
写得好,评价良好的评论非常宝贵.糟糕的评论往往比没有评论更糟糕.对我来说,没有任何评论表明代码作者的懒惰和/或傲慢.无论你对代码的作用有多么明显,或者你的代码有多么美妙,进入一个代码体并且弄清楚到底发生了什么是一项具有挑战性的任务.做得好的评论可以让人们快速了解现有代码.
"自我记录代码"理论存在一个悲剧性的缺陷.是的,阅读代码将告诉您它到底在做什么.但是,代码无法告诉您它应该做什么.
我认为可以肯定地说,当代码没有做它应该做的事情时,所有的错误都会引起:).因此,如果我们添加一些关键注释来为维护者提供足够的信息来了解一段代码应该做什么,那么我们就已经让他们能够修复大量的bug.
这就留下了我们要提出多少评论的问题.如果你提出太多评论,那么维护这些事情会变得乏味,并且评论将不可避免地与代码过时.如果你投入太少,那么它们并不是特别有用.
我发现定期评论在以下地方最有用:
1).h或.cpp文件顶部的简要说明,用于解释课程目的的课程.这有助于维护人员快速浏览,而无需筛选所有代码.
2)在执行非平凡函数之前的注释块,解释其目的并详细说明其预期输入,潜在输出以及调用函数时预期的任何奇怪之处.这使得未来的维护者不必破译整个功能来解决这些问题.
除此之外,我倾向于评论任何可能对某人感到困惑或奇怪的事情.例如:"由于等等,这个数组是基于1而不是基于0".
写得好,评价良好的评论非常宝贵.糟糕的评论往往比没有评论更糟糕.对我来说,没有任何评论表明代码作者的懒惰和/或傲慢.无论你对代码的作用有多么明显,或者你的代码有多么美妙,进入一个代码体并且弄清楚到底发生了什么是一项具有挑战性的任务.做得好的评论可以让人们快速了解现有代码.
我一直很喜欢Refactoring的评论:
我们在这里提到评论的原因是评论经常被用作除臭剂.令人惊讶的是,您经常查看厚度注释的代码,并注意到注释是存在的,因为代码很糟糕.
评论引导我们看到错误的代码,这些代码具有我们在本章其余部分讨论过的所有腐烂的气味.我们的第一个行动是通过重构消除难闻的气味.当我们完成后,我们经常发现评论是多余的.
尽管有争议,但对于我读过的代码来说,这是真的.公平地说,福勒并不是说永远不会评论,而是在你做之前考虑代码的状态.
您需要文档(以某种形式;而不是总是注释)以便本地理解代码.代码本身会告诉您它的作用,如果您阅读了所有内容并且可以牢记这一切.(更多内容见下文.)评论最适合非正式或半正式文档.
许多人说评论是代码气味,可以通过重构,更好的命名和测试来替换.虽然糟糕的评论(即军团)也是如此,但很容易跳到总结如此,哈利路亚,没有更多的评论.这使得本地文档的所有负担 - 我认为太多 - 在命名和测试上.
记录每个函数的契约,对于每种类型的对象,它代表什么以及对有效表示的任何约束(技术上,抽象函数和表示不变).在可行的情况下使用可执行的,可测试的文档(doctests,单元测试,断言),还要写一些简短的注释,给出有用的要点.(如果测试采用示例的形式,它们是不完整的;如果它们是完整的,精确的合同,它们可以像代码本身那样进行工作.)为每个模块和每个项目编写顶级注释; 这些可以解释保持所有其他注释(和代码)简短的约定.(这支持 命名为文档:建立了约定,并且我们可以期待找到微妙的地方,我们可以更频繁地确信这些名称告诉我们需要知道的所有内容.)更长,风格化,刺激性多余的Javadocs有它们的用途,但是帮助产生了反弹.
(例如,这个:
执行n次fr选.
@param n frobulate
@param 的次数x frobulation 中心的x坐标
@param y frobulation中心的y坐标
@param z frobulation中心的z坐标
可能就像"围绕中心(x,y,z)n次." 评论不一定是读写的苦差事.)
我不像我在这里说的那样做; 这取决于我对代码的重视程度以及我希望阅读的代码.但是学习如何用这种方式写作让我成为一个更好的程序员,即使是在偷工减料.
回到我们为了本地理解而记录的声明:这个函数有什么作用?
def is_even(n): return is_odd(n-1)
测试整数是否均匀?如果is_odd()
测试一个整数是奇数,那么是的,这是有效的.假设我们有这个:
def is_odd(n): return is_even(n-1)
同样的推理说,is_odd()
如果整数是奇数,则测试.当然,将它们组合在一起并且都不起作用,即使每个都有效也是如此.稍微更改它,我们的代码可以正常工作,但仅适用于自然数字,同时仍然在本地看起来像整数一样.在微观世界中,理解代码库是什么样的:跟踪圈内的依赖关系以尝试逆向工程假设,如果他们打扰,作者可以在一两行中解释.我讨厌在过去的几十年中,精神上没有思想的编码人员的代价让我这样做了:哦,这种方法看起来像是会产生挫败战争核心的副作用......总是如此?好吧,如果奇怪的crobuncles去饱和,至少; 他们呢?更好地检查所有crobuncle处理代码......这将对理解提出自己的挑战.好的文档将这个O(n)指针追逐到O(1):例如,知道一个函数的契约和它明确使用的事物的契约,函数的代码应该是有意义的,没有进一步的系统知识.(在这里,对自然数字进行说明is_even()
和is_odd()
处理的合同会告诉我们两个函数都需要测试n==0
.)
我唯一真正的规则是评论应该解释为什么代码存在,而不是它正在做什么或它是如何做的.这些事情可以改变,如果他们这样做,必须保持评论.代码首先存在的目的不应该改变.
评论的目的是解释背景 - 代码的原因; 这,程序员无法从单纯的代码检查中知道.例如:
strangeSingleton.MoveLeft(1.06); badlyNamedGroup.Ignite();
谁知道这是为了什么?但只有简单的评论,所有内容都显示出来:
//when under attack, sidestep and retaliate with rocket bundles strangeSingleton.MoveLeft(1.06); badlyNamedGroup.Ignite();
严肃地说,评论是为什么,而不是如何,除非如何不直观.
虽然我同意代码应该是自我可读的,但我仍然认为在添加广泛的注释块以解释设计决策方面有很多价值.例如"我做了xyz而不是abc的常见做法,因为这个洞穴......"带有错误报告的URL或其他东西.
我试着将其视为:如果我已经死了并且已经离开,而且大学毕业的人必须在这里修复一个错误,他们需要知道什么?
一般来说,我看到用于解释写得不好的代码的注释.大多数代码都可以以使注释冗余的方式编写.话虽如此,我发现自己在语义不直观的代码中留下了注释,例如调用具有奇怪或意外行为等的API ......
我也通常订阅自我记录代码的想法,所以我认为你的开发者朋友提供了很好的建议,我不会重复,但肯定有很多情况需要注释.
很多时候,我认为这可以归结为实现与普通或简单抽象的类型有多接近,未来的代码阅读器将会感到舒适,或者更普遍的是代码告诉整个故事的程度.根据编程语言和项目的类型,这将导致更多或更少的注释.
因此,例如,如果您在不安全的C#代码块中使用某种C风格的指针算法,您不应期望C#程序员能够轻松地从C#代码读取切换(这可能通常更具说明性或至少更低一些 - 级别指针操作),以便能够理解您的不安全代码正在做什么.
另一个例子是当你需要做一些工作来推导或研究一个算法或方程或某些不会在你的代码中结束的东西,但有必要了解是否有人需要显着修改你的代码.你应该在某个地方记录这一点,并且在相关的代码部分中至少有一个引用将有很大帮助.
我认为您的代码包含多少或几个评论并不重要.如果您的代码包含注释,则必须维护它们,就像代码的其余部分一样.
编辑:这听起来有点浮夸,但我认为有太多人忘记即使是变量的名称,或者我们在代码中使用的结构,都只是"标签" - 它们只对我们有意义,因为我们的大脑查看一串字符,例如customerNumber
并了解它是一个客户编号.尽管评论缺乏编译器的任何"强制执行",但它们并没有被删除.它们旨在向另一个人传达意义,一个正在阅读该程序文本的人类程序员.
如果代码在没有注释的情况下不清楚,首先使代码更清晰的意图声明,然后只根据需要添加注释.
评论有其自己的位置,但主要用于代码不可避免地微妙或复杂的情况(固有的复杂性是由于问题的性质得到解决,而不是由于程序员的懒惰或混乱的思维).
在代码行中要求评论和"衡量生产力"可能导致垃圾,例如:
/***** * * Increase the value of variable i, * but only up to the value of variable j. * *****/ if (i < j) { ++i; } else { i = j; }
而不是简洁(并且适当熟练的程序员清楚):
i = Math.min(j, i + 1);
因人而异