23

I just watched this talk by Greg Young warning people to KISS: Keep It Simple Stupid.

One of the things he suggested is that to do aspect-oriented programming, one does not need a framework.

He starts by making a strong constraint: that all methods take one, and only one, parameter (though he relaxes this a little later by using partial application).

The example he gives is to define an interface:

public interface IConsumes<T>
{
    void Consume(T message);
}

If we want to issue a command:

public class Command
{
    public string SomeInformation;
    public int ID;

    public override string ToString()
    {
       return ID + " : " + SomeInformation + Environment.NewLine;
    }
}

The command is implemented as:

public class CommandService : IConsumes<Command>
{
    private IConsumes<Command> _next;

    public CommandService(IConsumes<Command> cmd = null)
    {
        _next = cmd;
    }
    public void Consume(Command message)
    {
       Console.WriteLine("Command complete!");
        if (_next != null)
            _next.Consume(message);
    }
}

To do logging to console, one then just implements:

public class Logger<T> : IConsumes<T>
{
    private readonly IConsumes<T> _next;

    public Logger(IConsumes<T> next)
    {
        _next = next;
    }
    public void Consume(T message)
    {
        Log(message);
        if (_next != null)
            _next.Consume(message);
    }

    private void Log(T message)
    {
        Console.WriteLine(message);
    }
}

Then, the pre-command logging, command service, and post-command logging are then just:

var log1 = new Logger<Command>(null);
var svr  = new CommandService(log);
var startOfChain = new Logger<Command>(svr);

and the command is executed by:

var cmd = new Command();
startOfChain.Consume(cmd);

To do this in, for example, PostSharp, one would annotate the CommandService this way:

public class CommandService : IConsumes<Command>
{
    [Trace]
    public void Consume(Command message)
    {
       Console.WriteLine("Command complete!");
    }
}

And then have to implement the logging in an attribute class something like:

[Serializable]
public class TraceAttribute : OnMethodBoundaryAspect
{
    public override void OnEntry( MethodExecutionArgs args )
    {
        Console.WriteLine(args.Method.Name + " : Entered!" );   
    }

    public override void OnSuccess( MethodExecutionArgs args )
    {
        Console.WriteLine(args.Method.Name + " : Exited!" );
    }

    public override void OnException( MethodExecutionArgs args )
    {
        Console.WriteLine(args.Method.Name + " : EX : " + args.Exception.Message );
    }
}

The argument Greg uses is that the connection from the attribute to the implementation of the attribute is "too much magic" to be able to explain what's happening to a junior developer. The initial example is all "just code" and easily explained.

So, after that rather longwinded build-up, the question is: when do you make the switch from Greg's non-framework approach to using something like PostSharp for AOP?

13
  • 3
    +1: Definitely a good question. One might simply say "... when you already understand the solution without it." Jun 17, 2011 at 1:11
  • 1
    Maybe I'm just not used to the style, but the idea of writing an entire application like this strikes me as utterly insane. I'd rather use a method interceptor.
    – Aaronaught
    Jun 17, 2011 at 3:21
  • @Aaronaught: Yes, that's part of why I wanted to post here. Greg's explanation is that the system configuration is then just connecting up IN NORMAL CODE all the different IConsumes pieces. Rather than having to use external XML or some Fluent interface --- yet another thing to learn. One could argue that this methodology is "another thing to learn" also.
    – Peter K.
    Jun 17, 2011 at 12:32
  • I'm still not sure I understand the motivation; the very essence of concepts such as AOP is to be able to express concerns declaratively, i.e. through configuration. To me this is just reinventing the square wheel. Not a criticism of you or your question, but I think the only sensible answer is "I would never ever use Greg's approach unless every other option failed."
    – Aaronaught
    Jun 17, 2011 at 15:05
  • Not that it bothers me at all, but wouldn't this be slightly more of a Stack Overflow question? Jun 17, 2011 at 21:07

6 Answers 6

18

Is he trying to write a "straight to TDWTF" AOP framework? I seriously still haven't got a clue what his point was. As soon as you say "All methods must take exactly one parameter" then you've failed haven't you? At that stage you say, OK this imposes some seriously artificial constraints on my ability to write software, let's drop this now before, three months down the line we have a complete nightmare codebase to work with.

And you know what? You can write a simple attribute driven IL based logging framework quite easily with Mono.Cecil. (testing it is slightly more complicated, but...)

Oh and IMO, if you aren't using attributes, it isn't AOP. The whole point of doing the method entry/exit logging code at the post processor stage is so that it doesn't mess with your code files ans so you don't need to think about it as you refactor your code; that is its power.

