After two years of development at $work, we've finally completed our program of stability work. To the great credit of our 7 man Perl team, we've managed to reduce the (predicted and actual) downtime to about 1 hour per year for our billion dollar ecommerce site that you've never heard of.
With stability solved, our focus now turns to usability. And unlike downtime, which is easily measurable, we don't really have any decent metrics for usability. So in preparation for this massive new metrics development push, for about a year now I've been hacking away on Aspect.pm, Perl's Aspect-Oriented Programming framework.
My plan is to use this to write performance and metrics plugins for a generic Telemetry system, which would less us turn on and off metrics capturing for a library of different events, driven by configuration on the production hosts.
If you have never encountered Aspect-Oriented Programming before, you can think of it as a kind of Hook::LexWrap on steroids. The core "weaving" engine controls the selection and subjugation of functions, and the generation of the hooking code, and then two separate sugar-enhanced mechanisms are provided on top of this core to define what functions to hijack, and what to do when something we hit one of them.
The main power of AOP is in the ability to build up rich and complex hijacking conditions, and then write replacement code in a way that doesn't have to care about what is being hijacked and when.
This ability to manipulate large numbers of functions at once makes it a great tool for implementing "cross-cutting concerns" such as logging, tracing, performance metrics, and other kinds of functions that would normally result in having a bucketload of single lines scattered all through your implementation.
It also lets you do semi-evil "monkey-patching" tricks in a highly controlled manner, and potentially limited to only one single execution of one single process, without having to touch the target modules all the time everywhere.
For example, the following code shortcuts to false a particular named function only if it isn't being called recursively, and randomly for 1% of all calls, simulating an intermittent failure in the interface to some kind of known-unreliable hardware interface like a GPS unit.
use Aspect;While the original implementation of Aspect.pm wasn't too bad and did work properly, it had two major flaws that I've been working to address.
before { $_->return_value(undef); } call 'Target::function' & highest & if_true { rand() < 0.01 };
use Aspect;
# Hijack the first four calls to any foo_* method my @values = qw{ one two three four }; before { $_->return_value(shift @values); } call qr/^Target::foo_\w+$/ & if_true { scalar @values };
use Aspect;
# Trap calls to a function we know behaves badly in list or void contexts before { $_->exception("Dangerous use of function in non-scalar context"); } call 'Target::function' & ! wantscalar;
Adam, this is cool.
However, I'd be interested in the nature and details of your never-heard-of billion dollar site stability work. Any chance of you talking about that?
I should file this as a bug or fix it in SVN myself, but there's a small typo in the synopsis of 0.90: s/\$wormhole/\$aspect/
One thing I think you should note in your documentation is that it's very important to realize that a software must not rely on aspects to function. That is to say, if all pointcuts fail to match, the software must still be correct. Aspects should generally add behaviour, not alter it.
For example, one common example Java programmers use is to mark code as thread-safe. However, if a pointcut fails to match a function which must be thread-safe, the match fails silently. As a result, previously thread-safe code may no longer be thread-safe, but the programmer won't notice this and the tests are highly likely to not catch this.
So if your pointcuts match all methods named foo_\w+ and some bright programmer fails to notice this action at a distance (and who would fail to notice action at a distance?) and renames some functions that the pointcuts previously matches, the behaviour of the code is altered, but it's awfully hard to notice this.
This is one of the reasons I like roles. Consider this:
package Role::Munge;
use Moose::Role;
requires qw(some_method);
before 'another_method' => sub {
my $self = shift;
my $stuff = $self->some_method;
# do more stuff
};
If some enterprising programmer renamed either 'some_method' or 'another_method', this code fails at composition time and gives composition safety. AOP silent-failure modes and action at a distance were one of the reasons roles (traits) were created in the first place.
So if you find AOP useful, that's great. Just note that if your system is incorrect without aspects, you have a major design flaw.
Re:Some issues with AOP
Alias on 2010-05-28T00:02:58
I concur.
I think while AOP shows promise for stuff beyond just tracing, logging and what not, I can't see how you could scale it without similar problems to many other purely declarative systems like XSLT where actions happen in parallel and at a distance.
I'll look at adding some kind of Aspect Caveats section to the documentation.
Hey, what's with the bitwise operators? Would ordinary boolean operators work here or are you doing this for some invisible-to-me precedence reason?
Re:Bitwise operators?
Alias on 2010-06-03T00:34:14
Historical reasons.
The bitwise operators were the ones that the Aspect library used when I took it over.