2008-10-24

How I would like to write code

Aspect Oriented Programming looks great. It's something I have always wanted to use, but I avoid it because it doesn't work exactly how I would like it to. The first thing I want to avoid is lots of reflection at runtime (compile time is fine), it is for this reason I have mainly been interested in PostSharp.

PostSharp looks great! You decorate your class with attributes that you create yourself. After compiling your assembly PostSharp inspects the result for Attributes that are descended from one of its own special PostSharp AOP classes. When it sees these attributes it modifys your code in a specific way.

To use the same example as everyone else in the world (yawn) you could create an attribute from the method-boundard attribute. Override the methods declared on that attribute for entry/exit of the method, and write some code in there to write to the IDE's output window using System.Diagnostics.Debug.WriteLine(). Now when I add that attribute to a method on one of my classes I get that code injected into my method automatically

[Logging]
public void DoSomething()
{
}

would be the same as

public void DoSomething()
{
System.Diagnostics.Debug.WriteLine("Enter");
try
{
}
finally
{
System.Diagnostics.Debug.WriteLine("Exit");
}
}

So, this looks great, but what's so good / bad about it?

Good


On the good side it lets me describe code functionality rather than writing it. Instead of looking at loads of lines of code to see what something does we can look at a .NET attribute which simply tells us what generic task the class/method does. Take Assets as an example. An Asset is worth money so its location and movement history must always be tracked. To achieve this I would have assets always linked to an AssetHolder class...

Asset holder


Now, what qualifies as an asset holder? You could say that a person has an asset, a department could be using it, it could be in a stock room, or it could be out on hire to a customer.

Digressing slightly...Don't descend these classes from AssetHolder! Holding assets is something a person DOES, not something a person IS!

Person with asset holder


Now my Person class can hold assets. I can obtain its AssetHolder instance via its IAssetHolder interface, from there I can create AssetTransfer instances and so on. Now I have to implement the same thing for Department, Room, and Customer! Wouldn't it be nice if I could just do this?

[AssetHolder]
public class Person { }

[AssetHolder]
public class Department { }

[AssetHolder]
public class Room { }

[AssetHolder]
public class Customer { }

That's the good side of PostSharp. I could create an AOP attribute called "AssetHolderAttribute" and then build my business classes with that attribute adorning them. After a successful compilation PostSharp would discover these attributes and do the following to each of my classes

01: Add a member AssetHolder to the class.
02: Ensure that member is created within the constructor.
03: Implement IAssetHolder, returning this new member from the GetAssetHolder method

What we have is a very small amount of code (two words stuck together, wrapped in a pair of square brackets) which not only clearly identifies a feature of these classes, but even implements it

Bad


This is the only thing that is stopping me from using this approach to write applications. PostSharp cannot add the code until after the assembly has finished compiling. This means that an assembly consuming the business classes can easily write code such as

IAssetHolder holder = (IAssetHolder)person;

but code within the same assembly as the Person class cannot, because Person does not implement IAssetHolder until after the assembly has successfully compiled. A catch-22 situation!

How I would like to write code


I would love to see pre-compile time support for AOP, so that it feels like part of the development experience rather than like something that has been bolted onto my binary afterwards. Instead of a UML diagramming tool to help me to design my complex models, what I would like to do is to use the diagramming tool to design patterns that occur frequently in applications (archetypes), and then in simple code decorate my classes to identify what they are capable of...

[BusinessObject] //Adds persistence capability using your favourite framework
[AssetHolder] //Responsible for holding assets
[StockHolder] //Responsible for holding stock
[RoleHolder] //Can be assigned roles, such as Customer, Supplier, etc
public class Person
{
[DataMember] //This property is persistent
public string FirstName { get; set; }
}

//I could now write code like this
Person p = new Person(EcoSpace);
decimal heldValue = p.AssetHolder.GetHeldAssetValue();
heldValue += p.StockHolder.GetHeldStockValue();

Or for defining the model itself using simple code...

[BusinessObject]
public class PurchaseOrder
{
//Persistent property that is an AutoIncrementing column in the DB
[DataMember(SaveAction = SaveActionKind.DbAssigned)]
public int OrderNumber { get; private set; }
}

[BusinessObject]
public PurchaseOrderLine
{
//Also adds a property to PurchaseOrder called "Lines"
[Association(Aggregation = AggregationKind.Composite, OtherEndName="Lines")]
public PurchaseOrder Order {get; set; }
}

//I could now write code like this
var order = new PurchaseOrder(EcoSpace);
var line = new PurchaseOrderLine(EcoSpace);
line.Order = order;

//AOP created property "PurchaseOrder.Lines"!
if (order.Lines.Count != 1 || order.Lines[0] != line)
throw new ........

The future


I really hope to see pre-compiler support for AOP in the future in C#. I've been working on one of the guys at RemObjects recently who writes the Oxygene .NET language compiler (formerly known to me as "Chrome"). I have always thought their language has some really nice features in it, but never started using it because of two reasons

01: No ECO support.
02: I wanted to stay in C# to keep my skills more "main-stream".

Oxygene support is being worked on in ECO 5, which only leaves the second point. I really like some of their features as I said, but not quite enough to make me switch. However, if you are going to gain massively from working in a less popular language then switching to that language is the right thing to do, and this would be that feature for me! If in the future I need a C# job I believe my skills will remain current enough to be able to not exclude me from this part of the job market.

In the meantime I will continue to use C# and hope that some relentless annoying nagging will get me what I want on the Oxygene front, and that my dream of writing solid reliable code with very little effort takes that big leap towards becoming a reality! :-)

1 comment:

Dmitriy Nagirnyak said...

To avoid PostSharp's catch-22 I think it might be possible to have a separate BusinessObjects assembly with no any logic at all. And have the separate assembly that would implement the logic. Not sure how it can fit in ECO infrastructure.


Instead of a UML diagramming tool to help me to design my complex models, what I would like to do is to use the diagramming tool to design patterns that occur frequently in applications

So you'll have to use some non-UML designing tool. But with UML you can just have some preset of patterns (such as GoF).

Anyway the code you wrote is a bit similar to what I wanted (avoid UML designer for ECO) and talked about here. This is not exactly what you describe, but maybe first step to it.

Anyway, I think what you tried to do is achievable by separating Business Objects DEFINITION from their IMPLEMENTATION.

Not sure if Chrome allows something like that, but in C# best way of doing it would probably be having 2 separate assemblies:
1) BO.Definition - includes PostSharp aspects decorated model.
2) BO.Implementation - uses BO.Definition processed by PostSharp and imlements the logic.

But I still prefer to use more traditional approach and see the actual code than allowing a tool to generate half of my business objects.
I would definitely use PostSharp, but not as a main AOP tool. It just allows to inject Object-Oriented Aspects into the code. It's not a fully blown AOP tool.