98 comments

[ 4.4 ms ] story [ 161 ms ] thread
I've seen the classic examples of AOP being used to log or authorize, but you can easily do that without "cluttering your code" by using the decorator pattern? Decorators are also nestable (when you want to both log and authorize).
Decorators are one implementation of how to do some kinds of AOP afaik?

If you look at a system like PostSharp or AspectJ, they do it via attributes which modify the way in which a method is invoked, or even if it is invoked by the system.

There are other ways to use those systems, by programmatically targeting parts of code to modify via reflection, but that is the simplest and most common.

You start with reification of some part of your system/codebase. Once you have that, it's easy to adapt things. For example, in an OO context, once you've objectified your method call instance into an object that has reference to the caller, callee, parameters and the code to execute it's easy to log it, decide if the caller needs to be authorized aso...

The are some other considerations like when (compilation time, run time, ...) It's a really interesting code organization strategy and the only problem I've experienced with it is that (for example decorators) introduce a static layering (the order of the decorators) that works against the inter-aspect dynamics. For example, a call only needs to be logged if there is a necessity of authentication in a distributed context. In essence, you might not be able to have a static ordering that works for use cases. (security on top, or on the bottom ?)

Which would be a form of AOP that mostly emerged after the AOP hype of the late nineties. I was looking at things like aspect oriented programming and subject oriented programming around 1999-2000 time frame as part of my phh d thesis work. There were a few interesting derivatives.

The main aspectj work sort of folded into the spring framework which still uses some parts of that as part of the annotation processing in e.g. hibernate. That's the main reason, it bundles byte code manipulation libraries. So it can weave in some aspects. But most of the aspectj language level features never really became a mainstream thing.

Decorators, annotations, and similar language constructs emerged as a good enough thing that did most of what AOP was trying to accomplish. Python added those around 2003. Java gained annotations with Java 1.5 which was a few years after that. Other languages use macros or similar features to achieve the same thing. Mostly good enough though IMHO people over use them.

BTW. if you are into functional programming, this should feel really wrong. Basically you are deliberately adding side effects to your code by saying around or before/after a call to this, also do that. Of course the goto functional programming solution of monads has its own issues in terms of complexity, philosophical debates, and people having a hard time understanding them. The researchers in Utrecht where I studied in the mid nineties were into monads big time. My compiler course was basically about the compiler generator technology they were working on. Monads top to bottom. I liked aspectj a lot better after that. Easier to grasp for my primitive brain.

> BTW. if you are into functional programming, this should feel really wrong. Basically you are deliberately adding side effects to your code by saying around or before/after a call to this, also do that.

A decorator in Python (and I guess other languages) is a nothing but a function of a function. And you’re taking those side effects out of your function. What’s not to like?

You are confusing the implementation and the syntax. It's a special syntactical construct (an annotation) that gets translated into function calls that amount to doing AOP type things around the call to the original function. That's more or less how annotations work in other languages too. Some do it with reflection, some do it at compile time, etc. The goal is the same: get rid of the drudgery of spelling out the function call in order to introduce the side effect in the form of a thing that gets called before/after/around another thing.

Not against that btw. and I always appreciated how pragmatic python can be with a lot of things.

(comment deleted)
> A decorator in Python (and I guess other languages) is a nothing but a function of a function. And you’re taking those side effects out of your function. What’s not to like?

A Python decorator may be "just a function", but in order to do anything useful it generally has to do some kind of nasty hack - mutable state, global state, monkeypatching or the like - because the meat of what a decorator does usually lies in what happens when you call a decorated function, not in when you define it.

I created an AOP library for .net core. It uses the decorator pattern. Aspects are attributes you apply to classes and methods. It was fun to implement.

It was all the rage in game programming. I never used it myself. As all of the other comments have mentioned, it ends up being a kind of anti-pattern. Nobody wants logic strewn about your code base. It might as well be called side-effect based programming.

Ah this takes me back to my first job. Medical start up, working on a very long term project that the company was completely unequipped to deliver. I was young and keen so didn't appreciate the context, I just did stuff.

For some reason, we hired an "architect" from a large Bank that used Java. He used to make proclamations like "we should write tests", but never actually got into more detail than that. One of his preachings was about aspect oriented programming and how it was going to be future. He did a talk on it, and I remember someone asking "what else does it apply to apart from logging and Auth?"...no answer.

Not long after, I left. 2 years there, probably over 15 engineers, I don't think we built anything close to a functioning software product.

