关于可维护性的笔记

这就是问题变得复杂的地方。你的代码已经准备就绪,可以正常运行。你采用了一种新的编程方法,几个月来项目的初步开发进展顺利。很明显,你的项目可能没有任何基础,你很幸运地从一张白纸开始。然而,可维护性问题很快就会出现。无论你选择哪种编程技术,无论哪种人在工作,错误总会出现。你可能需要新的人来解决这些问题(从而向他们传授你的工作方法)。你确定自己已经掌握了足够的新方法,可以确保应用程序在数年内都能正常运行吗?这是完全有可能的,但你必须意识到这一点,并知道如果在维护应用程序时遇到困难该怎么办。

本章的目的并不是要阻止主动性和对新工作方法的测试。更重要的是要充分认识到选择新工作方法的风险,尤其是长期风险。稍后我们将看到,对于可能在一夜之间消失的最新趋势,我们必须无比谨慎。

同样的道理也适用于编程语法,这些语法乍一看可能很优雅,但实际上却是可维护性的噩梦。在这些实践中,我们可以找到接下来要强调的一些,但并非详尽无遗。

使用二元运算符和八进制、十六进制及二进制表示法

一般来说,在整数上使用二进制运算符来执行运算(左移和右移、逻辑 AND、逻辑 OR、位反转等)比什么都没用。二进制运算符的稀缺性使其成为一种看似优雅的语法,用于执行某些操作。但事实并非如此,掌握二进制操作不应该是理解 PHP 代码的先决条件。

使用八进制、十六进制和二进制符号有时是有道理的。例如,当你要处理文件权限时,可以使用八进制符号。如果想在方法上使用标志,可以使用十六进制符号,也可以使用二进制符号。但总的来说,除了让代码读起来更复杂外,并没有什么用处。

分配变量并使用 goto

在测试变量值的同时,可以对变量进行赋值。下面是一个例子:

if null === ($var = method()))

充其量只能节省一行代码。不过,我们已经很久没有关心过应用程序源文件的大小了,反正 PHP 解释器会在运行时对其进行优化。在测试前分配变量没有任何成本,而且代码的可读性也会立即提高。

使用 goto 指令可以跳过代码的整个部分,甚至在代码中 "向上"。虽然它在某些极其特殊的情况下可能有用,但在大多数情况下绝对不应该使用。多年来,大多数编程语言都不赞成使用 goto 语句。事实上,这些语句给理解代码流带来了极大的复杂性。过多地使用 goto 语句有一个名称:意大利面条代码(spaghetti code)。

过度使用注释

有时,我们会看到滥用注释的情况,有时几百行的注释会详细解释所有变量的类型、用途、函数引发的所有异常、返回值等等。我们甚至可以找到注释比代码还多的源文件。如果对方法和变量进行清晰的命名,这些细节在大多数情况下都可以省略。此外,在 PHP 的最新版本中,变量、参数和函数返回值的类型化也解决了这个问题。不过,使用注释来编写文档是完全合理的,在可能的情况下应该使用。没有人会因为 "文档太多" 而抱怨。你可以随意写上几十行关于类、接口、方法等的内容;更广泛地说,关于它的后代、技术和/或功能选择,等等。当我们谈论 "滥用注释" 时,我们指的是解释代码中发生了什么的注释。

使用三元比较

这是三元比较的示例:

$var === null ? 'is null' : 'is not null'

虽然三元比较法可以使代码简洁,并在函数中传递参数时包含一个条件,但它们不应被滥用,尤其是嵌套的三元比较法。下面的示例就证明了这一点:

$var === null ? 'is null' : is_int($var) ? 'is int' : 'is not null'

这行代码并不清晰,可读性也不高,当条件不是基本和简单的条件时,三元条件的阅读就会变得复杂,如第一个示例。

使用缩写

这里也许是最不提倡的常见做法:到处使用缩写。同样,在上个千年末期,我们可能有使用缩写的理由:当时的空间和存储空间比现在有限得多,代码编辑器也没有现在这么智能的自动补全功能。因此,将变量命名为 $userPasswordRequest 而不是 $usr 会让每个人的生活都变得更轻松:无论是你自己还是开发人员,他们在回看你的代码时都不必再问你缩写的含义。再说一遍,有了现在的自动完成工具,这样命名变量是没有意义的。

