« Person owns Dog... | Main | AspectJ Hacks - #1 From System.out.println to switchable debug output »

May 31, 2004

The Ted Neward Challenge (AOP without the buzzwords)

After the AOP panel at the TSS Java Symposium had finished, Ted Neward threw out a challenge to some of the participants to come up with "an explanation of AOP without resorting to buzzwords." I've been mulling that around in my head for a few days now... What follows should be considered an early version of an attempt to explain what's at the heart of AOP (from my perspective), without resorting to any buzzwords. Following the conclusion of this paragraph, the following words and phrases are hereby banned for the rest of today's entry: scattering, tangling, crosscutting, modularity, encapsulation, abstraction, dominant decomposition, concern.


When you are writing a piece of software it's generally a good idea if you can design the program in such a way that each unique idea, concept, requirement, etc. addressed by the program has a direct and clear representation in the source code. For example, if I'm writing a banking application, the concept of a bank account probably has a direct representation in the program as a BankAccount class. If I'm writing a health service application, the concept of a patient probably has a direct representation in the program as a Patient class. One design concept maps onto one implementation construct.

Now think of a simple model-view-controller application. There's a simple, single design-level requirement that following any change in the state of a model class, any registered views are notified - but it's much harder to map this single design requirement into a single implementation construct. In a typical implementation, you'll find calls to notifyObservers() spread throughout the state-changing methods of the model classes. This implementation has deviated from the goal of 1-to-1 mapping, and instead has a 1-to-n concept:implementation ratio.

The concepts and requirements that a design captures are also the most likely units of change as the software evolves. Design elements that have a 1-to-1 mapping to an implementation are easy to add, remove, and maintain. Elements that have a 1-to-n concept:implementation ratio are much harder to add, remove and maintain since there are multiple places in the implementation to be updated, and it is important to update all of them, and to update them consistently.

The most extreme concept:implementation ratio that you're ever likely to encounter in real software systems is that old chestnut, tracing. Take the simple, single design statement "when tracing is enabled, the entry and exit of every method should be recorded in a trace buffer." In one system I've studied, the concept:implementation ratio for that single requirement is well in excess of 1:100,000. Just think of all the implementation effort that has been expended creating and maintaining all those source code statements. When faced with such a situation, many programmers make a value judgement that the effort does not justify the return and instead opt for a different solution with a concept:implementation ratio of 1:0 (ie. they don't bother to add tracing at all).

A related example in that same system concerned a requirement to log failures - this had a concept:implementation ratio in excess of 1:8,000. But these are extreme examples, and you won't find too many situations where the ratios are this great. Much more common are situations with concept:implementation ratios in the order of 1:20 or 1:30. The model-view-controller requirement described previously could easily fall in this range. If you want an open-source example you can study for yourself, go and look at how the Eclipse JDT compiler handles progress notification (a single concept) during compilation.

Down at the other end of the spectrum, you'll find more subtle issues such as the requirement that a class implement a certain interface. We still want to retain a clear 1-to-1 mapping between the requirement and the implementation, but in many cases it is not explicit exactly which subset of the state and methods in a class are there in order to support the interface implementation. The requirement is addressed in one place, but the boundaries of the implementation are sort of fuzzy. A lot of programmers I know address this by simply grouping together in the source file the set of methods concerned with the interface implementation, and in very many situations that's a perfectly OK thing to do. All I'm attempting to do here is illustrate the diverse range of situations you can encounter when you start focusing on getting a clear 1-to-1 ratio between concept and implementation.

Now let's look at the flip side of the same coin. We want each design concept to map clearly to one implementation construct. We also want each implementation construct to map clearly to one and only one design concept. But we've already seen that some design concepts end up getting spread out through the implementation, and that means inevitably that there are some parts of the implementation which are dealing with multiple design concepts. This is the n-to-1 concept:implementation problem. Just think about the model-view-controller example one more time - the model classes are dealing with at least two design concepts: the notion of whatever it is they are modelling, and the requirement to notify any registered views whenever their state changes.

The n-to-1 problem presents just as many difficulties as the 1-to-n problem. It means for example that when a programmer is maintaining a class, he or she has to be aware of and correctly balance all of the design requirements that the class is trying to address. In some application domains this proved so painful that special frameworks were developed to handle commonly occuring cases of n-to-1 mappings. An example is enterprise application development, where it proved to be too much to ask programmers with business domain expertise to also manage and balance transaction, persistence, and security requirements within business domain classes. Classes that have logic and dependencies relating to multiple concepts in the design are harder to reuse, and harder to test (you want the model classes for example, but you've got to wire them into a different notification scheme).

So what we've really got in any non-trivial software application is not the ideal 1-to-1 mapping between concept and implementation, but an n:m mapping. No wonder software gets so hard to maintain, and so hard to understand, and so complex. And it's not your fault. The tools that object-oriented languages give us don't enable the clean mapping of every design concept into a single implementation construct (and we've seen several examples of that in this short article), and consequently neither do they allow each implementation construct to map cleanly onto a single design concept. This is the problem that aspect-oriented programming attempts to help us solve. It's about getting as close to a 1-to-1 mapping as we can. AOP addresses the problem by introducing a new construct known as an aspect that is able to capture in one place the implementation of design requirements, such as the view-notification requirement in MVC, which OOP cannot.

When you start to think of AOP in this way, I hope that you can see it as less of a bolt-on adjunct to existing software, and more as an integral part of the design and implementation of a software program. This is why AspectJ integrates the concepts of AOP directly in a programming language. Remember, we're after a 1-to-1 mapping between design concept and implementation, and whenever we deviate from that ratio in either direction we're in for trouble. AOP is about getting as close to 1-to-1 programming as we can.

Posted by adrian at May 31, 2004 09:06 PM [permalink]

Comments

nice try :)

