April 28, 2005
What would you do?
Dear readers of this site, I need your advice.
I'm writing a framework that might get a lot of public exposure, so I'm very obsessive about getting the design right. Currently I'm stuck with an issue where every solution has been "considered harmful" by someone. That's why I'm turning to you for help.
The situation is rather straightforward: A File can be annotated with many Tags. Tags are identified through URIs. There is an interface TagReader to read Tags from a File:
interface TagReader {
Tag readTag(File file, URI tagURI);
}
When a TagReader is told to read a Tag from a File, it might very well happen that there is no such Tag for the File. A missing Tag is not really an exceptional event, but it still has to be handled in some way.
My question is: What should happen when a Tag is not found?
- Return
null The most obvious idea, but there is a case for never returning null even if you have a gun pointed at your head. An important reason why I'm hesitant to go this way is that every consumer of
readTaghas to check its results fornulllest bad things might happen. As I expect many implementations ofTagReaderbeing used together, this is an issue.- Throw an exception
-
I could change the signature of
readTagto this:Tag readTag(File file, URI tagURI) throws TagNotFoundException;What I like about this is that the event "Tag not found" is directly codified into the API. Every consumer of
readTagis forced to deal with the case that the wantedTagisn't present.A downside is that, as mentioned, missing
Tags will be very commonplace and I've been told to reserve exceptions for exceptional things. Also throwing an exception is a very expensive operation, and I can very well imaginereadTagbeing called on 3000Files in a row with most of them bound to throw aTagNotFoundException. Finally checked exceptions make for a lot of code clutter. - Return a Null Object
-
There is an interesting design pattern called the Null Object which could be (rather violently) abused for my purpose. The idea in this case would be to return a singleton object
NullTagthat implementsTagbut plays dead on all messages it receives.Advantages: No
NullPointerExceptions. Downsides: Relies on convention as much as returningnull. Unexpected things might happen if a class is not aware of the possible presence ofNullTagand might, for instance, attempt to change its value. Violates the Fail Fast practice in unspeakable ways.
What would you do?
Update: The conclusion is up.
Comments
I think returning null would be the best way to do it. It's fast, can be documented just as well as throwing an exception (If you have Intellisense, you can see the notes on it faster than checking the code for what exceptions it throws), and like you said, in any case every caller has to deal with the results one way or the other.
Throwing an exception is a costly event, and in such numbers, i'm pretty sure you'll be seeing some heavy overhead due to them.
Returning a Null object seems to be problematic, since then the callers have to check for the Null object that you provided, and not a generic null. If i were using that framework, it would certainly surprise me to get an object that was all empty, i would have thought that it's a bug.
Posted by Miki Watts (#)
Ah, classic problem! Let us know when you've made a decision, and why :)
Posted by
Phil Wilson
(#)
Good luck! In your case I really don't know which one is the worst :)
Josh Bloch (a famous software architect) as some opinion about null here
(chap "Being Forgiving")
If you chose the null solution don't forget to add coments in your javadoc about return value...
Since your the "dictator" I would say "do as you'd like it to be"
Posted by benjamin Francisoud (#)
To decide, you could (just to decide) write 3 methodes:
- readTagNull
- readTagException
- readTagNullObject
and write the unit tests that goes with them.
Then you would see what it looks like to be the user of your own classes and in the "boundary cases" which one need less code to write or has cleaner code on the client side.
Posted by benjamin Francisoud (#)
Personally, of those three choices I'd return null. But then, I don't subscribe to the "nulls are bad" theory.
The Null Object offers no noticeable advantages over returning null in this case.
A fourth possibility is to return an Array or a List of all of the TagS, rather than one at a time. A variant would be to return a Iterator (or Enumeration). That's how I would do it: create a File.getTagIterator(URI tagURI) method that returns an Iterator. And scratch the TagReader interface entirely.
My thinking goes that an exception, while it has some benefits, is the worst choice. And once you leave that out, returning a null object seems bizarre for this use case. I do think nulls are bad in some cases, but I’m with Doug on this one, particularly his Array/Iterator suggestion.
What you really want is to offer the caller is a way to treat the “no such tag” case exactly the same as the “tag found” case, unless the caller is explicitly interested. By giving the caller a list of some form, this becomes possible: a list is a list is a list, whether it has 0 elements or 42.
If you were writing the code in a language with first-class lists (such as most dynamic ones) this approach would probably have been obvious.
Posted by Aristotle Pagaltzis (#)
The answer depends on what a Tag actually is. OK it's a class, but what are its members and functions, so what does it actually do?
This is important, since there may be a fourth solution: Return an empty Tag instance. As you would do when returning an instance of a string class, for example. This is somehow similar to the Null Object but not quite the same.
Second, the correct solution depends on the use cases you want to serve. It seems you want to retrieve known tags from known files, which looks like there is already some knowledge about the file's tags at this point. If so, retrieving a tag that doesn't exist is more or less unlikely - speak exceptional. So throwing an exception may be the right thing to do.
If, however, tags are retrieved using some kind of trial and error - you test all known tags against a file -, a less costly way of indicating no result would be better. As a C++ programmer I tend to just return the null pointer NULL, which represents "no instance" - and that's what we have here. So it seems the natural thing to do.
However, returning a list of all tags of a file and continue working on this is way better, as Aristotle already mentioned.
Posted by Gerd Riesselmann (#)
Post a comment
Thanks for signing in, . Now you can comment. (sign out)
(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)