接上篇:https://developer.aliyun.com/article/1228187?spm=a2c6h.13148508.setting.16.358c4f0eIHLsiZ
二、 一次失败的重构经历
1. 理论依据
当看到一个方法定义过长时或者这段方法需要很多注释才能让人理解的时候,这时候就要考虑是不是把这个方法的部分代码提取出来,形成一个新的方法,方便调用和理解,同时也减小方法的粒度。我们把这种方法叫做提炼函数(Extract Function),在Java语言中可叫做提炼方法(Extract Method)。
2. 重构步骤
• 创建一个新方法,并根据这个方法的意图来命名;
• 将待提炼的代码段从原方法中拷贝到新方法中;
• 检查提炼的代码段,把缺少的变量添加到方法的参数中;
• 如果部分参数成对出现,可以把这些参数合并为一个参数;
• 如果方法需要有返回值,确定返回值的类型,并在合适的位置返回;
• 在原方法中,删除被提炼的代码段,替换为新方法的调用。
3. 代码实现
/**
* 几何辅助类
*/
public final class GeometryHelper {
/** 原有静态常量 */
......
/**
* 划分线串
*
* @param lineString 原始线串
* @param segmentLengthes 分段长度数组
* @return 线串数组
*/
public static LineString[] splitLineString(LineString lineString, double[] segmentLengthes) {
// 原有计算逻辑
......
// 初始化参数值
int index = 1;
Coordinate[] coordinates = lineString.getCoordinates();
Coordinate coordinate = coordinates[0];
int length = targetLengthes.length;
LineString[] lineStrings = new LineString[length];
// 添加前面N段
for (int i = 0; i < length - 1; i++) {
lineStrings[i] = combineLineString(coordinates, index, coordinate, targetLengthes[i]);
}
// 添加最后一段
lineStrings[length - 1] = buildLineString(coordinates, index, coordinate);
// 返回线串数组
return lineStrings;
}
/**
* 组装线串
*
* @param coordinates 坐标数组
* @param index 当前序号
* @param coordinate 当前坐标
* @param targetLength 目标长度
* @return 线串
*/
private static LineString combineLineString(Coordinate[] coordinates, int index, Coordinate coordinate, long targetLength) {
// 添加线串坐标
long addupLength = 0L;
List coordinateList = new ArrayList<>();
coordinateList.add(coordinate);
for (; index < coordinates.length; index++) {
// 计算分段长度
long segmentLength = Math.round(coordinate.distance(coordinates[index]) * ZOOM_SCALE);
// 根据长度处理
boolean isBreak = true;
int compareResult = Long.compare(addupLength + segmentLength, targetLength);
// 根据长度处理: 未达目标长度
if (compareResult < 0) {
addupLength += segmentLength;
coordinate = coordinates[index];
coordinateList.add(coordinate);
isBreak = false;
}
// 根据长度处理: 超过目标长度
else if (compareResult > 0) {
long deltaLength = targetLength - addupLength;
coordinate = buildMiddleCoordinate(coordinate, coordinates[index], segmentLength, deltaLength);
}
// 根据长度处理: 等于目标长度
else {
index++;
coordinate = coordinates[index];
}
// 是否跳出循环
if (isBreak) {
break;
}
}
coordinateList.add(coordinate);
// 返回线串对象
return buildLineString(coordinateList);
}
/** 原有静态方法 */
......
}
4. 存在问题
粗看这段代码,似乎没有什么问题。但是,通过测试发现,并没有得到正确的结果。
5. 分析原因
在《Thinking in Java》中有这样一段话:
| When you’re passing primitives into a method,you get a distinct copy of the primitive. When you’re passing a reference into a method, you get a copy of the reference.
当您将基本类型传递到方法中时,您将得到该基本类型的副本。当您将对象引用传递到方法中时,您将得到该对象引用的副本。
原来参数index(当前序号)和coordinate(当前坐标)在方法combineLineString(组装线串)中的修改,只是对该方法中的参数副本进行修改,并没有体现到调用方法splitLineString(划分线串)中,从而导致每次调用都在使用参数的初始化值。其实,这是在提取方法的过程中,没有考虑到参数的作用域。
6. 检查技巧
这里给出一个作者累试不爽的检查技巧——把提取方法的所有参数添加上final关键字,编译后观察到哪个参数出现编译错误,就说明这个参数是一个输入输出参数(Inout Parameter)。
7. 解决方案
在Java语言中,没有直接的输入输出参数机制,无法简单地实现参数的输入输出功能。所以,需要借助其它解决方案,来实现参数的输入输出功能。在这里,作者通过实践总结,给出了以下几种解决方案。
接下篇:https://developer.aliyun.com/article/1228184?groupCode=java