3.1 短小
- 每个函数只做一件事,每个函数依序把你带到下一个函数中。
- if/else/while这样的语句中,代码块应该只有一行
- 函数的缩进不能多于2层
例1:看不懂版
例2:能看懂版
例3:简洁版
3.2 只做一件事
- 函数应该只做一件事,且做好这件事。
- 判断一个函数只做一件事的方法是:该函数是否可以再差分出其他不同抽象层的函数
3.3 每个函数一个抽象层
一个函数中包含多个抽象层,往往让人迷惑。读者无法判断某个表达式是细节还是基础概念。
3.4 switch语句
switch一般要执行N个任务,和前面说的一个函数只干一件事冲突,代码往往较长,并且switch的可扩展性差,一般用多态来替代。
3.5 使用描述性的的名称
将testableHtml改为SetupTeardownIncluder。
别怕长名称,长而具有描述性的名称,要比短而令人费解的名称好。
3.6 函数参数
函数参数的数量遵循“能少就少,最好没有”的原则。多个参数对于函数的阅读很不便。
3.8 将指令和询问分开
就是遵循函数只干一件事,而指令和询问是两件事情,不能放在一个函数,这样阅读起来会产生歧义。
函数要么做什么事,要么回答什么事,二者不可兼得!
如:定义set函数,功能是判断name是否为张三,不是则将name修改为张三,并返回true;是则返回false。
bool set(string &name, string value){
if(name == value){
return false;
}else{
name = value;
return true;
}
}
但是在读代码的时候,这个set的意思并不明确(不能被直接读出来),因为set内部执行了两个任务,一是测试name的值和value是否相等,二是对name进行修改,任务二其实是一个测试的分支。
if(set("name", "张三")){
...
}
为了让读者更清楚的读懂,应该将set函数拆成两个功能不同的函数,如下。
if(attributeExists("name")){
setAttribute("name", "张三");
...
}
attributeExists函数用来判断name是否为“张三”(即询问语句),如果不是张三,则执行if里面的语句,将“张三”赋值给name(即指令)。简易函数实现如下,可以看出将指令和询问任务分开了。
bool attributeExists(string name){
if(name == "张三") return false
return true;
}
void setAttribute(string &name, string value){
name = value
}
3.9 使用异常替代返回错误码
暂时没用到过,回头在看。
3.10 不要重复自己
有重复的地方用函数抽取出来,进行函数调用即可。
3.11 结构化编程
指的是每个代码块、函数都应该只包含一个输入,一个输出。如循环中不能有continue,break,goto等语句。
作者树对于短小的函数使用结构化编程的作用不大,不使用结构化会使代码更具有表达力。而对于goto语句,往往用在大函数中才有道理,尽可能少使用。
注:linux操作系统中的源码(c语言)大量使用了goto语句,这样很好的解决的了代码重复问题,并减小了阅读的难度。在内核代码中,有时候一个函数可能有几十行,属于大函数的范围了,虽然函数体很长,但却遵守着本章的主旨:一个函数只做一件事,或一个抽象层面上的事。另外在以前读过的人脸识别代码中,其实也是遵循了本章的主旨。
关于结构化编程的深入,后续有机会在学习。
3.12 如何写出这样的函数
一开始可以想到什么写什么,不要在意规则,先实现功能,即完成出版。然后在进行修改,如函数分解,修改名字,消除重复等,不断进行打磨,最终即得到好的函数。