代码整洁之道(二):函数篇
短小
函数的第一规则就是短小。在《代码整洁之道》中认为函数甚至应该短到只有数行。
代码块和缩进
在《代码整洁之道》中,
if语句、else语句、 while语句等,其中的代码块应该只有一行。该行大抵应该是一个函数调用语句。这样不但能保持函数短小,而且,因为块内调用的函数拥有较具说明性的名称,这也意味着函数不应该大到足以容纳嵌套结构。所以,函数的缩进层级不该多于一层或两层。当然,这样的函数易于阅读和理解。
我个人不倾向于这种写法。如此短的函数和频繁的函数调用不仅不会降低代码编写难度,还会提高难度。一般情况下,我个人写程序大概会在五六十行左右。
只做一件事
函数应该做一件事。只做这一件事。做好这件事。 重要的是如何确定是一件事和这件事是什么。在写函数时必须要确定要做什么事。 如果函数只是做了该函数名下同一抽象层的步骤,则函数还是只做了一件事。编写函数是为了把大一些的概念拆分为另一抽象层的一系列步骤。
一个函数一个抽象层级
要确保函数只做一件事,函数中的语句都要在同一抽象层级上。
自顶向下
我们想要让代码拥有自顶向下的阅读顺序。我们想要让每个函数后面都跟着位于下一抽象层级的函数,这样一来,在查看函数列表时,就能循抽象层级向下阅读了。我把这叫做向下规则。
switch语句
关于switch的介绍不得不引用全文:
写出短小的 switch语句很难。即便是只有两种条件的 switch语句也要比我想要的单个代码块或函数大得多。写出只做一件事的 switch语句也很难。 Switch天生要做N件事。不幸我们总无法避开 switch语句,不过还是能够确保每个 switch都埋藏在较低的抽象层级,而且永远不重复。当然,我们利用多态来实现这一点。
public Money calculatePay(Employee e)
throws InvalidEmployeeType{switch (e.type){case COMMISSIONED:return calculateCommissionedPay(e);case hourly:return calculateHourlypay(e);case SALARIED:return calculateSalariedPay(e);default:throw new InvalidEmployee Type(e.type);
}}
该函数有好几个问题。首先,它太长,当出现新的雇员类型时,还会变得更长。其次,它明显做了不止一件事。第三,它违反了单一权责原则( Single Responsibility Principle,SRP)因为有好几个修改它的理由。第四,它违反了开放闭合原则( Open Closed Principle,OCP),因为每当添加新类型时,就必须修改之。不过,该函数最麻烦的可能是到处皆有类似结构的函数。例如,可能会有:isPayday(Employee e, Date date),
或deliveryPay(Employee e, Money pay)
该问题的解决方案(如代码清单3-5所示)是将 switch语句埋到抽象工厂底下,不让任何人看到。该工厂使用 switch语句为 Employee的派生物创建造当的实体,而不同的函数,如 calculatePay、 isPayday和 deliverPay等,则藉由 Employee接口多态地接受派遣。对于 switch语句,我的规矩是如果只出现一次,用于创建多态对象,而且隐藏在某个继承关系中,在系统其他部分看不到,就还能容忍[G23].当然也要就事论事,有时我也会部分或全部违反这条规矩。
使用描述性的名称
长而具有描述性的名称,要比短而令人费解的名称好。长而具有描述性的名称,要比描述性的长注释好。使用某种命名约定,让函数名称中的多个单词容易阅读,然后使用这些单词给函数取个能说清其功用的名称。
函数参数
参数数量
越少越好。 尽量不要有输出参数,而是将输出设置为返回值。 如果参数较多的时候可以考虑使用类进行封装。
无副作用
副作用是一种谎言。函数承诺只做一件事,但还是会做其他被藏起来的事。有时,它会对自己类中的变量做出未能预期的改动。有时,它会把变量搞成向函数传递的参数或是系统全局变量。无论哪种情况,都是具有破坏性的,会导致古怪的时序性耦合及顺序依赖。
分隔指令与询问
这实际上内生的包含于一个函数只做一件事的要求中,但是还是有必要单独指出。函数要么做什么事,要么回答什么事,但二者不可兼得。
使用异常替代返回错误码
不要重复
如果你发现某两个函数用到了相同甚至相近的代码块应该迅速思考是不是可以将其抽取成单独的函数。 重复就是万恶之源。
结构化编程
Dijkstra认为,每个函数、每个代码块都应该只有一个入口一个出口。这意味着每个函数只能有一个return语句,循环中不能有break或continue,而且永远不能出现goto。 事实上,当代码相对短小的时候,适当多几个return、break、continue无伤大雅。当代码冗长时,这样的规则才能够发挥出其效力来。
总结