Caching, managing object life times in IOC containers, input validation, are some common uses I've seen.
Thanks - I'm sure there are other uses cases, I didn't mean to imply there weren't, just for me I always associate AOP with a time in my career where nothing useful was ever shipped, but there was lots of pontificating and philosophising about rather abstract things like this.
Lol. I associate C++ with that. Turns out the first big C++ project I was involved with had a lot of research types. To their credit, when they did something it was almost always the best thing to do. But there was A LOT of thought and discussion put into each line of code.

For a decade I thought about C++ as being "the language for people who are forced to think long and hard about every line of code."

And then I saw "normal" C++ code everyone else was writing...

I now sort of like it (C++), especially after recent language additions.

Transaction coordination, Error management (retry, backoff, fallback).

In the end, aspects were always used for the same transversal concerns, which any serious framework would provide explicit support for anyway.

Going beyond these to use aspects for application domain modeling is popping open an immense can of carnivorous worms on the dev team.

It seems to me a lot of these things are handled more plainly by delegation and composition.
It's also good for teasing out functionality for complex legacy code. We used AOP tools to construct a call graph in the spaghetti section of the codebase. It was then used to figure out what we had to refactor.

Worth thinking about if, like our app, it's crazy spaghetti and call graph is based on runtime state (which makes static analyzers less useful.)

I have also been there. I remember it vividly when using the ZEND framework in PHP. It is a fad, in my opinion.

Also architects doing their stuff: they for sure know best how to couple boxes in a PowerPoint presentation. ;)

Question: how do you think the binding of isaac lets you mix and match so many abilities? The number of item/effect synergies is really striking in that game. Is it a series of decorators, or something AOP like?

My personal theory is it's something decorator like that involves modifying something like a struct which gets converted into game effects after all the modifiers run

This article just made me wonder about this again

I'd expect any non trivial (like changing color/size) interaction involves custom coding of a wholly new, invisible item as in "if player has item 300 and item 278, make them inactive and give him secret item 1200".
Based on a link somebody else posted, that may be right. The number of combinations is so large though. It must have been exhausting
As Binding of Isaac is a money printing machine, it was definitely worth it.
This dev-blog article from 2015 pretty-much admits that it's all done manually: https://web.archive.org/web/20151108110741/http://bindingofi...

> now as some of you know, item combos have to be hard coded at this point due to how nutty some of the tears are in rebirth. the most requested update you guys wanted in afterbirth was item combos, so the team has been hard at work the past few weeks mostly focusing on said combos.

Oh my goodness that must have been exhausting by the time the game was done
(comment deleted)
Entity component system is popular for this
A more practical explanation of stuff you can do with AOP that would be challenging otherwise: https://debugagent.com/spring-boot-debugging-with-aspect-ori...
Looking at what AOP is still used for nowadays, it's pretty clear that the killer application for AOP was always logging (and nowadays that would also include tracing).

There were many other suggested use cases of course: transactions, caching, application-level security, dependency injection, retries, refactoring a legacy app...

The problem with all of the other suggested use cases is that AOP was never a great fit for them. AOP is always casting a wide net, which has the following two properties:

1. False positives: It's very easy to wrap all methods in the com.myapp.* package which fit the pattern "interact*()" with some transaction manager, or to cache their result. But a few years later a hapless developer adds another "interact" method which suddenly starts to misbehave. Logging has very little impact on behavior, but most of the other use cases (even caching and retries) can greatly impact behavior, so you'd usually want more control on them.

Of course, you could define a very narrow pointcut which doesn't risk a false positive — but in that case, why shouldn't you just use an annotation?

2. Large amount of join points: AOP works great to save you writing a lot of boilerplate code when you need to modify a large number of functions in a similar way. In modern programming languages you'd often use annotations (which postdate AOP, especially within Java), but even adding a single annotation on thousands of methods is tedious and error-prone. But again, out of all these use-cases, only logging really often needs to do the exact same thing for thousands of methods.

Caching, Retries and Dependency Injection require more in-site knowledge that is easier to achieve and more readable with annotations: which values you want to cache, what dependencies to inject and what would be their life time and source, which retry strategy should you use, etc.

I am happy that AOP never really caught on. The idea of modifying the behaviour of a class in a completely different place of the source tree has always horrified me. Makes it pretty much impossible to understand code by just looking at a single file.

Admittedly, IDEs were pretty rudimentary when AOP was first introduced, and today's IDEs can display aspects right where they are applied. But I still think that annotations/decorators are a far better solution for the problem that AOP tried to solve.

Yeah, I feel the same. I think that in the surface the idea was interesting, but the final implementation, with the "weaving" involving code transformation, and also the fact that there was some "inversion" of control flow (where the COMEFROM comparison comes from) weren't really good. All that flexibility was probably a good selling point, but it just wasn't necessary IMO.

