0%

代码整洁之道

代码整洁是我们一直追求的目标,不仅仅是为了成为更好的程序员,而且也为了节省他人时间,减少阅读、维护代码的时间成本,提高工作效率。本文将从命名、函数风格、注释等不同方面对《Clean Code》中的内容进行总结。

命名

使用有意义的变量名

1
2
3
4
5
// Bad
int d;
int ds;
int dsm;
int faid;
1
2
3
4
5
// Good
int elapsedTimeInDays;
int daysSinceCreation;
int daysSinceModification;
int fileAgeInDays;

使用可读的变量名

1
2
3
4
5
6
7
8
9
10
11
// Bad
class prsRecr {
private Date genymdhms;
private Date modymdhms;
}

// Good
class PersonRecord {
private Date generationTimestamp;
private Date modificationTimestamp;
}

使用可搜索的变量名

1
2
3
4
5
6
7
8
9
10
11
12
13
// Bad
int s = 0;
for (int j = 0; j < 10; j++) {
s += l[j];
}
return s;

// Good
int sum = 0;
for (int j = 0; j < NUMBER_OF_ADDITIONS; j++) {
sum += results[j];
}
return sum;

不要使用匈牙利表示法

在匈牙利表示法中,变量名以一个或多个小写字母开始,代表变量的类型,如下面的例子所示:

1
2
3
4
5
lAccountNum - variable is long
szName - zero terminated String
mFriend - private Friend
sFriend - package Friend
m_friend - member variable

类名和方法名

类名

  • 名词或者名词短语
  • 不使用抽象的词汇
1
2
3
4
5
6
7
8
9
10
// Good
Customer
Account
Address
AddressPrinter

// Bad
AccountHandler
Data
DataManager

方法名

  • 动词或者动词短语
1
2
3
4
5
6
7
8
// Good
postPayment
savePage
setName

// Bad
get
readResolve
  • 使用静态工厂方法代替重载构造函数

这条原则在《Effective Java》中也有提到,具体有如下几个优点:

  1. 静态工厂方法有名字,构造器名字固定,不易于表达方法签名的意义
  2. 不必在每次调用它们的时候都创建一个新对象
  3. 它们可以返回原返回类型的任何子类型的对象

类似如下的例子中,多个构造函数的重载形式,可读性较差

1
2
3
4
5
6
Date date0 = new Date();
Date date1 = new Date(0L);
Date date2 = new Date("0");
Date date3 = new Date(1,2,1);
Date date4 = new Date(1,2,1,1,1);
Date date5 = new Date(1,2,1,1,1,1);

如下的例子中,可以返回不同的子类,

1
2
3
4
5
6
7
8
9
10
Class Person {
public static Person getInstance(){
return new Person();
// 这里可以改为 return new Player() / Cooker()
}
}
Class Player extends Person{
}
Class Cooker extends Person{
}

使用解决域(Solution Domain)中的命名

阅读你代码的也是程序员,所以尽量使用算法名、模式名、数据结构名来提高可读性,类似如下的例子:

1
2
3
AccountBuilder
JobQueue
mergeSort

函数

尽可能的小

做一件事,并且做好

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Bad
function emailClients(clients) {
clients.forEach(client => {
let clientRecord = database.lookup(client);
if (clientRecord.isActive()) {
email(client);
}
});
}

// Good
function emailClients(clients) {
clients.forEach(client => {
emailClientIfNeeded(client);
});
}

function emailClientIfNeeded(client) {
if (isClientActive(client)) {
email(client);
}
}

function isClientActive(client) {
let clientRecord = database.lookup(client);
return clientRecord.isActive();
}

代码块和缩进风格一致

尽量控制参数在3个及以内

单个参数

  • 判断语句
    1
    boolean fileExists("myFile");
  • 对变量进行操作,转换并返回
    1
    InputStream openFile("myFile");
  • 事件响应
    1
    void passwordAttemptFailedNtimes(int attempts);

参数类

如果一个函数需要多于2-3的变量,可以考虑将他们放在一个类中。

1
2
Circle makeCircle(double x, double y, double radius);
Circle makeCircle(Point center, double radius);

参数列表

如果多个参数具有平等的关系,可以考虑将这些参数放在一个List中。

使用异常替代返回错误码

从指令式函数返回错误码轻微违反了指令与询问分隔的规则。它鼓励了在if语句判断中把指令当作表达式使用。

1
if (deletePage(page) == E_OK) 

这不会引起动词/形容词混淆,但却导致更深层次的嵌套结构。当返回错误码时,就是在要求调用者立刻处理错误。

但如果使用异常替代返回错误码,错误处理代码就能从主路径代码中分离出来,得到简化:

1
2
3
4
5
6
7
8
try {  
deletePage(page);
registry.deleteReference(page.name);
configKeys.deleteKey(page.name.makeKey());
}
catch (Exception e) {
logger.log(e.getMessage());
}

抽离Try/Catch代码块

Try/catch代码块丑陋不堪。它们搞乱了代码结构,把错误处理与正常流程混为一谈。最好把try和catch代码块的主体部分抽离出来,另外形成函数。

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

private void deletePageAndAllReferences(Page page) throws Exception {
deletePage(page);
registry.deleteReference(page.name);
configKeys.deleteKey(page.name.makeKey());
}

private void logError(Exception e) {
logger.log(e.getMessage());
}

别重复自己(Don’t Repeat Yourself - DRY)

不要书写重复代码,因为代码会因此而臃肿,且当算法改变时需要修改多处地方。而且也会增加犯错的可能性。

注释

注释原则

  • 不要给不好的名字加注释,一个好的名字比好的注释更重要
  • 在文件/类级别使用全局注释来解释所有部分如何工作
  • 一定要给常量加注释
  • 团队统一定义标记
  • TODO 待处理的问题
  • FIXME 已知有问题的代码
  • HACK 不得不采用的粗糙的解决方案
  • 在注释中用精心挑选的输入输出例子进行说明
  • 注释应该声明代码的高层次意图,而非明显的细节
  • 不要在代码中加入代码的著作信息,git可以干的事情不要交给代码
  • 源代码中的html注释是一种厌物, 增加阅读难度
  • 注释一定要描述离它最近的代码
  • 注释一定要与代码对应
  • 公共api需要添加注释,其它代码谨慎使用注释

烂注释

  • 不恰当的信息
  • 废弃的注释
  • 冗余注释
  • 糟糕的注释
  • 注释掉的代码

参考链接: