Is having a return statement just to satisfy syntax bad practice?
JavaReturnTry CatchJava Problem Overview
Consider the following code:
public Object getClone(Cloneable a) throws TotallyFooException {
if (a == null) {
throw new TotallyFooException();
}
else {
try {
return a.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
//cant be reached, in for syntax
return null;
}
The return null;
is necessary since an exception may be caught, however in such a case since we already checked if it was null (and lets assume we know the class we are calling supports cloning) so we know the try statement will never fail.
Is it bad practice to put in the extra return statement at the end just to satisfy the syntax and avoid compile errors (with a comment explaining it will not be reached), or is there a better way to code something like this so that the extra return statement is unnecessary?
Java Solutions
Solution 1 - Java
A clearer way without an extra return statement is as follows. I wouldn't catch CloneNotSupportedException
either, but let it go to the caller.
if (a != null) {
try {
return a.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
throw new TotallyFooException();
It's almost always possible to fiddle with the order to end up with a more straight-forward syntax than what you initially have.
Solution 2 - Java
It definitely can be reached. Note that you're only printing the stacktrace in the catch
clause.
In the scenario where a != null
and there will be an exception, the return null
will be reached. You can remove that statement and replace it with throw new TotallyFooException();
.
In general*, if null
is a valid result of a method (i.e. the user expects it and it means something) then returning it as a signal for "data not found" or exception happened is not a good idea. Otherwise, I don't see any problem why you shouldn't return null
.
Take for example the Scanner#ioException
method:
> Returns the IOException
last thrown by this Scanner's underlying Readable. This method returns null if no such exception exists.
In this case, the returned value null
has a clear meaning, when I use the method I can be sure that I got null
only because there was no such exception and not because the method tried to do something and it failed.
*Note that sometimes you do want to return null
even when the meaning is ambiguous. For example the HashMap#get
:
> A return value of null does not necessarily indicate that the map contains no mapping for the key; it's also possible that the map explicitly maps the key to null. The containsKey
operation may be used to distinguish these two cases.
In this case null
can indicate that the value null
was found and returned, or that the hashmap doesn't contain the requested key.
Solution 3 - Java
> Is it bad practice to put in the extra return statement at the end just to satisfy the syntax and avoid compile errors (with a comment explaining it will not be reached)
I think return null
is bad practice for the terminus of an unreachable branch. It is better to throw a RuntimeException
(AssertionError
would also be acceptable) as to get to that line something has gone very wrong and the application is in an unknown state.
Most like this is (like above) because the developer has missed something (Objects can be none-null and un-cloneable).
I'd likely not use InternalError
unless I'm very very sure that the code is unreachable (for example after a System.exit()
) as it is more likely that I make a mistake than the VM.
I'd only use a custom exception (such as TotallyFooException
) if getting to that "unreachable line" means the same thing as anywhere else you throw that exception.
Solution 4 - Java
You caught the CloneNotSupportedException
which means your code can handle it. But after you catch it, you have literally no idea what to do when you reach the end of the function, which implies that you couldn't handle it. So you're right that it is a code smell in this case, and in my view means you should not have caught CloneNotSupportedException
.
Solution 5 - Java
I would prefer to use Objects.requireNonNull()
to check if the Parameter a is not null. So it's clear when you read the code that the parameter should not be null.
And to avoid checked Exceptions I would re throw the CloneNotSupportedException
as a RuntimeException
.
For both you could add nice text with the intention why this shouldn't happen or be the case.
public Object getClone(Object a) {
Objects.requireNonNull(a);
try {
return a.clone();
} catch (CloneNotSupportedException e) {
throw new IllegalArgumentException(e);
}
}
Solution 6 - Java
The examples above are valid and very Java. However, here's how I would address the OP's question on how to handle that return:
public Object getClone(Cloneable a) throws CloneNotSupportedException {
return a.clone();
}
There's no benefit for checking a
to see if it is null. It's going to NPE. Printing a stack trace is also not helpful. The stack trace is the same regardless of where it is handled.
There is no benefit to junking up the code with unhelpful null tests and unhelpful exception handling. By removing the junk, the return issue is moot.
(Note that the OP included a bug in the exception handling; this is why the return was needed. The OP would not have gotten wrong the method I propose.)
Solution 7 - Java
In this sort of situation I would write
public Object getClone(SomeInterface a) throws TotallyFooException {
// Precondition: "a" should be null or should have a someMethod method that
// does not throw a SomeException.
if (a == null) {
throw new TotallyFooException() ; }
else {
try {
return a.someMethod(); }
catch (SomeException e) {
throw new IllegalArgumentException(e) ; } }
}
Interestingly you say that the "try statement will never fail", but you still took the trouble to write a statement e.printStackTrace();
that you claim will never be executed. Why?
Perhaps your belief is not that firmly held. That is good (in my opinion), since your belief is not based on the code you wrote, but rather on the expectation that your client will not violate the precondition. Better to program public methods defensively.
By the way, your code won't compile for me. You can't call a.clone()
even if the type of a
is Cloneable
. At least Eclipse's compiler says so. Expression a.clone()
gives error
> The method clone() is undefined for the type Cloneable
What I would do for your specific case is
public Object getClone(PubliclyCloneable a) throws TotallyFooException {
if (a == null) {
throw new TotallyFooException(); }
else {
return a.clone(); }
}
Where PubliclyCloneable
is defined by
interface PubliclyCloneable {
public Object clone() ;
}
Or, if you absolutely need the parameter type to be Cloneable
, the following at least compiles.
public static Object getClone(Cloneable a) throws TotallyFooException {
// Precondition: "a" should be null or point to an object that can be cloned without
// throwing any checked exception.
if (a == null) {
throw new TotallyFooException(); }
else {
try {
return a.getClass().getMethod("clone").invoke(a) ; }
catch( IllegalAccessException e ) {
throw new AssertionError(null, e) ; }
catch( InvocationTargetException e ) {
Throwable t = e.getTargetException() ;
if( t instanceof Error ) {
// Unchecked exceptions are bubbled
throw (Error) t ; }
else if( t instanceof RuntimeException ) {
// Unchecked exceptions are bubbled
throw (RuntimeException) t ; }
else {
// Checked exceptions indicate a precondition violation.
throw new IllegalArgumentException(t) ; } }
catch( NoSuchMethodException e ) {
throw new AssertionError(null, e) ; } }
}
Solution 8 - Java
> Is having a return statement just to satisfy syntax bad practice?
As others have mentioned, in your case this does not actually apply.
To answer the question, though, Lint type programs sure haven't figured it out! I have seen two different ones fight it out over this in a switch statement.
switch (var)
{
case A:
break;
default:
return;
break; // Unreachable code. Coding standard violation?
}
One complained that not having the break was a coding standard violation. The other complained that having it was one because it was unreachable code.
I noticed this because two different programmers kept re-checking the code in with the break added then removed then added then removed, depending on which code analyzer they ran that day.
If you end up in this situation, pick one and comment the anomaly, which is the good form you showed yourself. That's the best and most important takeaway.
Solution 9 - Java
It isn't 'just to satisfy syntax'. It is a semantic requirement of the language that every code path leads to a return or a throw. This code doesn't comply. If the exception is caught a following return is required.
No 'bad practice' about it, or about satisfying the compiler in general.
In any case, whether syntax or semantic, you don't have any choice about it.
Solution 10 - Java
I would rewrite this to have the return at the end. Pseudocode:
if a == null throw ...
// else not needed, if this is reached, a is not null
Object b
try {
b = a.clone
}
catch ...
return b
Solution 11 - Java
No one mentioned this yet so here goes:
public static final Object ERROR_OBJECT = ...
//...
public Object getClone(Cloneable a) throws TotallyFooException {
Object ret;
if (a == null)
throw new TotallyFooException();
//no need for else here
try {
ret = a.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
//something went wrong! ERROR_OBJECT could also be null
ret = ERROR_OBJECT;
}
return ret;
}
I dislike return
inside try
blocks for that very reason.
Solution 12 - Java
> The return null; is necessary since an exception may be caught, > however in such a case since we already checked if it was null (and > lets assume we know the class we are calling supports cloning) so we > know the try statement will never fail.
If you know details about the inputs involved in a way where you know the try
statement can never fail, what is the point of having it? Avoid the try
if you know for sure things are always going to succeed (though it is rare that you can be absolutely sure for the whole lifetime of your codebase).
In any case, the compiler unfortunately isn't a mind reader. It sees the function and its inputs, and given the information it has, it needs that return
statement at the bottom as you have it.
> Is it bad practice to put in the extra return statement at the end > just to satisfy the syntax and avoid compile errors (with a comment > explaining it will not be reached), or is there a better way to code > something like this so that the extra return statement is unnecessary?
Quite the opposite, I'd suggest it's good practice to avoid any compiler warnings, e.g., even if that costs another line of code. Don't worry too much about line count here. Establish the reliability of the function through testing and then move on. Just pretending you could omit the return
statement, imagine coming back to that code a year later and then try to decide if that return
statement at the bottom is going to cause more confusion than some comment detailing the minutia of why it was omitted because of assumptions you can make about the input parameters. Most likely the return
statement is going to be easier to deal with.
That said, specifically about this part:
try {
return a.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
...
//cant be reached, in for syntax
return null;
I think there's something slightly odd with the exception-handling mindset here. You generally want to swallow exceptions at a site where you have something meaningful you can do in response.
You can think of try/catch
as a transaction mechanism. try
making these changes, if they fail and we branch into the catch
block, do this (whatever is in the catch
block) in response as part of the rollback and recovery process.
In this case, merely printing a stacktrace and then being forced to return null isn't exactly a transaction/recovery mindset. The code transfers the error-handling responsibility to all the code calling getClone
to manually check for failures. You might prefer to catch the CloneNotSupportedException
and translate it into another, more meaningful form of exception and throw that, but you don't want to simply swallow the exception and return a null in this case since this is not like a transaction-recovery site.
You'll end up leaking the responsibilities to the callers to manually check and deal with failure that way, when throwing an exception would avoid this.
It's like if you load a file, that's the high-level transaction. You might have a try/catch
there. During the process of trying
to load a file, you might clone objects. If there's a failure anywhere in this high-level operation (loading the file), you typically want to throw exceptions all the way back to this top-level transaction try/catch
block so that you can gracefully recover from a failure in loading a file (whether it's due to an error in cloning or anything else). So we generally don't want to just swallow up an exception in some granular place like this and then return a null, e.g., since that would defeat a lot of the value and purpose of exceptions. Instead we want to propagate exceptions all the way back to a site where we can meaningfully deal with it.
Solution 13 - Java
Your example is not ideal to illustrate your question as stated in the last paragraph:
> Is it bad practice to put in the extra return statement at the end > just to satisfy the syntax and avoid compile errors (with a comment > explaining it will not be reached), or is there a better way to code > something like this so that the extra return statement is unnecessary?
A better example would be the implementation of clone itself:
public class A implements Cloneable {
public Object clone() {
try {
return super.clone() ;
} catch (CloneNotSupportedException e) {
throw new InternalError(e) ; // vm bug.
}
}
}
Here the catch clause should never be entered. Still the syntax either requires to throw something or return a value. Since returning something does not make sense, an InternalError
is used to indicate a severe VM condition.