I think in the end things like Python decorators follow similar ideas without the problems associated. No magic, just functions.

I do like that AOP helped "Cross Cutting Concern" become a common thing in development vocabulary, though.

> but it just wasn't necessary IMO

AOP makes Java style logging incredibly convenient. That was not only its main selling point, but also it's one single benefit that people managed to identify during the few years it was hyped.

IMO, Java style logging is bad by itself. But surely, AOP does make Java style logging much more convenient.

I have the opposite view. AOP didn't go far enough.

At this point we're reaching limits of IDE support[0] and language design. New languages have been piling up increasingly arcane mathematical abstractions to try and squeeze in a lil' bit more stuff in the same number of characters, but they too can't get past the fundamental problem: there is only so much you can fit in a piece of plaintext. You can't cover every cross-cutting concern at the same time in the same piece of code, and keep all of them easy to understand and work with. That is, we are being limited by insisting on directly authoring and manipulating source code that's plaintext and a "single source of truth".

AOP solved a lot of these issues, but it was hamstrung by being deeply embedded in that "plaintext single source of truth" code workflow. It was confusing to have "spooky action at a distance" that's invisible unless you're using a properly configured IDE, but the real problem here is that we expect everything to be local in the same piece of text. If we drop that expectation - learn to work on higher-level representations of the underlying source code - then none of it is a problem anymore. AOP becomes a subset of more general techniques of authoring software systems.

--

[0] - Perhaps with exception of LLMs, which promise to be able to both write heaps of code and explain what all that code does. We can probably ride quite far in that direction, but it'll also eventually hit the same wall: no more progress until we abandon the idea of working with the plaintext source code directly.

>It was confusing to have "spooky action at a distance" that's invisible unless you're using a properly configured IDE

This statement seems slightly ironic given the prevalence of IoC containers in the most commonly used backend languages and rampant 'interface everything' syndrome alongside it. You'd almost think such a world would embrace AOP.

If you look at something like Spring, IOC isn't the issue but annotations are.
It's a silent change, but I think IoC is becoming commonly accepted as a bad pattern. (And I'm sure we will see a overreach at some point where people do bad things to avoid it.)

The most commonly used languages are there because the most conservative people (that hire a large majority of people) pick them.

Which IoC? So many different things meant by it. Often overlapping with Dependency Injection.

In the most basic form - passing dependencies to a function/object constructor, instead of instantiating them within said function/object constructor - I feel it's mostly a good idea, but there's something that's bugging me about this, and I'm not yet able to pinpoint what that is.

The more advanced form - autowiring things with magic annotations - yeah, I hated that when I was working in Java. However, these days I think the problem isn't with the idea, but - again - with our insistence on working with the final, pre-binary, single source of truth plaintext code. That is, autowiring is the kind of stuff that really needs support from tooling able to tell you what exactly happens in a particular scenario - it feels icky, but it does so because we're not used to relying on tooling over raw plaintext. And what I'm saying is, we should ditch the plaintext and embrace the tooling, or else we'll remain forever stuck in endless debates about code style, readability, etc. - problems that, to me, seem to be fundamentally caused by hard limits on how many cross-cutting concerns you can cram into a single block of text.

> Which IoC?

Well, I have seen some heat specifically against the habit popular on Java and C# of passing dependencies into factories and getting classes with completely different interfaces as a result.

But you are correct, IoC is a very generic name that can cover even passing lambdas to a map function.

Anyway, the problem with deviating from the single source of truth plaintext is that when other sources of truth have bugs, you are usually forever unable to fix them¹. This is because when people create tools that affect the program behavior independently of your source, they inevitably make them stateful. A stateless tool could in theory not have this problem, but then, developers are annoyingly good into reducing your stateless tool "configuration script" into normal code and using that instead.

1 - There are many programing environments where tools affect the program behavior. Those are usually aimed into novices and non-programmers, because programmers with any experience are quick to reject them. A very common theme on those environments is that error that keeps happening despite everything being correct, and can only be solved by throwing your work away and doing everything again exactly the same way from scratch.

> Well, I have seen some heat specifically against the habit popular on Java and C# of passing dependencies into factories and getting classes with completely different interfaces as a result.

In other words, you saw a bug in a software implementation.

What leads you to look at a bug and from that lay broad and overreaching personal claims about fundamental design patterns?

> but I think IoC is becoming commonly accepted as a bad pattern.