All Greg has demonstrated there is the keep it stupid stupid paradigm.

7
  • 6
    +1 for keep it stupid stupid. Reminds me of Einstein's famous quote: "make everything as simple as possible, but not simpler." Jun 18, 2011 at 20:29
  • FWIW, F# has the same restriction, each method takes at most one argument.
    – Roman
    Jul 1, 2011 at 14:15
  • 1
    let concat (x : string) y = x + y;; concat "Hello, " "World!";; looks like it takes two arguments, what am I missing?
    – user23157
    Jul 1, 2011 at 15:17
  • 2
    @The Mouth -- what's actually happening is that with concat "Hello, " you're actually creating a function which takes just y, and has x pre-defined as a local binding to be "Hello, ". If this intermediate function could be seen, it'd look something like let concat_x y = "Hello, " + y. And then following that, you're calling concat_x "World!". The syntax makes it less obvious, but this lets you "bake" new functions -- for instance, let printstrln = print "%s\n" ;; printstrln "woof". Also, even if you do something like let f(x,y) = x + y, that's actually just one tuple argument. Jul 3, 2011 at 11:09
  • 1
    Lst time I did any functional programming was in Miranda back at university, I'll have to take a look at F#, it sounds interesting.
    – user23157
    Jul 4, 2011 at 8:39
8

My god, that guy is intolerably abrasive. I wish I'd just read the code in your question instead of watched that talk.

I don't think I'd ever use this approach if it's only for the sake of using AOP. Greg says it's good for simple situations. Here's what I'd do in a simple situation:

public void DeactivateInventoryItem(CommandServices cs, Guid item, string reason)
{
    cs.Log.Write("Deactivated: {0} ({1})", item, reason);
    repo.Deactivate(item, reason);
}

Yeah, I did it, I got rid of AOP entirely! Why? Because you don't need AOP in simple situations.

From a functional programming standpoint, allowing only one parameter per function doesn't really scare me. Nonetheless, this really isn't a design that works well with C# -- and going against the grains of your language doesn't KISS anything.

I'd only use this approach if it were necessary to make a command model to begin with, for instance if I needed an undo stack or if I were working with WPF Commands.

Otherwise, I would just use a framework or some reflection. PostSharp even works in Silverlight and Compact Framework -- so what he calls "magic" really isn't magical at all.

I also don't agree with avoiding frameworks for the sake of being able to explain things to juniors. It's not doing them any good. If Greg treats his juniors the way he suggests they be treated, like thick-skulled idiots, then I suspect that his senior developers aren't very great either, as they probably haven't been given much of an opportunity to learn anything during their junior years.

1
  • 1
    His talk is still available, so I decided to listen. I lasted about sixty seconds before he said "apparently [Java developers think] XML is a Turing-Complete language," and I turned it off. May 12, 2020 at 16:44
5

I did an independent study in college on AOP. I actually wrote a paper on an approach to model AOP with an Eclipse plug-in. That's actually somewhat irrelevant I suppose. The key points are 1) I was young and inexperienced and 2) I was working with AspectJ. I can tell you that the "magic" of most AOP frameworks is not that complicated. I actually worked on a project around the same time that was trying to do the single parameter approach using a hashtable. IMO, the single parameter approach really is a framework and it is invasive. Even on this post, I spent more time trying to understand the single parameter approach than I did reviewing the declarative approach. I will add a caveat that I have not watched the movie, so the "magic" of this approach may be in the use of partial applications.

I think Greg answered your question. You should switch to this approach when you think you are in a situation where you spend an excessive amount of time explaining AOP frameworks to your junior developers. IMO, if you are in this boat, you are probably hiring the wrong junior developers. I don't believe AOP requires a declarative approach, but for me, it's just much more clear and non-invasive from a design perspective.

1
  • +1 for "I spent more time trying to understand the single parameter approach than I did reviewing the declarative approach." I found the IConsume<T> example overly complicated for what's being accomplished. Jun 18, 2011 at 11:27
4

Unless I'm missing something the code you've shown is the 'chain of responsibility' design pattern which is great if you need to wire up a series of actions on an object (such as commands going through a series of command handlers) at runtime.

AOP using PostSharp is good if you know at compile time what behaviour you want to add will be. PostSharp's code weaving pretty much means there's zero run-time overhead and keeps the code very clean indeed (especially when you start using things like multicast aspects). I don't think basic usage of PostSharp is particularly complex to explain. The downside of PostSharp is that it does increase compile times signficantly.

I use both techniques in production code and although there's some overlap in where they can be applied I think for the most part they really aimed at different scenarios.

4

