函数(clean code 学习笔记)
短小
- 代码块和缩进
if
,else
,while
,其中的代码块应该只有一行。该行大抵是一个函数调用语句。这样不但能保持函数的短小,而且,因为块内调用的函数拥有比较说明性的名称,从而增加了文档上的价值。
所以函数的缩进层不应该多余一层或者两层。
bad
good
只做一件事情
- 函数应该做一件事情,做好一件事情,只做一件事情。
要判断函数是否不止做了一件事,就是看是否能拆除一个函数。 - 每个函数一个抽象层级
函数中混杂不同抽象层级,往往让人迷惑,读者可能我发判断某个表达式是基础概念还是细节,一旦细节与基础概念混杂,更多的细节就会在函数中纠结起来。
自顶向下读代码
我们想让代码拥有自顶向下的阅读顺序。我们想要让每个函数后面都跟着位于下一抽象层级的函数。
switch 语句
使用描述性的名称
好名称的价值怎么好评都不为过
别害怕长名称,长而具有描述性的名称,要比短而令人费解的名称好。
长而具有描述性的名称,要比描述性的长注释好。
命名方式保持一致。
函数参数
最理想的参数数量是零,其次是1,再其次是2,应尽量避免3。有足够特殊的理由才能用三个以上参数。
一元函数的普遍形式
- 操作该参数,将其转化为其他的什么东西在输出。例如:
InputStream fileOpen('MyFile')
把String
类型的文件名转换为InputStream
类型的返回值。 - 事件(event),这种形式有输入参数而无输出参数。程序将函数看作是一个事件,使用该参数修改系统状态。
尽量避免不遵循这两个形式一元函数。
标致参数
不要向函数参数传入布尔值,这样做等于大声宣布本函数不止做了一件事。
例如: render(Boolean isSuite)
,应该把该函数一分为二renderForSuite
和renderForSingleTest
。
二元函数
有两个参数的函数比一元函数难懂。但是当两个参数正好是单个值的有序组成部分二元参数就正好,比如new Point(0,0)
。
应该尽量利用一些机制将二元函数转为一元函数。
参数对象
如果一个函数看来需要两个或三个参数,就说明其中一些参数可以封装成类
可变参数
String.format("%s worked $.2f hours", names, hours)
如果可变参数像上例这样,就和类型为list的单个参数没有区别。这样一来String.format
实则是二元参数。
void dyad(String name, Integer ...args)
动词与关键字
对于一元函数和参数,应该形成一个非常良好的动词/名词对形式,例如 write(name)
,writeField(name)
这个名称更好,告诉我们name
是一个field
。
无副作用
这段代码的副作用在
Session.initialize()
。原本checkPassword
是用来检查密码的,该名称并没有按时他会初始化这次会话,所以当某个误信函数名称的使用者,想要检查用户的有效性,就有抹除当前会话的风险。这一副作用是时序性耦合,也就是说
checkPassword
只能在特定时刻调用。
“指令”与“询问”规则
函数要么做什么事,要么回答什么,但是两者不能混用
public boolean set(String attribute, String value)
调用:
if (set("username", "helen")){}
这样意义不明确,应该改为
if (attributeExit("username")) {
setAttribute("username", "helen");
}
上面就是“指令”与“询问”规则。
使用异常替代错误返回码
当我们直接返回错误码时,可能因为需要马上处理错误码,导致更深层次的嵌套,当错误码返回时要求调用者马上处理错误
如果使用异常替代返回错误码,错误代码就能从主路径代码中分离出来
抽离try/catch 代码块
最好把try catch的主体部分的代码抽离出来。比如上面的事例可以改为
public void delete (Page page) {
try {
deletePageAndAllReference(page);
}
catch (Exception e) {
logError(e);
}
}
private void deletePageAndAllReference(Page page) {
delete(page);
registry.deleteReference(page.name);
configKeys.deleteKey(page.name.makeKey());
}
private void logError(Excetion e) {
logger.log(e.getMessage());
}
这样delete
函数只与错误处理有关,deletePageAndAllReference
函数只与删除一个页面有关。
错误处理就是一件事
函数应该只做一件事,错误处理就是一件事,所以错误处理函数不应该做其他事,如果try
在某个函数中存在,他就该在这个函数的第一个单词,而且catch
,finally
后也不应该有其他代码。