R Dale Asberry
newsletters
 
• Home
• Qualifications
   - Resume
   - References
• Services
• Newsletters
   - Subscribe
   - Current
• Contact Me
• Useful Links

Aspect Oriented Programming (AOP): Using AspectJ to implement and enforce coding standards


Introduction

Aspect-oriented programming is a relatively new concept with the purpose of lowering complexity inherent to software development.  Essentially, AOP "captures" multi-class interactions scattered throughout code (a.k.a. cross-cutting concerns) and encapsulates them into a single, centralized, class-like unit called an "aspect".  For more details on AOP, see the Resources section below.

An additional "tool" to help reduce program complexity is a uniform coding standard.  A good coding standard goes a long way in encouraging coding best practices: it increases code readability by promoting consistency, and, it allows peers to focus on class design and algorithms rather than mundane details such as whitespace formatting during code reviews.

One of the downsides to coding standards and reviews is delayed feedback - sometimes non-trivial changes are needed in code that has been updated between the time the review was scheduled and performed.  Knowing that a change costs less in the long run is a strong motivator to determine changes as soon as possible.  Another downside is that developers are sometimes resistant to, as well as, forgetful of the standards - especially when a deadline is hanging over their heads.  The optimal solution is to compel developers to follow standards during compilation and unit testing.

Aspects can enforce or implement standards two ways:  detecting static program structure at compile-time and dynamically detecting program state at run-time.  Specifically, compile-time aspects can detect inappropriate program structure and then emit a compiler message.  Run-time aspects, on the other hand, can detect interactive, conditional behavior and then augment or replace the programmer-defined behavior with the standard-defined behavior.

Examples: Compile-Time Coding Standards

As objects become increasingly complex through their lifetime, changing a single member variable may require updating another variable or interacting with another class.  Coders are sometimes unaware of these obfuscated constraints due to lack of documentation and convoluted coding.  Forcing programmers to use setter methods provides a single point of entry to update state rather than having it scattered and/or (incorrectly) copied throughout the class.  The following pointcut and advice enforces setter methods:

pointcut directMemberAssignment():
	set(* *.*) && !withincode(* set*(..)) && 
	!withincode(*.new(..));
declare error: directMemberAssignment():
	"Coding standards require the use of a setter for all " + 
	"member variable assignments, even within the class itself.";

Another difficult situation occurs when client code has not been insulated through a layer of indirection from low level libraries.  Changing design decisions after construction has started creates hard-to-find bugs.  Sometimes these changes are the result of accommodating a maturing library, other times it is the result of needing to completely replace an inadequate library.  Regardless, programmers should be forced through a single point of entry for things such as persistence or security so that side-effects can be minimized.  The following code from the AspectJ Programmer's Guide demonstrates how to enforce this standard:

pointcut restrictedCall():
	call(* java.sql.*.*(..)) || call(java.sql.*.new(..));

pointcut illegalSource():
	within(com.foo..*) && !within(com.foo.sqlAccess.*);

declare error: restrictedCall() && illegalSource():
	"java.sql package can only be accessed from com.foo.sqlAccess";

Examples: Run-Time Coding Standards

One particularly nasty type of bug to track down occurs when programmers use the "on error condition, return null from method" anti-pattern.  The reason this is an anti-pattern is two-fold: all calls to the method that are not wrapped by error handling code will cause a latent NullPointerException, and, the error handling code must be found and updated manually everywhere it is used.  The preferred technique is to have the method throw an exception with as much context as possible to describe the error condition.  To avoid copying exception handling code everywhere the method is called, an aspect can be written that will consistently handle the error.  Here is a snippet which detects the anti-pattern and outputs a message to the error console:

//The first primitive pointcut matches all calls,
//The second avoids those that have a void return type.
pointcut methodsThatReturnObjects():
	call(* *.*(..)) && !call(void *.*(..));
Object around(): methodsThatReturnObjects()
{
	Object lRetVal = proceed();
	if(lRetVal == null)
	{
		System.err.println(
			"Detected null return value after calling " + 
			thisJoinPoint.getSignature().toShortString() + 
			" in file " + 
			thisJoinPoint.getSourceLocation().getFileName() +
			" at line " + 
			thisJoinPoint.getSourceLocation().getLine()
			);
	}
	return lRetVal;
}