Regarding his alternative - been there, done that. Nothing compares to the readability of a one line attribute.

Give a short lecture to new guys explaining them how things work in AOP.

4

What Greg describes is absolutely reasonable. And there is beauty in it as well. The concept is applicable in a different paradigm than pure object orientation. It's more a procedural approach or a flow oriented design approach. So if you are working with legacy code it will be quite difficult to apply this concept because a lot of refactoring might be needed.

I will try to give another example. Maybe not perfect but I hope it makes the point more clear.

So we have a product service which uses a repository (in this case we will use a stub). The service will get a list of products.

public class Product
{
    public string Name { get; set; }
    public decimal Price { get; set; }

    public override string ToString() { return String.Format("{0}, {1}", Name, Price); }
}

public static class ProductService
{
    public static IEnumerable<Product> GetAllProducts(ProductRepositoryStub repository)
    {
        return repository.GetAll();
    }
}

public class ProductRepositoryStub
{
    public ProductRepositoryStub(string connStr) {}

    public IEnumerable<Product> GetAll()
    {
        return new List<Product>
        {
            new Product {Name = "Cd Player", Price = 49.99m},
            new Product {Name = "Yacht", Price = 2999999m }
        };
    }
}

Off course you could also pass an interface to the service.

Next we want to show a list of products in a view. Therefore we need an interface

public interface Handles<T>
{
    void Handle(T message);
}

and a command which holds the list of products

public class ShowProductsCommand
{
    public IEnumerable<Product> Products { get; set; }
}

and the view

public class View : Handles<ShowProductsCommand>
{
    public void Handle(ShowProductsCommand cmd)
    {
        cmd.Products.ToList().ForEach(x => Console.WriteLine(x.ToString()));
    }
}

Now we need some code that executes all this. This we will do in a class called Application. The Run() method is the integrating method that contains no or at least very little business logic. The dependencies are injected into the constructor as methods.

public class Application
{
    private readonly Func<IEnumerable<Product>> _getAllProducts;
    private readonly Action<ShowProductsCommand> _showProducts;

    public Application(Func<IEnumerable<Product>> getAllProducts, Action<ShowProductsCommand> showProducts)
    {
        _getAllProducts = getAllProducts;
        _showProducts = showProducts;
    }

    public void Run()
    {
        var products = _getAllProducts();
        var cmd = new ShowProductsCommand { Products = products };
        _showProducts(cmd);
    }
}

Finally we compose the application in the main method.

static void Main(string[] args)
{
    // composition
    Func<IEnumerable<Product>> getAllProducts = () => ProductService.GetAllProducts(new ProductRepositoryStub(""));
    Action<ShowProductsCommand> showProducts = (x) => new View().Handle(x);
    var app = new Application(getAllProducts, showProducts);

    app.Run();
}

Now the cool thing is that we can add aspects like logging or exception handling without touching the existing code and without a framework or annotations. For exception handling e.g. we just add a new class:

public class ExceptionHandler<T> : Handles<T>
{
    private readonly Handles<T> _next;

    public ExceptionHandler(Handles<T> next) { _next = next; }

    public void Handle(T message)
    {
        try
        {
            _next.Handle(message);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

And then we plug it together during the composition at the entry point of the application. we don't even have to touch the code in the Application class. We just replace one line:

Action<ShowProductsCommand> showProducts = (x) => new ExceptionHandler<ShowProductsCommand>(new View()).Handle(x);

So to resume: When we have a flow oriented design we can add aspects by adding the functionality inside a new class. Then we have to change one line in the composition method and that's it.

So I think an answer to your question is that you cannot easily switch from one approach to the other but you have to decide on what kind of architectural approach you will go for in your project.

edit: Actually I just realized that the partial application pattern used with the product service makes things a little more complicated. We need to wrap another class around the product service method to be able to add aspects here as well. It could be something like this:

public class ProductQueries : Queries<IEnumerable<Product>>
{
    private readonly Func<IEnumerable<Product>> _query;

    public ProductQueries(Func<IEnumerable<Product>> query)
    {
        _query = query;
    }

    public IEnumerable<Product> Query()
    {
        return _query();
    }
}

public interface Queries<TResult>
{
    TResult Query();
}

The composition then has to be changed like this:

Func<IEnumerable<Product>> getAllProducts = () => ProductService.GetAllProducts(new ProductRepositoryStub(""));
Func<IEnumerable<Product>> queryAllProducts = new ProductQueries(getAllProducts).Query;
Action<ShowProductsCommand> showProducts = (x) => new ExceptionHandler<ShowProductsCommand>(new View()).Handle(x);
var app = new Application(queryAllProducts, showProducts);
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.