Tuesday, June 16, 2015

Maven dependency ranges must die!

When I first read about the capability of maven to loosely depend on an artifact using version ranges, I thought, "Wow, that's pretty cool!  Why doesn't everyone do that?"  After all, who wouldn't want the latest version of newly released awesomeness?  By way of example, this is how a dependency in maven usually looks, excerpted from Stefan Birkner's excellent JUnit system rules project:

<dependency>
<groupId>com.github.stefanbirkner</groupId>
<artifactId>fishbowl</artifactId>
<version>[1.1.1]</version>
<scope>test</scope>
</dependency>

This means your project depends on version 1.1.1, exactly, of something known as fishbowl in the group of things known as com.github.stefanbirkner.  The brackets around the version number are not usual, and are in fact redundant since they specify the same thing as <version>1.1.1</version> - that version and that version alone.  Going on further in the pom, we find this:

<dependency>
<groupId>junit</groupId>
<artifactId>junit-dep</artifactId>
<version>[4.9,)</version>
</dependency>

This construct is altogether different.  It specifies a dependency on the artifact known as junit in the group known as junit, but allows for any version greater than or equal to 4.9.  The bracket on the left is an inclusive operator and the parenthesis on the right is an exclusive operator.  The comma allows for any later revision.  By way of example, (4.9,5.0) would mean any version after but not including 4.9 but before and not including 5.0, and (4.9) would mean any version greater than and less than 4.9.  That wouldn't work at all.

This seems pretty powerful, but . . .

I have recently come 180° in my thinking about this.  I now hate them with a passion - for two very good reasons.

Awesome Reason I

This one is purely idealogical, which is not to say that it is without merit, it's just a caveat that it may be debatable.  If you depend, for instance, on apache commons-io 2.3, what reason do you have to expect it to work with 2.4?  Or even 2.3.1?  What assurances do you have from that developer community that 2.3.1 will not introduce a bug which impacts your library?  Certainly by version 2.4 the API might change and break compatibility with your code.  This is unpredictable and should not be part of a stable, release build.

Awesome Reason II++

This one is the biggie, and also exceedingly pragmatic.  The aforementioned system rules library, which while otherwise awesome, includes a dependency on junit 4.9 and onwards.  In a practical sense, though, depending on system-rules exposes my builds to periodic failures that are very difficult to track down or diagnose.  Just today I got this error again:

[ERROR] Failed to execute goal on project ssc-cli: Could not resolve dependencies for project org.bitbucket.bradleysmithllc.star-schema-commander:ssc-cli:jar:ssc.1.C: Failed to collect dependencies at com.github.stefanbirkner:system-rules:jar:1.9.0 -> junit:junit-dep:jar:[4.9,): No versions available for junit:junit-dep:jar:[4.9,) within specified range -> [Help 1]

What does this mean?  Why is it happening?  Well, after being plagued with this for a long time, I finally tracked it down.  Some transitive dependency of my project keeps retrieving crapped-up versions of junit, in this case 4.11-beta-1, which satisfies the dependency to be greater than or equal to 4.9, but is not an actual release - it is something that made it to central and then somehow into my repository.  My only (reasonable) recourse is to go to my local repository and delete the junit group tree (~/.m2/repository/junit) and rebuild.  Guess what happens next?

[ERROR] Failed to execute goal on project ssc-cli: Could not resolve dependencies for project org.bitbucket.bradleysmithllc.star-schema-commander:ssc-cli:jar:ssc.1.C: Failed to collect dependencies at com.github.stefanbirkner:system-rules:jar:1.9.0 -> commons-io:commons-io:jar:[2.0,): No versions available for commons-io:commons-io:jar:[2.0,) within specified range -> [Help 1]

Yep, you guessed it - another broken dependency range - this time apache commons-io.  Repeating the same process, I delete my cached commons-io artifacts (~/.m2/repository/commons-io) and now my project builds.

I do not know why exactly this keeps happening - and I am sure that there are many things that could be done to fix it - like track down bad dependencies and fix everyone else's poms, but that isn't an option in most cases nor is it even something I want to do.  My project has an explicit dependency on junit 4.11, which satisfies >= 4.9, so I don't know why it would consider any other version.  The bottom line is that there is no way to predict when a library will break compatibility with yours, and you should not try to anticipate when that will happen.  Builds must be stable and reproducible, and dependency ranges violate both of those goals.

Friday, June 5, 2015

Snob overflow . . .

Okay, another one of my many pet peeves. When people ask questions to Stack Overflow, the community often incorrectly assumes that you are too stupid to ask a real question and instead talks down to you and won't give a straight answer, even if there is one.

Here is a case-in-point: how-to-release-all-permits-for-a-java-semaphore

The question was immediately met with contempt: "What do you need this piece of code for?". This question was not asked for clarification, but so that the discussion could go in a completely different direction. The Stack Overflow moderators are such Nazis about intervening: "This is not a question!", "This is a survey!", "These are opinions!", "This is a duplicate!" - that I am surprised they don't get upset in this situation, because you have a clear question posed, and the selected answer goes in a completely different direction. Rather than answering the question "How to release all permits for a Java Semaphore", they answered the question "How do I better design my code to keep my thread pool in sync?"

The simple answer is:

semaphore.drainPermits();
semaphore.release(totalNumPermits);

Again, whether that discussion is valuable or not, it does not answer the very straightforward question that was asked.