Ninja programming

Ten subtle best practices when coding Java

Lukas Eder
ninja1

Lukas Eder takes us through ten subtle tips and tricks for ninja programming in less common situations involving API / SPI design.

This post was originally
published over at jooq.org, a blog focusing on Java from the
perspective of the developers of 
jOOQ.

This is a list of 10 best practices that are more subtle than
your average Josh Bloch Effective Java rule. While Josh
Bloch’s list is very easy to learn and concerns everyday
situations, this list here contains less common situations
involving API /
SPI
 design that may have a big effect nontheless.

I have encountered these things while writing and
maintaining jOOQ,
an internal
DSL
 modelling SQL in Java. Being an internal DSL, jOOQ
challenges Java compilers and generics to the max, combining generics,
varargs and overloading
 in a way that Josh Bloch probably
wouldn’t recommend for the “average API”.

Let me share with you 10 Subtle Best Practices When Coding
Java:

1. Remember C++ destructors

Remember C++ destructors? No? Then you might be lucky as you
never had to debug through any code leaving memory leaks due to
allocated memory not having been freed after an object was removed.
Thanks Sun/Oracle for implementing garbage collection!

But nonetheless, destructors have an interesting trait to them.
It often makes sense to free memory in
the inverse order of allocation. Keep this in
mind in Java as well, when you’re operating with destructor-like
semantics:

  • When using @Before and @After JUnit annotations
  • When allocating, freeing JDBC resources
  • When calling super methods

There are various other use cases. Here’s a concrete example
showing how you might implement some event listener SPI:

@Override
public void beforeEvent(EventContext e) {
    super.beforeEvent(e);
    // Super code before my code
}
 
@Override
public void afterEvent(EventContext e) {
    // Super code after my code
    super.afterEvent(e);
}8.

Another good example showing why this can be important is
the infamous Dining Philosophers problem. More info
about the dining philosophers can be seen in this awesome post:

The Rule: Whenever you implement logic using
before/after, allocate/free, take/return semantics, think about
whether the after/free/return operation should perform stuff in the
inverse order.

2. Don’t trust your early SPI evolution judgement

Providing an SPI to your consumers is an easy way to allow them
to inject custom behaviour into your library / code. Beware,
though, that your SPI evolution judgement may trick you into
thinking that you’re (not) going
to need that additional parameter
. True, no
functionality should be
added early
. But once you’ve published your SPI and once you’ve
decided followingsemantic
versioning
, you’ll really regret having added a silly,
one-argument method to your SPI when you realise that you might
need another argument in some cases:

interface EventListener {
    // Bad
    void message(String message);
}

What if you also need a message ID and a message source? API
evolution will prevent you from adding that parameter easily, to
the above type. Granted, with Java 8, you could add a defender
method, to “defend” you bad early design decision:

interface EventListener {
    // Bad
    default void message(String message) {
        message(message, null, null);
    }
    // Better?
    void message(
        String message,
        Integer id,
        MessageSource source
    );
}

Note, unfortunately, the defender method cannot
be made final
.

But much better than polluting your SPI with dozens of methods,
use a context
object (or argument object)
 just for this purpose.

interface MessageContext {
    String message();
    Integer id();
    MessageSource source();
}
 
interface EventListener {
    // Awesome!
    void message(MessageContext context);
}

You can evolve the MessageContext API much more easily than the
EventListener SPI as fewer users will have implemented it.

The Rule: Whenever you specify an SPI, consider
using context / parameter objects instead of writing methods with a
fixed amount of parameters.

Remark: It is often a good idea to also
communicate results through a dedicated MessageResult type, which
can be constructed through a builder API. This will add even more
SPI evolution flexibility to your SPI.

3. Avoid returning anonymous, local, or inner classes

Swing programmers probably have a couple of keyboard shortcuts
to generate the code for their hundreds of anonymous classes. In
many cases, creating them is nice as you can locally adhere to an
interface, without going through the “hassle” of thinking about a
full SPI subtype lifecycle.

But you should not use anonymous, local, or inner classes too
often for a simple reason: They keep a reference to the outer
instance. And they will drag that outer instance to wherevery they
go, e.g. to some scope outside of your local class if you’re not
careful. This can be a major source for memory leaks, as your whole
object graph will suddenly entangle in subtle ways.

