My problem with AOP has always been that it makes the simple case trivial and the hard case much harder.
Looking at transactions: The 99% solution is trivial: Every service call is a transaction. AOP can save me a few lines for every method and things look much cleaner.
But then comes the huge excel upload that is performance critical. Batch more service calls to fetch additional information in the background, commit every so-and-so records in a loop depending on the data size, do a custom roll-back if things fail.
And suddenly this whole separation of concerns breaks down and creates a huge mess.
The simple case saves a few minutes, the complicated case causes weeks of depression. Not a good tradeoff from my experience.
An LLM adding to the confusion by only sometimes getting things right and explaining that the separate documents are always valid, except when they are not, well, sounds like a fun experience.
torginus · 2026-06-29 18:04:19 UTC
This is a retread of the 'animal-cat-dog' inheritance stuff we learned in our intro to OOP classes, where some people got together and put forward their own idea of programming as 'the way forward'.
And me, like others have tried structuring our code like this, and failed, assuming the fault lay not with the idea itself but our skill level. Of course, by now it's kind of common knowledge that inheritance isn't a thing that can and should be used to solve every kind of problem.
Same thing with AOP - it might be sometimes nice, but on the whole, elevating this to the language level seems to be counterproductive.
mohamedkoubaa · 2026-06-29 18:54:07 UTC
>it's kind of common knowledge
If only.
mathisfun123 · 2026-06-29 18:56:14 UTC
yea it's amazing how many goofy ass "senior engineers" are still cargo-culting inheritance.
hbn · 2026-06-29 21:28:39 UTC
In my experience it's mostly pushed by university professors that haven't worked in the industry since the 90s.
rf15 · 2026-06-30 05:25:46 UTC
And thus the people who most graduates learned under, and sometimes start founding their own companies with these principles right after.
hbn · 2026-06-30 14:49:44 UTC
I think most university graduates from the past ~15 years are more likely to get taken in by trendy new cargo-cult fads than repeat things taught to them by dinosaurs in their boring university classes.
rf15 · 2026-06-30 05:27:49 UTC
It took me so long to beat the following into my team:
Inheritance and instantiation by default is a no-no. Use instances when state would be useful to the process, and use Inheritance when you have a lot of overlap between two processes/concepts and want to simplify/unify the code base.
Application of inheritance is a reaction to the current state of the code, not a foundation you start with.
rapind · 2026-06-29 18:54:31 UTC
I always thought AOP was super cool, but also that it completely destroys readability and the ability to understand a codebase. I also think it's probably one of the worst concepts to embrace in the age of agentic coding. That would be like a foot missile.
There are a limited number of patterns that absolutely do benefit from AOP though. The obvious one is logging. I don't think there's many though.
Regardless, AOP is the last thing I'll be using these days. With LLMs I've been moving in the opposite direction with a focus on explicitness and correctness. Typed, compiled, non-null languages with clear, obvious, and well documented conventions.
jolt42 · 2026-06-29 19:27:23 UTC
I've feel like AOP is Spring on steroids. Same downside for both IMO.
ctkhn · 2026-06-29 20:41:49 UTC
I think that's a good point, never thought about it like that. I like the abstraction level that Spring Boot brings, but working with a principal engineer who was very into AOP on my previous team was a huge pain. Like you and GP said, AOP absolutely destroys readability. Current team has code split into a million xyz-common libraries, which isn't my preference, but I can still click through to see the source of the library. I will never get what AOP truly improves on
rapind · 2026-06-29 21:59:01 UTC
I think the issue is that a lot of concerns that appear to be "cross-cutting" at first glance, don't hold true to that design... but teams will try to stay the course, possibly due to existing debt, and it goes south pretty quickly from there. That's what I mean when I say there are some patterns that are obvious and proven cross cutting concerns (like logging), but there's really not a ton of them IMO, and if you're going to experiment with new potential concerns, then you must be ready to rip it up when it proves not to be the shape you thought it was.
kazinator · 2026-06-29 21:59:42 UTC
> destroys readability and the ability to understand a codebase.
Aha! That's exactly the sort of thing that can make code impervious to LLM AI.
LLMs have no grasp of any issues that are not visible in syntax, like concurrency. Code that has deadlocks or race conditions (because of other code not seen elsewhere) can look right, but be wrong.
In other words, if you write a program using convoluted spaghetti logic full of invisible data members and control flows injected remotely by aspects, LLMs trained on the code will have no understanding of it; they will just predict tokens according to the naive, visible code.
Imagine if all code out there available for training LLMs was heavily AOP. LLMs trained on it wouldn't be worth a damn. They would not correctly crib the entire solution: generate the code, and bring in the invisible aspects needed to actually make it work. All their solutions would just be the naive surface code that must be invisibly instrumented by half a dozen aspects to be complete.
AOP-heavy code would have to be somehow cleverly preprocessed for training in order for token prediction to do meaningful things with it.
Zababa · 2026-06-30 13:29:17 UTC
This is not true, LLM can write and run code to check for deadlocks or race conditions.
kakacik · 2026-06-29 19:06:27 UTC
Aspects are one of those categories of 'too powerful to be considered', or 'return value not worth the cost of troubles it can bring'.
I completely agree with you, saved stuff is normally trivial, nightmare it can bring down the line makes those war stories that are fun to listen to, but certainly not fun to walk through. I simply skip them despite ie Spring offering powerful ways to manage transactions, logging etc. decoupled from places things are actually happening.
I can imagine it working well in a disciplined team who consists of senior folks knowing their craft. Certainly I have never been part of a team with only such composition.
coldtea · 2026-06-29 22:24:39 UTC
>return value not worth the cost of troubles it can bring
That, funnily, could be the motto of Forth
Someone · 2026-06-30 11:27:13 UTC
> My problem with AOP has always been that it makes the simple case trivial and the hard case much harder.
By carefully limiting what code you can inject, it prevents you from accidentally making hard cases hard to reason about.
HelloNurse · 2026-06-26 12:04:24 UTC
Is this a joke? Instead of using a formal aspect specification that people (and bots) can get wrong, like any other programming language, trust a bot to do the right thing spontaneously and implement an aspect oriented architecture without tools?
mrhottakes · 2026-06-29 16:59:46 UTC
A few weeks ago we were calling this "vibe coding"; I guess someone is trying out some new terminology.
wseqyrku · 2026-06-29 17:26:48 UTC
This is not vibe coding though, it's just vibing.
kstenerud · 2026-06-29 17:11:47 UTC
> And the "weaver", to use the AOP term, is simply the LLM that generates the program from the documents.
Oh HELL NO.
The LAST thing you want is a non-deterministic process monkey patching your code.
fatbird · 2026-06-29 17:18:48 UTC
That's where I got an immediate migraine.
12_throw_away · 2026-06-29 17:25:22 UTC
This is, indeed, the next generation of AOP: they've managed to evolve it from "extremely complex and hard to understand runtime behavior" into "completely undefined runtime behavior". UB as a service. True innovation!
microgpt · 2026-06-29 19:47:58 UTC
That's what all the customers are demanding.
febusravenga · 2026-06-29 20:42:01 UTC
Our customers are CEOs and CTOs - kinda checks out.
AlpacaJones · 2026-06-29 17:27:42 UTC
At the end of the day aren't we all just non-deterministic process monkeys patching code?
bigstrat2003 · 2026-06-29 17:29:26 UTC
Humans might be non-deterministic, but we can reason, we can learn, and we can have incentives to be careful and not just YOLO things, all of which mitigate that risk. None of those is true of LLMs.
ratelimitsteve · 2026-06-29 18:22:01 UTC
i feel like you've just accidentally stumbled upon primate-patching as an umbrella term that can be anything from monkey-patching to hominid-adjustment via apefoolery. As a coder for 8 years I know I'm personally capable of operating at any of these levels depending on the day and the strength of the local coffee.
sublinear · 2026-06-29 20:49:20 UTC
If you squint hard enough in ignorance, everything looks non-deterministic.
We need to stop the pseudophilosophy already. At this point, the AI bros think they have cornered some grand problem. If the universe is deterministic, then we can simulate it perfectly and humans and their other machines are all redundant. If the universe is non-deterministic, then stochastic AI "will eventually get better" and can replace humans too.
Have you considered there's something obviously missing with this reasoning, and you're wrong about this like the rest of us? This is not that profound.
gavinray · 2026-06-29 18:03:12 UTC
> The LAST thing you want is a non-deterministic process monkey patching your code.
I'm not poking fun of you, but the irony here is that code-as-written is mostly a "suggestion" to modern compilers and JIT interpreters and the actual instructions emitted often look nothing like your ver-batim code.
ratelimitsteve · 2026-06-29 18:20:30 UTC
okay but at least those are provably equivalent, unless my understanding is off. isn't that the whole impetus behind the idea of functional programming?
sublinear · 2026-06-29 20:38:31 UTC
Compilers are deterministic. You can control all the input to the compiler and the environment it runs in to get reproducible builds. This isn't an accident. That's best practice.
lmeyerov · 2026-06-29 17:16:58 UTC
AOP is a big influence for how we are designing hooks, including custom ones, for louie.ai's agent harness. More principled structure to what is already expected.
I'm unclear on AOP in general, esp as proposed here. That's a bigger leap...
nh23423fefe · 2026-06-29 17:17:58 UTC
Has anyone done AOP outside of Spring Framework? That's my only exposure and it feels very library level. Nothing I would use as a the primary way to structure the code.
rjbwork · 2026-06-29 17:22:02 UTC
Yeah. I've done w/ Fody, PostSharp, HTTP Handlers, ASP.NET Middleware, Castle DynamicProxy/Interceptors, Temporal Interceptors, and various custom framework interceptors.
agumonkey · 2026-06-29 18:53:23 UTC
How was it ? Useful or mild or dreadful?
peterabbitcook · 2026-06-29 19:42:59 UTC
In the Java-verse it’s also do-able with Guice. I’ve tried it with Dagger but bailed (square peg / round hole).
I think I prefer Springboot AOP, especially with SpEL.
The term “cross cutting concerns” is thrown around a lot when discussing AOP. Took me a while to appreciate just how powerful it is in this context - sprinkle an AOP annotation here or there to avoid massive refactors in a large codebase, or avoid rewriting classes in a way that makes your classes themselves “cross cut concerns.”
microgpt · 2026-06-29 19:48:21 UTC
Briefly with AspectJ
jdw64 · 2026-06-29 17:30:30 UTC
After reading this post, I got curious and went back to read the original AOP paper. What the OP argued feels like just top-down design. These days, the term 'SPEC-driven' is trending, but it seems like just another word for top-down. They're probably just rebranding it because top-down has a negative image.
Originally, AOP was about separating cross-cutting concerns by centralizing them in one place. It used weaving to separate infrastructure code, and implicitness was inherent in that approach. But the books I read back then said this led to 'ghost code.' It inevitably introduced unpredictability because the behavior wasn't visible in the code. And from a programmer's perspective, that becomes a problem when things break.
On top of that, while the cross-cutting concerns are centralized, they still end up being tied to the framework's syntax, like Spring AOP's Join Point syntax, so they become dependent on the framework itself.
That's why DDD became popular as another way to address OOP's limitations. DDD keeps the business logic pure and framework-agnostic, and that's where things like POJO emerged. At least that's what I read in books, just different approaches to the same problem.
AOP was first presented at ECOOP in 1997, and DDD is usually associated with Evans' book. Both are ways of handling OOP's complexity, but the article here doesn't seem to talk about problems with cross-cutting concerns, which is the core of AOP at all. And this is something AI doesn't handle well. AI makes the most mistakes with implicit knowledge.
Or maybe I'm wrong because I've been studying programming history through older books and have outdated knowledge. Maybe AOP has evolved since then.
What makes it difficult to talk about specific 'oriented' paradigms in programming is that as history moves on and certain problems get solved, if you bring up an older version, you'll get pushback from people who really know their stuff.
'That's a problem that was solved 5 to 10 years ago.'
'That issue has evolved and been covered in other books.'
So it's hard to talk about any 'oriented' approach because you have to specify which era's version you're referring to. For example, even with OOP, which everyone knows, there's a big difference between Smalltalk, C++, and the modern emphasis on composition over inheritance. Someone might say, 'Modern OOP is centered around composition and value objects — you're behind the times.'
AOP might have also evolved and introduced different solutions since then.
So while the perspective on a given 'oriented' paradigm does shift over time, it's really hard to have a conversation about programming because it all depends on which era the programmer is from and how far their knowledge goes.
That's why lately I've been thinking about problems and the approaches used to solve them, rather than focusing on the 'oriented' labels. I wish someone would write a history book about these paradigms. They'd get a lot of criticism, but for programmers like me, it would be much easier to understand.
Sometimes I think it's about time someone wrote a history book on programming
kazinator · 2026-06-29 22:06:46 UTC
I went to an AOP talk by Kiczales back in 2005. When he presented join points as being driven by regular expression matches on identifiers (e.g. Get*: for all methods starting with Get) I decided to heckle. Knowing he has a Lisp background I brought up how Lisp teaches us that symbols should be treated as atoms; good programs don't break atoms apart. I think he made some Lisp joke and brushed it aside. But it's actually an incredibly bad idea.
- Want to add a new method? Maybe it's covered by an existing joinpoint expression and pulled into a pointcut, before you've even written it.
- Want to rename something? Where is it referenced? You can't just search for its name as a whole-identifier match, but must find every join point expression that could contain a regular expression match for it, and then filter out those whose other conditions don't match (wrong class, or whatever).
It is really hokey; and AOP tooling is possible without it, just more cumbersome.
E.g. we can have a special annotation which indicates that method is a joinpoint target, written somewhere on the method. That annotation can list the pointcuts to which it belongs. That becomes more maintainable. The reader of the code knows that since the method is a joinpoint, it interacts with certain pointcuts. They are listed by name, so you can jump to their definitions. When someone adds a new method to that class, they will see that the existing methods have this cruft on them, and decide whether to crib it, and how much. Just because the new method has a certain name doesn't mean it should be in those point cuts, or maybe not all of them.
jdw64 · 2026-06-29 22:42:05 UTC
> it should be in those point cuts, or maybe not all of them.
I really enjoy hearing these kinds of war stories from senior programmers. I don't have a LISP background, but having learned Haskell, your point resonates perfectly.
In the Haskell mindset, letting string-matching rules alter the actual semantics of a program is fundamentally wrong. (At least, that's how I'm interpreting your LISP analogy since I don't know the LISP side well.)
I completely agree with you. Changing the meaning of logic based on string patterns is definitely an anti-pattern. (Though, to be brutally honest, when deadlines are incredibly tight, I've definitely caught myself mindlessly doing it too.)
kazinator · 2026-06-29 22:08:38 UTC
> So it's hard to talk about any 'oriented' approach because you have to specify which era's version you're referring to. For example, even with OOP, which everyone knows, ...
Ah, about that; regarding OOP, the "that problem has been solved" goes backwards in the revisions. Newer OOP breaks things. As in, "Ah, we have had that problem in Java since 2014, but did you know it was solved in Smalltalk 80". :) :)
jerf · 2026-06-29 17:34:46 UTC
I think there's a core of a good idea here, but as others have pointed out, letting the LLM be your "weaver" is going to be very tricky.
It's possible that what you have here is an idea for what I consider to be eventually very likely, which is a computer languages still built for humans to be able to understand and debug it, but more primarily for LLMs to write it. Write a language designed to be an aspect-oriented language from the beginning. Equip it with the ability to run something like a language server and point it at a system and get all the "aspects" running and you might have something.
But I'm skeptical of bodging this on to an existing language.
One of the reasons I suggest making it a new language is that AOP was hampered by being able to use only what languages already supported. The need for a "weaver" is a smell anyhow. Something where the aspect code is the native representation and the "weaving" simply dissolves into the compilation process would not only make the whole thing more appealing in general, I think it would also allow for some things that even code generation might have found a challenge, like aspects that can maintain guarantees because the whole process is more aspect-aware and not broken by the embedded "payload" code written by a human.
w10-1 · 2026-06-29 17:59:13 UTC
re: dissolving into compilation, I think the machine/human separation has been at work for some time. Modern languages (e.g., rust, swift) are already pioneering tracking aspects like effects, lifetimes, regions, etc. and then using whole-program optimization at compile- and link-time, largely based on intermediate languages like LLM IR/SIL, which are surfaced as user-visible features when they compose well with other user-visible language features. LLM training on these languages makes them suitable for generative AI; I doubt LLM's could pick up some new language, particularly if it weren't analogous to existing ones.
jerf · 2026-06-29 19:36:15 UTC
For what it's worth, the last time a language exploded on to the scene so quickly that people were picking it up commercially at a large scale before there was any sort of code base that LLMs could train on was Java. (Yes, there were no LLMs at the time, but I can back-project that as a measure OK.) And that was a money-infused attempt by Sun to buy their way into dominance on the Internet. It ultimately worked for Java, but not so much for Sun.
Any new language, of any kind, optimized for LLMs or not, is going to intrinsically take multiple LLM training cycles to grow anyhow. Net-net I would still expect AI adoption to accelerate those things and at least make it easier for hobbyiests to play with at some useful scale and get more feedback faster. Of course they'll also face increased competition from the other languages riding the same waves, which is possibly the bigger problem for anyone thinking of doing this.
TuringTest · 2026-06-29 18:17:56 UTC
I believe the sweet spot that makes it practical and reliable will be combining LLMs with formal verification, although I doubt current hardware is up to the task (yet).
LLMs basically solve the classic Frame problem that prevented general problem solvers to be able to reason logically about the real world; however on their own they are utterly unpredictable and unreliable.
However if the database of weights is merely used as a heuristic to guide the logical reasoning engine to promising regions of the problem space, and the program itself is written to specification directly by an inference engine, the result would be classic software not affected by hallucinations.
The LLM could even help debugging the specifications by pointing out unclear or contradicting requirements, improving the process without compromising the integrity of the result.
w10-1 · 2026-06-29 17:48:23 UTC
The idea of this post - to write separate requirements for each concern and let LLM's integrate them - is much closer to the Leo version of literate programming, which allowed documents to be composed (roughly via scatter/gather operations mediated by sentinels).
But the post entirely lacks the motivation for the AspectJ/AOP join point model: to have principled time/place for concern integration that was statically determined, type-safe, understandable to users -- and suitable for integration.
> I've also always hated the specific mechanism that AOP chose to implement it with – something called the "join point model" which basically amounts to runtime pattern matching on a program's call stack and running some code every time a pattern matches.
AspectJ's join point model is only dynamic where Java as a reference-based language could not support the static analysis. At compile-time, the "static shadow" of the pointcuts was calculated and implemented where staticly determinable; only the dynamic residue is deferred to runtime (e.g., is the caller to this method of type X?).
Many of AspectJ's join points and type extensions - method call or execution, exception throwing, field access - largely have been adopted in many languages (python context managers, swift getter/setters/extensions), and the residue are a bit hard to use.
But nothing really matches the power of pointcuts: to combine these predicates and the type-safe state-management - e.g., "when throwing an exception after a transaction, capture the span id along with the user id into a log message"
AOP was great for the 7% of code that it was intended for, but was largely displaced as too complicated. Now with LLM's it's a decent hypothesis that with proper training LLM's could actually handle the more complicated but ultimately cleaner programming model - cleaner because it avoids the scattering of similar code which makes it hard to change.
The key insight is that dominant concerns establish the basic structure of the application, leaving some important but residual aspects to fit themselves to the structure. That means the dominant structure must be suitable for the AOP integration (i.e., support the right pointcuts and type extensions); solve that and you've solved most integration issues. It's especially helpful for feature architectures, where you offer code in open-source to gain API adoption, paid for by closed-source library integrations with additional features.
pjmlp · 2026-06-29 18:08:21 UTC
It never went away on Java and .NET worlds.
Also one would say monkey patching on Python and Ruby frameworks is another way to do AOP.
dragonwriter · 2026-06-29 18:38:53 UTC
> Also one would say monkey patching on Python and Ruby frameworks is another way to do AOP.
IIRC—and it may have changed in the several years since I made use of it for this, but I don’t see why it would—the standard way to do AOP in Ruby is leveraging modules-as-mixins which are a core language feature, monkey patching is unnecessary (but since classes in Ruby are open, modules-as-mixins can be used to monkey patch classes provided by someone else just as easily as being done at class definition time.)
Aspect-oriented programming has annoying implementation in languages like Java which don’t natively support the right abstractions and where you are fighting the language to do it.
Its kind of unfortunate that those are also the languages also which do the most to shape people’s understanding of AOP.
pjmlp · 2026-06-30 04:34:16 UTC
Same outcome, different implementation, instead of compiler plugins or bytecode rewrite, it gets patched via the languages dynamism.
I would say not having language support is a king of way of language designers not approving of its widespread use.
If anything Java and .NET are slowly closing some avenues that make patching possible, like final really means final JEP in Java.
dragonwriter · 2026-06-30 15:19:42 UTC
> Same outcome, different implementation, instead of compiler plugins or bytecode rewrite, it gets patched via the languages dynamism.
While the robust dynamism of Ruby (or Python) can allow it to be used in a patching-like way, the only dynamism that is essential to the implementation is the dynamism of runtime polymorphism (common in statically-typed, AOT-compiled OO languages.) The language feature it relies on that C++ and Java don’t have is multiple inheritance that gets resolves to a defined order you can call back up from a concrete method with “super”. There is no reason a static OOP language that supports runtime polymorphism couldn’t support this; it wouldn’t even need to add overhead to compiled classes that don’t make direct super calls from their own methods.
cyberax · 2026-06-29 18:12:05 UTC
Yea, no. AOP was never worth the trouble.
And one giant problem with it is the reliance on global variables. An AOP wrapper has to modify something, and it typically does not have enough access to enough context to do it.
So it has to rely on ambient data that has to be saved in a global variable. And this is _bad_. It makes data flow opaque and impossible to follow.
And then there are issues with debugging. Where do you put a breakpoint? What happens if you try to step into an instrumented method?
PS: yes, a global variable can technically be a thread-local variable. It doesn't matter, it's still a non-local ambient state.
azkalam · 2026-06-29 18:30:35 UTC
Effect Systems are the answer to cross-cutting concerns in 2026.
amarant · 2026-06-29 18:56:32 UTC
Regarding Thomas first complaint about current implementations being runtime pattern matching, micronauts implementation of AOP is entirely compile time pattern matching.
yeah let's let the randomized sentence vomiter generate code at consistent join points
6gvONxR4sf7o · 2026-06-29 19:20:19 UTC
One piece of this that could be really nice in a normal language is if an LLM could generate a decent syntax highlighter or code-folding-spec or something for each "aspect." The idea of tooling to help us focus on one piece at a time is great, regardless of AOP.
guybedo · 2026-06-29 19:37:56 UTC
AOP is an interesting pattern but i've mostly tried to stay away from it mostly because:
- code readability and maintainability takes a hit. If you don't know things are defined using AOP in files x,y,z you can read the code and miss a whole lot of things.
- AOP implemented at runtime is a mess when you're trying to debug things
So yeah, instead of having aop defined somewhere else to wrap a function call, i tend to prefer doing it explicitly transaction(function())
dionian · 2026-06-29 19:52:10 UTC
The stuff i dreamed of doing but was smart enough to avoid in Java many years ago, is what I now use FP to do on the JVM, in scala, without any of the drawbacks of AOP. if i was stuck in java languiage on the jvm for some reason i could see the appeal of trying AOP now that LLM can assist with it. Thinking of the annoying stuff like setting up automated builds/compiler etc
twoodfin · 2026-06-29 20:21:36 UTC
Putting aside the question of whether AOP is a good model for software systems development, I wonder if the specific approach proposed is a good model for LLM-driven software systems development.
LLMs really like the most important context to be clustered in the most recent section of their window. Dividing up cross-cutting concerns into their own documents would seem to be pushing in the other direction.
heisenbit · 2026-06-29 21:20:30 UTC
Not necessarily. By handling certain aspects orthogonal to the main flow it reduces the context the llm has to keep track of and should enable deeper reasoning of the main functional logic.
orphereus · 2026-06-29 20:28:11 UTC
This is overly optimistic to say the least.
whartung · 2026-06-29 20:44:54 UTC
I appreciate the potential power of AOP, but I'm grateful I never had to use it directly.
Rather, I got to rely on the special handling within, notably at the time, the Java JEE ecosystem. It was not "generic" AOP by any means, it was specialized. But I can say that what they did support was more than enough for my applications. I never felt I could use a more flexible framework.
I know some folks that did, the embraced it whole hog, and really got some power out of it. AOP could be used to make some very powerful constructs, especially adding dynamic behaviors to existing systems.
But that was simply never something I really needed in my Java work.
tanepiper · 2026-06-29 20:53:28 UTC
Hadn't heard of AOP before, but funnily enough for a couple of years now I've been calling working with LLMs "aspective coding" - closer to switching context on different features. Will read up a bit more on AOP as it seems like the prior art on this way of thinking.
rowanG077 · 2026-06-29 22:15:29 UTC
I simply dislike AOP because it makes code much harder to follow. You suddenly have aspects which you need to be aware of at all times, worse aspects can interfere with each other in non-obvious ways. It breaks one of the core aspects of code, that you can do local reasoning.
quatonion · 2026-06-29 22:43:26 UTC
I did a lot of work on intentional programming at Microsoft back in the 90s on C++, and also intentional software later.
Our approach was quite a bit different to Kiczales at the time. We had "attribute providers" which were essentially compiler plugin DLLs.
These plugins could register themselves at various parse points and add work like enzymes on the AST.
So here we have a compile time attribute that writes all the boilerplate to add windowing support to a class. Adding message maps, hooking up the dispatch, life cycle etc.
[Window]
class MyWindow { };
It worked, but as others pointed out, it suffers from combinatorial explosion, and issues with debug ability.
That said, as long as you stick within well defined verticals it was still very useful and saved a lot of typing, and reduced cognitive load.
I have thought quite a lot about modern versions of this using LLMs and I can see why the article notices a parallel to prompts or spec based designs.
At some point I tried to make a version of the system we built almost 30 years ago, but using an LLM as the preprocessor instead of rigid AST hackery.
It works a lot better, and you can approach a more continuous interpolations between intent blocks and generated code. Even the combinatorial problem largely disappears.
I stopped working on it though because I couldn't really see anyone wanting to adopt a new language or some whacked out extensions these days.
Maybe it does have its place though in certain fields. It might be worth having another go at it with fresh hindsight.
torginus · 2026-06-30 07:35:39 UTC
The Roslyn C# has a very developed system for injecting code at compile time in a very similar manner to what you described.
On the 'experimental' side you have Jonathan Blow's language, Jai, which has integrated codegen (AST macros that look like code) + type inference into the language on a very deep level, and from the podcasts I've listened to with veteran C programmers, was that during the 90s, when the mass adoption of OOP began, but performance still mattered a lot - there were a lot of ideas around OOP that were different from how C++ ended up doing this.
The most famous example I guess being COM, which is a C object model, that solves a bunch of issues that plague C++ to this day, such as reflection and code reusability, among others.
But COM is C and entirely incompatible with C++. And the big issue imo with C++ is that like AOP here, it has elevated a bunch of arbitrary magic behavior to language level, that honestly could've been done very differently, and the C++ implementation often ends up worse (multiple inheritence is a typical example).
A similar battle played out in Linux-land, with GTK creating its GObject system, which was roughly analogous to COM, and Qt opting to hack up C++, not unlike MFC.
mncharity · 2026-06-29 22:52:36 UTC
Consider "spec -> plan -> implement" as an unremarkable agentic workflow. TFA suggests that Spec be organized by cross-cutting concerns. This seems a nice thought.
Doubts in comments so far seem around expectations of an AOP-like experience. Mainly that patching aspects/Spec makes happy changes to implementation. I'm not sure TFA intended that use. Nor that current AOP implementations provide the happy. But lets explore.
With a frontier model, a greenfield ends up with, say, some spec and plan docs, and a "woven" implementation. It's reasonable to wonder how well an LLM will then un-and-re-weave code as it makes changes. And how to express join points or equivalent. And how well code review agents will detect/check intended joins continuing to be woven correctly. And whether a baseline of the LLM simply using existing AOP tooling constitutes progress.
Coming from non-frontier models, some of that seems more straightforward. Harnesses use lots of small jobs/tasks, with subagent paperwork and reviews. Historically scarce context, precious context, encouraged tightly optimizing single-task context for each job. Including derived code "views", rather than raw code. And associated LLM-coded tooling around working with not-the-raw-code.
Thus working with name/signature pseudocode outlines. And filtering code/ast. Both get you closer to having unwoven forms, and to deterministic tooling for un/re-weaving. Consider i18n clutter - just to coddle the model, one might strip it (replace it with English), let the model work, and then restore it. Which is equivalent to un/reweaving an i18n aspect. And even read-only views, like function distillations which drop or abstract some specified variables and associated code, make that "check spec against implementation" code review easier. So, even interpreting TFA broadly, it seems at least potentially possible, no?
Big picture, I'd like to get away from traditional "repo as single point in design space, laboriously nudged around", to something which preserves the multiplicity of model/run/prompt ensembles, and allows playing on design space manifolds. "This exact code has been in prod" is useful information, if underutilized, but not so useful as to be worth discarding so many new possibilities.
aspect-oriented programming is an apt name for what i imagine will be llm-science 101 in the not-too-distant future... though i found the "joint point model" details superfluous, and the piece as a whole to be cursory... modern aspect-oriented programming will become a much richer & more robust ontological and application tapestry than its legacy predecessor... one of the core fundamentals of aspect-oriented programming in the context of llms is the need to accurately model the entanglement of related aspects... let's look at an over-reduced and isolated example using english expressions as our subject... let's define two aspects of an english expression: brevity and precision... if we amplify one aspect, the other begins to degrade... thus we need to align our models with the nature of english, and entangle the two aspects... entangling aspects changes the nature of optimization itself... we go from maximizing in one direction to pushing to the entanglement's frontier (pareto), and then steering along that frontier for taste... this doesn't mean we don't define aspects in isolation... it just means we need to align our aspects with reality and reflect its nature via entanglement...
democracy · 2026-06-30 06:39:58 UTC
One of the cowboys' techniques, only good for the writer, never for the reader
I actually dont see AOP ever coming back.
Primarily because its authors were modeling smalltalk which ironically didnt even need AOP.
At first it hooks you these cross cutting concerns, but then you realize it handled better as a simple dependency.
AOP as a concept is great, however in practice using real world tools never lived up to the implications.
Its all or nothing, almost if not all code can be written as a "cross cutting concern"
I could write one function for the rest of my life, and simply use AOP to inject a different program.
but WHY would I do that? Something so incongruous with my mission as a software engineer.
AOP was pretened smalltalk MVC glue for other languages that didnt have it at the time.
Comments
Looking at transactions: The 99% solution is trivial: Every service call is a transaction. AOP can save me a few lines for every method and things look much cleaner.
But then comes the huge excel upload that is performance critical. Batch more service calls to fetch additional information in the background, commit every so-and-so records in a loop depending on the data size, do a custom roll-back if things fail.
And suddenly this whole separation of concerns breaks down and creates a huge mess.
The simple case saves a few minutes, the complicated case causes weeks of depression. Not a good tradeoff from my experience.
An LLM adding to the confusion by only sometimes getting things right and explaining that the separate documents are always valid, except when they are not, well, sounds like a fun experience.
And me, like others have tried structuring our code like this, and failed, assuming the fault lay not with the idea itself but our skill level. Of course, by now it's kind of common knowledge that inheritance isn't a thing that can and should be used to solve every kind of problem.
Same thing with AOP - it might be sometimes nice, but on the whole, elevating this to the language level seems to be counterproductive.
If only.
Application of inheritance is a reaction to the current state of the code, not a foundation you start with.
There are a limited number of patterns that absolutely do benefit from AOP though. The obvious one is logging. I don't think there's many though.
Regardless, AOP is the last thing I'll be using these days. With LLMs I've been moving in the opposite direction with a focus on explicitness and correctness. Typed, compiled, non-null languages with clear, obvious, and well documented conventions.
Aha! That's exactly the sort of thing that can make code impervious to LLM AI.
LLMs have no grasp of any issues that are not visible in syntax, like concurrency. Code that has deadlocks or race conditions (because of other code not seen elsewhere) can look right, but be wrong.
In other words, if you write a program using convoluted spaghetti logic full of invisible data members and control flows injected remotely by aspects, LLMs trained on the code will have no understanding of it; they will just predict tokens according to the naive, visible code.
Imagine if all code out there available for training LLMs was heavily AOP. LLMs trained on it wouldn't be worth a damn. They would not correctly crib the entire solution: generate the code, and bring in the invisible aspects needed to actually make it work. All their solutions would just be the naive surface code that must be invisibly instrumented by half a dozen aspects to be complete.
AOP-heavy code would have to be somehow cleverly preprocessed for training in order for token prediction to do meaningful things with it.
I completely agree with you, saved stuff is normally trivial, nightmare it can bring down the line makes those war stories that are fun to listen to, but certainly not fun to walk through. I simply skip them despite ie Spring offering powerful ways to manage transactions, logging etc. decoupled from places things are actually happening.
I can imagine it working well in a disciplined team who consists of senior folks knowing their craft. Certainly I have never been part of a team with only such composition.
That, funnily, could be the motto of Forth
Not always. DTrace, for example, is a tool to use AOP with programs and/or the OS kernel that makes the normal cases trivial (https://en.wikipedia.org/wiki/DTrace#Command_line_examples) and the hard cases possible (examples at https://github.com/opendtrace/toolkit)
By carefully limiting what code you can inject, it prevents you from accidentally making hard cases hard to reason about.
Oh HELL NO.
The LAST thing you want is a non-deterministic process monkey patching your code.
We need to stop the pseudophilosophy already. At this point, the AI bros think they have cornered some grand problem. If the universe is deterministic, then we can simulate it perfectly and humans and their other machines are all redundant. If the universe is non-deterministic, then stochastic AI "will eventually get better" and can replace humans too.
Have you considered there's something obviously missing with this reasoning, and you're wrong about this like the rest of us? This is not that profound.
I'm unclear on AOP in general, esp as proposed here. That's a bigger leap...
I think I prefer Springboot AOP, especially with SpEL.
The term “cross cutting concerns” is thrown around a lot when discussing AOP. Took me a while to appreciate just how powerful it is in this context - sprinkle an AOP annotation here or there to avoid massive refactors in a large codebase, or avoid rewriting classes in a way that makes your classes themselves “cross cut concerns.”
Originally, AOP was about separating cross-cutting concerns by centralizing them in one place. It used weaving to separate infrastructure code, and implicitness was inherent in that approach. But the books I read back then said this led to 'ghost code.' It inevitably introduced unpredictability because the behavior wasn't visible in the code. And from a programmer's perspective, that becomes a problem when things break.
On top of that, while the cross-cutting concerns are centralized, they still end up being tied to the framework's syntax, like Spring AOP's Join Point syntax, so they become dependent on the framework itself.
That's why DDD became popular as another way to address OOP's limitations. DDD keeps the business logic pure and framework-agnostic, and that's where things like POJO emerged. At least that's what I read in books, just different approaches to the same problem.
AOP was first presented at ECOOP in 1997, and DDD is usually associated with Evans' book. Both are ways of handling OOP's complexity, but the article here doesn't seem to talk about problems with cross-cutting concerns, which is the core of AOP at all. And this is something AI doesn't handle well. AI makes the most mistakes with implicit knowledge.
Or maybe I'm wrong because I've been studying programming history through older books and have outdated knowledge. Maybe AOP has evolved since then.
What makes it difficult to talk about specific 'oriented' paradigms in programming is that as history moves on and certain problems get solved, if you bring up an older version, you'll get pushback from people who really know their stuff.
'That's a problem that was solved 5 to 10 years ago.' 'That issue has evolved and been covered in other books.'
So it's hard to talk about any 'oriented' approach because you have to specify which era's version you're referring to. For example, even with OOP, which everyone knows, there's a big difference between Smalltalk, C++, and the modern emphasis on composition over inheritance. Someone might say, 'Modern OOP is centered around composition and value objects — you're behind the times.'
AOP might have also evolved and introduced different solutions since then.
So while the perspective on a given 'oriented' paradigm does shift over time, it's really hard to have a conversation about programming because it all depends on which era the programmer is from and how far their knowledge goes.
That's why lately I've been thinking about problems and the approaches used to solve them, rather than focusing on the 'oriented' labels. I wish someone would write a history book about these paradigms. They'd get a lot of criticism, but for programmers like me, it would be much easier to understand.
Sometimes I think it's about time someone wrote a history book on programming
- Want to add a new method? Maybe it's covered by an existing joinpoint expression and pulled into a pointcut, before you've even written it.
- Want to rename something? Where is it referenced? You can't just search for its name as a whole-identifier match, but must find every join point expression that could contain a regular expression match for it, and then filter out those whose other conditions don't match (wrong class, or whatever).
It is really hokey; and AOP tooling is possible without it, just more cumbersome.
E.g. we can have a special annotation which indicates that method is a joinpoint target, written somewhere on the method. That annotation can list the pointcuts to which it belongs. That becomes more maintainable. The reader of the code knows that since the method is a joinpoint, it interacts with certain pointcuts. They are listed by name, so you can jump to their definitions. When someone adds a new method to that class, they will see that the existing methods have this cruft on them, and decide whether to crib it, and how much. Just because the new method has a certain name doesn't mean it should be in those point cuts, or maybe not all of them.
I really enjoy hearing these kinds of war stories from senior programmers. I don't have a LISP background, but having learned Haskell, your point resonates perfectly.
In the Haskell mindset, letting string-matching rules alter the actual semantics of a program is fundamentally wrong. (At least, that's how I'm interpreting your LISP analogy since I don't know the LISP side well.)
I completely agree with you. Changing the meaning of logic based on string patterns is definitely an anti-pattern. (Though, to be brutally honest, when deadlines are incredibly tight, I've definitely caught myself mindlessly doing it too.)
Ah, about that; regarding OOP, the "that problem has been solved" goes backwards in the revisions. Newer OOP breaks things. As in, "Ah, we have had that problem in Java since 2014, but did you know it was solved in Smalltalk 80". :) :)
It's possible that what you have here is an idea for what I consider to be eventually very likely, which is a computer languages still built for humans to be able to understand and debug it, but more primarily for LLMs to write it. Write a language designed to be an aspect-oriented language from the beginning. Equip it with the ability to run something like a language server and point it at a system and get all the "aspects" running and you might have something.
But I'm skeptical of bodging this on to an existing language.
One of the reasons I suggest making it a new language is that AOP was hampered by being able to use only what languages already supported. The need for a "weaver" is a smell anyhow. Something where the aspect code is the native representation and the "weaving" simply dissolves into the compilation process would not only make the whole thing more appealing in general, I think it would also allow for some things that even code generation might have found a challenge, like aspects that can maintain guarantees because the whole process is more aspect-aware and not broken by the embedded "payload" code written by a human.
Any new language, of any kind, optimized for LLMs or not, is going to intrinsically take multiple LLM training cycles to grow anyhow. Net-net I would still expect AI adoption to accelerate those things and at least make it easier for hobbyiests to play with at some useful scale and get more feedback faster. Of course they'll also face increased competition from the other languages riding the same waves, which is possibly the bigger problem for anyone thinking of doing this.
LLMs basically solve the classic Frame problem that prevented general problem solvers to be able to reason logically about the real world; however on their own they are utterly unpredictable and unreliable.
However if the database of weights is merely used as a heuristic to guide the logical reasoning engine to promising regions of the problem space, and the program itself is written to specification directly by an inference engine, the result would be classic software not affected by hallucinations.
The LLM could even help debugging the specifications by pointing out unclear or contradicting requirements, improving the process without compromising the integrity of the result.
But the post entirely lacks the motivation for the AspectJ/AOP join point model: to have principled time/place for concern integration that was statically determined, type-safe, understandable to users -- and suitable for integration.
> I've also always hated the specific mechanism that AOP chose to implement it with – something called the "join point model" which basically amounts to runtime pattern matching on a program's call stack and running some code every time a pattern matches.
AspectJ's join point model is only dynamic where Java as a reference-based language could not support the static analysis. At compile-time, the "static shadow" of the pointcuts was calculated and implemented where staticly determinable; only the dynamic residue is deferred to runtime (e.g., is the caller to this method of type X?).
Many of AspectJ's join points and type extensions - method call or execution, exception throwing, field access - largely have been adopted in many languages (python context managers, swift getter/setters/extensions), and the residue are a bit hard to use.
But nothing really matches the power of pointcuts: to combine these predicates and the type-safe state-management - e.g., "when throwing an exception after a transaction, capture the span id along with the user id into a log message"
AOP was great for the 7% of code that it was intended for, but was largely displaced as too complicated. Now with LLM's it's a decent hypothesis that with proper training LLM's could actually handle the more complicated but ultimately cleaner programming model - cleaner because it avoids the scattering of similar code which makes it hard to change.
The key insight is that dominant concerns establish the basic structure of the application, leaving some important but residual aspects to fit themselves to the structure. That means the dominant structure must be suitable for the AOP integration (i.e., support the right pointcuts and type extensions); solve that and you've solved most integration issues. It's especially helpful for feature architectures, where you offer code in open-source to gain API adoption, paid for by closed-source library integrations with additional features.
Also one would say monkey patching on Python and Ruby frameworks is another way to do AOP.
IIRC—and it may have changed in the several years since I made use of it for this, but I don’t see why it would—the standard way to do AOP in Ruby is leveraging modules-as-mixins which are a core language feature, monkey patching is unnecessary (but since classes in Ruby are open, modules-as-mixins can be used to monkey patch classes provided by someone else just as easily as being done at class definition time.)
Aspect-oriented programming has annoying implementation in languages like Java which don’t natively support the right abstractions and where you are fighting the language to do it.
Its kind of unfortunate that those are also the languages also which do the most to shape people’s understanding of AOP.
I would say not having language support is a king of way of language designers not approving of its widespread use.
If anything Java and .NET are slowly closing some avenues that make patching possible, like final really means final JEP in Java.
While the robust dynamism of Ruby (or Python) can allow it to be used in a patching-like way, the only dynamism that is essential to the implementation is the dynamism of runtime polymorphism (common in statically-typed, AOT-compiled OO languages.) The language feature it relies on that C++ and Java don’t have is multiple inheritance that gets resolves to a defined order you can call back up from a concrete method with “super”. There is no reason a static OOP language that supports runtime polymorphism couldn’t support this; it wouldn’t even need to add overhead to compiled classes that don’t make direct super calls from their own methods.
And one giant problem with it is the reliance on global variables. An AOP wrapper has to modify something, and it typically does not have enough access to enough context to do it.
So it has to rely on ambient data that has to be saved in a global variable. And this is _bad_. It makes data flow opaque and impossible to follow.
And then there are issues with debugging. Where do you put a breakpoint? What happens if you try to step into an instrumented method?
PS: yes, a global variable can technically be a thread-local variable. It doesn't matter, it's still a non-local ambient state.
It's really neat:
https://micronaut.io/2019/10/07/micronaut-aop-awesome-flexib...
- code readability and maintainability takes a hit. If you don't know things are defined using AOP in files x,y,z you can read the code and miss a whole lot of things.
- AOP implemented at runtime is a mess when you're trying to debug things
So yeah, instead of having aop defined somewhere else to wrap a function call, i tend to prefer doing it explicitly transaction(function())
LLMs really like the most important context to be clustered in the most recent section of their window. Dividing up cross-cutting concerns into their own documents would seem to be pushing in the other direction.
Rather, I got to rely on the special handling within, notably at the time, the Java JEE ecosystem. It was not "generic" AOP by any means, it was specialized. But I can say that what they did support was more than enough for my applications. I never felt I could use a more flexible framework.
I know some folks that did, the embraced it whole hog, and really got some power out of it. AOP could be used to make some very powerful constructs, especially adding dynamic behaviors to existing systems.
But that was simply never something I really needed in my Java work.
Our approach was quite a bit different to Kiczales at the time. We had "attribute providers" which were essentially compiler plugin DLLs.
These plugins could register themselves at various parse points and add work like enzymes on the AST.
So here we have a compile time attribute that writes all the boilerplate to add windowing support to a class. Adding message maps, hooking up the dispatch, life cycle etc.
[Window] class MyWindow { };
It worked, but as others pointed out, it suffers from combinatorial explosion, and issues with debug ability.
That said, as long as you stick within well defined verticals it was still very useful and saved a lot of typing, and reduced cognitive load.
I have thought quite a lot about modern versions of this using LLMs and I can see why the article notices a parallel to prompts or spec based designs.
At some point I tried to make a version of the system we built almost 30 years ago, but using an LLM as the preprocessor instead of rigid AST hackery.
It works a lot better, and you can approach a more continuous interpolations between intent blocks and generated code. Even the combinatorial problem largely disappears.
I stopped working on it though because I couldn't really see anyone wanting to adopt a new language or some whacked out extensions these days.
Maybe it does have its place though in certain fields. It might be worth having another go at it with fresh hindsight.
On the 'experimental' side you have Jonathan Blow's language, Jai, which has integrated codegen (AST macros that look like code) + type inference into the language on a very deep level, and from the podcasts I've listened to with veteran C programmers, was that during the 90s, when the mass adoption of OOP began, but performance still mattered a lot - there were a lot of ideas around OOP that were different from how C++ ended up doing this.
The most famous example I guess being COM, which is a C object model, that solves a bunch of issues that plague C++ to this day, such as reflection and code reusability, among others.
But COM is C and entirely incompatible with C++. And the big issue imo with C++ is that like AOP here, it has elevated a bunch of arbitrary magic behavior to language level, that honestly could've been done very differently, and the C++ implementation often ends up worse (multiple inheritence is a typical example).
A similar battle played out in Linux-land, with GTK creating its GObject system, which was roughly analogous to COM, and Qt opting to hack up C++, not unlike MFC.
Doubts in comments so far seem around expectations of an AOP-like experience. Mainly that patching aspects/Spec makes happy changes to implementation. I'm not sure TFA intended that use. Nor that current AOP implementations provide the happy. But lets explore.
With a frontier model, a greenfield ends up with, say, some spec and plan docs, and a "woven" implementation. It's reasonable to wonder how well an LLM will then un-and-re-weave code as it makes changes. And how to express join points or equivalent. And how well code review agents will detect/check intended joins continuing to be woven correctly. And whether a baseline of the LLM simply using existing AOP tooling constitutes progress.
Coming from non-frontier models, some of that seems more straightforward. Harnesses use lots of small jobs/tasks, with subagent paperwork and reviews. Historically scarce context, precious context, encouraged tightly optimizing single-task context for each job. Including derived code "views", rather than raw code. And associated LLM-coded tooling around working with not-the-raw-code.
Thus working with name/signature pseudocode outlines. And filtering code/ast. Both get you closer to having unwoven forms, and to deterministic tooling for un/re-weaving. Consider i18n clutter - just to coddle the model, one might strip it (replace it with English), let the model work, and then restore it. Which is equivalent to un/reweaving an i18n aspect. And even read-only views, like function distillations which drop or abstract some specified variables and associated code, make that "check spec against implementation" code review easier. So, even interpreting TFA broadly, it seems at least potentially possible, no?
Big picture, I'd like to get away from traditional "repo as single point in design space, laboriously nudged around", to something which preserves the multiplicity of model/run/prompt ensembles, and allows playing on design space manifolds. "This exact code has been in prod" is useful information, if underutilized, but not so useful as to be worth discarding so many new possibilities.
At first it hooks you these cross cutting concerns, but then you realize it handled better as a simple dependency.
AOP as a concept is great, however in practice using real world tools never lived up to the implications.
Its all or nothing, almost if not all code can be written as a "cross cutting concern"
I could write one function for the rest of my life, and simply use AOP to inject a different program.
but WHY would I do that? Something so incongruous with my mission as a software engineer. AOP was pretened smalltalk MVC glue for other languages that didnt have it at the time.
It didnt last long and it wont be back.