I have no idea where you got that from. Inversion of control is perhaps the most important design pattern ever devised, and IoC frameworks make or break any project.

Where exactly did you get your personal assertion from?

>Makes it pretty much impossible to understand code by just looking at a single file.

This always made OOP so frustrating to me. You have to go look in tons of places and build a mental model of all of them to have an idea what code does.

Unless you had a nice UML diagramm.

(But then you had to keep the diagramms in sync with the code)

And someone invented BPML. So one can have diagrams and code!
Are there programming paradigms for which this isn’t the case?
Well, the functional programming promise is that output is a function (pun intended) of inputs, no side effects allowed, thus you can understand any given function by reading only its code, no additional context required.
I find if you combine this with a strong type system like in F# the promise is upheld. You need strong type guarantees on the parameters to make it easy to understand the behavior though.
That's not a pun, that's just the definition of 'functional'.
Sure, but that's still you having to go to lots of places to understand what individual functions do, which doesn't seem different to the original complaint about OOP.
I at times still write spaghetti code. I switch to functional if the repetition justifies it. It can be quite beautiful.

OT: I remember someone noting that he tried to have modular code with as little functionality per module but reached a point where searching for things took writing it.

I sometimes ponder if it would be possible to create a DSL for every tiny niche part of each application. It would likewise depend on how awesome the documentation is and if you can even find it.

I think LLM's could make both ideas viable. One would be able to find anything and figure out how it works.

Could have a committee that judges how reusable your language extensions are and possibly grades you as a programmer with hints on how to improve.

Writing the coffee machine DSL should give you bragging rights.

A bit like languages nowadays accumulate new features.

In an ideal world one would write functions, the repetition of those would be documented and spawn language features, repetition and usage of those would spawn features in the lower language in which the language is implemented which in turn would spawn machine instructions. All using real data in stead of a cultivated hunch or popularity contest. How many installs, how often does that part of the code run, how many cycles can we save globally.

People don't want DSLs though. There's an enormous contingent of programmers these days who seem to object to literally any form of abstraction at all. They just want to see - imperatively - what something does from top to bottom.
It’s possible to write OOP code where each object stands alone in its own file and just passes messages to other objects. Breaking things up is supposed to make things easier and it usually does.
IME, most programmers just write procedural/imperative code using OO semantics rather than trying to write actual OOP code. If you're auto-generating your data classes and then writing static utility functions to operate on them, you're about as non-object oriented as you can get in an object oriented language.
In some ways I agree, but OO is best in moderation. The goal is to be a more useful programming technique and large class hierarchies filled with self contained objects end up being ridged when you want to change fundamental aspects of the project.

The objects in OO really don’t need to have anything to do with the outside world.

Projects where the majority of the code sits in a handful of giant bloated objects passing messages to each other tends to be quite flexible in practice. This works because rather than treating objects as consistent bundles of data and code throughout the program it’s often useful to think in terms of different contexts and then pass messages between those contexts. The advantage being you have fewer side effects to worry about when editing code, which is really the entire point of OO.

> don’t need to have anything to do with the outside world

I'd take that a step farther - if they do, that's probably a sign that you're taking things too literally. They probably _shouldn't_ have anything to do with the outside world. I die a little inside every time somebody introduces inheritance with that stupid Dog extends Animal example.

Yes. It pisses me off to no end that in most modern OOP schools there is a noticeable lack of "objects knowing about themselves", since a lot of focus is put on on reducing file LOC and splitting things mindlessly.

I've seen developers claiming that "encapsulation" is just about "having getters/setters and private fields". But those getter/setters never get any custom implementation, they're just accessors for other classes.

It's not really OOP anymore at this point. Classes are just records, or depositories of procedural subroutines.

In the name of Single Responsibility Principle, there's a lot of breaking up classes in a way that forces encapsulation to be broken. A class doesn't always have to expose all its data!

Why is OOP different in that regard? Functional programs can also be spread over many files.

The difference between FP/OOP and AOP is that with FP/OOP you know where to look. References outside the file you are currently looking at are usually declared at the top of the file. While with AOP there are no references in the files that are affected by AOP code.

There is good and bad OOP, but I think a frequent problem that occurs in _older_ OOP is that when people try to abstract things you end up with functionality in base-classes that can possibly be overriden in subclasses but it might be complicated for reasons and carries extra data, the control flow going from parent->child->parent->child,etc in unfortunate cases (some of this can probably also be attributed to programmers caring more about memory consumption back in the days OOP was popular, leading to many cludgles).

