Discussing the design, decisions and discovery involved in developing Mutability Detector, an open-source analysis tool for Java.

Sunday, 3 October 2010

Version 0.02 of Mutability Detector now available.

... because nothing says "Progress!" like the increment by one-hundredth of a major release.

Sarcasm aside, in this new release, I definitely see a further step towards "Actually useful". I'll use this blog entry to demonstrate some of the improvements, discuss the planned features for the next release, and list what's still keeping that v1.0 release out of reach.

Unit Testing Support
I consider one of the major barriers to the adoption of a static analysis tool is the 'bootstrapping' cost: the time and resource required to configure and run the analysis as part of the build. There's also the issue of adapting the results to suit your codebase - trimming the false positives, and keeping the results to a meaningful and manageable level. With this problem in mind, I decided to stand on the shoulders of giants, to reuse, rather than reinvent.

JUnit, and its ilk (TestNG being the most prominent rival) have long been a weapon in the arsenal of software development. Without attempting to be controversial, any Java development shop worth its salt will have unit tests regularly run as part of a continuous integration build. The beauty of JUnit is that while it is suitable for large teams with large projects, it also works for a single developer with a small project (about a third of the Java source files in Mutability Detector are JUnit test classes, run regularly through an IDE).

With widespread adoption and an ecosystem of knowledge and tools available, it seems a sensible option to hook into that, rather than trying to roll my own. The v0.02 release of Mutability Detector enables this.

By simply adding the jar to your classpath, it is possible to write this kind of test case:

import static org.mutabilitydetector.junit.MutabilityAssert.assertImmutable;

 public void theClassIsImmutable() {

Which will fail if, for example, someone later comes along and adds a setter method.

That's all it takes. And there's several advantages that come with this approach:

  • There's no lengthy configuration: just add a jar to your development classpath, there's certainly no need to add a runtime dependency on Mutability Detector, unless you want to (there is an API for doing runtime checks).
  • There's no need to annotate your code: meaning nothing except test code has to be cleaned up if you decide to stop using the jar; no dependency on a third-party annotation; and no nasty "magic" comments to enable or disable the check. Developers who employ TDD will be familiar with the idea that test code is the documentation, there's no need for redundant source annotations. 
  • There's also no separate build phase: since the checks are made as part of running the unit tests, if you already do that, there's nothing more to configure. But, if you don't regularly run unit tests don't worry, the option to run the tool from the command line is still there, and always will be.
  • It's extremely selective: you won't have to prune all the results to get the ones for classes you actually care about, the analysis will only be run on classes that you specify as intending to be immutable. This means no noisy false positives.

However, like anything else, there are downsides and caveats, and I'd be remiss for not mentioning them.

  • The assertion mechanism for checking immutability assumes a JUnit-like approach. That is, failed checks will throw a (subclass of) AssertionError on the thread the test is run on. If your unit testing library of choice does not use this kind of mechanism, the tool may not be suitable for you.
  • The flipside of being selective is that it may require a lot of busywork to go around your codebase adding in single-line test methods which only call assertImmutable(). I intend to address this problem (somehow) but support for lessening this burden is not here yet.
  • The unit test task may require more memory than previously. I chose the option of speed over memory footprint, using a single analysis session across all unit tests run in the current JVM process. This prevents the need to re-analyse classes several times, but it does mean that more memory is used to cache the results. Unfortunately, this is not something that I have been able to benchmark in a real-world scenario, but Mutability Detector can analyse the entire rt.jar of Sun's 1.6.12 JDK (~17,000 classes) with less than 96Mb of memory, which gives a decent indication of the memory footprint.

The support for unit testing immutability is the most significant addition to Mutability Detector, and, in my humble opinion, represents a useful and novel way of introducing static analysis into development workflow.

Other Changes With This Release
Along with this change, there are several other improvements:
  • Better detection of setter methods, covering some of the false results described in an earlier post.
  • Support for reporting only on specified classes with a --classlist option. Run java -jar MutabilityDetector.jar -help for further detail.
  • The --match option for the command line interface is now implemented.
  • Improvements to the way classes are loaded, to reduce the amount of PermGen space required.

But, before I get ahead of myself, I must point out, there is a very good reason why this release is not close to a v1.0: java.lang.String is still reported as mutable.

What's Next?
Obviously the analysis of java.lang.String is a problem: since first releasing the source code I have been researching and considering ways to correctly handle the case. Since this is the major stumbling block to a v1.0, and I want to get it right, the work on this is ongoing. Ideas and thoughts are most welcome.

Besides that, there are another couple of items of work which I will be working on in the near future (as time and other commitments permit, of course).

  • Implement the EscapedThisReferenceChecker described in an earlier post.
  • Expose an API within the MutabilityAssert methods to allow more fine grained control to override potential false positives. For example, if your class does have one method which lazy inits a field, and the tool isn't at the level of sophistication required to detect it, there is the mechanism to say "In regards to reassigning this particular field, in this method, don't worry, I know what I'm doing". It's inevitable that there's always going to be code which can fool the tool, this should help ease that pain a little.
  • Investigate the following features: an Eclipse plugin; a FindBugs plugin; a nicer report from the command line tool (perhaps a nice HTML page with the capability of filtering and searching the results); and an ant task to wrap the command line interface. Some of these features may be worthwhile, some may not, so it would be prudent to consider them.
  • Improve the tool to be much smarter about detecting mutations. For example, having a mutable field makes the owning class mutable, whether that field is mutated or not. It should be possible for an immutable class to have mutable fields - as long as they are not actually mutated.
  • The usual bug fixes and incremental improvements.

Hopefully I've demonstrated the main improvements over the last version, and possibly convinced you to try the tool out. As always, if there are any comments, questions or flames, feel free to post to the blog, or email me.

So that's v0.02: roll on v0.03!

No comments:

Post a Comment