0%

斗翔

翔的原身

先来看看翔的最初的样子

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
@Override
public List<ItemAddedTaskMO> queryItemAddedTask(String containerNo, String locationNo) {
TaskDO taskDO = taskService.findByContainerNo(containerNo, TaskDO.TaskType.TASK_TYPE_YIDONGSHANGJIA.toString());
if (taskDO == null) {
return null;
} else {
// 根据任务获取任务详情,包含批次
String taskNo = taskDO.getTaskNo();
List<TaskDetailDO> taskDetailList = taskDBDAO.queryByTaskNo(taskNo);

if (taskDetailList != null && taskDetailList.size() > 0) {
List<ItemAddedTaskMO> taskList = new ArrayList<ItemAddedTaskMO>();
String currentLocationNo = locationNo;
// 根据当前库位号查询库位所在上架路线位置
Integer currentPutawayLine = warehouseLocationDBDAO.findPutAwayLineByLocationNo(currentLocationNo);
if (null == currentPutawayLine) {
throw new BusinessException(I18nUtil.getMessage("move.location.errInfo7", currentLocationNo));
}

Map<String, List<TaskDetailDO>> taskDetailMap = new HashMap<String, List<TaskDetailDO>>();
// 根据播种位汇总任务信息
for (TaskDetailDO tempTD : taskDetailList) {
if (taskDetailMap.containsKey(tempTD.getContainerLocation())) {
taskDetailMap.get(tempTD.getContainerLocation()).add(tempTD);
} else {
List<TaskDetailDO> tdLt = new ArrayList<TaskDetailDO>();
tdLt.add(tempTD);
taskDetailMap.put(tempTD.getContainerLocation(), tdLt);
}
}

// 用于保存所有推荐位信息的队列,未排序
List<ItemAddedTaskMO> allRecInfoLt = new ArrayList<ItemAddedTaskMO>();
// 用于保存没有推荐库位的队列,不用排序
List<ItemAddedTaskMO> noRecInfoLt = new ArrayList<ItemAddedTaskMO>();
// 循环遍历详细任务对应的库位,按照当前所在库位进行计算
for (Entry<String, List<TaskDetailDO>> entry : taskDetailMap.entrySet()) {
List<TaskDetailDO> inventoryTDLt = entry.getValue();
String seedLocation = entry.getKey();
if (inventoryTDLt == null || inventoryTDLt.isEmpty()) {
throw new BusinessException(I18nUtil.getMessage("move.param.errInfo9", taskNo));
}
// 如果1个播种位上只有1个任务信息,按默认流程处理
if (inventoryTDLt.size() == 1) {
String fromLocationNo = inventoryTDLt.get(0).getFromLocationNo();
Set<String> fromLocationNoSet = new HashSet<String>();
fromLocationNoSet.add(fromLocationNo);
String sku = inventoryTDLt.get(0).getSku();
String status = inventoryTDLt.get(0).getItemStatus();
String lotNo = inventoryTDLt.get(0).getLotNo();
String shipperMo = inventoryTDLt.get(0).getShipperNo();
Set<String> lotNoSet = new HashSet<String>();
lotNoSet.add(lotNo);
ItemAddedTaskMO itemAddedTaskMO = new ItemAddedTaskMO();
itemAddedTaskMO.setSeedLocation(seedLocation);
// 计算推荐库位
// 1.优先推荐可以同品同批次合并库位,按上架路线排序,再按location_no排序
List<String> rejectNo = getRejectRegionNo(fromLocationNoSet);
List<String> itemStatus = getLegalItemStatus(inventoryTDLt.get(0).getItemStatus());
WarehouseLocationDO recWarehouseLocationDO = warehouseLocationDBDAO.findAscSPBLocAndPutAway(shipperMo, sku, getLegalRegionstatus(status), lotNoSet,
null, fromLocationNoSet, currentPutawayLine, rejectNo, itemStatus);
// 如果顺向存在同品同批次库位则直接选择该库位为推荐库位,不存在则逆向查找
if (recWarehouseLocationDO == null) {
recWarehouseLocationDO = warehouseLocationDBDAO.findDescSPBLocAndPutAway(shipperMo, sku, getLegalRegionstatus(status), lotNoSet, null,
fromLocationNoSet, currentPutawayLine, rejectNo, itemStatus);
if (recWarehouseLocationDO != null) {
itemAddedTaskMO.setLocationNo(recWarehouseLocationDO.getLocationNo());
// 计算库位号
itemAddedTaskMO.setPutAwayLine(recWarehouseLocationDO.getPutawayLine() - currentPutawayLine);
allRecInfoLt.add(itemAddedTaskMO);
if (allRecInfoLt.size() == 5) {
break;
}
continue;
}
} else {
itemAddedTaskMO.setLocationNo(recWarehouseLocationDO.getLocationNo());
itemAddedTaskMO.setPutAwayLine(recWarehouseLocationDO.getPutawayLine() - currentPutawayLine);
allRecInfoLt.add(itemAddedTaskMO);
if (allRecInfoLt.size() == 5) {
break;
}
continue;
}
// 2.空库位推荐
recWarehouseLocationDO = warehouseLocationDBDAO.findAscNullLocationNo(shipperMo, fromLocationNoSet, null, currentPutawayLine, rejectNo,
getLegalRegionstatus(status));
if (recWarehouseLocationDO == null) {
recWarehouseLocationDO = warehouseLocationDBDAO.findDescNullLocationNo(shipperMo, fromLocationNoSet, null, currentPutawayLine, rejectNo,
getLegalRegionstatus(status));
if (recWarehouseLocationDO != null) {
itemAddedTaskMO.setLocationNo(recWarehouseLocationDO.getLocationNo());
itemAddedTaskMO.setPutAwayLine(recWarehouseLocationDO.getPutawayLine() - currentPutawayLine);
allRecInfoLt.add(itemAddedTaskMO);
if (allRecInfoLt.size() == 5) {
break;
}
continue;
}
} else {
itemAddedTaskMO.setLocationNo(recWarehouseLocationDO.getLocationNo());
itemAddedTaskMO.setPutAwayLine(recWarehouseLocationDO.getPutawayLine() - currentPutawayLine);
allRecInfoLt.add(itemAddedTaskMO);
if (allRecInfoLt.size() == 5) {
break;
}
continue;
}
noRecInfoLt.add(itemAddedTaskMO);
}
// 如果1个播种位上有多个任务信息,按混批流程走
else {
Set<String> lotNoSet = new HashSet<String>();
Set<String> fromLocationNoSet = new HashSet<String>();
String sku = null;
String status = null;
String shipperNo = null;
// 循环遍历任务信息,获取任务的所有批次号(下架时必须保证1个播种位只能存在1个sku,不同批次为不同taskDetail)
for (TaskDetailDO taskDetailDO : inventoryTDLt) {
lotNoSet.add(taskDetailDO.getLotNo());
fromLocationNoSet.add(taskDetailDO.getFromLocationNo());
if (sku == null) {
sku = taskDetailDO.getSku();
}
if (status == null) {
status = taskDetailDO.getItemStatus();
}
if (shipperNo == null) {
shipperNo = taskDetailDO.getShipperNo();
}
}

ItemAddedTaskMO itemAddedTaskMO = new ItemAddedTaskMO();
itemAddedTaskMO.setSeedLocation(seedLocation);
// 计算推荐库位
// 1. 同品允许混批次库位推荐
List<String> rejectNo = getRejectRegionNo(fromLocationNoSet);
List<String> itemStatus = getLegalItemStatus(inventoryTDLt.get(0).getItemStatus());
WarehouseLocationDO recWarehouseLocationDO = warehouseLocationDBDAO.findAscSPBLocAndPutAway(shipperNo, sku, getLegalRegionstatus(status), lotNoSet,
"1", fromLocationNoSet, currentPutawayLine, rejectNo, itemStatus);
// 如果顺向存在同品允许混批次库位则直接选择该库位为推荐库位,不存在则逆向查找
if (recWarehouseLocationDO == null) {
recWarehouseLocationDO = warehouseLocationDBDAO.findDescSPBLocAndPutAway(shipperNo, sku, getLegalRegionstatus(status), lotNoSet, "1",
fromLocationNoSet, currentPutawayLine, rejectNo, itemStatus);
if (recWarehouseLocationDO != null) {
itemAddedTaskMO.setLocationNo(recWarehouseLocationDO.getLocationNo());
// 计算库位号
itemAddedTaskMO.setPutAwayLine(recWarehouseLocationDO.getPutawayLine() - currentPutawayLine);
allRecInfoLt.add(itemAddedTaskMO);
if (allRecInfoLt.size() == 5) {
break;
}
continue;
}
} else {
itemAddedTaskMO.setLocationNo(recWarehouseLocationDO.getLocationNo());
itemAddedTaskMO.setPutAwayLine(recWarehouseLocationDO.getPutawayLine() - currentPutawayLine);
allRecInfoLt.add(itemAddedTaskMO);
if (allRecInfoLt.size() == 5) {
break;
}
continue;
}
// 2.空库位推荐
recWarehouseLocationDO = warehouseLocationDBDAO.findAscNullLocationNo(shipperNo, fromLocationNoSet, "1", currentPutawayLine, rejectNo,
getLegalRegionstatus(status));
if (recWarehouseLocationDO == null) {
recWarehouseLocationDO = warehouseLocationDBDAO.findDescNullLocationNo(shipperNo, fromLocationNoSet, "1", currentPutawayLine, rejectNo,
getLegalRegionstatus(status));
if (recWarehouseLocationDO != null) {
itemAddedTaskMO.setLocationNo(recWarehouseLocationDO.getLocationNo());
itemAddedTaskMO.setPutAwayLine(recWarehouseLocationDO.getPutawayLine() - currentPutawayLine);
allRecInfoLt.add(itemAddedTaskMO);
if (allRecInfoLt.size() == 5) {
break;
}
continue;
}
} else {
itemAddedTaskMO.setLocationNo(recWarehouseLocationDO.getLocationNo());
itemAddedTaskMO.setPutAwayLine(recWarehouseLocationDO.getPutawayLine() - currentPutawayLine);
allRecInfoLt.add(itemAddedTaskMO);
if (allRecInfoLt.size() == 5) {
break;
}
continue;
}

noRecInfoLt.add(itemAddedTaskMO);
}

}
// 如果推荐库位信息已经等于5个,则直接排序,否则将根据当前推荐库位个数添加信息,筹足够5个
if (allRecInfoLt.size() == 5) {
Collections.sort(allRecInfoLt);
taskList.addAll(allRecInfoLt);
} else {
// 获取有推荐位的信息,如果信息数量大于0则排序后直接添加
int currentNum = allRecInfoLt.size();
if (currentNum > 0) {
Collections.sort(allRecInfoLt);
taskList.addAll(allRecInfoLt);
}
if (noRecInfoLt.size() > 0) {
int num = 5 - currentNum;
int foreachNum = 0;
if (noRecInfoLt.size() > num) {
foreachNum = num;
} else {
foreachNum = noRecInfoLt.size();
}
for (int i = 0; i < foreachNum; i++) {
taskList.add(noRecInfoLt.get(i));
}
}
}
int id = 0;
for (ItemAddedTaskMO temp : taskList) {
id++;
temp.setId(id);
}
if (CollectionUtils.isNotEmpty(taskList)) {
return taskList.stream().filter(f -> StringUtils.isNotBlank(f.getLocationNo())).collect(Collectors.toList());
}
return Collections.EMPTY_LIST;
} else {
return Collections.EMPTY_LIST;
}
}
}

