Hacker News new | ask | show | jobs
by nmehner 36 days ago
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.

4 comments

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.

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

This is not true, LLM can write and run code to check for deadlocks or race conditions.
Generating code on the topic of deadlocks and race conditions is different from understanding those issues and recognizing them in a wide variety of contexts.
I don't know about understanding but Claude and GPT can "recognize" lots of race conditions/possible deadlocks and then run Go with the race detector to figure out if they really happen (actually not all the time they tend to be overconfident that things are race conditions without testing first!).

I don't really know what to call that, if it's understanding, recognition, but it's clearly helping reduce the number of race condtions.

I've feel like AOP is Spring on steroids. Same downside for both IMO.
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
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.
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.

>it's kind of common knowledge

If only.

In my experience it's mostly pushed by university professors that haven't worked in the industry since the 90s.
And thus the people who most graduates learned under, and sometimes start founding their own companies with these principles right after.
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.
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.

yea it's amazing how many goofy ass "senior engineers" are still cargo-culting inheritance.
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.

>return value not worth the cost of troubles it can bring

That, funnily, could be the motto of Forth

> My problem with AOP has always been that it makes the simple case trivial and the hard case much harder.

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.