软件设计的哲学:第十七章 一致性
一致性是降低系统复杂性和使其行为更加明显的强大工具。如果一个系统是一致的,这意味着相似的事情以相似的方式完成,而不同的事情以不同的方式完成。 一致性创造了认知杠杆:一旦你学会了在一个地方如何做某件事,你就可以利用这些知识立即理解其他使用相同方法的地方。如果系统没有以一致的方式实现,开发人员必须分别了解每种情况。这需要更多的时间。
一致性可以减少错误。 如果一个系统是不一致的,两种情况可能看起来是相同的,但实际上它们是不同的。开发人员可能会看到一个看起来很熟悉的模式,并根据之前遇到的模式做出错误的假设。另一方面,如果系统是一致的,基于熟悉的情况做出的假设将是安全的。一致性使开发人员工作更快,错误更少。
17.1一致性的例子
一致性可以应用于系统的多个级别。这里有几个例子:
命名: 第14章已经讨论了以一致的方式使用名称的好处。
编码风格: 现在开发组织通常有样式指南,它将程序结构限制在编译器强制执行的规则之外。现代风格指南解决了一系列问题,比如缩进、大括号放置、声明顺序、命名、注释和对被认为是危险的语言特性的限制。样式指南使代码更容易阅读,并可以减少某些类型的错误。
接口: 具有多个实现的接口是一致性的另一个例子。一旦您理解了接口的一个实现,任何其他实现都会变得更容易理解,因为您已经知道它必须提供的特性。
设计模式: 设计模式是某些常见问题的普遍接受的解决方案,例如用户界面设计的模型-视图-控制器方法。如果您可以使用现有的设计模式来解决这个问题,那么实现将会进行得更快,更有可能工作,而且您的代码对读者来说也更容易理解。设计模式将在第19.5节中详细讨论。
不变量: 不变量是变量或结构的一个总是为真的属性。例如,存储文本行的数据结构可能强制一个不变式,即每行以换行字符结束。不变量减少了必须在代码中考虑的特殊情况的数量,使对代码的行为进行推理变得更容易。
17.2 确保一致性
一致性很难保持,特别是当许多人长时间从事一个项目时。一组人可能不知道另一组人建立的惯例。新来者不知道这些规则,所以他们无意中违反了这些约定,并创建了与现有约定冲突的新约定。以下是一些建立和保持一致性的建议:
文档: 创建一个文档,列出最重要的总体约定,比如编码风格指南。将文档放置在开发人员可能看到的位置,例如项目Wiki上的显眼位置。鼓励新加入团队的人阅读文档,并鼓励现有的人每隔一段时间就阅读一次。各种组织的一些风格指南已经在网上发布;考虑从其中之一开始。
对于更本地化的约定(如不变量),请在代码中找到适当的位置来记录它们。如果你不把这些惯例写下来,其他人就不太可能遵循它们。
强制规约: 即使有很好的文档,开发人员也很难记住所有的约定。执行约定的最佳方法是编写一个检查违规的工具,并确保代码不能提交到存储库,除非它通过了检查器。自动检查器对于低级语法约定特别有效。
我最近的一个项目有行终止字符的问题。一些开发人员在Unix上工作,行被换行终止;其他的工作在Windows上,行通常由一个carriage-return后跟一个换行来结束。如果一个系统上的开发人员对先前在另一个系统上编辑过的文件进行了小的编辑,那么编辑器有时会将所有行终止符替换为适合该系统的行终止符。这给人的感觉是文件的每一行都被修改了,这使得跟踪有意义的更改变得很困难。我们建立了一个约定,即文件应该只包含换行,但是很难确保每个开发人员使用的每个工具都遵循这个约定。每当一个新的开发人员加入这个项目,我们就会经历一连串的线路终止问题,而那个开发人员就会适应这个约定。
我们最终通过编写一个简短的脚本解决了这个问题,这个脚本在将更改提交到源代码存储库之前自动执行。该脚本检查所有已修改的文件,如果其中任何一个包含回车,则将中止提交。该脚本还可以手动运行,以修复损坏的文件,方法是用换行替换载波返回/换行序列。
这立即消除了问题,并帮助培训了新的开发人员。
代码审查为执行约定和对新开发人员进行约定教育提供了另一个机会。 代码评审人员越是吹毛求疵,团队中的每个人就会越快地了解约定,代码就会越干净。
入乡随俗: 最重要的惯例是,每个开发人员都应该遵循这句古老的格言“入乡随俗”。在处理新文件时,请查看现有代码的结构。是否所有的公共变量和方法都在私有变量和方法之前声明?这些方法是按字母顺序排列的吗?变量在firstServerName中使用“camel case”还是在first_server_name中使用“snake case”?当你看到任何看起来可能是约定的东西时,跟着它走。在做设计决策时,问问自己是否在项目的其他地方也做了类似的决策;如果是,找到一个现有的示例,并在新代码中使用相同的方法。
不要改变现有的惯例 抵制“改进”现有公约的冲动。有一个“更好的主意”并不足以成为产生矛盾的借口。你的新想法可能确实更好,但是一致性对不一致性的价值几乎总是大于一种方法对另一种方法的价值。在引入不一致的行为之前,问自己两个问题。首先,您是否有重要的新信息来证明您的方法是正确的,而这在旧的约定建立时是没有的?第二,新方法是否更好,值得花时间更新所有旧的用法?如果您的组织认为这两个问题的答案都是“是”,那么就继续进行升级;当你完成的时候,应该没有任何旧的惯例的迹象。但是,您仍然面临其他开发人员不知道新约定的风险,因此他们可能在将来重新引入旧的方法。总的来说,重新考虑已建立的约定很少能很好地利用开发人员的时间。
17.3 别做过了头
一致性不仅意味着相似的事情应该以相似的方式去做,还意味着不同的事情应该以不同的方式去做。如果您过于热衷于一致性,并试图强制将不同的东西放在相同的方法中,例如对真正不同的东西使用相同的变量名,或者对不适合该模式的任务使用现有的设计模式,那么您将创建复杂性和混乱。只有当开发人员确信“如果它看起来像x,那么它实际上就是x”时,一致性才会带来好处。
17.4 结论
一致性是投资心态的另一个例子。确保一致性需要一些额外的工作:决定约定的工作、创建自动检查器的工作、寻找类似的情况以在新代码中模拟的工作,以及在代码评审中培训团队的工作。 这种投资的回报是您的代码将更加明显。开发人员将能够更快、更准确地理解代码的行为,这将使他们工作得更快,bug更少。