Go at it top-down and then bottom-up. Top-down, draft significant aspects by first writing the comments to specify responsibilities. Advice responsibility usually takes the form, "When X, do Y." Pointcut responsibility for "When X" often takes the form, "When [join points] [in locations] [are ...]". These []'s often translate to named pointcuts (like `libraryCalls() && within(Client) && args(Context)`) which form a semantic bridge to the plain-text meaning in a comment (e.g., `// when the client passes only context into the library`). This gets you to a point where you can debug the parts of the pointcut independently.
Bottom up (to build each part), consider each primitive pointcut designator (PCD), then the composition, and then any implicit constraints:
What kinds of join points should it match? (constructor-call? field-get?)? This translates to using the kinded pointcuts (`call(..)`, `get(..)`, etc.).
Are these restricted to being lexically within something? This translates to using `within{code}(..)`. If this is true, it should always be used, to speed up weaving.
What runtime constraints and context should be true and available at each join point? This translates to `this()`, `target()`, `args()`, `cflow{below}()` and `if(..)`.
Are there any advice or implementation limitations at issue? This involves knowing the few constraints on AspectJ imposed by Java bytecode as listed in the AspectJ Programming Guide section on Implementation Notes.
It's much faster to iterate a pointcut at compile-time using declare warning (even better, some errors are identified at parse-time in the latest versions of AJDT). Start with the parts of the pointcut that are staticly-determinable (i.e., they do not involve the runtime PCD's listed above). If compiles themselves take too long because of all the AspectJ weaving, then try to only include the debugging aspect with the prototype pointcut, and limit the scope using within(..).
Some mistakes in primitive pointcuts:
`this(Foo) && execution(static * *(..))`: There is no `this` in a static context, so `this()` or `target()` should not be used in a static context or when targetting a static context (respectively). This happens most often when you want to say things like "all calls to Foo from Bar" and you only pick out calls to instance methods of Foo or you try to pick out calls from static methods of Bar.
`target(Foo) && call(new(..)`: This will never match. In constructor-call join points, there is no target because the object has not been created yet.
`call(* Foo.*(..))`: `Foo` refers to the compile-time type of the invoking reference, not the implementing class. In Java before 1.4, the compile-time type was rendered as the defining type, not the reference type; this was corrected in 1.4 (as shown when using ajc with the -1.4 flag) Most people should use `target(Foo) && call(...)`.
`execution(* Foo.bar(..))`: An execution join point for Foo is always within Foo, so this won't pick out any overrides of bar(..). Use `target(Foo) && execution(* bar(..))` for instance methods.
`within(Foo)`: anonymous types are not known at weave-time to be within the lexically-enclosing type (a limitation of Java bytecode).
Some mistakes in composition:
`call(* foo(Bar, Foo)) && args(Foo)`: This will never match. The parameters in `args(..)` are position-dependent, so `args(Foo)` only picks out join points where there is only one argument possible, of type Foo. Use the indeterminate-arguments operator '..' as needed, e.g., `args(Foo, ..)`.
`call(* foo()) && execution(* foo())`: This will never match. Each pointcut must be true at each join point matched. For a union of different kinds of join points (here, call or execution), use '||'. E.g., to match both method-call and field-get join points, use `call(* ...) || get(...)`.
Some mistakes in implicit advice constraints:
Some mistakes in implementation requirements:
`ajc` has to control the code for a join point in order to implement the join point. This translates to an implicit `within({code under the control of the compiler})` for all join points, with additional caveat for some join points. Take exception handlers, for example: there is no way to be sure from the bytecode where the original handler ends, so `ajc` can't implement after advice on handler join points. (Since these are on a per-join-point basis, they should be considered for each corresponding primitive pointcut designator.) Unlike the mistakes with the primitive PCDs above, the compiler will emit an error for these caveats.
`call(@SuperAnnotation Subclass.meth()`: Annotations are not inherited by default, so e.g., if the pointcut specifies an annotation, then subclass implementations of that method will not be matched.