函数作为代码中最重要的一环之一,如何让代码从晦涩难懂变得简单易读,是一个必须要考虑的问题。
1. 短小
函数的第一规则是要短小。第二规则是还要更短小。每个函数都应该一目了然,一个函数只说一件事,而且,每个函数都依序把你带到下一个函数。只做一件事的函数无法被合理的切分为多个区段。
2. 代码块和缩进
if语句、else语句、while语句等,其中的代码块应该只有一行。该行大抵应该是一个函数调用语句。这样不仅仅能保持函数短小,而且,因为块内调用的函数拥有较具说明性的名称,从而增强了文档上的价值。这也意味着函数不应该大到足以容纳嵌套结构。所以,函数的缩进层级不该多于一层或两层。
3. 每个函数一个抽象层级
要确保函数只做一件事,函数中的语句都要在同一抽象层级上。
4. 使用描述性的名称
别害怕长名称,长而具有描述性的名称,要比短而令人费解的名称好,要比描述性的长注释好。使用某种命名约定,让函数名称中的多个单词容易阅读,然后使用这些单词给函数取个能说清其功用的名称。命名方式要保持一致。使用与模块名一脉相承的短语、名称和动词给函数名称。
5. 函数参数
最理想的参数数量是零,其次是一,再次是二,应尽量避免三个数量的参数函数。输出参数比输入参数更难以理解。读函数时,我们惯于认为信息通过参数输入函数,通过返回值从函数输出。我们不太期望信息通过参数输出。所以,输出参数往往让人苦思之后才恍然大悟。
(1)如果函数看来需要两个、三个或三个以上的参数,就说明其中一些参数应该封装为类了。
(2)给函数取个好的名字,能较好地解释函数的意图,以及参数的顺序和意图。对于一元函数,函数和参数应形成一种非常良好的动词/名词对形式。例如,write(name)就相当令人认同;assertEqual改成assertExpectedEqualsActual(expected, actual)可能会好些。这大大减轻了记忆参数顺序的负担。
6. 无副作用
函数承诺只做一件事,但还是会做其他被藏起来的事。有时,它会对自己类中的变量做出未能预期的改动。有时,它会把变量搞成向函数传递的参数或是系统全局变量。无论哪种情况,都是具有破坏性的,会导致古怪的时序性耦合及顺序依赖。如果一定要时序性耦合,就应该在函数名称里说明,尽管这样可能会违反“只做一件事”的原则。
7. 分隔指令与询问
函数要么做什么事,要么回答什么事,二者不可得兼。函数应该修改某对象的状态,或是返回该对象的有关信息。
8. 使用异常替代返回错误码
从指令函数返回错误码轻微违反了指令与询问分隔的规则。它鼓励了在if语句判断中把指令当做表达式使用。这不会引起动词/形容词混淆,但却导致更深层次的嵌套结构。当返回错误码时,就是在要求调用者立刻处理错误。另一方面,如果使用异常代替返回错误码,错误处理代码就能从主路代码中分离出来,得到简化。
(1)Try/catch 代码块丑陋不堪。它们搞乱了代码结构,把错误处理与正常流程混为一谈。最好吧try/catch代码块的主题部分抽离出来,另外形成函数。
(2)函数应该只做一件事。错误处理就是一件事。因此,处理错误的函数不该做其他事。这意味着如果关键字try在某个函数中存在,它就该是这个函数的第一个单词,而且在catch/finally代码块后面也不该有其他内容。
9. 别重复自己
重复可能是软件中一切邪恶的根源。许多原则与实践规则都是为控制与消除重复而创建。
10. 结构化编程
每个函数、函数中的每个代码块都应该有一个入口、一个出口。遵循这些规则,意味着在每个函数中只该有一个return语句,循环中不能有break和continue语句,而且永永远远不能有任何goto语句。不过,对于小函数,这些规则助益不大。只有在大函数中,这些规则才会有明显的好处。所以,只要保持函数短小,偶尔出现的return、break或continue语句没有坏处,甚至还比单入单出更具有表达力。另一方面,goto只在大函数中才有道理,所以应该尽量避免使用。