« Good things happening to AJDT.... | Main | Demos, presentations, testimonials,... »

August 23, 2004

I don't want to know that... (writing robust pointcut expressions)

The topic of this post is writing robust pointcut expressions, by which I mean pointcuts that stand the maximum chance of continuing to match the intended join points, and only the intended join points, as a program evolves.

The first rule of thumb, is simply "don't tell me anything I don't need to know." For example, in a simple banking application we could write:

pointcut accountOperation() : execution(public * *(..)) && this(Account);

or we could write:

pointcut accountOperation() : execution(public Money Account.withdraw(Money)) || execution(public Money Account.getBalance()) || execution(public void Account.deposit(Money)) || execution(public Money SavingsAccount.getInterest()) || ... ;

Assuming we intend to match the execution of any public method on an Account object, I hope it's obvious why the first pointcut is much better than the second - even though they might match exactly the same set of join points when first written. The second pointcut is much more tightly coupled to implementation details of the Account hierarchy that may change. The issue crops up more subtley though in many more cases. For example, suppose you're only interested in the execution of the deposit and withdraw methods. Is it reasonable to write:

poincut accountTransaction() : (execution(public Money withdraw(Money)) || execution(public void deposit(Money)) ) && this(Account);
?

On first glance this looks ok, but let's push a little....

  • If the withdraw method was changed to take a second parameter, would we still want the pointcut to match?
  • If deposit was to return a value, would we still want the pointcut to match?
  • If either method had its visibility reduced, should the pointcut match?

Suppose we answer yes, yes and no. A better pointcut specification would therefore be:

pointcut accountTransaction() : ((execution(public * withdraw(..)) || execution(public * deposit(..)) ) && this(Account);

This pointcut is less tightly coupled to the details of the Account class, and will continue to match even if the arguments or return types are changed for the two methods.

For each piece of information you provide in a pointcut expression, ask yourself whether you would still want the pointcut to match at a join point where the value was slightly different. If you do, loosen (or remove) the condition. By doing this, you reduce coupling between the aspect and the rest of the application, and you have a more robust pointcut specification that will continue to match as you intended as the program evolves.

What about the possibility of other methods being introduced into the Account hierarchy that support other types of deposit and withdrawals - for example, depositWithReceipt(....) or withdrawUpTo(....). Should these hypothetical methods be matched too? I don't know - you decide. But if you decide they should match, how should you change the pointcut expression?

So if the first rule is "don't tell me anything I don't need to know," the second rule is "tell me everything that matters to you."

The goal here is to avoid unintended join point matches as the program evolves. Here's a classic example:

after() throwing(Exception ex): execution(public * *(..)) { // log exception... ... // and re-throw throw (ex); }

Do you really mean this advice to be in effect for the throwing of exceptions after the execution of any public method *anywhere*? Even if this is correct your application today, what if this aspect is included in a build with a wider set of types exposed to the weaver - do you want to match all of those too? Are you sure? Typically you might want to use a scoping pointcut here. I like to define a class SystemArchitecture in my applications that contains handy pointcut definitions for the application, the components in the application, and so on. Using something like that, a better advice specification might be:

after() throwing(Exception ex): execution(public * *(..)) && SystemArchitecture.inMyApplication() { // log exception... ... // and re-throw throw (ex); }

(or for extra points, do away with the anonymous pointcut expression altogether and give it a name).

Here's a more subtle example, from an application tracking the 'dirty' state of objects:

pointcut dirtyingAction(DAO dao) : set(* *) && target(DAO);

On first glance this looks pretty good, we haven't tied to any details about the names and types of fields - but there's one piece of information we do care about that we haven't specified, and that will result in possible unwanted join point matches in the future. Can you spot what it is?

Here's an improved version:

pointcut dirtyingAction(DAO dao) : set(!transient * *) && target(DAO);

presumably we don't want to treat a persistent object as dirty if a transient field is updated...

To complete a robust pointcut definition, you must ask yourself "are there any join points that I can envisage now or in the future, that would be matched by this pointcut expression, and that I don't want to match?" If the answer is yes, you need to narrow the pointcut expression to be more precise so as to avoid unwanted join point matches during program evolution.

That's really all there is to it. To write a robust pointcut expression make sure that you :

  • Don't tell me anything I don't need to know.
  • Tell me everything that matters to you.

Posted by adrian at August 23, 2004 05:48 PM [permalink]

Comments

I have a design question: What do you think about putting the accountTransaction pointcut in the Account class itself, which it exports publicly to any advice that might want to use it?

Posted by: Macneil Shonle at August 24, 2004 05:03 PM

I actually quite like that style (letting a class define a pointcut as part of its public interface). It has the advantage that maintenance of the pointcut can go hand in hand with changes to the class. The caveat is, if you do this, the pointcut should be genuinely part of the class' interface, and not particular to some other concern implemented in an aspect. In this latter case, I'd always put the pointcut definition in the aspect along with the rest of the concern implementation.

Posted by: Adrian at August 25, 2004 09:10 PM

How about using the annotation of JDK 5.0 for pointcuts. Here is an interessting article about it: http://www.onjava.com/pub/a/onjava/2004/08/25/aoa.html

Posted by: Dieter Bogdoll at August 26, 2004 09:59 AM

Annotations add metadata at join points which it is definitely interesting to be able to match on in pointcut expressions. AspectJ will support this feature, and also "declare annotation" for when you want to implement the modular addition of annotations to elements. See the brain dump at https://bugs.eclipse.org/bugs/show_bug.cgi?id=72766 for my current thoughts on what AspectJ should do in this space.

Posted by: Adrian at August 27, 2004 10:31 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?