这道翔的描述是获取容器下的上架任务列表

翔的问题是返回为空列表,造成后续业务出错,此翔没有任何对应的单元测试,总行数231。数据库查询结果都是有数据的。老司机如我,也看不懂这块写的是什么,跟别说带着数据去排查哪里出问题了。

第一步:降维

if/for等套用层次过深不利于我们分析问题,所以将不实用的if给去掉或者合理利用反逻辑提前作为卫判断可以帮我们降维目前的套用层次,降低分析问题难度。

改造点1:

1
2
3
if (taskDO == null) {
return null;
} else {

你if都return了,还else个寂寞啊。干掉}else{一层后,我们成功为后续223行减了一层压力。

改造点2:

1
2
3
4
5
if (taskDetailList != null && taskDetailList.size() > 0) {
...//处理逻辑
} else {
return Collections.EMPTY_LIST;
}

代码折叠后,你啰里八嗦那么多,不会反向思考一下吗?

改造后

1
2
3
4
if (CollectionUtils.isEmpty(taskDetailList)) {
return Collections.EMPTY_LIST;
}
...//处理逻辑

改造点3:

1
2
3
4
5
6
7
8
9
10
// 根据播种位汇总任务信息
for (TaskDetailDO tempTD : taskDetailList) {
if (taskDetailMap.containsKey(tempTD.getContainerLocation())) {
taskDetailMap.get(tempTD.getContainerLocation()).add(tempTD);
} else {
List<TaskDetailDO> tdLt = new ArrayList<TaskDetailDO>();
tdLt.add(tempTD);
taskDetailMap.put(tempTD.getContainerLocation(), tdLt);
}
}

这里改造方法有2种,一种是利用Map.getOrDefault,另外一种是java8的stream groupby

1
2
3
4
5
6
// 根据播种位汇总任务信息
for (TaskDetailDO tempTD : taskDetailList) {
List<TaskDetailDO> tdList = taskDetailMap.getOrDefault(tempTD.getContainerLocation(), new ArrayList<TaskDetailDO>());
tdList.add(tempTD);
taskDetailMap.put(tempTD.getContainerLocation(), tdList);
}
1
2
Map<String, List<TaskDetailDO>> taskDetailMap = taskDetailList.stream()
.collect(Collectors.groupingBy(TaskDetailDO::getContainerLocation));

按照选择随意,推荐第二种。

这个时候其实已经分析到核心问题点:

1
2
3
4
if (CollectionUtils.isNotEmpty(taskList)) {
return taskList.stream().filter(f -> StringUtils.isNotBlank(f.getLocationNo())).collect(Collectors.toList());
}
return Collections.emptyList();

就是taskList为空或所有的getLocationNo都返回为空,而taskList只和下面这段代码有关。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 如果推荐库位信息已经等于5个,则直接排序,否则将根据当前推荐库位个数添加信息,筹足够5个
if (allRecInfoLt.size() == 5) {
Collections.sort(allRecInfoLt);
taskList.addAll(allRecInfoLt);
} else {
// 获取有推荐位的信息,如果信息数量大于0则排序后直接添加
int currentNum = allRecInfoLt.size();
if (currentNum > 0) {
Collections.sort(allRecInfoLt);
taskList.addAll(allRecInfoLt);
}
if (noRecInfoLt.size() > 0) {
int num = 5 - currentNum;
int foreachNum = 0;
if (noRecInfoLt.size() > num) {
foreachNum = num;
} else {
foreachNum = noRecInfoLt.size();
}
for (int i = 0; i < foreachNum; i++) {
taskList.add(noRecInfoLt.get(i));
}
}
}

它本身的定义却放在最上面,食屎啊!

第二步:降低重复

重复的代码会干扰我们的实现,增加bug几率。

改造点1:

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
// 如果1个播种位上只有1个任务信息,按默认流程处理
if (inventoryTDLt.size() == 1) {
String fromLocationNo = inventoryTDLt.get(0).getFromLocationNo();
Set<String> fromLocationNoSet = new HashSet<String>();
fromLocationNoSet.add(fromLocationNo);
String sku = inventoryTDLt.get(0).getSku();
String status = inventoryTDLt.get(0).getItemStatus();
String lotNo = inventoryTDLt.get(0).getLotNo();
String shipperMo = inventoryTDLt.get(0).getShipperNo();
Set<String> lotNoSet = new HashSet<String>();
lotNoSet.add(lotNo);
//...
// 如果1个播种位上有多个任务信息,按混批流程走
else {
Set<String> lotNoSet = new HashSet<String>();
Set<String> fromLocationNoSet = new HashSet<String>();
String sku = null;
String status = null;
String shipperNo = null;
// 循环遍历任务信息,获取任务的所有批次号(下架时必须保证1个播种位只能存在1个sku,不同批次为不同taskDetail)
for (TaskDetailDO taskDetailDO : inventoryTDLt) {
lotNoSet.add(taskDetailDO.getLotNo());
fromLocationNoSet.add(taskDetailDO.getFromLocationNo());
if (sku == null) {
sku = taskDetailDO.getSku();
}
if (status == null) {
status = taskDetailDO.getItemStatus();
}
if (shipperNo == null) {
shipperNo = taskDetailDO.getShipperNo();
}
}
//...

这里skustatusshipperMo的获取与数量无关,默认只取第一个中的对应属性,而fromLocationNoSetlotNoSet有多个就取多个就行了呗。

提取出来后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
String sku = inventoryTDLt.get(0).getSku();
String status = inventoryTDLt.get(0).getItemStatus();
String shipperNo = inventoryTDLt.get(0).getShipperNo();
// 循环遍历任务信息,获取任务的所有批次号(下架时必须保证1个播种位只能存在1个sku,不同批次为不同taskDetail)
Set<String> fromLocationNoSet = inventoryTDLt.stream().map(TaskDetailDO::getFromLocationNo).collect(Collectors.toSet());
Set<String> lotNoSet = inventoryTDLt.stream().map(TaskDetailDO::getLotNo).collect(Collectors.toSet());
// 如果1个播种位上只有1个任务信息,按默认流程处理
if (inventoryTDLt.size() == 1) {
...
}
// 如果1个播种位上有多个任务信息,按混批流程走
else {
...
}

改造点2:

经过上述改造后,如果把if (inventoryTDLt.size() == 1)的两个分支拿去做对比的话就会发现,其实差异只有一个lotBatch参数是null,另一个lotBatch参数是"1"的差别而已。那么我们先把这个lotBatch根据分支判断好。

1
2
3
4
5
6
7
8
9
// 如果1个播种位上只有1个任务信息,按默认流程处理
String lotBatch = null;
// 如果1个播种位上有多个任务信息,按混批流程走
if (inventoryTDLt.size() > 1) {
lotBatch = "1";
}
// 计算推荐库位
// 2.空库位推荐
...

改造点3:

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
WarehouseLocationDO recWarehouseLocationDO = warehouseLocationDBDAO.findAscSPBLocAndPutAway(shipperNo, sku, getLegalRegionstatus(status), lotNoSet,
lotBatch, fromLocationNoSet, currentPutawayLine, rejectNo, itemStatus);
// 如果顺向存在同品同批次库位/同品允许混批次则直接选择该库位为推荐库位,不存在则逆向查找
if (recWarehouseLocationDO == null) {
recWarehouseLocationDO = warehouseLocationDBDAO.findDescSPBLocAndPutAway(shipperNo, sku, getLegalRegionstatus(status), lotNoSet, lotBatch,
fromLocationNoSet, currentPutawayLine, rejectNo, itemStatus);
if (recWarehouseLocationDO != null) {
itemAddedTaskMO.setLocationNo(recWarehouseLocationDO.getLocationNo());
// 计算库位号
itemAddedTaskMO.setPutAwayLine(recWarehouseLocationDO.getPutawayLine() - currentPutawayLine);
allRecInfoLt.add(itemAddedTaskMO);
if (allRecInfoLt.size() == 5) {
break;
}
continue;
}
} else {
itemAddedTaskMO.setLocationNo(recWarehouseLocationDO.getLocationNo());
itemAddedTaskMO.setPutAwayLine(recWarehouseLocationDO.getPutawayLine() - currentPutawayLine);
allRecInfoLt.add(itemAddedTaskMO);
if (allRecInfoLt.size() == 5) {
break;
}
continue;
}

看到这么多break和continue来控制逻辑,是毛线团做成的大脑吗?

这里的核心逻辑无非是

1.先从findAscSPBLocAndPutAway查,如果查不到再从findDescSPBLocAndPutAway查。

2.如果上述查到了,那么将结果给itemAddedTaskMO填充属性,追加记录到allRecInfoLt

3.如果第一步查不到,就去查空库位推荐,将结果给itemAddedTaskMO填充属性,追加记录到allRecInfoLt

4.如果allRecInfoLt满足了5个就退出整个库位推荐。

改造后:

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
// 如果1个播种位上只有1个任务信息,按默认流程处理
String lotBatch = null;
// 如果1个播种位上有多个任务信息,按混批流程走
if (inventoryTDLt.size() > 1) {
lotBatch = "1";
}
ItemAddedTaskMO itemAddedTaskMO = new ItemAddedTaskMO();
itemAddedTaskMO.setSeedLocation(seedLocation);
// 计算推荐库位
// 1.优先推荐可以同品同批次/同品允许混批次合并库位,按上架路线排序,再按location_no排序
List<String> rejectNo = getRejectRegionNo(fromLocationNoSet);
List<String> itemStatus = getLegalItemStatus(inventoryTDLt.get(0).getItemStatus());
WarehouseLocationDO recWarehouseLocationDO = warehouseLocationDBDAO.findAscSPBLocAndPutAway(shipperNo, sku, getLegalRegionstatus(status), lotNoSet,
lotBatch, fromLocationNoSet, currentPutawayLine, rejectNo, itemStatus);
// 如果顺向存在同品同批次库位/同品允许混批次则直接选择该库位为推荐库位,不存在则逆向查找
if (recWarehouseLocationDO == null) {
recWarehouseLocationDO = warehouseLocationDBDAO.findDescSPBLocAndPutAway(shipperNo, sku, getLegalRegionstatus(status), lotNoSet, lotBatch,
fromLocationNoSet, currentPutawayLine, rejectNo, itemStatus);
}
// 2.空库位推荐
if (recWarehouseLocationDO == null) {
recWarehouseLocationDO = warehouseLocationDBDAO.findAscNullLocationNo(shipperNo, fromLocationNoSet, lotBatch, currentPutawayLine, rejectNo,
getLegalRegionstatus(status));
}
if (recWarehouseLocationDO == null) {
recWarehouseLocationDO = warehouseLocationDBDAO.findDescNullLocationNo(shipperNo, fromLocationNoSet, lotBatch, currentPutawayLine, rejectNo,
getLegalRegionstatus(status));
}

if (recWarehouseLocationDO == null) {
noRecInfoLt.add(itemAddedTaskMO);
} else {
itemAddedTaskMO.setLocationNo(recWarehouseLocationDO.getLocationNo());
itemAddedTaskMO.setPutAwayLine(recWarehouseLocationDO.getPutawayLine() - currentPutawayLine);
allRecInfoLt.add(itemAddedTaskMO);
}
// 如果推荐库位信息已经等于5个,则退出推荐逻辑
if (allRecInfoLt.size() == 5) {
break;
}

所以,写那么多屁话干嘛啊?

第三步:隔离副作用(side effect)

抽离方法(export method)

作为较合理的改造,我们应该将有副作用的代码隔离出去,获取推荐库位调用了数据库,这种属于是副作用需要隔离。隔离后的结果是:

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
61
62
63
// 循环遍历详细任务对应的库位,按照当前所在库位进行计算
for (Entry<String, List<TaskDetailDO>> entry : locationDetailsMap.entrySet()) {
List<TaskDetailDO> inventoryTDLt = entry.getValue();
final String seedLocation = entry.getKey();
if (CollectionUtils.isEmpty(inventoryTDLt)) {
throw new BusinessException(I18nUtil.getMessage("move.param.errInfo9", taskNo));
}
String sku = inventoryTDLt.get(0).getSku();
String status = inventoryTDLt.get(0).getItemStatus();
String shipperNo = inventoryTDLt.get(0).getShipperNo();
// 循环遍历任务信息,获取任务的所有批次号(下架时必须保证1个播种位只能存在1个sku,不同批次为不同taskDetail)
Set<String> fromLocationNoSet = inventoryTDLt.stream().map(TaskDetailDO::getFromLocationNo).collect(Collectors.toSet());
Set<String> lotNoSet = inventoryTDLt.stream().map(TaskDetailDO::getLotNo).collect(Collectors.toSet());
// 如果1个播种位上只有1个任务信息,按默认流程处理
String lotBatch = null;
// 如果1个播种位上有多个任务信息,按混批流程走
if (inventoryTDLt.size() > 1) {
lotBatch = "1";
}
ItemAddedTaskMO itemAddedTaskMO = new ItemAddedTaskMO();
itemAddedTaskMO.setSeedLocation(seedLocation);
List<String> rejectNo = getRejectRegionNo(fromLocationNoSet);
List<String> itemStatus = getLegalItemStatus(inventoryTDLt.get(0).getItemStatus());
// 获取推荐库位
WarehouseLocationDO recWarehouseLocationDO = getRecommandWarehouseLocation(currentPutawayLine, sku, status, shipperNo, fromLocationNoSet, lotNoSet, lotBatch, rejectNo, itemStatus);
if (recWarehouseLocationDO == null) {
noRecInfoLt.add(itemAddedTaskMO);
} else {
itemAddedTaskMO.setLocationNo(recWarehouseLocationDO.getLocationNo());
itemAddedTaskMO.setPutAwayLine(recWarehouseLocationDO.getPutawayLine() - currentPutawayLine);
allRecInfoLt.add(itemAddedTaskMO);
}
// 如果推荐库位信息已经等于5个,则退出推荐逻辑
if (allRecInfoLt.size() == 5) {
break;
}
}


/**
* 获取推荐库位
*/
private WarehouseLocationDO getRecommandWarehouseLocation(Integer currentPutawayLine, String sku, String status, String shipperNo, Set<String> fromLocationNoSet, Set<String> lotNoSet, String lotBatch, List<String> rejectNo, List<String> itemStatus) {
// 计算推荐库位
// 1.优先推荐可以同品同批次/同品允许混批次合并库位,按上架路线排序,再按location_no排序
WarehouseLocationDO recWarehouseLocationDO = warehouseLocationDBDAO.findAscSPBLocAndPutAway(shipperNo, sku, getLegalRegionstatus(status), lotNoSet,
lotBatch, fromLocationNoSet, currentPutawayLine, rejectNo, itemStatus);
// 如果顺向存在同品同批次库位/同品允许混批次则直接选择该库位为推荐库位,不存在则逆向查找
if (recWarehouseLocationDO == null) {
recWarehouseLocationDO = warehouseLocationDBDAO.findDescSPBLocAndPutAway(shipperNo, sku, getLegalRegionstatus(status), lotNoSet, lotBatch,
fromLocationNoSet, currentPutawayLine, rejectNo, itemStatus);
}
// 2.空库位推荐
if (recWarehouseLocationDO == null) {
recWarehouseLocationDO = warehouseLocationDBDAO.findAscNullLocationNo(shipperNo, fromLocationNoSet, lotBatch, currentPutawayLine, rejectNo,
getLegalRegionstatus(status));
}
if (recWarehouseLocationDO == null) {
recWarehouseLocationDO = warehouseLocationDBDAO.findDescNullLocationNo(shipperNo, fromLocationNoSet, lotBatch, currentPutawayLine, rejectNo,
getLegalRegionstatus(status));
}
return recWarehouseLocationDO;
}

参数那么长,不觉得怪异,不觉得丑吗?会!在这种与翔的战斗中,你完全可能会先添加一些新的翔让翔膨胀一下后,在找到合适的点来优化掉这些痛点,像不像传说中的玲珑棋局?

参数优化

接下来,我们会发现

rejectNoitemStatusskustatusshipperNolotNoSet

这些都只是为了getRecommandWarehouseLocation而存在的,那么就应该更贴近其使用点,并且他们都只跟inventoryTDLt有关,所以,我们的参数就可以简化为inventoryTDLt

重构后:

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
 // 循环遍历详细任务对应的库位,按照当前所在库位进行计算
for (Entry<String, List<TaskDetailDO>> entry : locationDetailsMap.entrySet()) {
List<TaskDetailDO> inventoryTDLt = entry.getValue();
final String seedLocation = entry.getKey();
if (CollectionUtils.isEmpty(inventoryTDLt)) {
throw new BusinessException(I18nUtil.getMessage("move.param.errInfo9", taskNo));
}
ItemAddedTaskMO itemAddedTaskMO = new ItemAddedTaskMO();
itemAddedTaskMO.setSeedLocation(seedLocation);
// 获取推荐库位
WarehouseLocationDO recWarehouseLocationDO = getRecommandWarehouseLocation(currentPutawayLine, inventoryTDLt);
if (recWarehouseLocationDO == null) {
noRecInfoLt.add(itemAddedTaskMO);
} else {
itemAddedTaskMO.setLocationNo(recWarehouseLocationDO.getLocationNo());
itemAddedTaskMO.setPutAwayLine(recWarehouseLocationDO.getPutawayLine() - currentPutawayLine);
allRecInfoLt.add(itemAddedTaskMO);
}
// 如果推荐库位信息已经等于5个,则退出推荐逻辑
if (allRecInfoLt.size() == 5) {
break;
}
}

/**
* 获取是否是混批
* @param inventoryTDLt 明细数量
* @return 明细为单个是非混批,返回null;明细为多个是混批,返回"1"
*/
private String getLotBatch(List<TaskDetailDO> inventoryTDLt) {
// 如果1个播种位上只有1个任务信息,按默认流程处理
String lotBatch = null;
// 如果1个播种位上有多个任务信息,按混批流程走
if (inventoryTDLt.size() > 1) {
lotBatch = "1";
}
return lotBatch;
}
/**
* 获取推荐库位
* @param currentPutawayLine 上架线路位置
* @param inventoryTDLt 对应库位明细列表
* @return 有推荐库位返回WarehouseLocationDO, 无推荐库位返回null
*/
private WarehouseLocationDO getRecommandWarehouseLocation(Integer currentPutawayLine, List<TaskDetailDO> inventoryTDLt) {
String sku = inventoryTDLt.get(0).getSku();
String status = inventoryTDLt.get(0).getItemStatus();
String shipperNo = inventoryTDLt.get(0).getShipperNo();
// 循环遍历任务信息,获取任务的所有批次号(下架时必须保证1个播种位只能存在1个sku,不同批次为不同taskDetail)
Set<String> fromLocationNoSet = inventoryTDLt.stream().map(TaskDetailDO::getFromLocationNo).collect(Collectors.toSet());
Set<String> lotNoSet = inventoryTDLt.stream().map(TaskDetailDO::getLotNo).collect(Collectors.toSet());
String lotBatch = getLotBatch(inventoryTDLt);
List<String> rejectNo = getRejectRegionNo(fromLocationNoSet);
List<String> itemStatus = getLegalItemStatus(status);
List<String> legalRegionstatus = getLegalRegionstatus(status);
// 计算推荐库位
// 1.优先推荐可以同品同批次/同品允许混批次合并库位,按上架路线排序,再按location_no排序
WarehouseLocationDO recWarehouseLocationDO = warehouseLocationDBDAO.findAscSPBLocAndPutAway(shipperNo, sku, legalRegionstatus, lotNoSet,
lotBatch, fromLocationNoSet, currentPutawayLine, rejectNo, itemStatus);
// 如果顺向存在同品同批次库位/同品允许混批次则直接选择该库位为推荐库位,不存在则逆向查找
if (recWarehouseLocationDO == null) {
recWarehouseLocationDO = warehouseLocationDBDAO.findDescSPBLocAndPutAway(shipperNo, sku, legalRegionstatus, lotNoSet, lotBatch,
fromLocationNoSet, currentPutawayLine, rejectNo, itemStatus);
}
// 2.空库位推荐
if (recWarehouseLocationDO == null) {
recWarehouseLocationDO = warehouseLocationDBDAO.findAscNullLocationNo(shipperNo, fromLocationNoSet, lotBatch, currentPutawayLine, rejectNo,
legalRegionstatus);
}
if (recWarehouseLocationDO == null) {
recWarehouseLocationDO = warehouseLocationDBDAO.findDescNullLocationNo(shipperNo, fromLocationNoSet, lotBatch, currentPutawayLine, rejectNo,
legalRegionstatus);
}
return recWarehouseLocationDO;
}

收尾优化

剩余的一些优化(可能写这个翔的兄弟不太会用Math.min)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
List<ItemAddedTaskMO> taskList = new ArrayList<ItemAddedTaskMO>();
// 如果推荐库位信息已经等于5个,则直接排序,否则将根据当前推荐库位个数添加信息,筹足够5个
int currentNum = allRecInfoLt.size();
if (currentNum > 0) {
Collections.sort(allRecInfoLt);
taskList.addAll(allRecInfoLt);
}
if (currentNum < 5 && noRecInfoLt.size() > 0) {
int num = 5 - currentNum;
int foreachNum = Math.min(noRecInfoLt.size(), num);
for (int i = 0; i < foreachNum; i++) {
taskList.add(noRecInfoLt.get(i));
}
}
int id = 0;
for (ItemAddedTaskMO temp : taskList) {
id++;
temp.setId(id);
}
return taskList.stream().filter(f -> StringUtils.isNotBlank(f.getLocationNo())).collect(Collectors.toList());

完成

改造完成后:

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
61
62
63
64
65
66
67
68
69
70
71
72
73
@Override
public List<ItemAddedTaskMO> queryItemAddedTask(String containerNo, String locationNo) {
TaskDO taskDO = taskService.findByContainerNo(containerNo, TaskDO.TaskType.TASK_TYPE_YIDONGSHANGJIA.toString());
if (taskDO == null) {
log.warn("findByContainerNo {} taskDO is null", containerNo);
return null;
}
// 根据任务获取任务详情,包含批次
String taskNo = taskDO.getTaskNo();
List<TaskDetailDO> taskDetailList = taskDBDAO.queryByTaskNo(taskNo);
if (CollectionUtils.isEmpty(taskDetailList)) {
log.warn("queryByTaskNo {} taskDetailList is empty", taskNo);
return Collections.emptyList();
}
String currentLocationNo = locationNo;
// 根据当前库位号查询库位所在上架路线位置
Integer currentPutawayLine = warehouseLocationDBDAO.findPutAwayLineByLocationNo(currentLocationNo);
if (null == currentPutawayLine) {
throw new BusinessException(I18nUtil.getMessage("move.location.errInfo7", currentLocationNo));
}
// 根据播种位汇总任务信息
final Map<String, List<TaskDetailDO>> locationDetailsMap = taskDetailList.stream()
.collect(Collectors.groupingBy(TaskDetailDO::getContainerLocation));
// 用于保存所有推荐位信息的队列,未排序
List<ItemAddedTaskMO> allRecInfoLt = new ArrayList<ItemAddedTaskMO>();
// 用于保存没有推荐库位的队列,不用排序
List<ItemAddedTaskMO> noRecInfoLt = new ArrayList<ItemAddedTaskMO>();
// 循环遍历详细任务对应的库位,按照当前所在库位进行计算
for (Entry<String, List<TaskDetailDO>> entry : locationDetailsMap.entrySet()) {
List<TaskDetailDO> inventoryTDLt = entry.getValue();
final String seedLocation = entry.getKey();
if (CollectionUtils.isEmpty(inventoryTDLt)) {
throw new BusinessException(I18nUtil.getMessage("move.param.errInfo9", taskNo));
}
ItemAddedTaskMO itemAddedTaskMO = new ItemAddedTaskMO();
itemAddedTaskMO.setSeedLocation(seedLocation);
// 获取推荐库位
WarehouseLocationDO recWarehouseLocationDO = getRecommandWarehouseLocation(currentPutawayLine, inventoryTDLt);
if (recWarehouseLocationDO == null) {
//bug 修复的点就是在这里
itemAddedTaskMO.setLocationNo(locationNo);
noRecInfoLt.add(itemAddedTaskMO);
} else {
itemAddedTaskMO.setLocationNo(recWarehouseLocationDO.getLocationNo());
itemAddedTaskMO.setPutAwayLine(recWarehouseLocationDO.getPutawayLine() - currentPutawayLine);
allRecInfoLt.add(itemAddedTaskMO);
}
// 如果推荐库位信息已经等于5个,则退出推荐逻辑
if (allRecInfoLt.size() == 5) {
break;
}
}
List<ItemAddedTaskMO> taskList = new ArrayList<ItemAddedTaskMO>();
// 如果推荐库位信息已经等于5个,则直接排序,否则将根据当前推荐库位个数添加信息,筹足够5个
int currentNum = allRecInfoLt.size();
if (currentNum > 0) {
Collections.sort(allRecInfoLt);
taskList.addAll(allRecInfoLt);
}
if (currentNum < 5 && noRecInfoLt.size() > 0) {
int num = 5 - currentNum;
int foreachNum = Math.min(noRecInfoLt.size(), num);
for (int i = 0; i < foreachNum; i++) {
taskList.add(noRecInfoLt.get(i));
}
}
int id = 0;
for (ItemAddedTaskMO temp : taskList) {
id++;
temp.setId(id);
}
return taskList.stream().filter(f -> StringUtils.isNotBlank(f.getLocationNo())).collect(Collectors.toList());
}

后话

本文代码从最初又丑又大的231行降为了71行(还是丑,略显大),阅读的难度和复杂性降低,最大的if层次不超过3。

如果利用Tuple还可以将中间的获取allRecInfoLtnoRecInfoLt再次提取。

抽离代码的目的是为了降低维护难度(可读性)和增加可测试能力。我们可以针对

getRecommandWarehouseLocation,getLotBatch,getRejectRegionNo,getLegalItemStatus做对应的单元测试,保障整个代码的可靠性(getRecommandWarehouseLocation需要做一些简单的mock),在做分析的时候,可以将本文中的代码放到sublime中设置语法为java,不会因为缺少信息影响对重构过程的把握。

文中辞藻略显粗鄙,应用到的方法来源于《修改代码的艺术》这本老书的所得所想,感谢这本老书给了深陷于大量互联网的老代码工程痛苦中的我以甘甜清泉,强烈推荐给那些深陷于想改造老代码,又害怕改造风险的朋友,同时协同推荐《重构》,但是《重构》属于是只讲方法论,无法给你入手点和风险控制思路的书。