Slashdot Mirror


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."

22 of 470 comments (clear)

  1. For those unfamiliar with AOP by ravenspear · · Score: 5, Informative

    Wikipedia linkage

    In computing, the programming paradigm of aspect-oriented programming (AOP) centers on constructs called aspects, which treat concerns of objects, classes, or methods. The aim of AOP is to separate program code related to the main purposes of the application (its core concerns) from code related to secondary purposes (cross-cutting concerns).

    For example, a telecommunications application might have a core concern of routing calls, while code for timing and billing those calls would crosscut the whole object hierarchy. AOP aims to separate the billing concerns from the core concern. It moves code not related to solving the domain problem from the business logic into a separate module. The application code no longer contains pieces of crosscutting concerns scattered across modules; instead, programmers maintain crosscutting concerns in aspects; this makes it easier to maintain both core and crosscutting concerns.

    Any program has principled points (join points) where programmers can identify and modify the program semantics. In AOP, programmers specify join points using a language feature called a pointcut, and specify the behavior to join those points by using advice such as methods or functions. Some variants of AOP allow programmers to extend the types in the system. These features enable aspects to implement behavior for concerns that crosscut the core concern of the application.

    1. Re:For those unfamiliar with AOP by snorklewacker · · Score: 4, Informative

      If you want something to happen when a certain thing occurs, wouldn't you just call a function or a method when that whatever occurs? Why would you want a goto style statement there?

      The whole idea of AOP is that it's not a GOTO. The "interceptor" (or whatever the jargon of the week is) gets called, does its thing, returns, and your original function runs. This is nothing, lisp does this with defadvice, even emacs lisp can do this (and indeed emacs lisp makes good use of it).

      The neat thing about aspects is that you define them in ONE place. So you tell it "I want function setup_db_connection() and teardown_db_connection() called before every method on any Person, Room, and Resource" (imagine it's a scheduling app). This is just like subclassing, but you don't have to rewrite anything else in the app. In fact, VB programmers who did this sort of hook trickery actually did call it subclassing. If you use a factory pattern, you can do this sort of thing without AOP, but the idea of AOP is to not force you to rewrite everything to use factory patterns -- you just define the "aspect" once, in one place, and it affects everything you tell it to.

      I find AOP is a great way to impose "external" conditions like debugging, extra asserts, and tests. I don't think it's necessarily a great way to structure an entire program, though someone with more experience in the methodology might make it work. As a new methodology, it's rife with shoddy implementations, buzzwords, overuse, poor use, and general immaturity. This is how all methodologies start out however -- even structured programming went overboard when it was first introduced to the mainstream.

      --
      I am no longer wasting my time with slashdot
  2. A bit sketchy to be called an article by Goosey · · Score: 3, Informative

    The one-paragraph 'article' is just a tagline for a 250 dollar document. Quite dissapointing to read that lead in and find that I won't actually be aquiring any new information on the topic. :\

    --
    --- "End Of Line" - MCP
  3. $250 to read the article by LadyLucky · · Score: 2, Informative
    What the submitter failed to mention is that to read this article you have to stump up with $250. The comment in the submission is merely from the executive summary.

    'It's worth it'. Come on, how many people here read the article, let alone will pay $250 for it?

    --
    dominionrd.blogspot.com - Restaurants on
  4. Re:Good primer on aspect-oriented programming? by devillion · · Score: 2, Informative
  5. Re:Of course by Dwonis · · Score: 2, Informative
    "Goto", on the other hand, is an abomination.

    Quick! Somebody start a petition to demand that Intel drop the JMP instruction from its processors! It's an abomination!

  6. Re:I like GOTO! by Keeper · · Score: 4, Informative

    The goto operation can be rather useful if used sparingly and correctly. If I see a goto jump up (earlier in the function) instead of down, I tend to get worried. But it is useful to break out of nested loops or complicated logic statements.

    It is also exceedingly useful for error handling, in that it allows you to skip the bulk of a function and still hit the cleanup section of the function. This gives you two things:
    1) you avoid a deep, deep mess of nested if statements
    2) you eliminate duplication of code in error handlers (and the bugs that occur later on when someone forgot to update one of those handlers to release some resource they just added)

  7. Re:I like GOTO! by Kupek · · Score: 2, Informative

    Read some Linux kernel code, particularly the system calls. Most of them are structured in such a way that they do lots and lots of error checking, and do a goto to near the end of the function that performs cleanup (mainly, deallocation of structures) if any of the error checks fail. It's really the most elegant and obvious way to accomplish the task.

  8. Re:best tool for the task by AuMatar · · Score: 2, Informative

    1)Only C++ uses like that. Not all language are C++
    2)Even in C++ a lot of people avoid that usage. Its an example of everything thats wrong with operator overloading. ab means left shift a by b bits. Overriding it to now mean write to a stream is a horrible and confusing idea. Would you override + to mean something totally different than addition?

    --
    I still have more fans than freaks. WTF is wrong with you people?
  9. Re:I like GOTO! by top_down · · Score: 5, Informative

    You have to catch [execeptions] at every point to free stuff, and the nested structures become even uglier than gotos.

    The freeing of resources, all resources not just memory, is automatic in C++ once you learn to use RAII.

    Look it up. It is one of major improvements of standard C++ over C/C++.

    --
    Anyone who generalizes about slashdotters is a typical slashdotter.
  10. Re:best tool for the task by Anonymous Coward · · Score: 1, Informative

    AOP lets you define various side effects and entry/exit conditions for any operation, including primitive operators.

    As a contrived example, you could technically set an entry-condition that checks the denominator for division and logs before attempting to divide by zero.

    In another contrived example, you could acquire a semaphore in the entry condition for modifying a certain primitive type (and release in the exit condition).

  11. Re:I like GOTO! by top_down · · Score: 4, Informative
    Show me code how to assign callbacks.

    Sure. This is an example using the boost::function and boost::bind libraries which have both been available for a long time and are in the process of becoming part of the standard C++ library.
    #include <iostream>
    #include <boost/function.hpp>
    #include <boost/bind.hpp>

    using namespace std;
    using boost::function;
    using boost::bind;

    void hello() { cout << "hello from function" << endl; }

    struct Object {
    void hello() { cout << "hello from member function" << endl; }
    };

    int
    main()
    {
    // callback with a normal function
    function<void()> f(hello);
    f();

    // callback with a member function
    Object o;
    f = bind(&Object::hello, &o);
    f();
    }
    If you don't want to use libraries just assign function objects or static class functions.

    If you want to do several callbacks at once check out one of the many available signal/slot libraries.

    --
    Anyone who generalizes about slashdotters is a typical slashdotter.
  12. Re:Of course by Anonymous Coward · · Score: 3, Informative

    Last I checked, assembler wasn't a high-level language.

    And? High level languages have immediate targetted transfer of control instructions as well that one cannot easily get rid of. For example, C has break, continue, and the closing squiggles of for and while statements.

    "High" level languages makes programmers not as aware as they should be when they're moving a different value into the instruction pointer, but their programs are still doing precisely that at various points.

    One could argue that C and other compiled languages are low level, like machine code and assembler, and that interpreted things such as Java, C#, and BASIC are high level, but it wouldn't have much bearing on the difficulty of living without unconditional transfer.

  13. Re:I like GOTO! by maxwell+demon · · Score: 3, Informative
    Callbacks are everywhere in C++ although these days they are often function objects instead of function pointers

    Great! I am looking for an example.

    Ok, here you go:
    #include <vector>
    #include <iterator>
    #include <algorithm>
    #include <iostream>
    #include <cmath>

    bool is_negative(double x) { return x < 0; }

    int main()
    {
    std::vector<double> v;

    // read numbers from standard input
    std::copy(std::istream_iterator<double>(std::cin),
    std::istream_iterator<double>(),
    std::back_inserter(v));
    // those iterators are actually callbacks packed into an iterator interface:
    // istream_iterator encapsulates the input operator on streams,
    // back_insert_iterator (the iterator which the function back_inserter
    // returns) encapsulates a callback to a container's push_back member function

    // replace all negative numbers with zero
    std::replace_if(v.begin(), v.end(), is_negative, 0.0);
    // is_negative is of course a classic callback (alternatively,
    // a function object obtained by binding the second argument
    // of std::less<double> to 0.0 could have been used)

    // calculate square_roots
    std::transform(v.begin(), v.end(), std::sqrt);
    // and here we have a classic callback to std::sqrt

    // sort from largest to smallest
    std::sort(v.begin(), v.end(), std::greater<double>);
    // std::greater<T> is a function object which just encapsulates
    // the greater than operator

    // output the results
    std::copy(v.begin(), v.end(),
    std::ostream_iterator<double>(std::cout);
    // the ostream_iterator again is just a callback to stream output,
    // encapsulated in an iterator interface
    }
    All standard C++, using the C++ standard library. And also it's all genuine C++ code, not just C code moved to C++.

    Show me code how to assign callbacks.

    I don't know what it gives you, but here you are:
    callback = f;
    I'm of course assuming that callback and f are of compatible types.

    These example should be pure OO.

    Why? If you haven't yet found out, C++ is not a pure object-oriented language, but a multi-paradigm language. The code I've shown actually uses generic programming (ok, it just scratches at the surface of GP, since it only uses the STL algorithms, but doesn't define one on it's own).

    But then, if you want a typical OO version of callback, just look up the observer pattern. (And no, I'm not going to show that here as well, it's already hard enough to get the above code through Slashdot's lame[ness] filter!)

    NO C global functions. (Every function is part of some object.)

    Well, in the example above, I've had only 3 C global functions: is_negative (but this would probably be better done with standard function objects and binding anyway), std::sqrt (the standard library just has this as function, but it could be encapsulated as object as well), and main (whiuch you just cannot avoid in C++ anyway). The rest are either function templates or objects (which is typical for generic programming).
    --
    The Tao of math: The numbers you can count are not the real numbers.
  14. Re:Where are all the aspect gurus? by Hast · · Score: 3, Informative

    When I tried AOP a few years back it was before they added terms like joint and concern. The principle is the same though.

    We were doing a compiler in that project and that was a very good time to use AOP. From the CC programs you get a bunch of object code, but you don't want to edit these files as they are generated by other programs. Instead we used aspects which are then "weaved" into the generated code (this is what is called joints and concerns now). In reality it's pretty much a bunch of pre-compiler steps.

    Eg we did an Aspect of the compiler code called Interpret. This aspect added a method "interpret()" in all classes, and you can easily define it's default behaviour as well as adding specific behaviours for some classes. So in our case we defined expressions to return their values and print statements to do a println and so on.

    Run the pre-compiler steps, then compile and you've got a compiler that can parse and interpret code. Now add a new stamtement type to the grammar, and do it again and you have a new compiler working. No need to hand edit a bunch of files.

    Naturally since this was in the early stages one problem was that looking for compiler errors in generated code was a bit of a bother. All in all it was a great help.

  15. Objective-C has these, and no one bats an eyelash. by israfil_kamana · · Score: 5, Informative

    A very well respected (if a bit obscure) OO language is Objective-C. It has a few features that are analogous to aspects.

    First it has categories, which allow you to add new methods, or replace the implementation of a method in a given class. You add a category to that class that includes, say, a replacement description method, and the new implementation replaces the old. Or you can add a path utility methods to the String class, as another example.

    It also has "poseAs", which allows you to insert an object into a hierarchy. Say you have Class B which extends Class A. But you want custom behaviour to be accessible to all As children (including, for example, B). You create Class C, which extends A, and make it poseAs Class A. Now the runtime hooks all clesses that inherit A off of C, which replaces A. A still exists, but is in the background, providing normal inherited functionality to C.

    These abilities were one of the things that made Objective C a very powerful OO language. In particular, it allows a particular kind of reuse, by virtue of allowing one to patch someone else's library without having source. So if you want to add the aforementioned path utilities to NSString, you just added a Category in your own code, and your code would run with the extended functionality in NSString (no one elses would, unless they used your category too). This means that often you can make the most out of someone else's code without a complete rewrite.

    It isn't all powerful. There are access and visibility restrictions. It is also used to partition code for ease of organization and better modularization. Here's the point:

    Because it is a well known feature of the language, you think in terms of categories, and you know to look for categories. This can be aided by tools, but you most definitely need to pay attention to them. But because of this, they are only "dangerous" if you aren't paying attention. And guess what! That's true of programming, period.

    --
    i - This sig provided by /dev/random and an infinite number of monkeys at keyboards.
  16. Re:I like GOTO! by antientropic · · Score: 3, Informative

    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).

    Wow, this is stunningly wrong. That's what destructors are for - they will free any resources that your local objects allocated/acquired. That's the resource acquisition is initialisation paradigm, and it ensures that resources are always freed when you leave a scope. This is contrary to using a goto for error handling, where you still have to be careful that every execution path goes through the cleanup code. And of course, how are you going to ensure that some future maintainer doesn't add a "return" somewhere, bypassing your cleanup code altogether?

    This is one of the really nice features of C++, by the way. It's much cleaner than finally blocks.

  17. Re:Objective-C has these... by israfil_kamana · · Score: 2, Informative

    Woops, forgot to add a C2 Wiki entry for Objective-C.

    --
    i - This sig provided by /dev/random and an infinite number of monkeys at keyboards.
  18. Re:I like GOTO! by marcosdumay · · Score: 2, Informative

    GOTOs can destroy compiler optimization. Also, it destroys the flow logic of most of the programs that uses it, making it harder to read (but not all programs).

    Exceptions are what was created to avoid GOTOs on error handling, it solves problems 1 and 2 that you point, but C doesn't have them (C is old, C++ has).

    Several people use GOTOs on interruption handling, it makes their code a mess and very hard to maintain. But slightly faster.

    So, if you know how to use GOTOs (and most important, how to not use), go ahead and use them. But if you are just an old BASIC programer that dont want to take time to learn how to program on a real structurate language, get out of the way.

  19. AOP has its uses (without going to production!) by bsletten · · Score: 4, Informative

    AOP is not the answer to everything, but one of the main points of a talk I give at the NFJS show is that you can find tons of uses w/ development aspects without ever having to migrate them into production.

    You can enforce (and detect violations of) various architectural decisions (i.e. no direct JDBC connectivity without going through a facade), define and enforce contracts (and leave them out of production), detect threading issues (How would you find all Swing thread abuse cases in a large, multi-threaded application?), get a sense of code coverage, etc. around any arbitrary cut of your system.

    If you take the time to build around interfaces and support Object decoration via some variant of factories, you can do some of these things using Decorator and Dynamic Proxies, but you don't always have that luxury.

    Tool support has been fundamentally lacking until things like recent versions of the AJDT. It still isn't perfect, but should serve to answer many of the early critics complaining about debuggability and losing track of what is going on.

    I am an advocate of AOP in production systems, but you don't have to drink the whole glass to find aspects tremendously useful in development alone. With support for Ant-based builds and whatnot, it isn't even really an onerous task to give it a try.

    Don't use AOP for things it isn't a good fit for, but there are quite a few things that can't be done as easily and elegantly without it (at least in Java). As others have pointed out, languages like Lisp and Objective-C have support for AOPish things built in.

  20. Re:Of course by Anonymous Coward · · Score: 2, Informative

    Procedures are for doing reusable or at least logically separate things, not for pure code formatting.


    Different branches of an if statement are, by definition, logically separate.


    You don't want proc that calls proc1 and proc2 (because there are no meaningful names for what they are doing) with 10 arguments each.


    Splitting code into procedures provides an opportunity to invent meaningful names and write documentation. More often that not, both can be constructed for otherwise nameless code blocks and improve their readability.


    Functionality unique to your application often can not be and, as long as it's not reusable, there is no reason to waste time thinking about it.


    Apart from the readability argument, there is another benefit from extracting a chunk of code into a procedure: It is natural to attach specifications to procedures, much less common to do it for code blocks. Provided that the specification is easier to understand than its implementation (often, but not always the case), your code becomes easier to comprehend:

    1. The newly introduced procedure can be read, analysed and tested without its original invocation context.
    2. The invocation context can be reasoned about without the need to understand the procedure's implementation (the specification alone will do).

    You can also check the specification by including runtime assertions. Entry and exit points of procedures are a perfect location for such checks.

    Your statement that procedures are for avoiding code duplication is correct. However, there also have other legitimate purposes, which I hope you will recognize when assigned the task of understanding and testing other people's code.

    Concerning your remark about passing a big number of parameters, you should use the opportunity to encapsulate sets of semantically related parameters into a named data structure or class (if using an OO language).

    Are you familiar with the "Design By Contract" concept? If not, I can heartily recommend reading up about it.
  21. Re:I like GOTO! by top_down · · Score: 2, Informative

    Resource acquisition is allocation is a technique, not a language facet.

    Of course, check out http://c2.com/cgi/wiki?ResourceAcquisitionIsInitia lization

    it works perfectly well in C.

    How does it work without destructors?

    --
    Anyone who generalizes about slashdotters is a typical slashdotter.