I've been researching Java flow control and exception handling for a while here and have these generally accepted rules:
Along with general rules I try to follow:
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;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With