《代码整洁之道》读书笔记(一)——命名与函数

为什么要读《代码整洁之道》

作为一个写了多年代码的程序猿,对“最痛苦的事情莫过于读别人写的代码”深有体会。事实上,即使是自己写的代码过段时间再去读也同样痛苦不堪。希望通过学习这本书,让以后写出来的代码更具有易读性。方便自己,方便他人。

命名的几条原则

名副其实

好的变量名,函数名或类名应该能告诉别人关于这个变量的所有大问题。如变量或类的名字应该告诉别人这个变量或类表示什么,函数的名字能清楚地表示函数的意图。

1
int d;      //消逝的时间,以日计

上面这段代码中,d什么信息都没说明,需要注释才能读懂。下面的命名更能说明问题:

1
int daysSinceCreation;

避免误导

  1. 避免使用特殊词
    用来表示一组账号,accountGroup会比accountList更好,除非它真的是List类型。
  2. 同样的概念,同样的拼写

做有意义的区分

  1. 数字系列命名没有意义

    1
    2
    3
    void copyChars(char a1[], char a2[]){
    copy a1 to a2
    }

    上面代码中a1, a2并不为读者提供信息,改为source, destination更合适。

  2. 不要加废话
    nameString会比name更好吗?

名称可读

不要用莫名其妙的单词缩写代替原有代词

名称可被搜索

单字母或数字常量很难搜索,适合局部变量。对于全局,长名称会更合适。

类名与方法名

类名应该是名词或名词短语,方法名应当是动词或动词短语。

别用双关语

避免将同一单词用于不同的概念。向列表中插入insert和append比add更合适,虽然add也有类似的含义。

使用领域名称

只有程序员才会读你的代码,所以尽量用计算机领域专业术语来作为名称。如果找不到,使用所要解决问题领域的词汇。

添加有意义的语境

street,houseNumber,city,state搁一块很容易知道是个地址,如果在其他地方碰到孤零零一个state,是否可想到state是某个地址的一部分。这种情况下,添加一个Address类更好。

总的原则

不管起什么样的名字,都是为了方便阅读,明确概念。为起一个好名字值得一改再改

什么样的函数是好的

短小

让函数变短小,并不意味着使用复杂的语法,让代码显得高深莫测。而是要进行适当的抽象和分解,将任务放到下层函数和其他同级函数中。函数的缩进层不应该多于一层或两层。代码20行封顶最佳。

只做一件事

让函数做的更少的事情是让函数短小的一种方法,事实上,只做一件事的函数最佳。

使用描述性的名称

函数的名字需要很好的描述函数要做的事。长而具有描述性的名称要比短而令人费解的名称好。

参数尽量少

最理想的参数数是0,其次是1,再次是2,尽量避免3及以上。有几个理由:

  1. 参数越多,测试时考虑的情况越多
  2. 参数的自然顺序很容易搞错,比如:
    调用assertEqual(expected, actual)的时候,很容易搞混expected和actual

尽量不要使用输出参数

string transform(const string &str)void transform(string &str)更好

无副作用

不要在函数中修改不应该在该函数中修改的变量。如下面代码:

1
2
3
4
5
6
7
8
public class UserValidator{
public boolean checkPassword(String userName, String password){
if(user with userName exists and password is right){
Session.initalize();
return true;
}
}
}

Session.initalize()不应该在checkPassword中调用,这造成了时序性耦合,也就是说checkPassword只能在初始化会话安全的时候调用。

使用异常处理代替返回错误码

if (deletePage(page) == E_OK){}的错误码处理方式,虽语义清晰,但却会导致更深层次的嵌套结构。当返回错误码时意味着需要立刻处理错误。

1
2
3
4
5
6
7
8
9
if(deletePage(page) == E_OK){
if(registry.deleteReference(page.name) == E_OK){
if()...
}else{
log error
}
}else{
log error
}

这时使用异常代替返回错误码就能将错误处理代码从主路径代码中分离出来:

1
2
3
4
5
6
7
try{
deletePage(page);
registry.deleteReference(page.name);
...
}catch (Exception e){
log e.message
}

异常处理代码搞乱了代码结构,把错误处理与正常流程混为一谈,最好对try/catch代码块进行进一步分离:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void delete(Page page){
try{
deletePageAndAllReference(page);
}catch (Exception e){
logError(e);
}
}

private void deletePageAndAllReference(Page page) throws Exception{
deletePage(page);
registry.deleteReference(page.name);
...
}

private void logError(Exception e){
log e.message
}

怎样写出这样的函数

向写文章一样反复修改