I suppose this scratches a vast topic, but what is the best approach in handling program work-flow other than the ideal variant when everything gets done the best way.
Let's be a bit more specific:
a class has a method, that method operates on its parameters and returns result.
e.g.:
public Map<Object,OurObject> doTheWork(OtherObject oo);
One possible outcome that I ruled out was returning null
if something went other way, but the ideal.
Is there a correct way ("silver bullet", or so called "best practice") of handling that situation?
I see three other outcomes:
1 - the method returns EMPTY_MAP;
2 - the method has checked exceptions;
3 - throwing a RuntimeException;
If there is no generally correct answer to that question - what are the conditions that should be considered?
Regarding the principles of design by contract (in brief, the method's responsibility is to take care of the output, assuming input parameters are correct) - is it correct for the method to throw any exception or it is better to return empty result, though correct in structure (e.g. not null)
Returning null is like setting a trap for someone. (In cases like HashMaps we've been trained to look out for nulls, so going along with precedent is ok.) The user has to know to look out for the null, and they have to code an alternative path to handle that case (which sounds a lot like handling an exception), or risk an NPE (in which case an exception gets thrown anyway, just a less informative one, at a distance from the place where the original problem occurred, so information about what went wrong will be missing).
Returning an empty map makes it unclear whether anything went wrong or not. It isn't hurtful like returning null, but it is totally ambiguous. If somehow returning the map data is purely a best-effort thing and the user doesn't really need to know, this could be ok. (But this is pretty rare.)
If there is anything you expect the user to be able to do about the exception, throwing a checked exception may be appropriate.
If there is nothing the user can do about the exception, or if the exception would only occur due to a programming error, or due to something that should be caught during testing, then an argument can be made for using an unchecked exception. There is a lot of room for disagreement about what should be checked and what should be unchecked.
Which option would be most beneficial to the callee? If all you need to know is an empty map or null is returned, then that is fine. If you need more information, then an exception would be a better choice.
As for what type of exception, if the callee really needs to do something in order to recover, then a checked exception is appropriate. However, an unchecked exception is likely cleaner.
As for returning null or an empty map, it depends on what the callee is going to do with it. If you expect the callee to take that map and pass it around elsewhere, then an empty map is a better choice.
If something in you method breaks because of erroneous input the user needs to know. Therefore, an exception is the best way to go. Returning empty or null data could potentially make the user think that nothing is wrong and that is definitely not what you want.
I'll bet that there's plenty of good answers for this in stackoverflow. Before I search for them:
- Exceptions are thrown in exceptional situations only - they are not used for normal control flow.
- Checked exceptions are thrown when the caller should be able to recover from the exception
- Unchecked exceptions are thrown when the caller probably should not recover from the exception e.g. exceptions thrown due to programming errors.
An empty collection should be returned if it is a normal state. Especially when the result is a collection, the return value should not be null. If the the return value is null, then it should be clearly documented. If there's need for more complicated analysis of the return state, you might be better of with a appropriately modeled result class.
It all depends on how big of a problem it is if doTheWork
.
If the problem is so large that the application might need to shutdown then throw a RuntimeException.
If the callee might be able to recover from the problem then a CheckedException is common.
If an empty Map has no meaning then it can be used as a return, but you have to be careful that an empty Map is not a valid value to return in a successful case.
If you do return just a null, I would suggest making a note in the Javadoc about the return value.
The best pattern in many cases is the try/do pattern, where many routines have two variations, e.g. TryGetValue() and GetValue(). They are designed to handle three scenarios:
- The object was obtained successfully
- The value couldn't be obtained, for a reason the caller might reasonably anticipate, and the system state is what the caller would expect given such a failure.
- The value couldn't be obtained, for some reason the caller probably isn't anticipating, or else the system state is corrupted in a way the caller won't expect.
The idea behind the try/do pattern is that there are times when a caller will be prepared to handle a failure, but there are other times where the only thing the caller could do in case of failure would be to throw an exception. In the latter case, it would be better for the method which failed to throw the exception rather than requiring the caller to do it.