The Rule: Whenever you write an anonymous,
local or inner class, check if you can make it static or even a
regular top-level class. Avoid returning anonymous, local or inner
class instances from methods to the outside scope.

Remark: There has been some clever practice
around double-curly braces for simple object instantiation:

new HashMap<String, String>() {{
    put("1", "a");
    put("2", "b");
}}

This leverages Java’s instance initializer as specified
by the JLS §8.6
. Looks nice (maybe a bit weird), but is really
a bad idea. What would otherwise be a completely independent
HashMap instance now keeps a reference to the outer instance,
whatever that just happens to be. Besides, you’ll create an
additional class for the class loader to manage.

4. Start writing SAMs now!

Java 8 is knocking on the door. And with Java 8
come lambdas
, whether you like them or not. Your API consumers
may like them, though, and you better make sure that they can make
use of them as often as possible. Hence, unless your API accepts
simple “scalar” types such
as intlongString,Date,
let your API accept SAMs as often as possible.

What’s a SAM? A SAM is a Single Abstract Method [Type]. Also
known as afunctional
interface
, soon to be annotated with the @FunctionalInterface
annotation
. This goes well with rule number 2, where
EventListener is in fact a SAM. The best SAMs are those with single
arguments, as they will further simplify writing of a lambda.
Imagine writing

listeners.add(c -> System.out.println(c.message()));

Instead of

listeners.add(new EventListener() {
    @Override
    public void message(MessageContext c) {
        System.out.println(c.message()));
    }
});

Imagine XML processing through jOOX, which features a couple
of SAMs:

$(document)
    // Find elements with an ID
    .find(c -> $(c).id() != null)
    // Find their  child elements
    .children(c -> $(c).tag().equals("order"))
    // Print all matches
    .each(c -> System.out.println($(c)))

The Rule: Be nice with your API consumers and
write SAMs / Functional interfaces already now.

Remarks: A couple of interesting blog posts
about Java 8 Lambdas and improved Collections API can be seen
here:

5. Avoid returning null from API methods

I’ve blogged about Java’s NULLs once
or twice. I’ve also blogged about Java 8’s introduction
of Optional.
These are interesting topics both from an academic and from a
practical point of view.

While NULLs and NullPointerExceptions will probably stay a major
pain in Java for a while, you can still design your API in a way
that users will not run into any issues. Try to avoid returning
null from API methods whenever possible. Your API consumers should
be able to chain methods whenever applicable:

initialise(someArgument).calculate(data).dispatch();

In the above snippet, none of the methods should ever return
null. In fact, using null’s semantics (the absence of a value)
should be rather exceptional in general. In libraries
like jQuery (or jOOX,
a Java port thereof), nulls are completely avoided as
you’re always
operating on iterable objects
. Whether you match something or
not is irrelevant to the next method call.

Nulls often arise also because of lazy initialisation. In many
cases, lazy initialisation can be avoided too, without any
significant performance impact. In fact, lazy initialisation should
be used carefully, only. If large data structures are involved.

The Rule: Avoid returning nulls from methods
whenever possible. Use null only for the “uninitialised” or
“absent” semantics.

6. Never return null arrays or lists from API methods

While there are some cases when returning nulls from methods is
OK, there is absolutely no use case of returning null arrays or
null collections! Let’s consider the hideous java.io.File.list() method.
It returns:

An array of strings naming the files and directories in the
directory denoted by this abstract pathname. The array will be
empty if the directory is empty. Returns null if this abstract
pathname does not denote a directory, or if an I/O error
occurs.

Hence, the correct way to deal with this method is:

File directory = // ...
 
if (directory.isDirectory()) {
    String[] list = directory.list();
 
    if (list != null) {
        for (String file : list) {
            // ...
        }
    }
}

Was that null check really necessary? Most I/O operations
produce IOExceptions, but this one returns null. Null cannot hold
any error message indicating why the I/O error occurred. So this is
wrong in three ways:

  • Null does not help in finding the error
  • Null does not allow to distinguish I/O errors from the File
    instance not being a directory
  • Everyone will keep forgetting about null, here

In collection contexts, the notion of “absence” is best
implemented by empty arrays or collections. Having an “absent”
array or collection is hardly ever useful, except again, for lazy
initialisation.

The Rule: Arrays or Collections should never be
null.

7. Avoid state, be functional

