内联文档
一个特例是我们中的许多人从开始编写软件起就经常做的文档:注释。这些注释直接写在代码中,开发人员一眼就能看到,因此似乎是放置文档的好地方。但是,注释真的应该被视为或用于文档吗?
我们认为,一般情况下应避免使用注释。让我们在接下来的几页中看看一些论据。
注释不是代码
注释不是代码的一部分。虽然可以通过 PHP 的 Reflection API 解析注释,但注释原本并不是用来存储元信息的。理想情况下,去掉所有注释后,你的软件仍能正常运行。
但如今,情况往往不再如此。对象关系映射器(ORM)等框架和软件包使用 DocBlock 注释来存储信息,如路由定义或数据库对象之间的关系。一些代码质量工具使用注解来控制它们对代码某些部分的行为。
如果注解有误,PHP 不会抛出错误信息。如果注释有重要作用,你的测试有望在部署到生产环境之前发现错误。更好的选择是属性,它是一种真正的语言结构。在本章前面谈到 API 文档时,我们对属性进行了更详细的讨论。
无法读取的代码
此外,正如我们在第 12 章 "在团队中工作 "中所讨论的,注释往往是代码过于复杂的标志。与其解释你的代码,不如写出不需要注释的代码。
将整个函数压缩成一句话可能是个有趣的练习—例如,使用一些四重嵌套的三元运算符或一个没有人会理解的复杂得吓人的 if 子句。当生产环境中出现一个高优先级的错误时,你就会后悔写了这个函数,因为你必须在完全不知道你的神秘杰作应该做什么的情况下修复它。
或者,更糟糕的是,你的新同事第一次值班,就有幸在警报不断传来的深夜进行调试。有更好的方式开始工作关系。
过时的注释
注释写得快,忘得也快。由于 PHP 解释器不会对注释进行解析,因此当注释不再正确时,你将不会得到通知—例如,当注释本应解释的函数被重写并突然用于不同的目的时。除了开发人员尝试阅读并理解注释的含义,并将其与实际函数代码进行比较之外,没有其它方法可以验证注释的正确性。
在写这篇文章时,这听起来可能不是什么问题,但想象一下,一年后再回到课堂上,你会发现有一条注释你已经无法理解了。你当初为什么要写它呢?如果你都不知道为什么,别人又怎么会知道呢?
过时的注释是代码中的错误信息。由于开发人员的时间不是免费的,它们会分散注意力,而且代价高昂。因此,在添加注释之前请三思而后行。
无用的注释
尽量避免在注释中陈述显而易见的内容,而不添加任何进一步的信息。下面的代码片段就是一个真实的例子:
// write the string to the log file
file_put_contents($logFileName, $someString)
虽然开发人员花时间解释 file_put_contents
函数可以说是一种很好的姿态,但它并没有为代码增加价值。如果你不知道某个函数,你可以查找它。除此之外,这只是阅读代码时需要扫描的一行多余代码。
有时要区分有用注释和无用注释并不容易。你可以使用代码审查来解决这个问题;正如第 12 章 "团队工作" 中所讨论的,让团队中的其它人对你的代码进行诚实的审查将有助于避免类似的注释。
错误或无用的 DocBlock
我们在第 12 章 "团队工作" 中介绍编码指南时,已经讨论过 DocBlocks 及其问题所在。简而言之,由于 DocBlocks 基本上是注释(但遵循一定的结构),如果函数调用的参数发生变化,而 DocBlocks 中没有更新必要的变化,那么 DocBlocks 就会很快过时或出错。你的集成开发环境可能会发出警告,但 PHP 不会。
在 PHP 中引入了更好的类型提示后,许多 DocBlock 就可以直接删除了。冗余没有任何益处,如果实际代码与注释不一致,反而会使读者感到困惑。
TODO 注释
注释不适合用来存储任务。你可能知道这样的注释:
// TODO refactor once new version of service XY is released
虽然有些集成开发环境可以帮助你管理 TODO 注释,但这种方法只有在项目只有你一个人的情况下才会奏效。一旦你在一个团队中工作,使用 JIRA、Asana 甚至 Trello 等工作管理工具,写这样的注释就是在制造技术债务,或者换句话说,你是在把任务推卸给未来某个不确定的日子。希望有一天别人会修复它—不过大多数情况下,这不会发生。
与其发表评论,不如考虑在你选择的工作管理工具中创建一个任务。这样,对你的同事来说是透明的,也更容易计划这项工作。
当注释有用时
在讨论了不应注释的内容之后,是否还有注释有用的用例?的确,没有那么多了,但在某些情况下,注释仍然是有意义的,比如以下情况:
-
避免混淆:如果你预计其它开发人员可能会不明白你为什么选择这种实现方式,那么你就应该通过添加注释来说明更多情况。
-
实现复杂算法时:即使我们尽量避免,有时也不得不编写难以理解的代码—例如,如果我们需要实现某种算法或某些未知的业务逻辑。在这种情况下,一个简短的注释可以救我们的命。
-
供参考:如果你的代码所实现的某些逻辑在其它地方已有解释—例如在 wiki 或 ticket 中—你可以添加一个指向相应源的链接,以方便他人查找更多相关信息。这应该只是一个例外,而不是规则。
请记住,我们不想教条化。如果你觉得有必要发表评论,那就写吧。它仍然可以被删除,可能是在与其它开发人员讨论代码审查中的主题之后。
测试作为文档
编写测试的开发人员经常说,这些测试也是文档。我们在第 10 章 "自动化测试 "中谈到自动化测试的好处时,也提出了这种说法。如果你不知道一个类的目的是什么,你至少可以从测试中推断出它的预期行为,因为这正是测试的作用:它们提出了代码将被测试的断言。通过观察这些断言,你就能知道代码应该做什么。
如果测试失败,你至少可以知道断言与实际代码之间存在差异,而且你现在不能相信它们。除非在项目中测试失败通常不会被忽略,否则你可以肯定很快就会有人修复它们,或者换句话说,隐含文档会得到更新。
如果所有的测试都通过了,你就知道可以信任类的实现—只要测试写得好,而且不只是测试一个模拟对象的实现,就像我们在第 10 章 "自动化测试 "中谈到单元测试时所讨论的那样。
当然,阅读和理解测试并不是最简单的文档形式,但如果没有其它文档,它们可以成为可靠的真相来源(SOT)。然而,它们不应成为项目中唯一的文档类型。
总结
编写干净的代码不仅意味着自己知道如何去做,而且还要确保其它开发人员也遵循这条道路。 为了能够做到这一点,他们需要了解适用于该项目的规则。
在本章中,我们讨论了如何创建可以帮助你实现此目标的文档。 我们讨论了手动编写文档以及创建信息丰富且可维护的图表的最佳实践。 最后,我们介绍了从代码生成文档的方法,并详细阐述了内联文档的优缺点。
恭喜! 你已读完本书。 我们希望你喜欢阅读它,并且现在有充分的动力编写干净的代码。
你可能不会立即成功。 在从事商业项目时,加强编码技能是一个令人沮丧的过程,有时甚至很难做到。 试着保持耐心,随着时间的推移,你会变得越来越好。
仅仅阅读一本关于干净代码的书肯定是不够的。 在本书的学习过程中,我们通常只能触及主题的表面,我们鼓励你更深入地研究你感兴趣的主题,以及那些你一开始可能认为不感兴趣的主题。 它需要更多的学习、开放的心态以及愿意接受他人的反馈来提高你的技能。
然而我们相信,通过这本书,我们为你作为一名优秀的 PHP 开发人员的未来之旅提供了一个坚实的起点。 如果你也这么认为,我们会很高兴。