开发者

How do I manage complex flow control in Java?

I've been researching Java flow control and exception handling for a while here and have these generally accepted rules:

  1. don't use exception handling for flow control
  2. avoid checked exceptions unless the client expects to recover (which is a nice way of saying that you're forcing the client to handle the exception so they might as well try to recover from it?)

Al开发者_如何学编程ong with general rules I try to follow:

  1. if doSomething() fails to "do some thing", the caller should be made aware of this
  2. methods should be concerned with doing one thing

This makes for a bad mix in some cases. I'm finding that I'm catching checked exceptions within methods and returning boolean everywhere and continually having to check successive calls, making something like this:

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
}

I'm getting 5-6 nested if-else's deep in some parts and it's pretty ugly. Not to mention managing a bunch of boolean variables to keep track of what worked and what didn't.

Any advice? Would this be a place where 'goto' is acceptable?


You should look into the doA(), doB(), and doC(). If they are reasonably unlikely to fail, throw an exception when they fail.

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

Examples of unreasonable failures abound, IOException, IllegalParameterException, etc.

If they are reasonably likely to fail

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

Examples of reasonable failures are a bit less emphasized in Java. Some examples include read() returning -1 when there's nothing more to read. If your doA() is actually named closer to attemptA() then perhaps returning a boolean indicating the success of the attempt is appropriate. Think of add(...) and addAll(...) in the Collections interface, they return true if the resulting Collection was modified.

A traditional goto is not a good choice in most languages, as when reviewing the code, it is practically impossible to know where the code "came from." This lack of knowledge of the state prior to entering the goto makes it impossible to guarantee a consistent environment before entry to the goto block. Incidentally, this is why a traditional goto is not available in Java, and only a limited continuation goto is.

To covert from a poorly nested structure to one that's less nested, use a few refactoring techniques:

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;

is equivalent to

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;
}

which is equivalent to

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;
}

If the code in "I know A failed" includes a return, then you don't need to worry about code under the condition doA() being true falling into the code below; so you can promote the lower block, like so:

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;


Yes. Use exceptions rather than return codes. You can wrap exceptions in your own, if you like.

It is perfectly reasonable to use exceptions for flow control -- in exceptional conditions. The usual recommendation is to avoid their use for ordinary flow control.


I think your rule "don't use checked exception unless the client expects to recover" is wrong. It is pretty common to let the caller not recover at all, but instead pass the exception along to its caller, possibly translating the exception to something else.


I would use The State Pattern. Doing so can make your code much easier to read as you can only see what possible states you can go to from the current state that you're in.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