Code Smell : Deeply Nested Code : 深度嵌套的代码

问题概述

在类似Java的语言中,非常容易见到多层嵌套的for循环和if条件判断判断,特别是在Java8出现以前.这类代码对于维护低级的数据结构(arrays,collection,…)来说是完全可以接受的.但是当这样的代码出现在你的业务领域代码中时,就会令你的代码恶丑无比,特别是嵌套很深的时候.

症状

  • 深层嵌套的代码(超过1层,就要注意,超过2层就会开始发臭),通常是for/while循环和if条件判断.

    通过代码的缩进程度可以很明显的嗅到臭味.

  • 链式调用的getter方法.加入一个链式调用的语句中,每个调用都是getter方法,那么也应当视为一个深层嵌套的情况.

    Builder模式或其他Domain-DSL的链式调用情况除外.

可能的解决方案

  • 嵌套的for循环往往是通过遍历数据结构来检查是否满足业务逻辑或提取部分数据进行后续处理.可以考虑将检查数据的代码放入持有该数据结构的的对象中,然后通过一个良好命名的方法来回答其他对象的问题.

  • 检查你的代码,考虑数据结构是否需要直接泄露给其他类.对数据的处理和职责是否可以限定在持有类中,然后通过helper方法提供给其他类进行操作.这样可以将对数据的处理逻辑放置在靠近数据结构的地方,提高代码内聚性.

  • 集合封装:使用这种模式意味着你正在将一种数据结构当做领域对象来使用,考虑将这种数据结构封装在领域对象自身中,与其暴露内部实现,不如通过提供helper方法来解决其他业务对象的问题.

示例代码

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
public MappedField getMappedField(final String storedName) {
for (final MappedField mf : persistenceFields) {
// 可以将内嵌的循环直接放入MappedField中
for (final String n : mf.getLoadNames()) {
if (storedName.equals(n)) {
return mf;
}
}
}
return null;
}

// 改为如下
public class MappedField {
private List<String> loadNames = ...;

public boolean hasName(String storedName){
for (final String n : mf.getLoadNames()) {
if (storedName.equals(n)) {
return mf;
}
}
}

// 使用Java8可以定义为如下
public boolean hasName(String storedName){
return getLoadNames().stream().anyMatch(Objects::equals);
}
}

public MappedField getMappedField(final String storedName) {
for (final MappedField mf : persistenceFields) {
if(mf.hasName(storedName)){
return mf;
}
}
return null;
}

// 使用Java8可以定义为如下
public MappedField getMappedField(final String storedName) {
persistenceFields.stream()
.filter(mf->mf.hasName(storedName))
.findFirst()
.orElse(null);
}

// 避免Null-Smell可以改为直接返回Optional
public Optional<MappedField> getMappedField(final String storedName) {
persistenceFields.stream()
.filter(mf->mf.hasName(storedName))
.findFirst();
}