Why "vs"? It is not "vs". You can use Aspect Oriented programming in combination with functional programming, but also in combination with Object Oriented one. It is not "vs", it is "Aspect Oriented Programming with Object Oriented Programming".
To me AOP is some kind of "meta-programming". Everything that AOP does could also be done without it by just adding more code. AOP just saves you writing this code.
Wikipedia has one of the best examples for this meta-programming. Assume you have a graphical class with many "set...()" methods. After each set method, the data of the graphics changed, thus the graphics changed and thus the graphics need to be updated on screen. Assume to repaint the graphics you must call "Display.update()". The classical approach is to solve this by adding more code. At the end of each set method you write
void set...(...) {
:
:
Display.update();
}
If you have 3 set-methods, that is not a problem. If you have 200 (hypothetical), it's getting real painful to add this everywhere. Also whenever you add a new set-method, you must be sure to not forget adding this to the end, otherwise you just created a bug.
AOP solves this without adding tons of code, instead you add an aspect:
after() : set() {
Display.update();
}
And that's it! Instead of writing the update code yourself, you just tell the system that after a set() pointcut has been reached, it must run this code and it will run this code. No need to update 200 methods, no need to make sure you don't forget to add this code on a new set-method. Additionally you just need a pointcut:
pointcut set() : execution(* set*(*) ) && this(MyGraphicsClass) && within(com.company.*);
What does that mean? That means if a method is named "set*" (* means any name might follow after set), regardless of what the method returns (first asterisk) or what parameters it takes (third asterisk) and it is a method of MyGraphicsClass and this class is part of the package "com.company.*", then this is a set() pointcut. And our first code says "after running any method that is a set pointcut, run the following code".
See how AOP elegantly solves the problem here? Actually everything described here can be done at compile time. A AOP preprocessor can just modify your source (e.g. adding Display.update() to the end of every set-pointcut method) before even compiling the class itself.
However, this example also shows one of the big downsides of AOP. AOP is actually doing something that many programmers consider an "Anti-Pattern". The exact pattern is called "Action at a distance".
Action at a distance is an
anti-pattern (a recognized common
error) in which behavior in one part
of a program varies wildly based on
difficult or impossible to identify
operations in another part of the
program.
As a newbie to a project, I might just read the code of any set-method and consider it broken, as it seems to not update the display. I don't see by just looking at the code of a set-method, that after it is executed, some other code will "magically" be executed to update the display. I consider this a serious downside! By making changes to a method, strange bugs might be introduced. Further understanding the code flow of code where certain things seem to work correctly, but are not obvious (as I said, they just magically work... somehow), is really hard.
Update
Just to clarify that: Some people might have the impression I'm saying AOP is something bad and should not be used. That's not what I'm saying! AOP is actually a great feature. I just say "Use it carefully". AOP will only cause problems if you mix up normal code and AOP for the same Aspect. In the example above, we have the Aspect of updating the values of a graphical object and painting the updated object. That is in fact a single aspect. Coding half of it as normal code and the other half of it as aspect is what adds the problem.
If you use AOP for a completely different aspect, e.g. for logging, you will not run into the anti-pattern problem. In that case a newbie to the project might wonder "Where do all these log messages come from? I don't see any log output in the code", but that is not a huge problem. Changes he makes to the program logic will hardly break the log facility and changes made to the log facility will hardly break his program logic - these aspects are totally separated. Using AOP for logging has the advantage that your program code can fully concentrate on doing whatever it should do and you still can have sophisticated logging, without having your code being cluttered up by hundreds of log messages everywhere. Also when new code is introduced, magically log messages will appear at the right time with the right content. The newbie programmer might not understand why they are there or where they came from, but since they will log the "right thing" at the "right time", he can just happily accept the fact that they are there and move on to something else.
So a good usage of AOP in my example would be to always log if any value has been updated via a set method. This will not create an anti-pattern and hardly ever be the cause of any problem.
One might say, if you can easily abuse AOP to create so many problems, it's a bad idea to use it all. However which technology can't be abused? You can abuse data encapsulation, you can abuse inheritance. Pretty much every useful programming technology can be abused. Consider a programming language so limited that it only contains features that can't be abused; a language where features can only be used as they were initially intended to be used. Such a language would be so limited that it's arguable if it can be even used for real world programming.
AOP addresses the problem of cross-cutting concerns, which would be any kind of code that is repeated in different methods and can't normally be completely refactored into its own module, like with logging or verification. So, with AOP you can leave that stuff out of the main code and define it vertically like so:
function mainProgram()
{
var x = foo();
doSomethingWith(x);
return x;
}
aspect logging
{
before (mainProgram is called):
{
log.Write("entering mainProgram");
}
after (mainProgram is called):
{
log.Write( "exiting mainProgram with return value of "
+ mainProgram.returnValue);
}
}
aspect verification
{
before (doSomethingWith is called):
{
if (doSomethingWith.arguments[0] == null)
{
throw NullArgumentException();
}
if (!doSomethingWith.caller.isAuthenticated)
{
throw Securityexception();
}
}
}
And then an aspect-weaver is used to compile the code into this:
function mainProgram()
{
log.Write("entering mainProgram");
var x = foo();
if (x == null) throw NullArgumentException();
if (!mainProgramIsAuthenticated()) throw Securityexception();
doSomethingWith(x);
log.Write("exiting mainProgram with return value of "+ x);
return x;
}
Best Answer
Aspect Oriented Programming is so much more than just logging, reporting et cetera, as you will see if you have a look at PostSharp's web site. Personally I haven't done so much static IL weaving, mostly dynamic IL generation to create AOP interceptors and when doing so I have mostly been using it to wrap and intercept resolves from inversion of control containers.
AOP can improve exception handling, improve tracing, improve transaction interception.
NHibernate for example has a sort of AOP, even though it's static at compile-time in terms of simple event handlers; but for certain events in the engine you can attach interceptors (aka aspects, the events being the point-cuts etc) -- I use this to inject, using IoC business entities into my domain objects.
Powerful AOP-frameworks allow you to generalize and even more powerful allow you to generalize w/o overhead at runtime; in principle you have a few different ways of doing it:
(0). (not really) "pre-processor" AOP aka templates in C++, ifdefs etc
Usages in transactions Have a look at Castle.Facilities.AutomaticTransactionManagement.TransactionFacility for a nice way of handling transactions using AOP and the intercepting abilities of DynamicProxy2. The transaction facility integrated with System.Transcations and System.EnterpriseServices is you are using the distributed transaction coordinator (COM-component) for managing transactions. Also, there are multiple examples of p/invoke into the kernel to take care of the TxF and TxR components of the Vista-kernel (aka Server 2008) which allow you to use transactions on NTFS and on the registry, thereby making sure CRUD you do is ACID, which also nicely integrates with System.Transactions for creating nested transactions.
Usages in invariant verification You can also use them for design by contract, by appending some attributes to your parameters.
The problem with this at the moment is the overhead of attaching this meta-data and checking it at runtime. However, you could specify the constraint-checking aspect only to be applied when you're compiling with DEBUG and then this meta data-wouldn't lead to much deterioration in performance.
If you are looking to get into axiomatic proofs, have a look at Sing#/Spec# instead, since that's more formal and the work is done by the compiler.
Things to be aware of The most important point to be aware of is that if a concern, i.e. some piece of code that runs before or after your method is altering the control-flow, possibly returning an unexpected type, returning too early or in general doesn't behave along the intents of the method you were calling you may get errors which are hard to debug.
Also, beware of the throwing of exceptions from attributes, because you never know when or from what assembly, reflection occurrs; the reflection on your attribute may not happen when you expect to. This happened to myself when I was attaching types in attributes and carefully checking them.
Also beware of the fact that you're opening a possible attack vector in adding global "point-cuts" which, if someone gets access to, can be used to redirect large portions of your system.
Other frameworks If you are interested in learning more about AOP in general I suggest you check out Rickard Öberg's presentations on Qi4J, it's a very good framework in Java for AOP (java has slightly different object-inherited semantics though which makes a wee bit tricker to use in C#/F#/Nermle/Boo whatever.
AOP + AddIns Another interesting possibility in using aspect oriented programming with runtime-generated assemblies such as those dynamicproxy2 creates, is that you can also use them to wrap objects that cross application boundaries, thereby simplifying the creation of a add-in-pipeline. I had secretly hoped that Microsoft would use this when they created their AddIn-framework for 3.5, but they chose to go the static code-gen way unfortunately, leading a a rather big overhead in creating add-ins, for the developer. The problem is that a type loaded for "more than reflection" into an AppDomain can't be unloaded again unless the complete AppDomain is unloaded, so you need 1) to reflect on the plugin without loading it to see what it is capable of unless you allow for lots of manual meta-data to be written or generated (and believe in that) and 2) some object to hold the handle to your object so that it's not GCed and you don't know the type (hence the IContract assembly and AddInHandle-class) -- this could probably be done in a nice way with a dynamic proxy/AOP.
Using AOP for global garbage collection ... in a distributed system running on linux/windows on the common language infrastructure. The paper was a bit hard to download so I uploaded it to my server so I know where it is.
Post Scriptum (If you are using a non-standard language on the CLR and not the DLR IL-weaving might create non-standards compliant code. Especially interesting for F#, I think, because the use a lot of non-standard code to great benefit of the language (tuples say) -- you could mark your assembly with [assembly: CLSCompliant] if you want to get compile-time warnings of this.)