Aspect-Oriented Programming Considered Harmful
kupci writes "The 'x considered harmful' cry is a little overused, but there is a Forrester report that discusses some of the pro's and con's of Aspect-oriented Programming, and includes some interesting links. It is mainly based on papers from the University of Passau. It's worth it just for Clark's 'COME FROM' article." From the article: "Aspect-oriented programming (AOP) is intended to address common problems that object-oriented programming (OOP) doesn't address well, plus some problems that OOP itself created. However, AOP is a risky solution: It is a very generic mechanism for solving some very specific concerns and has been likened to a kind of "GOTO" statement for OOP. Like GOTO, it can cause more harm than good."
You're on crack. AOP is not specific to Java -- I've seen it done with Python -- see this article for example. AOP can be used for great evil, but it can also be used for great good. When I first read up on it, my first reaction was "wow! This must be how God programs!" :)
who is going to shell out that cash for 5 pages?
"Powerful language constructs can be dangerous when misused"
Thank you for the wonderful advice and welcome to 1971!!
As for the GOTO comparison, it is disingenuous. The problem with GOTO is not that it is a flow control change, the problem is that it is an [i]unstructured[/i] flow control change. AOP is nothing if not structured, in fact its potential flaw lies in that the structures it represents can be so complicated the programmer can lose track. It's the absolute opposite of GOTO.
Log.printLog("some message " + someObject.toString());
everywhere you want to log something. But with AOP you can used kleen operators (*, +, ?, etc) to add Log.printLog() to certain methods of certain objects. Aspects allow you to inject code into method call boundaries. But like all programming constructs it can be abused. This is because you might get an exception stack trace that indicates a specific method call has thrown an exception when the exception came from code inserted by an aspect. But other languages can be abused too (Perl anyone). So I think this type of critism of aspects says more about the critic than about the programming construct. I don't use C++ to implement logic evaluators (I would use Prolog or another logic or unification based language). Nor do I try to write OO code in LISP (despite the horrid OO extensions for LISP) because each language has it strengths and weaknesses. For each application choose the language that best suites the task.
On another note, I think these types of critisms are really from people who are afraid of learning new languages or skills. They have worked long and hard to get good at C (or Java or some other language) and don't want to have to learn another language. Well suck it up, there aren't many Fortran programmers anymore and if you want to keep working, you must keep learning new languages and skills. Not that you should jump on every bandwagon, but if I'm writing something that needs to be really, really fast I use C. If I'm writing something that needs to be maintainable forever and speed isn't that important I use Java. The best tool for the task.
"Those that start by burning books, will end by burning men."
I find the example rather silly. For anyone doing telecommunications, the business of billing calls is mission-critical, and therefore should be designed into the core application.
They seem to portray AOP as trick for lazy small-picture designers who want to ignore the boring problems, because someone else can hack it into the system using AOP.
Plain C:
....
void foo()
{
do some thing
if(error)
goto err1;
do something more;
if(error)
goto err2;
return;
err2:
free stuff
err1:
free more stuff
}
For anything else I shun goto.
GOTO doesn't cause harm (or its syntax sugared grandchildren), it's the programmer who is not able to understand when to use it or not. Such programmers are a harm by themselves :-)
Perl Programmer for hire
This article is part of an early FUD PR campaign by a large software company on the west coast, which fears the rise of CL among developers.
It may seem like a cheat at first sight, but the haskell approach to IO/global state is really elegant infact, and stays within the boundaries of a purely functional language. Most other functional programming languages do use a cheat, and are thus impure (and strict instead of lazy).
:: String -> IO ()
What haskell infact lets you do with its IO monad is construct instructions for a separate IO interpreter (that is not pure) within the Haskell runtime. It is a kind of DSL (domain-specific language), a concept that is used a lot in Haskell programs.
What the type signature
putStr
can be interpreted to mean is that putStr is a function that given a string returns instructions for IO interpreter on how to print it out, without returning other value (()=void).
Goto only has a place because C++ is a weak language. If c++ had a proper runtime exception mechanism or support for continuations (which can be used to implement the former but generally only serve to obfuscate code), then gotos would be wholely unneccesary.
Yes it works, but it's not clean. The only reason I use gotos in c/c++ code is to break out of multiple levels of scope or for a basic 'goto end' pattern in a long/complex routine that needs to ensure cleanup before returning. I suppose I'd also use it for performance reasons if I was that desperate, but I've never been there.
The fact that it works, though, isn't enough to make me use it except in the situations where there's no other choice. I see a lot of gotos in peoples' code where they don't need to be. The creators of C/C++ left goto in for a reason--there are times when there's no other practical choice.
A quick grep of 20klocs of code I wrote for a client indicates 12 uses of goto in 2 routines. One was a parser with a lot of multilevel stuff to break out of. The other was a non-blocking i/o routine that needed to ensure some state cleanup before returning from a lot of places, again in a nested hierarchy. Both were relatively long routines.
The idea in that construct is to let the first error handler continue to the second, so that the freeing code does not need to be duplicated. Breaking the freeing code into functions is a bit heavy solution, having to pass them all the variables to be freed and so on, and it would only increase the amount of code.
A function longer than a screenfull is too long, I never have such.
CCITT Number 7.
Signalling System 7.
SS7.
This beast takes care of the mission critical billing, timing, routing, SMS and a thousand other things, all in a single little packet switched channel. It's a small box with nice flashing LED's.
It is perhaps as common in telecommunications now as McDonalds is a household name.
You could, if you want hard to read slower code...
But I was shocked to see that CALLBACKS were put down by C++ purists
???
Callbacks are everywhere in C++ although these days they are often function objects instead of function pointers. Somebody who puts down callbacks is certainly no C++ purist.
Anyone who generalizes about slashdotters is a typical slashdotter.
How do I get suckers^H^H^H^H^H^H^Hfolks to pay $250 for my 5 page speculations on computer science topics? Seriously. I'm an academic, and I write 20+ pages per month of this stuff for free. If I could get even 100 people to pay $50 per page each for it, that would be $100K per month: that's way more than I make in a year at my current job. Leaving aside the question of the price of my immortal soul, it doesn't sound like too bad a gig.
I started to list my qualifications, but got tired of typing. Suffice it to say that they're way more than that Forrester guy has. Let me know folks: if 100 of you pay me $50 per page each, I'll research pretty much whatever you want.
The concepts behind AspectJ, TMO, are not bright. They break encapsulation, they are too ambitious and far from real-world understanding of programming concerns. All in all, they introduce too many problems.
TMHO,Aspect Oriented Programming should be different, and should be based on something much simpler, much more down-to-earth ideas and more consistent with real-world needs.
Today it is easily possible to do using Java Annotations, that will specify class member's affinity with an aspect, and thus provide:
A) a mechanism by which the compiler could limit access (errors & warnings) to members according to their affinity with a common aspect or aspects (common to it and its caller);
B) a programmer, using a proper IDE, can view a breakdown of his code according to aspects.
C) in runtime, the current aspect should be visible to the program thus extending the ability to: I) log, and trace errors; II) affect work-flow according to the aspect in action (that's an intense feature so im not too sure about that)
Doing more than the above, looks to be like an abuse of proper programming concepts.
The Annotation mechanism introduced in Java 5 is quite powerful and maybe already provide all that is needed for the job, including hierarchical arrangement of aspects (slash annotations), attributes, etc. Very little is needed in order to implement what i propose.
Maybe i'll propose is to the JCP. What'd ya think?
So let me get this straight..
AOP basically means that when an object is created, or a method is called you wan't to run some code?
So if we tried to put this into a non-OOP example you would basically be calling something else whenever certain functions were called? Eg calling a 'save', 'load' or 'start' function would result in running the 'logging' function too?
But as I gather (and its pretty hard to find a simple explanation around here) the idea of AOP is that it allows you to do this without having to put any function calls in your code, ie it 'captures events'. So if for example you had to implement some security checks but you were worried that someone in your project would forget to put the 'if(secure)' lines into their modules you could rest assured that AOP would be on the case.
Then theres COME FROM which is basically an event handler that says 'when you get to this line/label in your code, come over here and run me.
I see no problem with this in a relatively high-level environment where the goal is to write less code and where so much crap is going on in the background anyway (dynamic this and managed that) that another overlay is just going to make things look simpler.
Isn't this however almost exactly the same as an interrupt?
This comment does not represent the views or opinions of the user.
And since Aspect-Oriented programming is a patented technique, basically nobody can legally use it unless you're a personal friend of the inventor.
So, who really cares if its theoretically any good, when legally it is worthless?
I've been following AOP (cautiously) for some years now. Here's a few salient points for those who don't have $250 to splash out.
The underlying principle of AOP is about "separation of concerns", a term introduced by Dijkstra back in 1974. Separation of concerns is a Good Thing[tm], but there's more than one way to do it. It's a conceptual thing more than it is any one particular implementation technique.
Both structured and OO programming offer techniques that allow the hacker to more clearly separate concerns: by organising their code into subroutines, modules, objects, methods and so on. The problem with OOP is that real world problems don't always break down into a set of clearly defined, independant object classes. In some cases you can end up with a problem fragmented into so many small pieces that you can no longer see the wood for the trees.
AOP tries to address this by allowing you to identify those concerns that don't fit neatly into an object model. These "cross-cutting" concerns are typically things like logging, debugging and security that affect many of the objects in your system. If you decide to change the way logging is handled, for example, you don't want to have to go and edit every single object that generates logging information. But that's often what happens in OO based systems - you design your class hierarchy with Products, Customers, Orders and other real-world entities in mind and implement them as "black-boxes" with internal functionality neatly hidden away. That's fine when the functionality really is local to the object, but not when it relates to a system-wide aspect like logging, etc. These are the kind of undesirable artifacts that can arise from the decomposition of a problem into objects.
However, that's not to say that there aren't ways of achieving the good parts of AOP in a non-AOP language. Many Design Patterns are examples of separating concerns. The Model/View/Controller and Model/Visitor patterns come immediately to mind. Going back to the early logging example, we could implement this in AOP fashion in an OO language, by creating a "Logger" object which implements all the logging functionality. Just make sure all your other objects delegate to the logger for logging rather than trying to do it themselves. Now you have all your logging code in one place, and you just have to worry about how you're going to pass the logger object around so that all your other objects can call on it... (and this is often the start of the rest of the problems...)
So AOP-a-like can be done in OO languages, but most OO languages aren't really cut out for it - you have to code the magic manually if you want it. Hence the rise of AOP languages (usually just bolt-on syntax additions to existing languages) that make this process that little bit easier.
AOP in Java does smell a little like GOTO, IMHO. In brief, it uses "join points" to connect different aspects together (e.g. call this logging method just before calling that other method). One can certainly argue that it's a more structured form of GOTO, but I believe the same fundamental problems remain: control flow jumping all over the place, with actions-at-a-distance waiting to catch out the unsuspecting programmer.
So my advice on AOP would be to treat it like OOP, XML, Java, and all the other "silver bullets" that over the years have claimed to be the next big thing that will save our collective software sanity. Recognise the problem that it's trying to solve, realise the benefits of the particular solution(s) presented, and ignore all the hype!
As the goto statement does not exist in Java, it is a given that we're talking about C++.
... making a pure virtual function call is cheap in comparison.
While anything allocated on the stack will be destroyed when an exception is thrown, any resources allocated during execution will leak (ex: handles or memory allocated on the heap). As the data on the stack is deallocated (the very items which were referencing the afore mentioned resources), you never get an opportunity to clean up after the error condition. To make matters worse, you never get an opportunity to rollback the state of the operation when you throw the exception.
Not to mention the complications that arise when SEH is thrown into the mix (destructors are never called on objects allocated within an SEH block)...
Obviously you can write code to work around that. However, it is far more complicated and error prone than ensuring that the flow of execution always hits the end of the method.
Then, there is the overhead of actually throwing an exception
More and more languages support named continue/break statements. Don't remember if gcc supported it yet. Such statements are more powerful than goto because they imply cleanup code that may or may not be implied by a goto. For example, you know that the for-post code block will be invoked on a labeled continue, as well as running the conditional.
FOO: for(..) {
BAR: for (..) {
if (..)
continue FOO;
if (..)
break BAR;
Perl was nice because it had even more flow-control statements for clearity.. "redo", for example would go to the start of the loop-block without running those two for-blocks. This is nothing particular to perl, just a conspectual-clearity which was valued by the compiler writer; to avoid justifying use of a goto.
-Michael
Aspects 'inject' behaviour into your classes. This is fine if the behaviour is completely orthogonal to the workings of your class, e.g. you're injecting enter/exit logging. If you start to inject functionality, however, the workings of the class can be substantially obscured. A number of the examples I've seen can be as properly done with more broken-down normal methods and a naming convention (e.g. before_x, after_x).
That said, chances are that the true benefits of aspect-oriented programming are not going to come from it being a mere curiosity to add to object-oriented programming. Someone is going to have to discover how to program in AOP where the focus is on AOP.
Possibly, it will have a journey like object-oriented programming. Started out with being used as object-based programming (just a more convenient way to associate data with functions), grew to some standard uses, then exploded into class libraries and eventually patterns.
Even 'interface-oriented programming' requires a slightly different tack than plain object-oriented programming does.
That's not to say that AOP will be the next actual big thing, or even really find its niche. There are plenty of good ideas that never really 'made it'. Time, and a whole passel of people with a lot of on their hands, will tell :)
Binary geeks can count to 1,023 on their fingers
I use RAII to free resources in a deterministic order all the time. You might not have total control, you still have a lot of control. And if you really need total you control you can just handcode it.
And in the process your code just got a lot more complicated. Because now you use RAII in some places, and hand coding in other places. It gets worse if you do both in the same function, as then there is a large amount of confusion about when, where, and how certain resources are being removed. And don't get me started about what happens when someone manually cleans up RAII objects to enforce ordering, which tends to confuse the living hell out of people (ie: code maintainability and clarity is reduced).
Something else that hasn't been mentioned is side effects of RAII objects can sometimes be non-obvious. People tend to get used to treating them as the primitive types that they are, when really they are a wrapper class for a primative type. Sometimes the operations performed on that primative type have a non-obvious side effect, which leads to 'mysterious' bugs that people spend hours tracking down.
A good example of this is using RAII to manage a BSTR type. If you pass the RAII object into some method and a copy constructor is called to clone the object, you start off ok. The problem is once that method completes, the destructor on the RAII object is called. This calls a system method that overwrites the last error result, which was just set by the method you called. So your code ends up reporting a failure of ERROR_SUCCESS because you used an RAII type. This type of problem isn't limited to RAII types, but it is most unexpected with this sort of type due to the expectations of a person using it.
There are other variations of this problem involved with trashing the results of method calls you can make (imagine your function has a BSTR RAII type in it, and you set the last error information before you return; the destructor for that RAII type will trash your last error result).
When resources are interdependent than this should reflect in the design: they probably should be released in the same destructor.
Once you exceed a certain level of functionality, you're not using RAII -- you're writing a class framework. While I'm not opposed to writing class frameworks to wrap complicated functionality and represent it in a simple fashion, I am opposed to writing class frameworks to manage simple operations.
Indeed, and now inheritance and polymorphism are probably the most over-used programming language features on the planet.
I'm not saying you're wrong to try to isolate self-contained and independent blocks of code -- you're not -- I'm just saying that it's not always possible, even when you look at "obvious" dividers like branches or loop bodies.
If you disagree, post your argument. (-1, Overrated) isn't your personal censorship tool for views you don't like.