如何在Java中管理复杂的流控制?

时间:2011-04-13 19:33:02

标签: java exception-handling controls flow

我一直在研究Java流量控制和异常处理,并且有这些普遍接受的规则:

  1. 不使用异常处理进行流量控制
  2. 避免检查异常,除非客户端希望恢复(这是一种很好的方式,说你强迫客户端处理异常,所以他们也可以尝试从中恢复?)
  3. 除了一般规则,我试着遵循:

    1. 如果doSomething()无法“做某事”,调用者应该知道这个
    2. 方法应该关注做一件事
    3. 在某些情况下,这会造成混乱。我发现我在方法中捕获已检查的异常,并在任何地方返回布尔值,并不断检查连续的调用,做出类似这样的事情:

      if(doA()){
        if(doB()){
          if(doC()){ 
            // only here do I know it's good
          }else{
            // I know C failed
          }
        }else{
          // I know B failed
        }
      }else{
        // I know A failed
      }
      

      我得到5-6嵌套if-else在某些部分很深,而且非常难看。更不用说管理一堆布尔变量来跟踪哪些有效,哪些无效。

      有什么建议吗?这会是'goto'可以接受的地方吗?

4 个答案:

答案 0 :(得分:5)

您应该查看doA()doB()doC()。如果它们不太可能失败,则在失败时抛出异常。

try {
  doA();
  doB();
  doC();
} catch (FailureException e) {
  // handle failure
}

不合理的失败案例比比皆是IOExceptionIllegalParameterException等。

如果他们有可能失败

if (!doA()) {
  // handle A's failure
  return;
}
if (!doB()) {
  // handle B's failure
  return;
}
if (!doC()) {
  // handle C's failure
  return;
}

合理失败的例子在Java中不那么重要了。一些例子包括read()返回-1时,没有什么可读的。如果您的doA()实际上更接近attemptA(),那么可能会返回boolean,表示尝试成功是合适的。考虑add(...)界面中的addAll(...)Collections,如果生成的true被修改,则会返回Collection

传统的goto在大多数语言中都不是一个好选择,因为在查看代码时,几乎不可能知道代码“来自何处”。在进入goto之前缺乏对状态的了解使得在进入goto块之前无法保证一致的环境。顺便说一下,这就是为什么传统的goto在Java中不可用,只有有限的延续goto

要从嵌套不良的结构转换为嵌套较少的结构,请使用一些重构技术:

if(doA()){
  if (doB()) {
    if (doC()) { 
      // only here do I know it's good
    } else {
      // I know C failed
    }
  } else {
    // I know B failed
  }
} else {
  // I know A failed
}
return;

相当于

if (doA()) {
  if (doB()) {
    if (doC()) { 
      // only here do I know it's good
    } else {
      // I know C failed
    }
  } else {
    // I know B failed
  }
  return;
} else {
  // I know A failed
  return;
}

相当于

if (!doA()) {
  // I know A failed
  return;
} else {
  if (doB()) {
    if (doC()) { 
      // only here do I know it's good
    } else {
      // I know C failed
    }
  } else {
    // I know B failed
  }
  return;
}

如果“我知道A失败”中的代码包含一个返回,那么您无需担心条件doA()为真的代码属于下面的代码;所以你可以像下面这样推广下面的块:

if (!doA()) {
  // I know A failed
  return;
}
if (doB()) {
  if (doC()) { 
    // only here do I know it's good
  } else {
    // I know C failed
  }
} else {
  // I know B failed
}
return;

答案 1 :(得分:1)

是。使用例外而不是返回代码。如果愿意,您可以将自己的异常包装起来。

在特殊条件下使用流量控制异常是完全合理的。通常的建议是避免将它们用于普通流量控制。

答案 2 :(得分:0)

我认为你的规则“不使用已检查的异常,除非客户希望恢复”是错误的。让调用者根本不恢复是很常见的,而是将异常传递给它的调用者,可能会将异常翻译成其他东西。

答案 3 :(得分:0)

我会使用The State Pattern。这样做可以使您的代码更多更容易阅读,因为您只能看到您可以从当前状态进入的状态。