PHP 是一门自由度很高的编程语言。它是动态语言,对程序员有很大的宽容度。作为 PHP 程序员,要想让你的代码更有效,需要了解不少的规范。很多年来,我读过很多编程方面的书籍,与很多资深程序员也讨论过代码风格的问题。具体哪条规则来自哪本书或者哪个人,我肯定不会都记得,但是本文(以及接下来的另一篇文章) 表达了我对于如何写出更好的代码的观点:能经得起考验的代码,通常是非常易读和易懂的。这样的代码,别人可以更轻松的查找问题,也可以更简单的复用代码。
降低函数体的复杂度
在方法或者函数体里,尽可能的降低复杂性。相对低一些的复杂性,可以便于别人阅读代码。另外,这样做也可以减少代码出问题的可能性,更易修改,有问题也更易修复。
在函数里减少括号数量
尽可能少的使用 if, elseif, else 和 switch 这些语句。它们会增加更多的括号。这会让代码更难懂、更难测试一些(因为每个括号都需要有测试用例覆盖到)。总是有办法来避免这个问题的。
代理决策 ("命令,不用去查询(Tell, don't ask)")
有的时候 if 语句可以移到另一个对象里,这样会更清晰些。例如:
if($a->somethingIsTrue()) { $a->doSomething(); }
可以改成:
$a->doSomething();
这里,具体的判断由 $a 对象的 doSomething() 方法去做了。我们不需要再为此做更多的考虑,只需要安全的调用 doSomething() 即可。这种方式优雅的遵循了命令,不要去查询原则。我建议你深入了解一下这个原则,当你向一个对象查询信息并且根据这些信息做判断的时候都可以适用这条原则。
使用map
有时可以用 map 语句减少 if, elseif 或 else 的使用,例如:
if($type==='json') { return $jsonDecoder->decode($body); }elseif($type==='xml') { return $xmlDecoder->decode($body); }else{ throw new \LogicException( 'Type "'.$type.'" is not supported' ); }
可以精简为:
$decoders= ...;// a map of type (string) to corresponding Decoder objects if(!isset($decoders[$type])) { thrownew\LogicException( 'Type "'.$type.'" is not supported' ); }
这样使用 map 的方式也让你的代码遵循扩展开放,关闭修改的原则。
强制类型
很多 if 语句可以通过更严格的使用类型来避免,例如:
if($a instanceof A) { // happy path return $a->someInformation(); }elseif($a=== null) { // alternative path return 'default information'; }
可以通过强制 $a 使用 A 类型来简化:
return $a->someInformation();
当然,我们可以通过其他方式来支持 "null" 的情况。这个在后面的文章会提到。
Return early
很多时候,函数里的一个分支并非真正的分支,而是前置或者后置的一些条件,就像这样:// 前置条件
if(!$a instanceof A) { throw new \InvalidArgumentException(...); } // happy path return $a->someInformation();
这里 if 语句并不是函数执行的一个分支,它只是对一个前置条件的检查。有时我们可以让 PHP 自身来完成前置条件的检查(例如使用恰当的类型提示)。不过,PHP 也没法完成所有前置条件的检查,所以还是需要在代码里保留一些。为了降低复杂度,我们需要在提前知道代码会出错时、输入错误时、已经知道结果时尽早返回。
尽早返回的效果就是后面的代码没必要像之前那样缩进了:
// check precondition if(...) { thrownew...(); } // return early if(...) { return...; } // happy path ... return...;
像上面这个模板这样,代码会变动更易读和易懂。
创建小的逻辑单元
如果函数体过长,就很难理解这个函数到底在干什么。跟踪变量的使用、变量类型、变量声明周期、调用的辅助函数等等,这些都会消耗很多脑细胞。如果函数比较小,对于理解函数功能很有帮助(例如,函数只是接受一些输入,做一些处理,再返回结果)。
使用辅助函数
在使用之前的原则减少括号之后,你还可以通过把函数拆分成更小的逻辑单元做到让函数更清晰。你可以把实现一个子任务的代码行看做一组代码,这些代码组直接用空行来分隔。然后考虑如何把它们拆分成辅助方法(即重构中的提炼方法)。
辅助方法一般是 private 的方法,只会被所属的特定类的对象调用。通常它们不需要访问实例的变量,这种情况需要定义为 static 的方法。在我的经验中,private (static)的辅助方法通常会汇总到分离的类中,并且定义成 public (static 或 instance)的方法,至少在测试驱动开发的时候使用一个协作类就是这种情形。
减少临时变量
长的函数通常需要一些变量来保存中间结果。这些临时变量跟踪起来比较麻烦:你需要记住它们是否已经初始化了,是否还有用,现在的值又是多少等等。
上节提到的辅助函数有助于减少临时变量: