Tuesday, January 25, 2011

The trouble with being conventional

"Convention over configuration" is a popular paradigm in developer circles these days. Instead of explicit instructions over every detail or action, you would follow the framework's conventions and get most of the work done implicitly and for free. Ruby on Rails made this idea really take off, and we see it in action in many frameworks. The popular build tool Maven also adopts convention over configuration, and in the process demonstrates how not to do it.

I've had a pretty rough time getting comfortable with Maven. This was a green field Java project: we were starting absolutely from scratch. There were no legacy build processes to support, so we were ready to follow Maven convention everywhere. Nevertheless, it has been a long, painful (one colleague said "character building") journey. Why did it have to be so hard?

(Before somebody uses the term "learning curve" here: do you know what exactly is a "learning curve"? I don't. What is measured on the X and Y axis, and why is a "steep learning curve" supposed to be a bad thing?)

Losing discoverability

A big part of the problem has to with discoverability. This is the obvious trade-off if you decide to go with convention over configuration: that which is explicit and discoverable is now implicit. The Java world began with explicitness, and the Java language itself is often painful in its verbosity. But in that verbosity,  most people could eventually figure out what is going on. This is also true with Maven's explicit counterpart, Ant: you can follow an Ant script in all its ugly glory and figure out what it does. Maven by contrast drops you in a maze of twisty passages, all alike.

Consider how you would figure out an Ant script. You could do it the hard way, by reading it. Or if it's well designed you could type "ant -p" to get a list of targets. But in either case, the answer is in front of you in the XML somewhere. If you need help, most of Ant's tasks in common use are documented right in the manual. Maven? The conventions for directory structures are easy enough, but the actions are quite opaque. You just have to float in the Maven sea long enough to absorb things. For example, suppose you want to do a quick incremental build. How do you turn off unit tests?

Is it -Dmaven.tests.skip=true?

Is it -Dmaven.skip.test=true?

Is it -Dmaven.run.tests=false?

Let me try "mvn --help". Gee, that was helpful.

Maven has a huge constellation of plugins, and since it can load them on demand, the universe of possible actions, options and goals is endless. While having a huge feature set can be a good thing, the trouble is figuring out which of those plugins are relevant to what you are doing. Conventions are good if they are limited and obvious. Maven is neither. If you have to Google for help to do routine build tasks, maybe something is wrong.

Going our own way

Happy families are all alike; every unhappy family is unhappy in its own way
-- Leo Tolstoy

Tolstoy might as well be talking about Maven. One problem is that software builds have a lot of variability. Marrying a convention-based tool to a problem domain that has significant variability leads to a lot of teams that are unhappy in their own way. Consider the task of deploying a webapp. What command do you use? I hope one of these works: "mvn deploy", "mvn deploy:deploy", "mvn jboss:deploy", "mvn war:deploy", "mvn tomcat:deploy", "mvn tomcat:redeploy".

There is a certain magical quality to Maven, and I don't mean in a good way. Classic short fairy tales have a certain arbitrary quality to them. You need to know the right incantation at the right time and at the right place, or suffer a horrible end. Maven is something like that. Say "Open Sesame", and the cave will open. Kiss that particular frog and find wedded bliss. Type "sql:execute" in that particular directory, and your DB will get initialized.

Escaping Maven-land

There is one common complaint that I don't hold too strongly against Maven: its performance. This is because I've concluded that the key to developer productivity is to bypass the build tool as much as possible. I've documented this somewhat when I was using Ant, and it is all the more important for a banana slug like Maven. I know Maven's developers are working on its performance. Still, there is no way it can be as fast as a properly configured Eclipse setup that will compile and deploy any Java class as fast as I can save it. (This applies to editable, non-compiled resources too.) My approach to a Maven project is to understand the build well enough to escape Maven most of the time.


Maven's huge universe of possible invocations, the absence of discoverability, the lack of unified documentation and the difficulty in figuring out the relevant action in a particular context means it's generally hard to figure out what to type after the "mvn" on the command line. Convention over configuration works really well if you can know the convention. If the convention is neither limited nor discoverable, you're lost.

The way I see it, you have a certain "complexity quota" in any project. Once you exceed that complexity quota, working within that project becomes a burden because it becomes impossible to keep all the relevant instructions in your head at once. Every convention you adopt adds to the required knowledge of the project, increasing the likelihood of exceeding that complexity quota. The decision to adopt Maven must trade off the consumption of that complexity quota that could have been allocated elsewhere.

Still, Maven's popularity demonstrates that it has a lot of strong points. And I do appreciate its power. In theory, everything sounds great. "Tell me what you want", Maven says soothingly, "and I will take care of you". Unfortunately, trying to tell Maven what I want often finds me tongue-tied, and what Maven ultimately decides to do is often not what I want. I hope this pain was ultimately worth it.


  1. Good post. I added a link on dzone for it.

  2. Nice article, thanks! I totally agree that Maven way of convention over configuration is done so wrong. You have to spend a whole lot of time, before you manage to tweak it in a way to get expected result - even for simple tasks. I wish somebody did it right in Java, with proper defaults and covering simple use-cases with some kind of way to also fulfill the more complex tasks. You know the Rails way of doing things :].

  3. I find it super easy to create a build with Maven and definitely easier than understanding the spaghetti of your typical Ant script. And once you have learned it once with your first Maven build you know exactly how it will behave for the next project's build. With Ant you have to start over again because no two builds are the same.

  4. Following your Tolstoy metaphor, every happy Ant build is happy in its own way. I think that is the biggest problem in the build universe. Most of the maven issues come down to lack of documentation on the topic of what conventions they assume.

  5. I find that to 'do' Maven I have to have a certain level of black magic at my beck and call. Certainly I need to sacrifice the correct binary avian to keep the dark Maven gods at bay.

    However, if the people in charge of your build system come to accept a certain limited set of all the possibilities you can get things done in a great hurry. I've gone down the Ant road, spent many years there. The Maven road isn't ideal, it's bumpy, but the views are stunning.