Declaratively Setting Policy With PostSharp

Tags: postsharp, aop, clean-code, aspect-oriented-programming

Much of my development work surrounds the development of ASCOM drivers, which are a kind of driver plug-in for controlling telescopes and astronomical instrumentation. ASCOM drivers are analogous to printer drivers: you buy your telescope; install it’s ASCOM driver; and it works with all your astronomy programs.

If we consider the Telescope driver, certain operations don’t make sense unless the device is actually connected, so many times throughout the code there will be a check to see if the device is connected (there is a boolean Connected property). Similarly, if the device is “parked” then it must first be “unparked” before it can be used, so again there are many checks to see if the device is parked. What makes this situation worse is that many of the methods will call another method to get some work done. For example, there is one method to move the telescope and wait until the move completes (a blocking call) and there is also a non-blocking version that starts the move and returns immediately. Typically, the blocking call simply calls the non-blocking call and then sits in a loop waiting for a flag to be set. When debugging a complex driver, it quickly becomes obvious that there is a lot of duplication of effort going on, because the diagnostic trace is very polluted with all the unnecessary calls, making it harder to work out what’s really going on.

To address these problems, I set out to use PostSharp, an Aspect Oriented Programming (AOP) framework, which has a free version, to reduce the boilerplate code and to eliminate all the redundant checks that were happening. For nested calls, the checks only really need to be done once at the top level.

As an example, I have chosen one of the shortest methods in the driver: AbortSlew. The purpose of the method is to cancel any movement that might be in progress, and this task is simply handed off to a lower level component. The method really has just one line of active code, but before using PostSharp the logging and safety checks clutter up the code and obscure the method:

public void AbortSlew()
    {
    Diagnostics.Enter();
    if (!Connected)
        throw NotConnected;
    if (SystemIsParked)
        throw Parked;
    try
        {
        awr.WriteCommand(Protocol.AbortGoTo); // No reply expected.
        }
    finally
        {
        Diagnostics.Exit();
        }
    }

As you can see, there are two checks at the start of the method and, although I haven’t shown it, those checks are repeated in most of the other methods in the class. You can see that I’m generating diagnostic trace information at the start and end of the method. When you consider that the Connected and SystemIsParked properties emit their own diagnostics, and imagine that other methods might call AbortSlew and those other methods might have their own checks which emit their own diagnostics…. well it’s a big ball of fur.

I have written two custom PostSharp Aspects, called MustBeconnected and NotParked. These are applied as attributes, so the code above becomes:

 

[MustBeConnected, NotParked]
public void AbortSlew()
    {
    Diagnostics.Enter();
    try
        {
        awr.WriteCommand(Protocol.AbortGoTo); // No reply expected.
        }
    finally
        {
        Diagnostics.Exit();
        }
    }

It saves a little bit of code, it may not seem much but repeated dozens of times through the codebase, it all adds up. I also think it is more readable and the intent is clearer. AOP lets me state my intentions, not the method of achieving them. Of course somewhere the method of achieving those intentions has to be stated, and that’s the code that makes up the custom aspect. Here is the code for the MustBeConnected aspect:

/// <summary>
///     Connected aspect. Verifies that the controlled device is connected and if not,
///     throws a <see cref="NotConnectedException" />.
/// </summary>
[Serializable][ProvideAspectRole("ASCOM")]
public sealed class MustBeConnectedAttribute : OnMethodBoundaryAspect
    {
    static int nesting = 0;
    /// <summary>
    ///     Verifies that the telescope device is connected.
    ///     Throws <see cref="NotConnectedException" /> if not.
    /// </summary>
    /// <param name="args">
    ///     Event arguments specifying which method
    ///     is being executed, which are its arguments, and how should the execution continue
    ///     after the execution of
    ///     <see cref="M:PostSharp.Aspects.IOnMethodBoundaryAspect.OnEntry(PostSharp.Aspects.MethodExecutionArgs)" />.
    /// </param>
    /// <exception cref="InvalidOperationException">Aspect can only be used in a class that implements ITelescopeV3</exception>
    /// <exception cref="NotConnectedException">Thrown if the device is not connected.</exception>
    public override void OnEntry(MethodExecutionArgs args)
        {
        base.OnEntry(args);
        if (!(args.Instance is ITelescopeV3))
            throw new InvalidOperationException("Aspect can only be used in a class that implements ITelescopeV3");
        var instance = args.Instance as ITelescopeV3;
        if (nesting++ > 0) return;  // Optimization - no need to check in nested calls.
        if (!instance.Connected)
            {
            var name = args.Method.Name;
            var message = string.Format("{0} requires that Connected is true but it was false", name);
            throw new NotConnectedException(message);
            }
        }

    /// <summary>
    /// Method executed <b>after</b> the body of methods to which this aspect is applied,
    /// even when the method exists with an exception (this method is invoked from
    /// the <c>finally</c> block).
    /// </summary>
    /// <param name="args">Event arguments specifying which method
    /// is being executed and which are its arguments.</param>
    public override void OnExit(MethodExecutionArgs args)
        {
        base.OnExit(args);
        nesting--;
        }
    }

 

This is a Method Boundary Aspect, which has OnEntry and OnExit methods that magically get called before and after each method that the aspect applies to. The PostSharp weaver inserts these calls during the build process. In the OnEntry part, the aspect first checks that it is applied to the right type of object; in this case it must be something the implements ITelescopeV3 (I’ve since learned that this can be more efficiently done at compile time rather than at runtime). Next, the static nesting field is incremented, and if it’s greater than 1, then the aspect returns without performing any checks, allowing the rest of the method to execute. This ensures that connectivity (etc.) checks are only performed on the top level call in a hierarchy, avoiding redundant checks and cleaning up the diagnostic trace considerably. Finally, the aspect determines the instance of the class it is being invoked on and checks the instance.Connected property. If the property is false, then it throws an exception, with the name of the currently executing method in the exception message. 

The OnExit part of the aspect, which is guaranteed to run no matter how the method exits, simply decrements the nesting level. The method is guaranteed to run, so the nesting level is correctly updated if if the method throws exceptions.

The implementation of the NotParked aspect is very similar, so not reproduced here.

In fact we have also produced another aspect called NLogTraceWithArguments, which traces the entry and exit from each method, complete with argument values and any return value (see my previous blog post about Simplifying Logging with PostSharp for more on that). This apsect emits detailed diagnostic trace information using NLog and is applied to the entire assembly in the AssemblyInfo.cs file:

[assembly: NLogTraceWithArguments(AttributePriority = 999)] // Apply tracing to all methods with low priority

Having done that, we were also able to remove the explicit logging calls within our methods, so the original code is further reduced to:

[MustBeConnected, NotParked]
public void AbortSlew()
    {
    awr.AbortGotoSlew();
    }

With all the boilerplate removed and the policies now set declaratively as attributes, the true simplicity of the method is finally revealed. PostSharp makes your code more compact, cleaner and easier to read!

No Comments

Add a Comment

Let Us Help You

Find us on Facebook