Top 10 Useful, Yet Paranoid Java Programming Techniques
After coding for a while (eek, almost 20 years or so in my case, time flies when you’re having fun), one starts to embrace those habits. Because, you know…
This is why people embrace “defensive programming”, i.e. paranoid habits that sometimes make total sense, and sometimes are rather obscure and/or clever and perhaps a bit eerie when you think of the person who wrote it. Here’s my personal list of top 10 useful, yet paranoid Java programming techniques. Let’s go:
1. Put the String literal first
It’s just never a bad idea to prevent the occasional NullPointerException
by putting the String
literal on the left side of an equals()
comparison as such:
// Bad if (variable.equals("literal")) { ... } // Good if ("literal".equals(variable)) { ... }
This is a no-brainer. Nothing is lost from rephrasing the expression from the less good version to the better one. If only we had true Options though, right? Different discussion…
2. Don’t trust the early JDK APIs
In the early days of Java, programming must’ve been a big pain. The APIs were still very immature and you might’ve walked across a piece of code like this:
String[] files = file.list(); // Watch out if (files != null) { for (int i = 0; i < files.length; i++) { ... } }
Looking paranoid? Perhaps, but read the Javadoc:
If this abstract pathname does not denote a directory, then this method returns null. Otherwise an array of strings is returned, one for each file or directory in the directory.
Yeah right. Better add another check, though, just to be sure:
if (file.isDirectory()) { String[] files = file.list(); // Watch out if (files != null) { for (int i = 0; i < files.length; i++) { ... } } }
Bummer! Violation of rule #5 and #6 of our 10 Subtle Best Practices when Coding Java list. So be prepared and add that null
check!
3. Don’t trust that “-1”
This is paranoid, I know. The Javadoc of String.indexOf()
clearly states that…
the index of the first occurrence of the character in the character sequence represented by this object [is returned], or -1 if the character does not occur.
So, -1
can be taken for granted, right? I say nay. Consider this:
// Bad if (string.indexOf(character) != -1) { ... } // Good if (string.indexOf(character) >= 0) { ... }
Who knows. Perhaps they’ll need ANOTHER encoding at some point in time to say, the otherString
would have been contained if checked case-insensitively… Perhaps a good case for returning -2
? Who knows.
After all, we’ve had billions of discussions about the billion dollar mistake, which is NULL
. Why shouldn’t we start discussions about -1
, which is – in a way – an alternative null
for primitive type int
?
4. Avoid the accidental assignment
Yep. It happens to the best (although, not to me. See #7).
(Assume this is JavaScript, but let’s be paranoid about the language as well)
// Ooops if (variable = 5) { ... } // Better (because causes an error) if (5 = variable) { ... } // Intent (remember. Paranoid JavaScript: ===) if (5 === variable) { ... }
Again. If you have a literal in your expression, put it to the left side. You can’t accidentally go wrong here, when you meant to add another =
sign.
5. Check for null AND length
Whenever you have a collection, array, etc., make sure it’s present AND not empty.
// Bad if (array.length > 0) { ... } // Good if (array != null && array.length > 0) { ... }
You never know where those arrays come from. Perhaps from early JDK API?
6. All methods are final
You can tell me all you want about your open/closed principles, that’s all bollocks. I don’t trust you (to correctly extend my classes) and I don’t trust myself (to not accidentally extend my classes). Which is why everything that is not explicitly intended for subtyping (i.e. only interfaces) is strictly final
. See also item #9 of our 10 Subtle Best Practices when Coding Java list.
// Bad public void boom() { ... } // Good. Don't touch. public final void dontTouch() { ... }
Yes. It’s final. If that doesn’t work for you, patch it, or instrument it, or rewrite the byte code. Or send a feature request. I’m sure that your intent of overriding the above isn’t a good idea anyway.
7. All variables and parameters are final
As I said. I don’t trust myself (to not accidentally overwrite my values). Having said so, I don’t trust myself at all. Because…
… which is why all variables and parameters are made final
, too.
// Bad void input(String importantMessage) { String answer = "..."; answer = importantMessage = "LOL accident"; } // Good final void input(final String importantMessage) { final String answer = "..."; }
OK, I admit. This one, I don’t apply very often, really, although I should. I wish Java got it right like Scala, where people just type val
all over the place, without even thinking about mutability – except when they need it explicitly (rarely!), via var
.
8. Don’t trust generics when overloading
Yes. It can happen. You believe you wrote that super nice API which totally rocks and is totally intuitive, and along comes some user who just raw-casts everything up to Object
until the darn compiler stops bitching, and suddently they’ll link the wrong method, thinking it’s your fault (it always is).
Consider this:
// Bad <T> void bad(T value) { bad(Collections.singletonList(value)); } <T> void bad(List<T> values) { ... } // Good final <T> void good(final T value) { if (value instanceof List) good((List<?>) value); else good(Collections.singletonList(value)); } final <T> void good(final List<T> values) { ... }
Because, you know… Your users, they’re like
// This library sucks @SuppressWarnings("all") Object t = (Object) (List) Arrays.asList("abc"); bad(t);
Trust me. I’ve seen everything. Including things like
It’s good to be paranoid.
9. Always throw on switch default
Switch… One of those funny statements where I don’t know whether to petrify with awe or to just cry. Anyway, we’re stuck with switch
, so we may as well get it right when we have to. I.e.
// Bad switch (value) { case 1: foo(); break; case 2: bar(); break; } // Good switch (value) { case 1: foo(); break; case 2: bar(); break; default: throw new ThreadDeath("That'll teach them"); }
Because that moment where value == 3
is introduced into the software, it’ll come for sure! And don’t say enum
, because it’ll happen to enums
as well!
10. Switch with curly braces
In fact, switch
is the most wicked statement anyone has every allowed to get into a language while they were either drunk or lost a bet. Consider the following example:
// Bad, doesn't compile switch (value) { case 1: int j = 1; break; case 2: int j = 2; break; } // Good switch (value) { case 1: { final int j = 1; break; } case 2: { final int j = 2; break; } // Remember: default: throw new ThreadDeath("That'll teach them"); }
Within the switch
statement, there is only one scope defined among all the case
statements. In fact, these case
statements aren’t even really statements, they’re like labels and the switch
is a goto call. In fact, you could even compare case
statements with the astonishing FORTRAN 77 ENTRY statement, a device whose mystery is only exceeded by its power.
This means that the variable final int j
is defined for all the different cases, regardless if we issue a break
or not. Not very intuitive. Which is why it’s always a good idea to create a new, nested scope per case
statement via a simple block. (but don’t forget the break
within the block!)
Conclusion
Paranoid programming may seem weird at times, as code often turns out to be a bit more verbose than really needed. You might think, “oh this is never gonna happen”, but as I said. After 20 years or so programming, you just don’t want to fix those stupid little unnecessary bugs anymore that exist only because the language is so old and flawed. Because you know…
Now, it’s your turn!
What’s your most paranoid quirk in programming?
Reference: | Top 10 Useful, Yet Paranoid Java Programming Techniques from our JCG partner Lukas Eder at the JAVA, SQL, AND JOOQ blog. |
Sorry, but I don’t understand what is wrong with the code segment in number 8 item when yo say “Trust me. I’ve seen everything. Including things like”…
You don’t see what’s wrong with a full database roundtrip for a simple String.replace() operation?