days
0
3
hours
1
3
minutes
3
1
seconds
4
2
search
Paranoid? Probably...

Top 10 useful, yet paranoid Java programming techniques

Lukas Eder
Aluminium image via Shutterstock

Lukas Eder shares his personal tips and habits after more than 20 years of programming, which he groups under the banner of “defensive programming”. Surely he’s not the only one who gets paranoid now and then?

This post was originally published over at jooq.org, a blog focusing on all things open source, Java and software development from the perspective of jOOQ.

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…

Anything that can possibly go wrong, does.

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 suddenly 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:

7h6fah71

 

 

 

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…

What’s your most paranoid quirk in programming?

Author
Lukas Eder
Lukas is a Java and SQL aficionado. He’s the founder and head of R&D at Data Geekery GmbH, the company behind jOOQ, the best way to write SQL in Java.

Comments
comments powered by Disqus