With a more functional style you often rather make consumers to pass a function with behavior modifying code, the upside is that the specialization is given from the call-site and that the callee can is more explicit about passing out things without needing to care about the specialization context, and often it's easy to compose specializations in this way in one place.

Those are not the only options. Old-style procedural structural programming while avoiding unnecessary side effects, globals etc is just fine.
A couple reasons this happens in practice.

OOP emphasizes encapsulation, decomposition, and dependency injection more aggressively than other paradigms. See SOLID. [1] While not necessarily bad, it does result in more "pieces."

Java -- arguably holding the #1 spot for OOP popularity -- requires that each (public) class be in its own file. This results in a lot of files.

[1] https://en.wikipedia.org/wiki/SOLID

Ditto.

AOP was pretty big in Enterprise Java shops in ~2007 era and I'm also glad that it never took off. It was one of those fads driven by "Architecture astronauts" and it resulted into lots of bloated code and delayed/failed projects.

I vaguely remember one major project from Visa that used AOP heavily and was an unmaintainable hot potato. It was passed on to different consulting companies and everyone messed it further by adding their idea of AOP to it.

As with all things, it is a tool to be used wisely, and refrain from seeing everything as a nail when you have this hammer.

It is notably extremely useful as a concept for instrumenting software, which is exactly what e.g APMs are doing. ASMs can go a step further and not just gather information but take action (sanitising arguments or return values, or even interrupting calls)

(disclaimer: I used to work at Sqreen, now work at Datadog)

Short of having first class support for AOP we're left implementing our own solutions to hook and instrument on various languages.

Some examples:

https://github.com/DataDog/datadog-instrumentation-gateway-r...

https://github.com/sqreen/go-agent/blob/master/doc/instrumen...

I totally agree. A few months ago I was debugging some AOP related code and it was not a fun experience.

Let’s making coding even more complicated so you don’t know what the src output will be.

But you will still have a job when everyone around you is replaced by a large language model. :)
macros in rust have access to the AST as well and is used a TON in language. AOP is at times tricky to debug, but super useful for cross cutting concerns.
Separately from AOP being an incredibly bad idea, implementations of it contain another bad idea: using pattern-matching on symbol names to select a point cut.

As a Lisp person, Kiczales should have know better than to go around splitting atoms.

You're really planting a bomb when you capture not only every currently available method that begins with "Get", but any future one someone may add.

Computer programs should ideally have the property, that any identifier occurring in the system can be replaced by one having a different name (other than that of some existing identifier), such that the program's behavior doesn't change. The only thing significant about identifiers is whether two identifiers are the same one or not. An identifier's name is just for debugging, and for linkage across subsystem boundaries that do not admit the passage of object identity.

It is bad for changing the behavior of the programs intent (for lack of a better word), but it is good for observing it. Non-intrusive selective logging is the obvious use case.

Generally though I agree, architecting your software around AOP features is a terrible idea. Strike the O in any programming paradigm and you will be better off.

I think AOP is basically a tool that is most valuable to people who are dealing with some monstrous legacy code base that they don't want touch (let alone fully understand), but are under pressure to deliver some solution to a new requirement.

It lets you play games like "when the legacy code makes certain calls here and here, we do our thing, with our state, without disturbing anything else". If it doesn't work out, the AOP stuff can be easily backed out and disabled (or further massaged), rather than reverting some commit over 37 files of legacy code.

It's kind of like the software equivalent of patching a circuit board with wires. You have a bad circuit board, already manufactured in some quantity, and need to demo the hardware tomorrow. No time to spin another rev of the board.

> Makes it pretty much impossible to understand code by just looking at a single file.

But note the irony there! The motivation behind AOP is to have separation of concerns. For each new requirement heaped onto to the system, we can ideally just write one new module (using AOP), so that that requirement's implementation is concentrated in one place.

Oh, someone wants logging for all the database calls? No problem; don't touch the database calls: let's write an AOP module which invisibly adds logging to them from a far. Then when you're looking at the database calls, you can pretend that logging of database calls doesn't exist (or not even know). When you do know there is such a thing, you have it in one place. Yay!

The thing is, why is it the database calls that are at the bottom? Because they were written first? Something has to get written without AOP in order for AOP to have something to hook into. One problem in AOP is that we can't start with an empty program, and then go through a list of concerns and implement them with AOP. It's just a kind of parasite that needs a host.

Having too many concerns involved with a piece of code is the problem. AOC doesn’t fix that, it makes it more likely to happen.

In the time since AOP, a lot more API designs have sprouted support for closures. That’s “around” semantics and the degenerate cases of around are before and after, which covers most of the space. Async/await fills in some of the remaining gaps.

