领先技术

源代码的可读性提示

Dino Esposito

Dino Esposito您是否听说过国际 C 语言混乱代码竞赛?简言之,这是一种开放式的竞赛,从一系列使用极其模糊混乱的 C 语言代码解决任意问题的 C 语言程序中选出一个获胜者。您可以访问 ioccc.org/years.html,从中找到往年获胜程序的源代码。

C 语言混乱代码竞赛以一种轻松的方式展示编程风格及可读性的重要性。本专栏总结了一些最重要的实践,您可以遵循这些实践来编写易于您自己及同事阅读和理解的代码。

可读性是一种属性

在软件开发方面,可维护性是指可以轻松地用来修改现有代码以达到相关目标的一种属性,而这些目标包括修复漏洞、内务处理、实施新功能或只是重构某些模式。根据 ISO/IEC 9126 文件,可维护性是软件的根本属性之一。有关软件属性的更多信息,请访问 bit.ly/VCpe9q 来查看该文件。

代码的可维护性由多种因素构成,可读性便是其中之一。不易于阅读的代码同样不易于理解。开发人员着手编写自己都不是十分清楚和理解的代码只会让该代码更加糟糕。

遗憾的是,可读性是非常主观的问题。实际上,开发一种自动工具来检查和报告代码的可读性级别是不可能的。但是,即使可以自动衡量可读性,也没有人认为和相信这类工具有很高的可靠性。最终,可读性属于人工属性,每个开发人员应该整体检查其代码。能够编写易于阅读的代码应属于每个开发人员文化责任感的一部分,同时也扩展了他们的技能知识。

总而言之,可读性是一种代码属性,您可以且应该在自己的编程生涯之初就学着采纳,并在日后不断提高自己这方面的能力。如同风格和出色的设计一样,可读性不应该留给专家来解决。更重要的是,不应将该问题拖到您有足够的时间时再来处理。

可读性的编程之法

编写可读性强的代码是对其他开发人员的一种尊重。正如一位 StackOverflow 用户曾经所说:“你在进行编码的时候应该始终觉得,最终维护你代码的人可能是一位知道你住处的、充满暴力的精神病患者。”此外,您还应该想到某天最终维护您代码的人可能就是您自己。

当您看到他人的代码时,总有些事情可能将您逼疯。代码难以阅读一方面可能是因为数据结构和算法的目标不明确。另一方面是代码中运用的策略不明确、难以确定,而且没有通过备注进行注解。例如:

// Find the smallest number in a list of integers
private int mininList(params int[] numbers)
{
  var min = Int32.MaxValue;
  for (var i = 0; i < numbers.length; i++) {
    int number = numbers[i];
    if (number < min)
      min = number;
  }
  return min;
}

虽然我为了清晰起见使用 C# 作为示例,但我不得不承认所有 C# 开发人员都不会考虑编写这段代码。原因在于,通过 C# 和 Microsoft .NET Framework,您可以使用 LINQ 获取许多相同的结果。除了是采用 Java 编写以外,我在一个项目中遇到了类似的代码。曾经某个时候,我受聘将代码库迁移到 .NET Framework 和 C#。

此外,您可能在实际的 .NET 项目中也会遇到类似的代码。您可以应用一些可读性注意事项,从而保留您的初衷。该函数中首先不能发挥作用的是名称。惯例不无道理。该名称缺少动词,并且使用混合的外层逻辑。类似于 GetMinFromList 的名称可能更好。但是,该代码最具争议的一点是名称中使用的专用限定符。

任何不经意的读者看到这段代码都会认为该函数充当实用程序。换而言之,它表示一种潜在的可重用代码段,您可以从代码库其余部分内的多个位置进行调用。因此,将其设定为专用有时候没什么意义。但是,开发人员知道 YAGNI 原则(You Ain’t Gonna Need It,YAGNI)的强大威力,适当地只公开极为需要的代码。

代码的编写者可能已预见该函数潜在可重用,但并非在编写代码时。这就是为什么该函数被编写为可以轻松地转变成可重用帮助器函数,但却被标记为专用以至于只能在主机类内看到。对于外部读者而言,可能无法即时理解此编码策略。但是,只要确定添加几行备注来解释其动因即可。如果您不添加适当的备注,则您在编码界将不是一个好公民。虽然读者最终能够理解其动因,但是会浪费一些时间,更糟糕的是这会让读者对代码编写者充满敌意。

可读性的实践规则

尽管代码的可读性是已被广泛认可的重要主题之一,但是还不一定规范。与此同时,若没有规范,代码的可读性几乎还只是个徒有其表的概念。总而言之,您可以按照 3C 原则来处理可读性:备注、一致性和清晰性相结合的原则。

与几年前相比,IDE 工具现在更加智能化。开发人员不一定非要编写帮助文件并在 Visual Studio 项目中集成他们的自定义文档。工具提示随处可见,并且可在备注中自动创建。通过现代的 IDE,可以更轻松地定义指导性备注,您只需思考文本,剩下的全部交给 IDE 来处理即可。指导性备注是向方法和类中添加的经典备注,可以对相关目标提供简明的描述。您应该遵循平台或语言标准来编写这些备注。对于您创建的任何公用代码段,您还应该强制添加这些备注。

