Re: IllegalArg vs NullPtr Exceptions
"Tom McGlynn" <tam@milkyway.gsfc.nasa.gov> wrote in message
news:2f464af7-3db8-4d5f-9b61-7bd7172d21c7@e34g2000vbm.googlegroups.com...
On Sep 30, 1:26 am, "Scott A. Hightower" <VastEr...@SpamGourmet.com>
wrote:
...
A related question is whether to test for an empty List passed as an
argument value and throw an IllegalArgumentException or to let
NoSuchElementException through and document that it is *supposed to*
happen.
The utility accepts Lists as arguments to several public methods. The
only
thing that it does with each List is get an Iterator from it and extract
the
elements of the list for comparison and storage.
A null List or a List with no elements is meaningless in the context of
the
utility. The documentation explains why and currently warns that a
NullPointerException or NoSuchElementException will be thrown,
respectively.
...
While it's very possible that your situation differs, I would be chary
of explicitly
precluding non-null lists of 0 length. I've frequently found that
being able to treat lists or arrays of 0 length the same as lists or
arrays with elements makes for cleaner logic. Are you making callers
of your utility write
if (list.size() > 0) {
utility(list);
}
rather than just
utility(list);
No, I had more in mind something like this:
List k;
if (list==null) k=nullSurrogate;
else if (list.size()==0) k=emptySurrogate;
else k=list;
utility(k);
and
List k, r;
r=utility();
if (r==nullSurrogate) k=null;
else if (r==emptySurrogate) k=something;
else k=r;
This is a utility. If a null or empty list has meaning to a client, they
will have to use a placeholder whose behavior is defined (by their rules)
with respect to non-null, non-empty lists. Believe me, I am all for doing
as much as I can, so that the thousands of clients (HAH!) have less to do.
But I cannot do this for them, because its behavior is by nature undefined.
BTW, a surrogate is essentialy an instance of the Null Object pattern. More
specifically, it is a List that contains at least one special element whose
methods provide the desired behavior.
so that you are transferring the cost of understanding the special
status
of 0 length lists a level further up the chain? That may be right but
I've generally been happier with the other choice. I'd have my
utility method do nothing with an empty list.
If doing nothing were always the right behavior, that's what I would do.
But again, that depends upon the client's rules, which I cannot know in
advance.
I did consider (briefly) providing surrogates and configuration options to
specify their behavior. That got out of hand very quickly, and I realized
that I could not anticipate every possible set of rules. Such rules are,
after all, completely arbitrary.
Just to make it a little more concrete, the lists are merely collections of
elements. It is really the behavior of the elements with respect to each
other that is of interest.
One operation of interest is how two Lists compare, element by element. If
the comparison is simply for equality, a null List is certainly not equal to
one that is not null. Likewise for an empty List. But are a null List and
an empty List equal? If the comparison is by natural order, where does a
null List fit? Or an empty List? Or, for that matter, a List that contains
one or more null elements? If these really have meaning to the client code,
and are not just error byproducts, the client has to define the rules.
I can provide examples of surrogate Lists and elements, including element
methods that will behave as desired (for example, when compared to other
elements). I even toyed with the idea of a surrogate object factory, but
again, how many different ways can one arbitrarily fit a point into a line,
when the point does not naturally lie on the line?
Typically with the for each syntax, code handles the 0 length case
transparently.
void myUtility(List<X> input) {
for (X val: input) {
...
}
}
It sounds like you want to do something special with the first
element. Then perhaps
boolean first = true;
for (X val: input) {
if (first) {
first = false;
...
}
...
}
It's not so much that the first element is special as that different
algorithms are used for different operations, and the code may shift gears
(and algorithms) depending upon results. The subsequent algorithm needs
both the iterator (for subsequent elements) and sometimes the current
element. I found myself putting in a lot of extra code trying to decide
when to capture the current element, with multiple exit conditions possible.
It turned out much cleaner when I abandoned the for each and "primed the
pump" by fetching the first element before entering the first loop.
It would be no big deal to add a check before fetching that first element.
But I don't think that's the right place to do it. Either let it fail in
the iterator (and it will fail, even if the iterator does not throw the
mandated NoSuchElementException) or check for an empty List at each public
method. That way, the exception is thrown immediately upon invoking the
public method, instead of somewhere in an iterator invoked by a private
method. Then it's clear that the fault is in the client code, not mine and
not the iterator.
[Without the checks for nulls that have been discussed elsewhere in
this thread].
Just a thought. Ymmv.
Regards,
Tom McGlynn
Believe me, I appreciate the thoughts and the time taken to express them.
Even if it's ground that I covered long ago, it never hurts me to look at
the code again and ask myself why I did it that way.
In fact, that's exactly what I'm doing in this thread: I made a decision to
"let it fail" and thoughts expressed in another thread caused me to question
whether I'm doing right by the customer.