《编程可读代码的艺术》读书笔记

最近在学习如何重构代码,在此记录一些重构代码的技术和方法。

代码应当易于理解,代码的写法应当使别人理解它所需的时间最小化。

表面层次的改进

选择更好的名字,写好的注释,整洁的代码格式

把信息装进名字里

  1. 选择专业的词,可以使用同义词词典。
单词 更多选择
send deliver, dispatch, announce, distribute, route
find search, extract, locate, recover
start launch, create, begin, open
make create, set up, build, generate, compose, add, new
  1. 避免使用tmp,retval这样泛泛的名字。
    除非有好的理由,除了用在变量值交换的时候,其他时候最好用变量的具体含义起名字。在多层嵌套的for循环中,使用前缀比直接使用i, j, k能有效的防止出错。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
for(int i=0; i < clubs.size(); ++i) {
for(int j=0; j < clubs[i].members.size(); ++j) {
for(int k=0; k < users.size(); ++k) {
if(clubs[i].members[k] == users[j]) {
...
}
}
}
}

// 上面的代码index多了容易出错,把member和users的index弄混了。
// 使用前缀的写法如下
for(int ci=0; ci < clubs.size(); ++ci) {
for(int mi=0; mi < clubs[ci].members.size(); ++mi) {
for(int ui=0; ui < users.size(); ++ui) {
if(clubs[ci].members[mi] == users[ui]) {
...
}
}
}
}
  1. 用具体的名字代替抽象的名字
    例如:—run_locally,目的是使用本地的数据库,应该改为—use_local_database

  2. 为名字附带更多信息
    有关变量的重要信息可以加入变量名中。例如string id存储的是16进制的ID,那么可以改名为hex_id。

  3. 带单位的值
    如果变量是一个度量的话,最好把名字带上单位。例如变量尾部追加ms代表毫秒。

  4. 变量的长度
    作用域小的标识符可以使用短的名字,作用域大的标识符名字要包含更多的信息。

  5. 首字母缩略词和缩写
    如果缩写不能让新成员看懂,那么就不要用缩写。常见的有string -> str。

  6. 丢掉没有用的单词
    例如ConverToString可以改为ToString。

使用不会误解的名字

有些名字会被理解成其他的含义,例如:BIG_LIMIT=10,可以理解成<10,也可以理解成<=10。容易带来歧义。

  1. 使用min,max来表示包含的极限。在要限制的东西前面加上max或者min

  2. 使用first,last来表示包含的范围。

  3. 使用begin,end来表示包含/排除的范围。

  4. 对于bool值,前面加上is,has,can,should这样的词可以把bool值变得更加明确。不要使用反义名字,名字表示肯定意思。例如:不要使用bool disable_ssl=false,使用bool use_ssl=false。

  5. 使用与含义相匹配的名字。例如:对于get函数,期望的复杂度是O(1),如果复杂的是O(n)则会影响使用。

审美

三条基本原则:

  • 使用一致的布局,让读者很快就习惯这种风格。
  • 让相似的代码看上去相似。
  • 把相关的代码行分组,形成代码块。
  1. 重新安排换行来保持一致和紧凑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class PerformanceTest {
public static final TcpConnectionSimulator wifi =
new TcpConnectionSimulator(
500, /* Kbps */
80, /* millisecs latency */
200, /* jitter */
1 /* packet loss % */
);
public static final TcpConnectionSimulator t3_fiber =
new TcpConnectionSimulator(
45000, /* Kbps */
80, /* millisecs latency */
200, /* jitter */
10 /* packet loss % */
);
public static final TcpConnectionSimulator cell =
new TcpConnectionSimulator(
100, /* Kbps */
80, /* millisecs latency */
1 /* packet loss % */
);
}

上面的代码把注释复制了三遍,占用了过多的纵向空间。通过下面的写法可以写的更加紧凑。

1
2
3
4
5
6
7
8
9
public class PerformanceTest {
// TcpConnectionSimulator(throughput, latency, jitter, packet_loss)
// [Kbps] [ms] [ms] [percent]
public static final TcpConnectionSimulator wifi =
new TcpConnectionSimulator(500, 80, 200, 1);
public static final TcpConnectionSimulator t3_fiber =
new TcpConnectionSimulator(45000, 10, 0, 0);
public static final TcpConnectionSimulator cell =
new TcpConnectionSimulator(100, 400, 250, 5);
  1. 把声明按块组织起来,例如:构造函数,析构函数放在一起,request,post方法放在一起。

  2. 把代码分成段落,用空行按逻辑分隔,并写法一致。

注释

  1. 好的代码自带解释性,起好的函数名能够自己解释清楚。
  2. 记录思想,例如:注释教会读者一些东西,防止做无谓的优化。解释代码为什么写的不整洁。
  3. 加入TODO,FIXME等标签来记录代码的瑕疵。
  4. 给常量加注释,解释常量可以告诉读者为什么这么用。例如:0.72是经过尝试的最好的参数。
  5. 站在读者的角度,把读者想要提问的地方加上注释。

什么地方不需要注释:能够代码本身推断的事实;用来粉饰烂代码的拐杖。

应该记录的想法包括:对于代码为什么写成这样而不是那样的理由;代码中的缺陷;常量背后的故事,为什么是这个值。

站在读者的立场上思考:预料到读者的问题;为意料之外的代码加上注释;在文件/类级别上使用“全局观”注释来解释所有部分是如何一起工作的;用注释来总结代码块,是的这不要迷失在细节上。

写出言简意赅的注释