我们正在重构一个很长的方法;它包含一个包含许多for
语句的长continue
循环。我想使用 Extract Method 重构,但Eclipse的自动化重构不知道如何处理条件分支。我也没有。
我们当前的策略是引入一个keepGoing
标志(一个实例变量,因为我们想要提取方法),在循环的顶部将其设置为false,并将每个继续替换为将标志设置为true,然后将所有以下内容(在不同的嵌套级别)包装在if (keepGoing)
子句中。然后执行各种提取,然后用提取的方法中的早期返回替换keepGoing
赋值,然后去掉标记。
有更好的方法吗?
更新:在回复评论时 - 我无法分享代码,但这是一个匿名的摘录:
private static void foo(C1 a, C2 b, C3 c, List<C2> list, boolean flag1) throws Exception {
for (int i = 0; i < 1; i++) {
C4 d = null;
Integer e = null;
boolean flag2 = false;
boolean flag3 = findFlag3(a, c);
blahblahblah();
if (e == null) {
if (flag1) {
if (test1(c)) {
if (test2(a, c)) {
Integer f = getF1(b, c);
if (f != null)
e = getE1(a, f);
if (e == null) {
if (d == null) {
list.add(b);
continue;
}
e = findE(d);
}
} else {
Integer f = getF2(b, c);
if (f != null)
e = getE2(a, f);
if (e == null) {
if (d == null) {
list.add(b);
continue;
}
e = findE(d);
}
flag2 = true;
}
} else {
if (test3(a, c)) {
Integer f = getF2(b, c);
if (f != null)
e = getE2(a, f);
if (e == null) {
if (d == null) {
list.add(b);
continue;
}
e = findE(d);
}
flag2 = true;
} else {
if (d == null) {
list.add(b);
continue;
}
e = findE(d);
flag2 = true;
}
}
}
if (!flag1) {
if (d == null) {
list.add(b);
continue;
}
e = findE(d);
}
}
if (e == null) {
list.add(b);
continue;
}
List<C2> list2 = blahblahblah(b, list, flag1);
if (list2.size() != 0 && flag1) {
blahblahblah();
if (!otherTest()) {
if (yetAnotherTest()) {
list.add(b);
continue;
}
blahblahblah();
}
}
}
}
答案 0 :(得分:8)
这是有趣的一种,没有单一的模式可以帮助你。
我会迭代地工作。
首先,我试着看看我是否不能使用早期继续删除其中一个ifs级别。检查条件并提前返回(或者在你的情况下继续)比使用深层嵌套ifs更清晰的代码。
接下来我想我会采取一些内部块,看看它们是否无法被提取到一个单独的方法中。看起来前两个大块(在“if(test2(a,c)){”及其else语句中)非常相似。切割和粘贴逻辑应该是相同的。
最后,在清理完东西后,您可以开始查看实际问题 - 您需要更多课程。整个陈述可能是3-5个兄弟类中的三线多态方法。
它非常接近抛弃和重写代码,一旦你确定了你的实际类,这整个方法就会消失,并被一些如此简单的东西所取代。事实上它是一个静态实用方法应该告诉你一些事情 - 你不需要这类代码中的一个。
编辑(看了一会儿之后): 这里有很多东西,通过它真的很有趣。请记住,当你完成后你不需要代码重复 - 而且我很确定整个事情可以写成没有单一的if - 我认为所有你的ifs都是可以/应该很容易被多态处理的情况。
哦,作为你的日食问题的答案,不想这样做 - 甚至不要尝试自动重构这个,只需手动完成。第一个if()中的东西需要被拉出到一个方法中,因为它实际上与其else()中的子句相同!
当我做这样的事情时,我通常会创建一个新方法,将代码从if移动到新方法中(只在if中调用新方法),然后运行测试并确保你没有什么都不打破。
然后逐行进行并检查以确保if和其他代码之间没有区别。如果存在,则通过将差值作为新变量传递给方法来补偿它。在确定所有内容完全相同后,使用调用替换else子句。再次测试。在这一点上可能会有一些额外的优化变得明显,如果将它的逻辑与你传递的变量结合起来区分这两个调用,你很可能会失去整个优化。
继续做这样的事情并迭代。重构的诀窍是使用非常小的步骤并在每个步骤之间进行测试以确保没有任何改变。
答案 1 :(得分:4)
continue
基本上是早期回归的类比,对吗?
for (...) {
doSomething(...);
}
private void doSomething(...) {
...
if (...)
return; // was "continue;"
...
if (!doSomethingElse(...))
return;
...
}
private boolean doSomethingElse(...) {
...
if (...)
return false; // was a continue from a nested operation
...
return true;
}
现在我必须承认,我并没有完全遵循你现在的策略,所以我可能会重复你所说的话。如果是这样,那么我的答案就是我想不出更好的方法。
答案 2 :(得分:2)
如果我遇到你的情况,我会考虑使用其他重构技术,例如“用多态替换条件”。这就是说你应该一次做一件事,所以如果你第一次想要提取方法,你有两个选择:
在这两个选项中,我认为keepGoing标志更好。提取方法后,我不会停止重构。我相信一旦你有一个更小的方法,你会找到一种方法来删除这个标志,并有更清晰的逻辑。
答案 3 :(得分:0)
我将在这里总结答案,同时接受Bill K的答案是最完整的。但是每个人都有一些好东西可以提供,下次我遇到这种情况时我可能会使用这些方法。
mmyers :剪切循环体,将其粘贴到新方法中,并用continue
替换所有return
s。 这非常好用,虽然如果在循环中有其他控制流语句,比如break和return,它会有麻烦。
Bill K :迭代地将它分开;寻找重复并消除它。利用多态类来替换条件行为。使用非常小的步骤。 是;这是一个很好的建议,具有更广泛的适用性,而不仅仅是这个具体案例。
Aaron :使用keepGoing
标志替换continue
或抛出异常。 我没试过这个,但我认为Exception选项是一个非常好的选择,而且我没有考虑过。