Posted by: Anonymous at June 1, 2004 10:17 AM

Well done...My only familiarity with AOP is from articles I've read, so I'm far from an authority. However, this seems to elegantly capture what I understand as the thrust of AOP with intuitive examples.

Posted by: Mike Dunbar at June 1, 2004 11:52 AM

Well done - as a certified AOP newbie, I can say this is the best introduction to aspects I've ever read.

I didn't quite get the paragraph about the "which of these methods are implementations of interface X" problem. I can't see where the problem is. For me, if I need to check whether a method is there to support an interface, that's a design smell (maybe I should split that interface?). For me, this paragraph broke the flow of the excellent explaination of mapping the problem to the solution.

Posted by: Nusco at June 2, 2004 07:25 AM

Declarative Observers and attached objects

I have been thinking about this some time, many things can be done keeping
the OO concepts to exploit new ideas mainly on how frameworks work.


For example,

1) declarative Observers decrease decouple and define dynamic interaction between domain classes

2) attached objects is a clearly example of how we must relate framework's objects
to domain clases, in fact, the framework itseft is another domain. Some ideas
from IoC containers are interesting and address some of this issue. This some
idea that I have in main about DomainClasses-FrameworkObject mix, avoiding to
code a new class keep them in their respective domains, just make them interact.


class BankAccount {
String number;
BankAccount (String number) {
....
}
}

class Log {
public void addEntry(Object o, java.lang.reflect.Method method, Object [] parms) {
System.out.println("Method " + method + " executed from " + o + " with " + parms);
}
}

BankAccount-attach.xml
<object id="logger" class="Log">
<!-- constructor or property based instantiation -->
</object>

BankAccount-observers.xml
<observer object="logger">
<notify member="BankAccount" invocate="addEntry">
<parameter type="object" />
<parameter type="method" />
<parameter type="paramlist" />
</notify>
</observer>

The notify can be improved adding a new parameter type "exception" (thrown), conditional
notification, etc. The attached object can be a different levels class, domain
or may be (why not) a component.

Posted by: Lester at June 2, 2004 02:18 PM

I don't see how AOP is a general-purpose solution for model-view logic in typical GUI code. (despite the fact that every AOP article inevitably points out how AOP can work with MVC). I looked over several recent GUI data models I've written, and the event notification is rarely anything simple enough to automate, nor do I find myself duplicating code. Take a custom table model, for instance. You don't just fire events when the model changes. You fire *very* fine-grained events including correct column and row numbers, event types (such as ADD/REMOVE/CHANGE), and other flags such as "value is adjusting". In these complex model-view scenarios, I think plain-old Java code is easier to understand because it's all right there in the source code. Each type of change to the model typically fires a different type of event, so each method requires different logic in order to send out the correct event.

I get your point about the "simple model-view-controller application", but I'm not sure most model-view events are that simple once you move beyond the trivial examples.

Posted by: Eric Burke at June 5, 2004 02:18 PM

Good view. I've made a Chinese translation on my blog :)

Posted by: Neo at June 6, 2004 11:54 AM

Post a comment

Thanks for signing in, . Now you can comment. (sign out)

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)


Remember me?