禁止备注过于明显是提高可读性的另一基本步骤。明显的备注只是添乱,并不能提供相关信息。根据定义,备注是指所有解释性文字,对您在编码中所做的不能即时被人所理解的决定进行解释。备注应该只是有关代码特定方面的见解性评注。

第二个“C”代表一致性。每个团队必须遵循相同的准则来编写代码。如果这些准则在全公司范围内使用,那是再好不过了。提到准则,许多人往往停留在准则的定义应该是什么,并试图弄明白什么是对,什么是错。我敢说,在整个代码中始终坚持同样的方式做同样的事情,要比探讨什么是对以及什么是错来得重要。

假设,您在某个时刻编写一个执行字符串操作的库。在此库的多个位置,您可能需要检查某个字符串是否包含给定的子字符串。怎么办呢?在 .NET Framework 和 Java SDK 中,您至少可以通过两种方法来获取相同的结果。您可以使用 Contains 或 IndexOf 方法。尽管这两种方法的用途不同,但是得出的结果相同。

Contains 方法返回一个布尔值答复,并直接告诉您某个子字符串是否包含在给定字符串内。IndexOf 方法返回基于 0 的指数,其中定位了已搜索的字符串。如果没有子字符串,则 IndexOf 返回 -1。因此,纯粹从功能角度而言,Contains 和 IndexOf 都可以使用,它们实现的目标是相同的。

但是,它们为阅读该代码的读者传达了不同的信息,并且强制读者二次查看代码,以便确定是否有特殊的原因来使用 IndexOf 而不是 Contains。当然,一次回读该代码段不成问题。但是,当整个代码库中含有成千上万条代码段时,这对时间乃至成本都会造成严重影响。这是代码可读性不高的直接成本。

具备代码一致性的意识应该是您与生俱来的一种责任感。作为一名开发人员,您应该在第一次编码时就致力于编写出干净的代码,而不要寄希望于以后留出足够的时间进行清理。作为团队领导,您应该在检查政策中强制贯彻代码一致性准则。理论上,您不应该允许那些未通过一致性测试的代码进入到检查环节。

最新版的 ReSharper 在很大程度上有助于使这一理念具体化。您可以使用这些免费的命令行工具(一套独立的工具)来将代码质量分析的各种形式直接集成到持续集成 (CI) 或版本控制系统。这些命令行工具可以执行离线代码检查。这和您可以在 Visual Studio 内在线执行的一组代码检查相同,其中 Visual Studio 安装了 ReSharper 用于捕捉代码中的重复内容。根据您的 CI 定制功能,您可能需要将这些命令行工具打包在临时组件中。有关 ReSharper 的更多信息,请查看 bit.ly/1avsZ2R

清晰性是代码可读性的第三个原则,也是最后一个原则。如果您的代码风格易于阅读,则表示您的代码清晰明了。这包括适当的分组和嵌套。总而言之,IF 语句会给代码造成很多混乱。有时,您无法避免条件语句(编程语言的支柱),但是尽量限制 IF 语句的数量会让嵌套保持在可控制的范围内,从而使代码更加易于阅读。您还以使用 IF… ELSE … IF … ELSE 结构来代替嵌套的 IF 语句。

有些任务可能需要几行代码,这可能很难或者不适合进行“提取方法”重构。在这种情况下,通过空白行让这些行在块中分开来比较好。其不会更改代码的实质内容,但是可以让代码更易于阅读。最后,如果您要寻找有关如何设计您源代码风格的灵感,请参阅一些开源项目。

越短越好

较长的行不利于肉眼进行阅读。这就是为什么报纸和杂志按列打印文字的原因。对于您的代码,也应该这样做,限制行的横向长度以及垂直滚动方式。那么,一个方法的正文的理想长度是多少?通常来说,30 行应该是极限水平,超出这个限度就会提醒您要考虑进行重构。最后,项目文件夹的合理安排以及文件夹和命名空间之间的匹配通常会反映每个代码元素的合理安排。

当您提到可读性和干净代码的主题时,通常会面临大伙一致反对——编写干净的代码很难而且费时。您应该想方设法消除这一观念,您有两种方法可以采用。一种是使用代码助理工具,该工具通过建议重构、检查您的代码以找出不好的模式以及检查死码或重复代码来帮助您编写干净的代码。如果所有这些功能均可实现,您真的没有任何理由不编写更加干净且可读性更高的代码。主要的供应商都会提供代码助理工具。只要任意选择一个即可。

另一种方法是强调自我提高和态度的重要性,敦促每个开发人员将编写更加干净的代码当成通用惯例。经验丰富的开发人员在这方面做得不错。能否大致意识到自己距离最终代码版本的差距然后进行真正的清理是区分经验丰富和缺乏经验的开发人员的主要依据。


Dino Esposito 是《Microsoft .NET:Architecting Applications for the Enterprise》(Microsoft Press,2014 年)和《Programming ASP.NET MVC 5》(Microsoft Press,2014 年)的合著者。作为 JetBrains 的 .NET Framework 和 Android 平台的技术推广人员,Esposito 经常在全球行业活动中发表演讲,并在 software2cents.wordpress.com 上以及 twitter.com/despos 上的推文中分享他对于软件的愿景。

衷心感谢以下 Microsoft 技术专家对本文的审阅:James McCaffrey