Saturday, March 22, 2014

Code: Why Google Guice is Evil

Google Guice (pronounced like "juice") is a dependency injection framework for Java. We use it quite often at work. It’s awful. I think the evils of Guice can best be explained by relating a true story as a second person narrative:
You have a huge project in a huge codebase and your team is using Guice. You notice an instance variable of some type (say, InitHelper) in some class, and the variable is injected into the constructor. You want to find out where that variable is initialized so you can tell what it's settings are. Sure, you can port the whole project into your IDE, compile it and run it with some fake arguments for the backends (or take an hour to initialize the real backends), pause the debugger and see what what the settings were for the that variable, but that would take a long time and you feel that you shouldn't have to run and debug code just to see what it is doing.

Luckily, you work for a top tier company with a searchable code repository. You look for all instances where the class constructor is called.

Nowhere. The constructor is called nowhere (except the tests) because your team is using Guice. No matter. You just need to look in the module. Your team had the foresight to place the module that binds the class in the same directory as the class. "The day is mine!" you exclaim, perhaps a bit prematurely.

Huh. It's not bound in the module. That's unfortunate, but just one of the side effects of being Guicy: sometimes your teammates inject variables from different directories. No biggie, just see who's using your module. Surely there's an @Inject or @Provides that will initialize your variable in another module that includes your module.

Everyone. There must be 100 classes using that module. Is that Fortran code from 1970s that uses the module? Okay, you're going about this the wrong way. You know where the main class of your binary is. Look at the main class and work from there. Is the variable bound there?

No, it's not. Because that would be easy. If it were easy, everyone would do it. And we can't have that. Instead, the main class must include a module that binds the variable you're looking for. Just look through the module(s) included in the main class. How many could there possibly be?

All of them. All of the modules ever written since time immemorial are included in the getModules() method of the main class. Well, adversity builds character. What a great person you're going to be by the end of this! Time to get super serious and just search the repository for "bind(InitHelper.class)".

You found it! The class is bound in a module... in some other team's code you've never seen before. And as far as you can tell, your team doesn't use it. You could grab your best hunting dog and follow the trail to see if something that uses this module is used in your code, or you save a lot of time not following what is probably a dead end. You go with the latter.

Look, you're smarter than this. And can do multiline searches in your repository. If it's not bound manually with a call to bind() then it's provided with a provider, right? Just search for "@Provides" followed by an optional "@Singleton" followed by a newline followed by a variable amount of space followed by the variable's class name. By the power of regex!

Quite a few modules. Let's narrow that search to your team's directory.

Nothing. That's fine, you didn't really want to find it anyway.


If that story didn’t do anything for you, let me explain it in more explicit terms. Guice is awful, not just because it turns your improper type casts from compile-time errors to runtime errors, but also because it can turn your code into spaghetti code, where searching for a simple injected variable becomes a quest to find a single needle in a silver haystack.

Disclaimer: Obviously, a code framework cannot be “evil.” And Guice may not always be necessarily bad. When used properly, I'm sure you can achieve Guice’s benefits while still writing maintainable, easy-to-read code. I’m just not quite sure what those benefits are. I've certainly never seen any code where it made things easier to read or to write.

An example of the futility of Guice can be found on the Motivation page of the Guice User’s Guide [link updated; the new Motivation page doesn't have a comments section].  The Motivation page describes a situation where Guice is used to make swapping test components and real components easier in a billing service.  That page ends with the following example of Guice injection:

Injector injector = Guice.createInjector(new BillingModule());
BillingService billingService = injector.getInstance(BillingService.class);

In the comments section, one person asks a good question:
As a cynic of [dependency] injection frameworks, can someone tell me what the advantage is of

Injector injector = Guice.createInjector(new BillingModule());
BillingService billingService = injector.getInstance(BillingService.class);

over

BillingService billingService = new BillingService(new BillingModule());

please?
He (or she) has a point. And in my opinion, his question is never answered satisfactorily. Why depend on annotations and reflection (the GOTOs of the Java language) when you can use the perfectly legitimate new operator? In his example, BillingService gets all its instance variables from the module argument. The constructor for BillingService would look something like this:

public RealBillingService(BillingModuleInterface realOrFakeBillingModule) {
    this.processor = realOrFakeBillingModule.getProcessor();
    this.transactionLog = realOrFakeBillingModule.getTransactionLog();
}

In this case, BillingModuleInterface is an interface for a module that may contain real or mocked-out billing components. It’s easy to read because it’s using good old fashioned typical Java. It’s easy to write because any missed dependencies or casting errors will be found at compile time. It’s easy to add a new module because you can simply create a new class that extends BillingModuleInterface.

In conclusion, the Guice framework is a poor choice for most, if not all, projects. It’s especially bad for large projects in large codebases where finding an injected dependency may be a Sisyphean ordeal.

7 comments: