Reputation: 5736
Hi I have written a little function like
public void foo(MyClassA paraA) {
if (paraA == null) return;
MyClassB paraB = doSomeStuff(paraA);
if (paraB == null) return;
MyClassC paraC = doMoreStuff(paraB);
if (paraC == null) return;
....
}
The above fails fast and is nice to read (i.e. the intention to return on null values is clear). But now instead of simply returning, I want to do some error logging, so I changed to
public void foo(MyClassA paraA) {
if (paraA == null) {doLog(); return;}
MyClassB paraB = doSomeStuff(paraA);
if (paraB == null) {doLog(); return;}
MyClassC paraC = doMoreStuff(paraB);
if (paraC == null) {doLog(); return;}
....
}
The above is also clean and easy to read, but I have to repeat doLog() a couple of times. So I change again to
public void foo(MyClassA paraA) {
if (paraA != null) {
MyClassB paraB = doSomeStuff(paraA);
if (paraB != null) {
MyClassC paraC = doMoreStuff(paraB);
if (paraC != null) {
....
return;
}
}
}
doLog();
}
The above calls doLog() only just once but I ended with some deeply-nested if statements, which are very ugly and hard to read. So how do I keep the same cleanliness like before and have doLog() just once? Note that returning something else rather than void for foo() is not allowed. And I also read that using try/catch as oppose to null check is an anti-pattern.
If I am to try, I want to write something like
public void foo(MyClassA paraA) {
while(true) {
if (paraA == null) break;
MyClassB paraB = doSomeStuff(paraA);
if (paraB == null) break;
MyClassC paraC = doMoreStuff(paraB);
if (paraC == null) break;
....
return;
}
doLog();
}
The above fulfills all my needs(fail fast, clean, no nested if), but is the use of the while loop here an anti-pattern as the while loop here is never meant to run more than once?
Upvotes: 2
Views: 1774
Reputation: 77474
IMHO your second code snippet is what ypu should do.
Do not try to make your code short. This is an antipattern.
if (a==null) {
log("Failed in step a");
return;
}
B b = a.doSomething();
is very fast to read and understand. You save nothing by compressing this code. Zero. Nada. Leave it to Hotspot VM, and focus on making code understandable. "if null then log return" is a classic, well understood and accepted pattern.
It had become popular to try to make code "readable" with lambda antipatterns like this:
B b = ifNullLog(a, () -> a.doSomething())
where
T ifNullLog(Object guard, Function<T> func) {
if (guard == null) { doLog(); return null; }
return func.run();
}
but IMHO this is a total antipattern. In fact, it is a best practise to even require braces for every if, else, for, while to make it easy to insert such a log statement without risking to break the code.
Code like your first snippet:
if (a == null) return;
are dangerous. Look at various bugs like Apples SSL disaster If someone adds doLog without noticing the missing brackets, the function will always return null. The apple SSL bug (or was it heartbleed?) esdentially was a
if (a==null)
return;
return;
B b = a.doSomething();
See how subtle the bug is? You Java compiler will fortunately warn you if it's about unreachable code - it will not necessarily warn you otherwise... by always using brackets and well formatted code such bugs can easily be avoided. Format code to avoid errors, not for aestetics.
It is also well acceptable to use return codes. Just don't make success default (see heartbleed again).
Code c = execute(a);
if (c != Code.SUCCESS) {
doLog(c);
return;
}
where
Code execute(A a) {
if (a == null) { return Code.FAILED_A_NULL; }
B b = a.doSomething();
if (b == null) { return Code.FAILED_B_NULL; }
...
return Code.SUCCESS;
}
A classic use case of "return", another good pattern.
Upvotes: 1
Reputation: 7576
Java has a nifty labeled break construct that might help you, here.
public void foo(MyClassA paraA) {
block: {
if (paraA == null) { break block; }
MyClassB paraB = doSomeStuff(paraA);
if (paraB == null) { break block; }
MyClassC paraC = doMoreStuff(paraB);
if (paraC == null) { break block; }
...
return;
}
doLog();
}
If you used polymorphism better, you could do this:
public void foo(MyInterface para) {
while (para != null) {
para = para.doStuff();
}
doLog();
}
If you absolutely can't use polymorphism like that, use a dispatcher.
But I've seen this before, and it looks like a state machine. Give "java enum state machine" a search. I have a feeling that's what you're really trying to do.
Upvotes: 3
Reputation: 6572
Do you think that this is clean
public void foo(MyClassA paraA) {
MyClassB paraB = paraA != null?doSomeStuff(paraA):null;
MyClassC paraC = paraB != null?doMoreStuff(paraB):null;
if (paraC != null) {
....
}
doLog();
}
Upvotes: 1