Speaking of around semantics, which brings me to the CLOS topic, I first learned about AOP in 2005 at a live presentation by Kiczales.

He made disparaging remarks about Lisp and said that CLOS should be pronounced "see loss". Remember, that's the author of the Art of the Metaobject Protocol, and of the Closette project (portable implementation of CLOS-like object system).

Anyone using Python, Ruby, Java and .NET frameworks is using AOP, how much of it depends on the framework.
I remember writing a state machine system for a project to replace Windows Workflow Foundation (shudders) using Aspect.Net (IIRC) many years ago and it working but being a faff.

But then I think to something like the bracket function from Haskell: https://hackage.haskell.org/package/base-4.18.0.0/docs/Contr... That's 2 orders of magnitude simpler and doesn't need arbitrary function definitions so a pointcut can be defined against a block of code.

The paper linked to in the Criticism section of the article [1] is well with reading, IMO. It points out that AOP only works when there is "obliviousness of application", i.e the code to which the aspect is applied doesn't need to know about the aspect, and argues that this kind of obliviousness is pretty rare.

1: http://onward-conferences.org/2009/files/steimannessay.pdf

Oh, I made an AOP library for python when I started uni back in 2008. That's actually where my nickname comes from: aspyct (a concatenation of aspect, python and aspyc).

Sadly I lost the contents of the old wiki. I did pour a lot of energy into that :) But I still have a page on my old blog: https://www.aspyct.org/aspyct/ (funny URL though \o/)

It's never been a huge hit, but from time to time I would get emails from students who were investigating it for a university project. That was fun :)

Is was adding some interesting features (notably pointcuts), but ultimately it didn't bring enough added value compared to a simple decorator in python, and I ceased development.

It's still available on SourceForge, for those interested: https://sourceforge.net/projects/aspyct/files/aspyct/

EDIT: wow that was 15 years ago. I'm not getting younger :p

In my experience, it's best to keep AOP usage limited to very well defined areas. Logging is fine, making your own AOP stuff is often a recipe for overly clever code.
Great way to get mired down in bullshit.
Since I learned what is it it's been my strong feeling that AOP is a very big (and somewhat misleading) name for such a small thing
I was already dubious of the concept, but the account example in the page makes me even more so.

"Transactions, security, and logging all exemplify cross-cutting concerns."

Logging, maybe. Transactions and security? Hard disagree.

And then in the example it checks for enough funds to make the withdrawal, and then makes the withdrawal, not as an atomic ation. I don't think that's just nit-picking on a simple example - how AOP interacts with atomic actions is fundamental to the use-case.

I did an AOP project back in ~2007, where we used crosscutting to separate the frontend from the backend, due to the belief that in the future we would swap out the thick client frontend for a web client.

It was kind of a nightmare (to put it kindly). This was an insurance app where updates in the frontend would automagically be pushed into the backend algorithm, which would automagically push changes to the frontend, which would automagically push changes to the backend...

The client also didn't pay for regression tests (2007 was a long time ago!) so basically every week it infinite loop locked in some new and undebuggable way.

I dabbled a bit using AOP in college, mostly trying to get software to do new things via dynamic updates.

It was the most awful paradigm to use, had to develop a test harness just to figure out the configuration and control flow for experiments.

For it's very niche use cases I'm sure it's interesting, for anything else it's just too difficult.

Emacs Lisp has AOP built in [0], and in this context it is extremely useful. The common use case is advising functions of a third-party library without modifying the library itself (which wouldn't survive e.g. an upgrade). This is a bit atypical, though, in that the end user is a programmer here. A blog post I wrote last year [1] is one example of advising functions sitting pretty deep in the chain of function calls to achieve something useful.

That said, even the manual says [2]:

> advice-add can be useful for altering the behavior of existing calls to an existing function without having to redefine the whole function. However, it can be a source of bugs, since existing callers to the function may assume the old behavior, and work incorrectly when the behavior is changed by advice. Advice can also cause confusion in debugging, if the person doing the debugging does not notice or remember that the function has been modified by advice.

> For these reasons, advice should be reserved for the cases where you cannot modify a function’s behavior in any other way.

[0] https://www.gnu.org/software/emacs/manual/html_node/elisp/Ad... [1] https://mbork.pl/2022-09-17_Safe_killing_with_Emacs [2] https://www.gnu.org/software/emacs/manual/html_node/elisp/Ad...

I've always put advice (and Perl's Hook::LexWrap and other implementations) in a different bucket to aspect-oriented programming, with the idea that AOP is used to build an application and advice is used to monkey-patch around a bug that isn't yet fixed upstream. But you're right, they're similar to some degree.
AOP creates 'hidden coupling', something I found extremely common while working in java.