What’s nice about HTTP is the fact that it is stateless. All
relevant state is transferred in each request and in each response.
This is essential to the naming of REST: Representational
State Transfer
. This is awesome when done in Java as well.
Think of it in terms of rule number 2 when methods receive stateful
parameter objects. Things can be so much simpler if state is
transferred in such objects, rather than manipulated from the
outside. Take JDBC, for instance. The following example fetches a
cursor from a stored procedure:

CallableStatement s =
  connection.prepareCall("{ ? = ... }");
 
// Verbose manipulation of statement state:
s.registerOutParameter(1, cursor);
s.setString(2, "abc");
s.execute();
ResultSet rs = s.getObject(1);
 
// Verbose manipulation of result set state:
rs.next();
rs.next();

These are the things that make JDBC such an awkward API to deal
with. Each object is incredibly stateful and hard to manipulate.
Concretely, there are two major issues:

  • It is very hard to correctly deal with stateful APIs in
    multi-threaded environments
  • It is very hard to make stateful resources globally available,
    as the state is not documented

Theatrical poster for Forrest Gump,
Copyright © 1994 by Paramount Pictures. All Rights
Reserved. It is believed that the above usage fulfils what is known
as Fair Use

The Rule: Implement more of a functional style.
Pass state through method arguments. Manipulate less object
state.

8. Short-circuit equals()

This is a low-hanging fruit. In large object graphs, you can
gain significantly in terms of performance, if all your
objects’ equals() methods dirt-cheaply
compare for identity first:

@Override
public boolean equals(Object other) {
    if (this == other) return true;
 
    // Rest of equality logic...
}

Note, other short-circuit checks may involve null checks, which
should be there as well:

@Override
public boolean equals(Object other) {
    if (this == other) return true;
    if (other == null) return false;
 
    // Rest of equality logic...
}

The Rule: Short-circuit all your equals()
methods to gain performance.

9. Try to make methods final by default

Some will disagree on this, as making things final by default is
quite the opposite of what Java developers are used to. But if
you’re in full control of all source code, there’s absolutely
nothing wrong with making methods final by default, because:

  • If you do need to override a method (do you
    really?), you can still remove the final keyword
  • You will never accidentally override any method anymore

This specifically applies for static methods, where “overriding”
(actually, shadowing) hardly ever makes sense. I’ve come across a
very bad example of shadowing static methods in Apache Tika, recently. Consider:

TikaInputStream extends TaggedInputStream and shadows its static
get() method with quite a different implementation.

Unlike regular methods, static methods don’t override each
other, as the call-site binds a static method invocation at
compile-time. If you’re unlucky, you might just get the wrong
method accidentally.

The Rule: If you’re in full control of your
API, try making as many methods as possible final by default.

10. Avoid the method(T…) signature

There’s nothing wrong with the occasional “accept-all” varargs
method that accepts
an Object... argument:

void acceptAll(Object... all);

Writing such a method brings a little JavaScript feeling to the
Java ecosystem. Of course, you probably want to restrict the actual
type to something more confined in a real-world situation,
e.g. String.... And because you don’t want to
confine too much, you might think it is a good idea to replace
Object by a generic T:

void acceptAll(T... all);

But it’s not. T can always be inferred to Object. In fact, you
might as well just not use generics with the above methods. More
importantly, you may think that you can overload the above method,
but you cannot:

void acceptAll(T... all);
void acceptAll(String message, T... all);

This looks as though you could optionally pass a String message
to the method. But what happens to this call here?

acceptAll("Message", 123, "abc");

The compiler will infer <? extends Serializable
& Comparable<?>>
 forT, which
makes the call ambiguous!

So, whenever you have an “accept-all” signature (even if it is
generic), you will never again be able to typesafely overload it.
API consumers may just be lucky enough to “accidentally” have the
compiler chose the “right” most specific method. But they may as
well be tricked into using the “accept-all” method or they may not
be able to call any method at all.

The Rule: Avoid “accept-all” signatures if you
can. And if you cannot, never overload such a method.

Conclusion

Java is a beast. Unlike other, fancier languages, it has evolved
slowly to what it is today. And that’s probably a good thing,
because already at the speed of development of Java, there are
hundreds of caveats, which can only be mastered through years of
experience.

Ninja programmer image via
Shutterstock

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