1. 给函数起个好名字

动词 + 关键词 : 例如把JUnit中的assertEquals改成 assertExpectedEqualsActual(expected, actual)可能会更好些,大大减轻了记忆参数顺序的负担

2. 函数参数

最理想的参数数量是 0(零参函数)–》其次是1(单参函数)–》 再次是2 (双参函数)–》 应该尽量避免3(三参函数)

如果函数看起来需要2个,3个或者3个以上参数,那说明其中一些需要封装成为类了。

例如:

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

当一组参数被传递,就像上例中的x和y,往往就是该有自己名称的某个概念的一部分(学会将一组基本变量抽象成为对象)

3. 改变输入参数,从而导致参数成为了输出参数

这一点在java在写业务代码时经常会碰到,改变了输入参数,造成很多迷惑行为

例如:

1
2
//给字符串添加Report添加footer
public void appendFooter(StringBuffer report);

如果不添加注释会产生疑惑:是给report追加东西还是,把report添加到其它东西上?所以写成如下会比较好理解

1
report.appendFooter();

4. 杜绝标识参数

标识参数(True|False)非常丑陋。使用标记参数做了两件事情,则应该拆分它。

1
render(boolean isSuite) => renderForSuite() , renderForSingleTest()

5. 提取try/catch块

try/catch块在函数中很丑陋,将包裹在try/catch中的代码提取出来,减少try/catch的臃肿程度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void writeToClient(HttpServletResponse response, String filePath) {
try (ServletOutputStream os = response.getOutputStream();
InputStream stream = new FileInputStream(file)){
File file = new File(filePath);
response.setContentLength((int)file.length());
int length = 0;
byte[] buff = new byte[1024];
while ((length = stream.read(buff)) > 0) {
os.write(buff, 0, length);
}
} catch (IOException e) {
log.error("Download happened error", e);
} finally {
if (!file.delete()) {
log.error("The file {} is failed to be deleted!", filePath);
}
}
}

提取try中的业务代码:

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
private void writeToClient(HttpServletResponse response, String filePath) {
try (ServletOutputStream os = response.getOutputStream();
InputStream stream = new FileInputStream(file)){

doWrite(os, stream, reponse, filePath)
} catch (IOException e) {
log.error("Download happened error", e);
} finally {
if (!file.delete()) {
log.error("The file {} is failed to be deleted!", filePath);
}
}
}

private void doWrite(ServletOutputStream os,
InputStream stream,
HttpServletResponse response,
String filePath){
File file = new File(filePath);
response.setContentLength((int)file.length());
int length = 0;
byte[] buff = new byte[1024];
while ((length = stream.read(buff)) > 0) {
os.write(buff, 0, length);
}
}

4. 理解OO

面向对象的设计有一个原则(我最初是从 Grady Booch 那里听到的):“如果觉得设计太复杂,那就生成更多对象。”这种说法既违反直觉,又简单得可笑,但我发现它很有用(“生成更多对象”通常等同于“再增加一层抽象”)。总的来说,如果发现有些地方代码很乱,就要考虑用哪种类可以清理代码。通常清理代码带来的副作用是使系统更灵活并且结构更好。

5. 清理火车代码

杜绝过长的调用链如:

隐藏委托关系(Hide Delegate)

迪米特法则(Law of Demeter),这个原则是这样说的:

  1. 每个单元对其它单元只拥有有限的知识,而且这些单元是与当前单元有紧密联系的;
  2. 每个单元只能与其朋友交谈,不与陌生人交谈;
  3. 只与自己最直接的朋友交谈。
1
2
3
4
5
6
7
8
9
10
book.getAuthor().getName();

//重构后:
class Book {
...
public String getAuthorName() {
return this.author.getName();
}
...}
String name = book.getAuthorName();

要想摆脱初级程序员的水平,就要先从少暴露细节开始。声明完一个类的字段之后,请停下生成 getter 的手,转而让大脑开始工作,思考这个类应该提供的行为。

基本类型偏执

对于返回值,和参数能封装为类就封装为类;

比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public double getPrice() { ...}

double price = this.getPrice();
if(price <= 0){
throw new RuntimeException("价格不能为0");
}

//重构:封装为对象后
class Price{
double price;
public Price(final double price) {
if (price <= 0) {
throw new RuntimeException("价格不能为0");
}
this.price = price;
}
}