为你的代码带来微观优化

微优化是对代码所做的非常细微的改动,这些改动可能会影响代码的可读性,而优化代码是合理的。这样,代码执行起来就会更快。问题是,这往往没有什么用处,首先因为你不需要把指令优化到纳秒级(因为现在的处理器功能强大),而且很多优化都是由语言解释器和编译器完成的。因此,你牺牲了代码的部分可读性,换来的却是无用的自动优化。此外,这还经常引发无谓的争论,谁也不比谁更正确。在这些针对 PHP 的微优化争论中,我们尤其会发现增量运算符 (++) 和减量运算符 (--) 在变量之前或之后的位置、标准 PHP 库 (SPL) 方法前面反斜杠的使用、匿名函数的静态声明与否等问题。答案还是:考虑与其它代码的一致性,务实一点。你肯定不需要(也许在某些情况下)因为你的优化而节省 10 纳秒的时间,因为你的优化会在开发团队中引起激烈的争论。

重新编写 SPL 的方法

我们需要大量使用拥有非常广泛的标准库的语言。标准库是一组类和方法,随 PHP 的每次安装一起提供。不幸的是,我们很快就意识到,它的未知性和提供的可能性比你想象的要多得多。因此,我们经常会发现,由于相关开发人员不知道标准方法的存在,项目中的 SPL 方法被重新编码。这是非常不幸的,在某些情况下,这也是一个真正的问题,原因如下:

  • SPL 方法随处可见。不必担心它们是否在这个或那个安装或设置中可用。

  • PHP 解释器的开发人员已经对这些方法进行了测试,而你的方法则不一定。

  • 如果这些方法中的某一种可以优化或安全,那要归功于成千上万的语言贡献者和研究者。

  • 这些方法都是由那些以创建最高效算法为己任的人们思考和构思出来的,目的是尽可能提高效率。

  • SPL 方法可以直接用 C 语言编写。这意味着无论你在 PHP 中做什么,它们的性能都是无可匹敌的。如果不利用这一巨大优势,特别是在执行时间至关重要的应用程序中大量使用的方法,那就太可惜了。此外,由于这些方法是用 C 编写的,因此 C 编译器可以直接使用汇编代码对这些方法进行非常底层的优化。而用 PHP 编写方法则无法做到这一点。

请随时查看 PHP 官方文档;一些方法(如 natsort())可能会让你大吃一惊,并为你节省几个小时的开发时间!

这样的例子不胜枚举,但问题是,虽然你可能会喜欢使用这些东西,但只有你自己才会感到满意。初级开发人员看到这些实践可能会完全迷失方向,而高级开发人员在有更清晰、更简单的实践时也不会理解使用这些实践的价值。你的代码必须让尽可能多的人理解。针对一个起初看起来过于复杂的问题,编写一些简单、琐碎和可读的代码,以展示你的技能、知识和熟练程度。

总结

你理解 "在我们已知的基础上继续前进" 的含义吗?我们事后才意识到,这又是一种常识和利他主义,是为下一个将使用我们代码的开发人员着想。在这里,不存在童子军、SOLID、Keep It Simple, Stupid(KISS)或其它原则的问题。而是要重新思考我们编写代码的方法。

我们必须记住,基础知识是可以(也应该)被质疑的,而不是一成不变的。自信是一件美妙的事情,如果你能将自信与不断质疑自己的习惯结合起来,从持续改进的角度出发,你就会走上成为一名优秀开发人员的正确道路,能够自然而然地编写出简洁的代码,并在实践中带动你的合作者。

主动出击是一件了不起的事情;了解风险并根据自身情况对其进行评估是追求完美的关键。这样,你就能知道这样做是否真的值得,也能向项目负责人证明你的选择是正确的。在这里,我们要再次强调的是,当涉及到清洁代码时,我们要能够证明自己所有的选择和行为都是合理的。简洁代码不仅仅是避免使用二进制运算符或十六进制符号。它意味着要考虑项目的环境、约束条件和周边环境。干净的代码不仅仅是代码。幸运的是,这正是我们将在下一章看到的内容。