Tuesday, February 02, 2010

Java Annotations have Become Pixie Dust

I was giving a talk about RESTful services using JAX-RS and Jersey recently and was asked why I had used Mark Volkmann's WAX for generating HTML and XML. The person asking the question pointed out that Jersey has integration with JAXB.

There were two answers to that question. One answer is that I am leery of anything which automatically converts my Java objects into a serialized format (bitten by Java's object serialization in the past). Incompatible object changes can be difficult or impossible to reconcile in a backward-compatible manner.

But the main answer I gave got some chuckles and further questions. I explained I was trying to avoid too much "pixie dust". In the example code, I was already using the Java Persistence API (JPA) and JAX-RS and their associated annotations. If I had not been careful, there would have been annotations for Spring and JAXB as well. All of these annotations are small in the code but have very large effects. Those innocent looking annotations subject my poor unsuspecting code to some very big (and some would argue bad) frameworks. Understanding how these frameworks interact is not only hard, but those interactions change as the frameworks change (possibly resulting in the system breaking with no code changes).

I have real misgivings about the number of annotation-based technologies that should be applied to any one project. Each annotation you use represents some amount of code you don't have to write. And that is, of course, a good thing from a development perspective. But every annotation you use represents 'pixie dust', behavior which is hidden from you and generally performed at runtime. That means that when things go wrong, there isn't any code to look at. It also means that small differences between configurations can produce large changes in behavior. That's a very bad thing for testing, production deployment, production maintenance, etc.

I've been thinking about this issue for some time*, so I was pleasantly surprised to find Stephen Schmidt's post admonishing us to Be careful with magical code. His post is not specific to annotations (he calls out aspect-oriented programming, for example - I agree that AOP is another kind of pixie dust). And he points out some examples of the "pixie dust" phenomenon. While I don't agree with his 'magic containment' example, it's a good post. You should read it.

As a rule of thumb, I think two kinds of pixie dust is the maximum to sprinkle on a project. So think hard and choose wisely when picking which ones to use: the more kinds of pixie dust you sprinkle, the harder it will be for you and others to understand and troubleshoot things, now and especially in the future.

*Thanks to Mike Easter for planting the idea of talking about the state of annotations in Java


David Jacobs said...

As a long-time user of "magical" frameworks, employing progressively more "magic" as "sufficiently advanced technologies" evolve, I've been bitten more times than I can count. Thus, I can sympathize with the sentiment, but still firmly believe that a toxic cure is often preferable to the disease. A slightly leaky abstraction that is otherwise well-designed, well-tested, well-documented, and well understood by tens of thousands of developers, is more likely to be maintainable, performant, and reliable than a home-grown solution. (Caveat: If, and only if, the underlying complexity of the problem exceeds the complexity of the "magic". That is, don't introduce a magical hammer as tooling for a single nail!)

One could argue that the automobile is much less reliable than foot or bicycle transportation, and when the "magic" fails and the user is left stranded, it requires esoteric knowledge and equipment to successfully perform the ritual to regain the favor of the transportation deity. Why would one willingly be put at the mercy of such fragile magic? Because in some contexts, the trade-off is worthwhile.

There's definitely wisdom in limiting the number of interacting (voodoo) frameworks, as fragility (and thus total cost) tends to be multiplied by every additional type of magic employed, due to the exponential increase in runtime interactions and disparities in lifecycle models. But again, the cost must be compared to the benefits. "I'll accept the complexity of the automobile, and the similar mechanical magic of aircraft, but the dissimilar magic of computers with another level of abstraction? No way! And anyone who wants to stack this complexity on the already complex mechanical systems of automobiles and aircraft should be burned at the stake!" Obviously, this way of thinking can be limiting.

That said, annotations are horrible micro-language DSLs that poorly implement declarative meta-programming extensions. Co-location with their interaction points (e.g., annotations over a method) can enhance readability and maintainability, reducing the "pixie dust" factor, but can also introduce inflexible coupling. Consider an ORM implemented with JPA annotations, where classes are tightly coupled to the database schema. This can be inappropriate if alternate mappings are needed for multiple similar schemas, or if there's need to re-use a POJO independently of the persistence context. More generally, a mapping is an adapter, and an adapter should not be interwoven with or specified by the adaptee!

[continued below]

David Jacobs said...

I think this problem is a relic of our continued preference for viewing/editing raw files to specify programs. This is too limiting when metadata and meta-behavior need to be co-comprehended, but not co-located and locally bound. The file-based model puts these goals in conflict. To fully support declarative techniques with simplified comprehension and sensible refactoring support for the scope and constraints of their bindings, an integrated view of a behavioral unit is needed, that expresses systemic context by modeling runtime behavior. The flat and intrinsically non-relational nature of files (as we currently use them for code) creates a cognitive mismatch between "design-time" and "runtime" behavior, because it doesn't model interaction semantics, placing the burden entirely upon our cognition about the file, with each developer holding a distinct mental model that is incomplete and error-laden. This is the root cause of our inability to reason about complex system interactions.

We need a radical change in our fundamental building blocks that enables fully decomposed and de-coupled atomic units (unfit for raw consumption by humans), and more pixie dust in our development tools to provide integrated, contextual, interaction-aware conceptual views of those blocks that can be navigated and modified using models more naturally aligned to cognition by being oriented around discrete goals (captured in the model through linking outcome expectations, ala integration/functional tests, but oriented around goal semantics), transparently supported by automated dependency/regression impact analysis through simulations of the runtime impact upon other goals. This could also eliminate the conceptual gap in functional programming, by providing contextually appropriate high-level usage views that need not be isomorphic to a single underlying organizational system...meaning, for example, an object-oriented, goal-directed conceptual view, transparently composed of functional building blocks, where "co-location" is a conceptual convenience, but not inherent in the physical representation. Thus, maximum decoupling, reusability and composability can be achieved without sacrificing cognitive simplicity or increasing fragility.

But that's going to take a lot of pixie dust.

phil said...

The problem is you have to play by the rules of whatever pixie-dust you choose to use. If you don't have complete knowlege of the rules, then you get into trouble.

Take SEAM for example, if you don't know what a @In injection of a conversational contextual component is then you might be having problems.

Brian Gilstrap said...


You are right that you have to play by the rules of the frameworks you use. The trouble is that many of the frameworks have poorly specified rules (with phrases like "Use the source, Luke!" as excuses). That makes the pixie dust density of those frameworks higher.