The logic seems to be that loose coupling is better than tight coupling, and what's looser than zero coupling? It's perfect!

The trouble is that zero coupling is not really possible in a working system. Instead what you've done is introduce 'hidden coupling', where one can't see the connections between components in the system from the code.

Hidden coupling makes understanding and maintaining a software system much harder than it would be otherwise.

This is such a fantastic way to put this! Coupling is extremely important: if these two things GO together, why not make that as clear as possible?

The reason we avoid tight coupling is so that our software can remain “soft.” We can continue to mould and manipulate it even as out requirements or understandings change.

But software with hidden coupling might as well be the Internet. Sure, Google might crawl Wikipedia, and it provides some information as a result, and your software might rely on the result of some query. But then when your query breaks do you need to make a change to Wikipedia?

That’s probably a pretty bad analogy, but it’s the way I routinely felt when I was a junior working in a “hidden coupling” software system where even knowing what queries were being made was a challenge.

It's hidden from the perspective of code represented as individual byte stream files, but that doesn't need to be the only representation, though obviously it is the most common form. Or, your IDE could surface this information.
> It's hidden from the perspective of code represented as individual byte stream files, but that doesn't need to be the only representation

This is unarguably true.

However, it seems that the tooling often doesn't offer that support, or it isn't broadly known that it does. You are also marrying the tool as well as the language at that point, which might be deemed acceptable by some, but probably not all (I am not a fan of Lombok for similar reasons to why I would not be a fan of this).

To me, you are better off with simpler abstractions and practices than amping up the base requirements of your developers and tooling.

As others have noted, AspectJ was/is used in Spring, so it had a long, useful life. IBM also used it to layer in closed-source enterprise features on their open-source web server, particularly for error-handling.

AOP/AspectJ won the space, beating out the more analytical program-slicing approaches which were incomprehensible to most. Pointcuts were more understandable than slices.

It's best not to reduce AOP to interception or weaving. While "crosscutting concerns" was the animating motivation, Kiczales regretted giving any toe-hold for mechanistic ideas of AOP and removed the notion of "crosscut" or "weaving" from the language and messaging (even though load-time binary weaving was the driving feature for adoption). There were (toy) implementations in C++ et al.

