把代码分割成更小的单元块
大部分人阅读代码的习惯都是,先看整体再看细节。所以,我们要有模块化和抽象思维,善于将大块的复杂逻辑提炼成类或者函数,屏蔽掉细节,让阅读代码的人不至于迷失在细节中,这样能极大地提高代码的可读性。不过,只有代码逻辑比较复杂的时候,我们其实才建议提炼类或者函数。毕竟如果提炼出的函数只包含两三行代码,在阅读代码的时候,还得跳过去看一下,这样反倒增加了阅读成本。
这里我举一个例子来进一步解释一下。重构前,在 invest() 函数中,最开始的那段关于时间处理的代码,是不是很难看懂?重构之后,我们将这部分逻辑抽象成一个函数,并且命名为 isLastDayOfMonth,从名字就能清晰地了解它的功能,判断今天是不是当月的最后一天。这里,我们就是通过将复杂的逻辑代码提炼成函数,大大提高了代码的可读性。代码具体如下所示:
1 | // 重构前的代码 |
避免函数参数过多
函数包含 3、4 个参数的时候还是能接受的,大于等于 5 个的时候,我们就觉得参数有点过多了,会影响到代码的可读性,使用起来也不方便。针对参数过多的情况,一般有 2 种处理方法:
考虑函数是否职责单一,是否能通过拆分成多个函数的方式来减少参数。示例代码如下所示:
1
2
3
4
5
6public User getUser(String username, String telephone, String email);
// 拆分成多个函数
public User getUserByUsername(String username);
public User getUserByTelephone(String telephone);
public User getUserByEmail(String email);将函数的参数封装成对象。示例代码如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13public void postBlog(String title, String summary, String keywords, String content, String category, long authorId);
// 将参数封装成对象
public class Blog
{
private String title;
private String summary;
private String keywords;
private String content;
private String category;
private long authorId;
}
public void postBlog(Blog blog);
除此之外,如果函数是对外暴露的远程接口,将参数封装成对象,还可以提高接口的兼容性。在往接口中添加新的参数的时候,老的远程接口调用者有可能就不需要修改代码来兼容新的接口了。
勿用函数参数来控制逻辑
不要在函数中使用布尔类型的标识参数来控制内部逻辑,true 的时候走这块逻辑,false 的时候走另一块逻辑。这明显违背了单一职责原则和接口隔离原则。我建议将其拆成两个函数,可读性上也要更好:
1 | public void buyCourse(long userId, long courseId, boolean isVip); |
不过,如果函数是 private 私有函数,影响范围有限,或者拆分之后的两个函数经常同时被调用,我们可以酌情考虑保留标识参数。示例代码如下所示:
1 | // 拆分成两个函数的调用方式 |
除了布尔类型作为标识参数来控制逻辑的情况外,还有一种根据参数是否为 null 来控制逻辑的情况。针对这种情况,我们也应该将其拆分成多个函数。拆分之后的函数职责更明确,不容易用错。具体代码示例如下所示:
1 | public List<Transaction> selectTransactions(Long userId, Date startDate, Date endDate) |
函数设计要职责单一
对于函数的设计来说,更要满足单一职责原则。相对于类和模块,函数的粒度比较小,代码行数少,所以在应用单一职责原则的时候,没有像应用到类或者模块那样模棱两可,能多单一就多单一。具体的代码示例如下所示:
1 | public boolean checkUserIfExisting(String telephone, String username, String email) |
移除过深的嵌套层次
代码嵌套层次过深往往是因为 if-else、switch-case、for 循环过度嵌套导致的。我个人建议,嵌套最好不超过两层,超过两层之后就要思考一下是否可以减少嵌套。过深的嵌套本身理解起来就比较费劲,除此之外,嵌套过深很容易因为代码多次缩进,导致嵌套内部的语句超过一行的长度而折成两行,影响代码的整洁。解决嵌套过深的方法也比较成熟,有下面 4 种常见的思路:
去掉多余的 if 或 else 语句。代码示例如下所示:
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
28
29
30
31
32
33
34
35
36
37
38
39
40// 示例一
public double calculateTotalAmount(List<Order> orders)
{
if (orders == null || orders.isEmpty())
{
return 0.0;
}
else // 此处的 else 可以去掉
{
double amount = 0.0;
for (Order order : orders)
{
if (order != null)
{
amount += (order.getCount() * order.getPrice());
}
}
return amount;
}
}
// 示例二
public List<String> matchStrings(List<String> strList, String subStr)
{
List<String> matchedStrings = new ArrayList<>();
if (strList != null && subStr != null)
{
for (String str : strList)
{
if (str != null) // 跟下面的 if 语句可以合并在一起
{
if (str.contains(subStr))
{
matchedStrings.add(str);
}
}
}
}
return matchedStrings;
}使用编程语言提供的 continue、break、return 关键字,提前退出嵌套。代码示例如下所示:
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
28
29
30
31
32
33
34
35
36// 重构前的代码
public List<String> matchStrings(List<String> strList, String subStr)
{
List<String> matchedStrings = new ArrayList<>();
if (strList != null && subStr != null)
{
for (String str : strList)
{
if (str != null && str.contains(subStr))
{
matchedStrings.add(str);
// 此处还有 10 行代码...
}
}
}
return matchedStrings;
}
// 重构后的代码:使用 continue 提前退出
public List<String> matchStrings(List<String> strList, String subStr)
{
List<String> matchedStrings = new ArrayList<>();
if (strList != null && subStr != null)
{
for (String str : strList)
{
if (str == null || !str.contains(subStr))
{
continue;
}
matchedStrings.add(str);
// 此处还有 10 行代码...
}
}
return matchedStrings;
}调整执行顺序来减少嵌套。具体的代码示例如下所示:
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41// 重构前的代码
public List<String> matchStrings(List<String> strList, String subStr)
{
List<String> matchedStrings = new ArrayList<>();
if (strList != null && subStr != null)
{
for (String str : strList)
{
if (str != null)
{
if (str.contains(subStr))
{
matchedStrings.add(str);
}
}
}
}
return matchedStrings;
}
// 重构后的代码:先执行判空逻辑,再执行正常逻辑
public List<String> matchStrings(List<String> strList, String subStr)
{
if (strList == null || subStr == null) // 先判空
{
return Collections.emptyList();
}
List<String> matchedStrings = new ArrayList<>();
for (String str : strList)
{
if (str != null)
{
if (str.contains(subStr))
{
matchedStrings.add(str);
}
}
}
return matchedStrings;
}将部分嵌套逻辑封装成函数调用,以此来减少嵌套。具体的代码示例如下所示:
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60// 重构前的代码
public List<String> appendSalts(List<String> passwords)
{
if (passwords == null || passwords.isEmpty())
{
return Collections.emptyList();
}
List<String> passwordsWithSalt = new ArrayList<>();
for (String password : passwords)
{
if (password == null)
{
continue;
}
if (password.length() < 8)
{
//...
}
else
{
//...
}
}
return passwordsWithSalt;
}
// 重构后的代码:将部分逻辑抽成函数
public List<String> appendSalts(List<String> passwords)
{
if (passwords == null || passwords.isEmpty())
{
return Collections.emptyList();
}
List<String> passwordsWithSalt = new ArrayList<>();
for (String password : passwords)
{
if (password == null)
{
continue;
}
passwordsWithSalt.add(appendSalt(password));
}
return passwordsWithSalt;
}
private String appendSalt(String password)
{
String passwordWithSalt = password;
if (password.length() < 8)
{
//...
}
else
{
//...
}
return passwordWithSalt;
}
除此之外,常用的还有通过使用多态来替代 if-else、switch-case 条件判断的方法。这个思路涉及代码结构的改动。
学会使用解释性变量
常用的用解释性变量来提高代码的可读性的情况有下面 2 种:
常量取代魔法数字。示例代码如下所示:
1
2
3
4
5
6
7
8
9
10
11public double CalculateCircularArea(double radius)
{
return (3.1415) * radius * radius;
}
// 常量替代魔法数字
public static final Double PI = 3.1415;
public double CalculateCircularArea(double radius)
{
return PI * radius * radius;
}使用解释性变量来解释复杂表达式。示例代码如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19if (date.after(SUMMER_START) && date.before(SUMMER_END))
{
//...
}
else
{
//...
}
// 引入解释性变量后逻辑更加清晰
boolean isSummer = date.after(SUMMER_START) && date.before(SUMMER_END);
if (isSummer)
{
//...
}
else
{
//...
}