The dreaded DefaultAbstractHelperImpl
A while ago, we have published this fun game we like to call Spring API Bingo. It is a tribute and flattery to Spring’s immense creativeness when forming meaningful class names like
- FactoryAdvisorAdapterHandlerLoader
- ContainerPreTranslatorInfoDisposable
- BeanFactoryDestinationResolver
- LocalPersistenceManagerFactoryBean
Two of the above classes actually exist. Can you spot them? If no, play Spring API Bingo!
Clearly, the Spring API suffers from having…
To name things
There are only two hard problems in computer science. Cache invalidation, naming things, and off-by-one errors
– Tim Bray quoting Phil Karlton
There are a couple of these prefixes or suffixes that are just hard to get rid of in Java software. Consider this recent discussion on Twitter, that inevitably lead to an (very) interesting discussion:
Having
Interface: PaymentService
Implementation: PaymentServiceImpl
The test should be called PaymentServiceImplTest not PaymentServiceTest— Tomek Bujok (@tombujok) October 8, 2014
Yes, the Impl
suffix is an interesting topic. Why do we have it, and why do we keep naming things that way?
Specification vs. body
Java is a quirky language. At the time it was invented, object orientation was a hot topic. But procedural languages had interesting features as well. One very interesting language at the time was Ada (and also PL/SQL, which was largely derived from Ada). Ada (like PL/SQL) reasonably organises procedures and functions in packages, which come in two flavours: specification and body. From the wikipedia example:
-- Specification package Example is procedure Print_and_Increment (j: in out Number); end Example; -- Body package body Example is procedure Print_and_Increment (j: in out Number) is begin -- [...] end Print_and_Increment; begin -- [...] end Example;
You always have to do this, and the two things are named exactly the same: Example
. And they’re stored in two different files called Example.ads
(ad for Ada and s for specification) and Example.adb
(b for body). PL/SQL followed suit and names package files Example.pks
and Example.pkb
with pk for Package.
Java went a different way mainly because of polymorphism and because of the way classes work:
- Classes are both specification AND body in one
- Interfaces cannot be named the same as their implementing classes (mostly, because there are many implementations, of course)
In particular, classes can be a hybrid of spec-only, with a partial body (when they’re abstract), and full spec and body (when they’re concrete).
How this translates to naming in Java
Not everyone appreciates clean separation of specs and body, and this can certainly be debated. But when you’re in that Ada-esque mind set, then you probably want one interface for every class, at least wherever API is exposed. We’re doing the same for jOOQ, where we have established the following policy to name things:
*Impl
All implementations (bodies) that are in a 1:1 relationship with a corresponding interface are suffixed Impl
. If ever possible, we try to keep those implementations package-private and thus sealed in the org.jooq.impl
package. Examples are:
Cursor
and it correspondingCursorImpl
DAO
and it correspondingDAOImpl
Record
and it correspondingRecordImpl
This strict naming scheme makes it immediately clear, which one is the interface (and thus public API), and which one is the implementation. We wish Java were more like Ada with this respect, but we have polymorphism, which is great, and…
Abstract*
… and it leads to reusing code in base classes. As we all know, common base classes should (almost) always be abstract. Simply because they’re most often incomplete implementations (bodies) of their corresponding specification. Thus, we have a lot of partial implementations that are also in a 1:1 relationship with a corresponding interface, and we prefix them with Abstract
. Most often, these partial implementations are also package-private and sealed in the org.jooq.impl
package. Examples are:
Field
and it correspondingAbstractField
Query
and it correspondingAbstractQuery
ResultQuery
and it correspondingAbstractResultQuery
In particular, ResultQuery
is an interface that extends Query
, and thus AbstractResultQuery
is a partial implementation that extends the AbstractQuery
, which is also a partial implementation.
Having partial implementations makes perfect sense in our API, because our API is an internal DSL (Domain-Specific Language) and thus has thousands of methods that are always the same, no matter what the concrete Field
really does – e.g. Substring
Default*
We do everything API related with interfaces. This has proven highly effective already in popular Java SE APIs, such as:
- Collections
- Streams
- JDBC
- DOM
We also do everything SPI (Service Provider Interface) related with interfaces. There is one essential difference between APIs and SPIs in terms of API evolution:
- APIs are consumed by users, hardly implemented
- SPIs are implemented by users, hardly consumed
If you’re not developing the JDK (and thus don’t have completely mad backwards-compatibility rules), you’re probably mostly safe adding new methods to API interfaces. In fact, we do so in every minor release as we do not expect anyone to implement our DSL (who’d want to implement Field
‘s 286 methods, or DSL
‘s 677 methods. That’s mad!)
But SPIs are different. Whenever you provide your user with SPIs, such as anything suffixed *Listener
or *Provider
, you can’t just simply add new methods to them – at least not prior to Java 8, as that would break implementations, and there are many of them.
Well. We still do it, because we don’t have those JDK backwards-compatibility rules. We have more relaxed ones. But we suggest our users do not implement the interfaces directly themselves, but extend a Default
implementation instead, which is empty. For instance ExecuteListener
and the corresponding DefaultExecuteListener
:
public interface ExecuteListener { void start(ExecuteContext ctx); void renderStart(ExecuteContext ctx); // [...] } public class DefaultExecuteListener implements ExecuteListener { @Override public void start(ExecuteContext ctx) {} @Override public void renderStart(ExecuteContext ctx) {} // [...] }
So, Default*
is a prefix that is commonly used to provide a single public implementation that API consumers can use and instantiate, or SPI implementors can extend – without risking backwards-compatibility issues. It’s pretty much a workaround for Java 6 / 7’s lack of interface default methods, which is why the prefix naming is even more appropriate.
Java 8 Version of this rule
In fact, this practice makes it evident that a “good” rule to specify Java-8 compatible SPIs is to use interfaces and to make all methods default with an empty body. If jOOQ didn’t support Java 6, we’d probably specify our ExecuteListener
like this:
public interface ExecuteListener { default void start(ExecuteContext ctx) {} default void renderStart(ExecuteContext ctx) {} // [...] }
*Utils or *Helper
OK, so here’s one for the mock/testing/coverage experts and aficionados out there.
It’s TOTALLY OK to have a “dump” for all sorts of static utility methods. I mean, of course you could be a member of the object-orientation police. But…
Please. Don’t be “that guy”!
So, there are various techniques of identifying utility classes. Ideally, you take a naming convention and then stick to it. E.g. *Utils.
From our perspective, ideally you’d even just dump all utility methods that are not stricly bound to a very specific domain in a single class, because frankly, when did you last appreciate having to go through millions of classes to find that utility method? Never. We have org.jooq.impl.Utils
. Why? Because it’ll allow you to do:
import static org.jooq.impl.Utils.*;
This then almost feels as if you had something like “top-level functions” throughout your application. “global” functions. Which we think is a nice thing. And we totally don’t buy the “we can’t mock this” argument, so don’t even try starting a discussion
Discussion
… or, in fact, let’s do start a discussion. What are your techniques, and why? Here are a couple of reactions to Tom Bujok’s original Tweet, to help get you started:
@tombujok No. PaymentServiceImplTestImpl!
— Konrad ‘ktoso’ Malawski 🐟🏴☠️🇺🇦 (@ktosopl) October 8, 2014
@tombujok get rid of the interface
— Simon Martinelli (@simas_ch) October 8, 2014
@tombujok Impl everything!
— Bartosz Majsak (@majson) October 8, 2014
@tombujok @lukaseder @ktosopl root cause is the class should *not* be called *Impl, but I know you were trolling us all the time anyway
— Peter Kofler (@codecopkofler) October 9, 2014
Let’s go !
Reference: | The dreaded DefaultAbstractHelperImpl from our JCG partner Lukas Eder at the JAVA, SQL, AND JOOQ blog. |