A reason to believe otherwise is that they have followed these rules in
their own code, and are currently calling another method from their own
code.
Or they are calling code from another library that follows these rules,
I said that in response to the concept that it's somehow implicitly more
reasonable to assume that a reference is non-null than to make any of
gazillions of other assumptions (such as that an integer in non-negative
or less than 100, or that an object represents a valid key in some Map,
or contains a valid social security number, etc.) If that were true,
then that might justify going to extreme lengths to avoid having
reference variables with a value of null.
but I do say that it would be better if the programmer could correctly
make that assumption about more code, because realistically,
programmers do make that assumption.
What programmers are you talking to? Assuming they aren't confused
about more basic concepts (such as with the JFrame example you posted
earlier), perhaps they need to be taught to read documentation or learn
the language better. In any case, I don't want them working on my
project.
I know quite a few bad programmers, but I don't notice that they have a
tendency to assume non-null values for references, any more than to make
any other flawed assumptions.
I don't. I told them how they can prevent NullPointerExceptions.
You're telling me that you AREN'T advocating these techniques? You're
just interested in the abstract thought that following them would
prevent NullPointerExceptions, for the same reasons that people study
non-Euclidean geometries that have no useful applications in science or
engineering? If that's true, I think you are confusing other people.
It sure sounded like you were implying not only that your techniques
prevented NullPointerExceptions, but that you thought that's a good
thing.
(Incidentally, it's been demonstrated several times that your techniques
are insufficient to prevent NullPointerExceptions. If you are really
here for the mathematical buzz, perhaps that satisfies it.)
It is better, where
applicable, though, to not add any extra null references into your
application. Except where they are mandated by the problem domain.
And there, of course, is the crux. You can certainly avoid NEARLY all
observable null values (though not all) by introducing arbitrary levels
of complexity to the application... but that doesn't necessarily mean
it's a good thing. And you were kind enough to demonstrate it...
interface MightHaveAddress
{
boolean hasAddress();
String getAddress() throws IllegalStateException;
}
final class DoesntHaveAddress implements MightHaveAddress
{
public boolean hasAddress()
{
return false;
}
public String getAddress()
{
throw new IllegalStateException();
}
}
final class HasAddress
{
private final String address;
public HasAddress(final String address)
{
this.address=address;
}
public boolean hasAddress()
{
return true;
}
public String getAddress()
{
return address;
}
}
I don't think I broke any of 'my rules' there.
Wow, you sure didn't. What I had in mind was the slightly less
complicated and again following your rules (with or without the odd
single quotes you keep adding.. they are yours, aren't they? Or did you
get them from elsewhere?):
final class AddressHolder
{
/* constrained to have length 0 or 1 */
private final List<String> address = new ArrayList<String>(1);
...
public boolean hasAddress()
{
return (address.size() > 0);
}
public String getAddress()
{
if (!hasAddress()) throw new IllegalStateException();
return address.get(0);
}
}
(This is why I made comments about being forced to keep a List of length
zero or one before... I didn't anticipate your even more complex
solution to the quandary.)
Agreed, and that is exactly what I do, and the people I teach learn how
to solve NullPointerExceptions. But then they make the same mistake
again, and they get to learn how to solve NullPointerExceptions very
quickly. They are making a systematic error, i.e., one that is
predictable and repeated. It would be better if they didn't make this
error.
Either they basically lack the capacity to be programmers, or they are
really being informed of specific assumption that they had made that are
false... assumptions that exist at a higher semantic level than "oops, I
just didn't think about the possibility that it would be null". They
got the null value from somewhere, so one of these must be true:
1. It was returned from some API. They must have misunderstood the
purpose of the API, if they didn't anticipate that the value they wanted
might not be relevant. The NullPointerException teaches them something
at a higher level of abstraction about the API.
2. It was passed in from somewhere as a parameter. Either they designed
the API to accept null, in which case they ought to do something with
it, or they didn't, in which case a NullPointerException is the right
thing to do.
3. There's an uninitialized field. These errors do often occur from
sloppiness, and I'd actually agree with you if you had just advised to
be careful about initialization.
The remaining possibilities (for example, the programmer fully
understands when some APIs might not apply, but persistently uses them
anyway and doesn't bother with what happens in that case just because a
deep sense of apathy) generally indicate an unsuitable temperament for
working on software.
If the field is only intended to be assigned to once, then yes. Many
fields are.
Some fields are, sure. I agree that they should be declared as final.
That's not what you said in the article, though. You said "Make all
fields final" -- which is, of course, a rather dumb rule to try to
follow in Java.
In either case, whether a field is final or not has NO relationship to
whether it should be able to contain a null value or not.
Not a problem, because they know both techniques.
Wasn't the goal to simplify things somehow? If not, then why do the
rules exist? The new programmer has to BOTH know how to write code in
the normal way AND memorize the rules? But they have accomplished the
former, then why the latter?
Not at all. The wrapping method is so tiny, probably 6 lines of code,
4 of which are braces, that it can't really confuse anybody who knows
Java.
The problem isn't the number of lines of code used to implement the
method. It's the number of places the method is used, and as a
correlation, the number of programmers who have to learn what your
method does even though they've known for ages how File.listFiles
behaves.
Of course, if this is the only time you do this in a project, it's not a
big deal. But this is a REALLY minor thing. You haven't really added
any functionality to the original, made any code shorter, prevented any
duplication, or accomplished any other objectively positive goal. If
you do this every time the standard API grates against you the wrong
way, then you're going to have a huge mess by the time your project
reaches 100,000 or so lines of code. Anyone you hire for the project
will have a tough time until they memorize how you've renamed all those
pieces of the standard API that you don't like.
Call it a convenience method. People write them, because they're
convenient. I write this one because it gives me a different interface
to an implementation, an interface I prefer. I don't see this as a
problem at all.
I do. Convenience methods are fine, if they operate at a significantly
higher level of abstraction than the code they replace. If not, then
they make the code harder to read and understand, and do more harm than
good. I figure the test (though mostly applied subconsciously) of
whether a convenience method helps or not is twofold: can you think of a
name for the method that (a) more or less completely describes the
method's unifying purpose? and (b) is shorter and/or more recognizable
than the code itself? In this case, I can't think of such a thing.
You've got:
MyUtils.listFiles - fails test (a)
MyUtils.listFilesButThrowExceptionIfNotDirectory - fails test (b)
Your wish that listFiles worked differently is irrelevant.
It's better than lots of repeated code that does null
checking.
But there's not lots of null checking. If you don't know that a File
object represents a directory, you call isDirectory() first. If you do,
then you just call listFiles and get the result. If you assume that the
object represents a directory when really it doesn't, then the result is
null, you've screwed up, and you want your code to crash with a nice,
descriptive error message that tells you where the problem occurred...
which is exactly what it does the instant you try to get the length or
some element of that array.
It's simply a myth that proper use of listFiles involves checking the
result against null. You made it up, and it's not true. You aren't
saving any code at all by wrapping it in a convenience method. I'd
prefer if the method in java.io.File threw an IllegalStateException
instead because the resulting error could be made clearer (yes, I agree
with you there)... but that's water under the bridge. You don't play
standard API designer when you're writing an application.
Very slightly harder, in the order of less than a minute per
maintainer, for the amount of time it takes to look at the
documentation/implementation and realise what it does.
Nope. You're ignoring the second and third and fourth times that the
maintainer looks up the method, since I've met few maintainers with
perfect memories... and you're ignoring all the extra time and mental
effort spent recalling that definition for the next few hundred times,
since they'll still know the standard API definition better than your
convenience method. That mental effort could have been spent
understanding actual stuff about the code and its purpose, not why you
don't like the standard API.
If I was using concepts that would take a
maintainer weeks to understand, I would see your point.
Quite the contrary, if you had concepts that would take the maintainer
weeks to understand, you'd be very well advised to build abstractions
from them. It's at this level, where what essentially amounts to
administrative overhead of the memory is the dominant factor and the
level of abstraction isn't much raised at all, that building so-called
abstractions is ill-advised.
I'm not sure I understand you at this point, but a compiler can pick up
an uncaught exception.
Not an exception that you'd typically use to replace something that
would generate a NullPointerException. You don't get to claim the
advantages of checked exceptions without incurring substantial cost to
the user of the API... something that, while safer, is considerably wore
wordy and more of a pain than testing for null. That pain is only
justified for things that should actually be handled anyway by
application code.
I think knowing these rules could be useful, but they are not the whole
story. I'd like to encourage programmers to be 'considerate' about
giving null to API users, and even to other APIs.
Sure. If you'd said that, I would agree. APIs should be well-
documented, and it's uncommonly likely to see the standard API neglect
to document when a return value can be null or when a parameter can
legally be null. I have even proposed and argued for, in the past,
language extensions to provide nullability of references as a part of
the reference type in the language (for historical interest only, see
http://cdsmith.twu.net/professional/java/pontifications/nonnull.html).
It's not that bit that I disagree with. It's the fact that you proposed
a ridiculous twisting of the language into contortions to avoid any
possibility of null... a solution that's far out of proportion with the
potential problem. You seem quick to back off those ridiculous
contortions when someone points them out, yet there they remain in
imperative language in your essay.
--
www.designacourse.com
The Easiest Way To Train Anyone... Anywhere.
Chris Smith - Lead Software Developer/Technical Trainer
MindIQ Corporation