While the
JVM is a stack-based machine, the Java language doesn’t really offer you any way to access that Stack. Even if sometimes, in rare occasions, it would be very useful.
An example
Method result values are put on the stack. If you look at the following example:
public int method() {
if (something)
return 1;
...
if (somethingElse)
return 2;
...
return 0;
}
If we ignore the
halting problem, error handling, and other academic discussions, we can say that the above method will
“certainly” return any value of
1
,
2
, or
0
. And that value is put on the stack prior to jumping out of the method.
Now, sometimes, it may be a use-case to take some action only when a given result value is returned. People might then be lured into starting the old flame-war
discussion about whether multiple return
statements are EVIL™ and the whole method should have been phrased like this, instead:
public int method() {
int result = 0;
if (something)
result = 1;
...
if (somethingElse)
result = 2;
...
// Important action here prior to return
if (result == 1337)
log.info("hehehe ;-)");
return result;
}
Of course, the above example is wrong, because, previously, the
if (something) return 1
and
if (something) return 2
statements immediately aborted method execution. In order to achieve the same with the “single-return-statement” technique, we’ll have to rewrite our code like this:
public int method() {
int result = 0;
if (something)
result = 1;
else {
...
if (somethingElse)
result = 2;
else {
...
}
}
// Important action here prior to return
if (result == 1337)
log.info("hehehe ;-)");
return result;
}
… and, of course, we can continue
bike-shedding and flame-waring the use of curly braces and/or indentation levels, which shows we haven’t gained anything.
Accessing the return value from the stack
What we really wanted to do in our original implementation is a check just before returning to see what value is on the stack, i.e. what value will be returned. Here’s some pseudo-Java:
public int method() {
try {
if (something)
return 1;
...
if (somethingElse)
return 2;
...
return 0;
}
// Important action here prior to return
finally {
if (reflectionMagic.methodResult == 1337)
log.info("hehehe ;-)");
}
}
The good news is: Yes we can! Here’s a simple trick that can be done to achieve the above:
public int method() {
int result = 0;
try {
if (something)
return result = 1;
...
if (somethingElse)
return result = 2;
...
return result = 0;
}
// Important action here prior to return
finally {
if (result == 1337)
log.info("hehehe ;-)");
}
}
The less good news is: You must never forget to explicitly assign the result. But every once in a while, this technique can be very useful to “access the method stack” when the Java language doesn’t really allow you to.
Of course…
Of course you could also just resort to this boring solution here:
public int method() {
int result = actualMethod();
if (result == 1337)
log.info("hehehe ;-)");
return result;
}
public int actualMethod() {
if (something)
return result = 1;
...
if (somethingElse)
return result = 2;
...
return result = 0;
}
… and probably, most often, this technique is indeed better (because slightly more readable). But sometimes, you want to do more stuff than just logging in that
finally
block, or you want to access more than just the result value, and you don’t want to refactor the method.
Other approaches?
Now it’s your turn. What would be your preferred, alternative approach (with code examples?) E.g. using a Try monad? Or aspects?
Like this:
Like Loading...
More precisely:
in case the actualMethod() throws an exception.
Using the value of an assignment (e.g.: return a=1;) in Java is a sin.
Try compiling it ;-)
Don’t forget about potential flame wars about side effects and having a method do just one thing…
What I wonder about is why you seem to want to avoid the elses. It seems to me a construct of if / else if …/ else is clearer about there being mutually exclusive code paths, and less prone to errors of someone later making a change that affects more code paths than intended.
It may be obvious to the person writing this code that somethingElse and something are guaranteed to be mutually exclusive, but it may not be to someone else who works on it later. An if else construct leaves no doubt. It also may protect the code from breaking if the conditions that made those things guaranteed to be mutually exclusive changes to leave them not so. (Probably more so the case if the ifs did more than just set the return value.)
It makes it clearer to the compiler as well which needs to be able to reason about your code in order to optimize it.
Is logging already a side-effect? That is a very strict functional programming point of view :-)
That was just an example. I might as well have written:
But sometimes, branching isn’t strictly a matter of
true/false
but rather a matter of “if this and that and that, but not that, then…”, however, “if this and that but that no way, and that maybe, then…”, yet again…On the flip side,
if/else
constructs nest more deeply thanif .. return
chains… Pick your poison. Either way, the code will risk being ugly :)These kinds of branches are very hard to optimise – you may as well give up… (it may as well not be necessary)
My point is that the code communicates more about its code path structure and the compiler knows more in the if / if else / else case than in the if ; if ; if; case. You can think about your particular code and decide (at the moment) the compiler isn’t likely do anything with the information, but you shouldn’t be second guessing the compiler, you should be simply always giving it as much information as possible. But knowing that much of the rest of a function will never need to be executed one a given if branch has been taken CAN be used to optimize the code.
(And no, I don’t particularly consider logging a side effect, though some would. ;^) It was in the spirit of “…” and “something else” where the log statement in your code is just a simple stand in.)
Actually nevermind about a big portion of that point. I was forgetting that “return” tells the compiler that the rest of the code will not need to executed as effectively as “else”. :^)
I think you over-value the decisions that the Java compiler can make compared to what is really possible in the JIT, once the runtime starts collecting statistics about the productive execution paths. Quite possibly, the two programming styles turn out to be exactly equivalent, on average.
BTW another benefit of the 3rd version of your code, is if you had left off the initialization value of result (i.e. wrote “int result;” not “int result = 0”) is your IDE would have told if you had a code path that left it uninitialized. It’s a good practice to NOT initialize local variables when you define then, unless you really DO end up with a code structure where having a default fall through case makes it simpler. But you should prefer NOT to.
Good point, although I tend to wind up with
if-else
branch structures where a default is inevitable… I guess that’s another area where there is no single answer, although I agree with preferring NOT to use defaults.I think Einstein had a name for this: “spooky action at a distance”.
This seems like a very creative way to make code more confusing. Run screaming in the opposite direction!
Einstein clearly was ahead of his time!
Actually you can just do this:
Which avoids having multiple exits of the method and do not execute extra stuff :)
Yep, that’s more or less the same as in the third code block…
Not even close! The third code block is madness, think about a method with 20, 30, 50 conditions like this (yes that’s a lot but could happen) and see the differences:
-Multiple return approach: 50 different returns, which will complicate the debug of the method for sure
-Single return chained (3rd code block): The last if will have a incredible looooong tabulation.
-Single return with else: in case you want to debug you just need to put your breakpoint in the return and see your value. Also you don’t need the finally, which is not that obvious what is doing.
What do you think?
How do you guarantee your “different” solution doesn’t deteriorate into the third code block? I agree with tabulation, though – it’s also mentioned in the article.
I think this discussion just shows that there won’t be a nice solution to these kinds of problems (and that bikeshedding programming style is also inevitable… ;) )
It doesn’t seem like having access to the stack would help with this. ‘return’ is a flow control command, what you’re trying to do here is intercept the flow control. Or am I missing something about how a stack accessing language would accomplish this?
The stack thing was just prose to get the article started. The trick that was illustrated here obviously doesn’t really access the stack, but it emulates accessing the stack in a way that it feels like doing stuff with the stack.
But probably, that stack discussion was just misleading…