The final example coding standard is more related to debugging convenience and auditing rather than preventing poor programming practices.  In particular, distributed applications often run as a child process under a J2EE application server.  Based on the specific situation and application server, the application may not have access to the console for message display.  Also, these applications are running on multiple machines which can make debugging very difficult.  With these considerations, using a logging package is preferred to using System.out or System.err.

This example takes the concepts presented earlier and extends them by allowing a programmer to "opt-in" to aspect logging and use the features of the java.util.logging package.  To achieve this, an application must implement the ILoggable tag interface.  The aspect then uses static introduction to add methods and member variables to the implementing class for full access to logging.  The following snippet initializes logging:

private static Logger fLogger;

//Static introduction!
private static Logger ILoggable.fLogger;

pointcut initLogger():
  execution(*.new(..)) && !within(ForceLogging) && 
  !within(lib.aspects..*) && !within(ILoggable);

Object around(): initLogger()
  {
    //If the logger is not initialized, do it here.
    if(fLogger == null)
    {
      //Factory the logger based on the FQN of the object being 
      //instantiated.
      System.out.println("Creating a default logger named \"" +
        thisJoinPointStaticPart.getSignature().getDeclaringType().getName() + 
        "\"");

      /* The next line invokes advice that sets ForceLogging.fLogger 
       * to the same value.  This approach has been taken so that 
       * the Logger is easily used by both ForceLogging for 
       * intercepting System.out and System.err and for ILoggable 
       * default behavior.
       */
      ILoggable.setLogger(
        Logger.getLogger(
          thisJoinPointStaticPart.getSignature().getDeclaringType().getName()));
      try
      {
        String lUserDir = System.getProperty("user.dir");
        String lPathSep = System.getProperty("file.separator");
        System.out.println("File logging defaulting to <user.dir>" + 
          lPathSep + "<fully-qualified-class-name>.log:\n\t" + 
          lUserDir + lPathSep +
          thisJoinPointStaticPart.getSignature().getDeclaringType().getName());
        Handler lHandler =
          new FileHandler(
          lUserDir + lPathSep +
          thisJoinPointStaticPart.getSignature().getDeclaringType().getName() +
          ".log");
        fLogger.addHandler(lHandler);
      }
      catch(Exception e)
      {
        /* This is not the preferred way to notify a user of 
         * failing to create the handler because the preferred way 
         * is to use the handler that was supposed to be created!
         * However, this is only a minor error, so swallow the 
         * exception so that the application can continue.
         */
        e.printStackTrace();
      }
    }
    Object o = proceed();

    /* The log level is defaulted to the value set in 
     * ${java.home}/lib/logging.properties.
     *
     * The ConsoleHandler is the default Handler and is automatically 
     * added to the "parent" logger of all loggers.  Remove it from the 
     * logging.properties file if you don't want this behavior.
     */

    return o;
  }

//Static Introduction! -- also conforms to the "setter" coding standard.
public static void ILoggable.initLogging(Logger pLogger, Handler pHandler)
{
	setLogger(pLogger);
}

//Pointcut that joins on the setLogger() method
//that was statically introduced in this aspect!
pointcut setILoggableLogger(Logger pLogger):
	call(void ILoggable.setLogger(Logger)) && args(pLogger);
before (Logger pLogger): setILoggableLogger(pLogger)
{
	//This sets the Logger for the ForceLogging aspect.
	setLogger(pLogger);
}

//Pointcut that joins on the initLogging() method
//that was statically introduced in this aspect!
pointcut initILoggableLogger(Logger pLogger, Handler pHandler):
	call(void ILoggable.initLogging(Logger, Handler)) && 
	args(pLogger, pHandler);
		
before(Logger pLogger, Handler pHandler): initILoggableLogger(pLogger, pHandler)
{
	setLogger(pLogger);
	fLogger.addHandler(pHandler);
}

Please see ForceLogging.java aspect to see the more detailed Javadoc for these pointcuts and their related advice.  TestCodingStandards shows how to opt-in to logging and demonstrates how the EnforceCodingStandards aspect works.

Conclusion

Coding standards are a critical tool to ensure that programmers follow coding best-practices.  They do this by guaranteeing consistency and allowing peers to focus on more pertinent issues such as class structure, implementation, and purpose.  Aspects are a very handy technology to give programmers more immediate, uniform and ego-less feedback than peer code reviews.

 
/Home | Qualifications | Services | Newsletters | Contact Me | Useful Links

Send mail to: webmaster@daleasberry.com with questions or comments about this web site.
Last modified: 01/24/11

© 2002-2011, R. Dale Asberry