The core idea is that a program has well-understood places and times - where's and when's - that a language should capture as one when they have integrity while preserving language semantics. So the join point model targets programmer-understandable things (like method call or exception handling), not just anything (like loops), and preserves type-safety and access control. Similarly, type-extensions ("inter-type declarations") were binary-compatible with the original code. Unlike extensions in Swift or Objective-C, these extensions could add state to an object, which could be published or just used privately by the aspect (traits or mixin's anyone?). Aspect instances could be created staticly, one-per-instance of an object, and even once per control-flow/thread, esp. to tunnel information from high in the call stack down below (i.e., through library call-backs, if in thread).

Tooling support was always a strong concern. It started with its own debugger and IDE, but was really a perfect match for Eclipse (and Java's then-new support for debug context for any language in bytecode). The Eclipse compiler beat javac by focusing on incremental builds, making Eclipse the best place to code. The compiler has always had to be forked and extended to support AspectJ, which is something of a black art. But it did mean you could browse any pointcuts in a code base, leaving some to use it for its program-slicing abilities :)

Obliviousness (like private static) is the safe default, but AspectJ also supported aspect-aware code with public extensions. So there were a range of aspects, from benign observers, through essential but still oblivious security/audits, to public aspects forming feature layers in a product-family scheme.

Today, Swift has many AOP-like features, and Python context managers transformed the language (so yeah, loops are sensible join points). But AspectJ was tied to Java, and all the enterprise programming-container models were careful to keep programs dependent on the containers (read: oracle databases with in-memory VM's and IBM JVMs). There's no after advice for handler join points because Java bytecode doesn't have determinate points for that. Generics and method and field handles strained the language when there has been just a few heroic maintainers. There was lots of discussion for extending the notion of control flow across threads and machines (like tracing now), but no real support in bytecode (and not enough consistency in thread models or package loading conventions to make any implementation broadly available).

And there were hard problems: can you weave an already-woven class? After much analysis and discussion, the simple answer won: The original class was preserved in the woven class as an annotation (bloating the disk size, but not the size as loaded), and that was used instead of trying to unweave the woven bytecode.

AspectJ survived for a long time both through careful semantics and by making a lot of pragmatic engineering decisions, in both cases stretching to make it usable for ordinary programmers. But it was always a derivative of Java, and was embraced/extended (and monetized and contained) b...

I worked on AspectJ during a summer internship at Xerox PARC i n 2000. Very interesting, but did not convince me that AOP was a good thing. They had a few good examples (logging was the big one), but added so many advanced features to support other use cases that the code ended up being very hard to understand. Same problem I have with Guice.
It's odd to see posts saying "Aspect-Oriented Programming never caught on."

All game developers know Aspect-Oriented Programming, just not by that name, because it's at the core of one of the most successful platforms in the industry.

Unity3d is a $14B company built in large part on Aspect-Oriented Programming and Aspect-Oriented Design.

Every time you implement a Unity Behavior in C#, you're doing Aspect-Oriented Programming in a text editor. Every time you drag a Behavior onto a Game Object in the Unity Editor, you're doing Aspect-Oriented Programming in a graphical programming environment.

I have never heard of anyone referring to the Unity way of doing things as AOP, and the docs do not call it AOP. In my view, if it was AOP, you could change the behavior of all Foo instances without dragging anything on them, and without changing the definition of Foo. If anything, Unity follows the composition-over-inheritance principle.
The article says implemented in Python, but doesn't say how or where. Anyone know? Decorators?
I think AOP is a fair tool.. for maybe 1% of of your day to day problems.

I'd say it's okay to use in combination with OOP when you (and your team) manage to strictly keep the Domain/Business stuff in OOP world and the infrastructural stuff (logging) in AOP. Whenever you start mixing any of these together or have overlapping concerns, then things will quickly get very messy. While logging is fine with AOP e.g. with Access control things easily get mixed up over time.

When I look back the last 1-2 decades, the pattern that I see is that one principle always won: Simplicity.

I'd like to end up with a little war story:

In 2017 I happened to work at a startup (~60 engineers) that started with a gazillion of Microservices in dozens of different languages and ecosystems. Then there was this Clojure guy that didn't even do that much of missionary work. When people started reading each others codebases they realized the simplicity of Clojure and later started porting/rewriting their things to it. At the same time, everything Scala was frowned upon; I vividly remember trying to understand the request a supposedly simple CRUD service was making to another service and I couldn't understand the program flow for like 2h. Needless to say that probably like 80% of the services ended up being rewritten in Clojure and the Clojure guy got called the Johnny Appleseed of the company.

I recall learning about aspect-oriented programming (AOP) waaaaay back in the day. This was before inversion of control patterns and dependency injection frameworks mostly solved that problem space in the OOP languages I was dealing with. Even before that, most people just used singletons or some kind of service locater pattern.

Now that I spend a lot of time using Typescript, I find I rarely have the same issues that required those solutions. I believe that is due to the module system but I'm not entirely sure. I'm trying to think of the classic AOP problems, like structured log providers. Maybe it is the standardization of logging interfaces and the fact that I can trivially write adapters that makes this a non-issue for me these days?

I suppose, things like context managers [1] in React solve a similar problem space. That is probably the closest modern equivalent annoyance I can think of to the kind of problem that AOP was designed to solve.

To be honest, the tools I've had available were always able to solve the problem well enough. Every AOP solution I have seen is an order of magnitude more complicated than the problem it was trying to solve.

1. https://react.dev/reference/react/useContext

There is no level of abstraction at which you can write code that won't simultaneously be too obstruse for some, and an impossible maintenance burden to others. There is no programming paradigm, language, framework or tool that is going to fix this.

Fun to argue though.

won't simultaneously be too obstruse for some, and an impossible maintenance burden to others

I think you mean either abstruse or obtuse.

Wouldn't something too difficult to comprehend be an impossible maintenance burden? Why would these be too different things?

There is no programming paradigm, language, framework or tool that is going to fix this.

Seatbelts and backup cameras in cars don't solve car accidents, but refinements are still steps forward, because systemic problems are not all or nothing.

You are correct about my spelling, apologies and shame!

Things that make low level code easier to understand and manipulate make whole systems harder to understand and manipulate. More abstractions make larger systems easier to reason about in the whole, but harder to debug at a low level. Each programmer gets angry at a different place in this spectrum. There is no refinement that helps here because every step you take is further towards one person’s taste and away from someone else’s.

The reason react hooks get so much hate it's because that it's basically Aspect-Oriented Programming. If ur project scope is small and u don't get why you need it in the first place, then it looks weird and unintuitive. But when u work on bigger projects and u have some many shared HOCs which are stacked, they become callback hells to manage and debug.

But hooks solve this cross cutting problem. It's like going from promise callback hells to async await.