I have fond memories of Aspect-Oriented Programming, which I feel should've been in this list.
For those not in the know, AOP is basically the Intercal COME FROM statement (itself a pun on goto, ie it's a jump but in the opposite direction) (think that through a bit). Yes, that's a totally nuts idea, but it turns out that with a bit of effort you can formulate it in such a way that you can do conference talks about it and people nod and think "o wow nice I gotta tell the team".
More concretely, AOP is usually a bit of compiler infrastructure that lets you inject code into other people's functions/methods etc and make arbitrary changes. It's nice for eg inserting logging code at the beginning of each method, with the method name and argument. You'd be able to eg define a query somewhere and say "for all methods with annotation X, inject code Y at the beginning of the method".
But naturally, teams that adopted it always had this one team member who thought they knew better and started using it for injecting all kind of behavior-altering code in methods defined far, far away. When reading such a method's source, there was absolutely no way to tell that code was going to get injected. If the AOP crowd was well behaved there'd be an annotation/attribute/etc that would trigger the behavior, but that's not a requirement. I've seen people inject code that sanitized data, which was very surprising if said function didn't actually take user data and suddenly while debugging a string's content had changed and there was no indication whatsoever what had caused it.
This is very similar to my biggest frustration with PubSub, or heavy event driven systems in general.
The concept is so appealing, “Hey I’ll just fire this message off and whoever needs to know about it will do what they need”.
It’s great to have that ability to focus on just the component or module you’re working on, but the lack of visibility on exactly what is about to happen, and what has happened, has caused many late nights of debugging and frustration.
It’s just how you described “there’s absolutely no way to tell the code that was going to get injected”
I’m guilty of contributing to this problem as well. Once you become familiar with the event system you’re working in, windows, DOM, whatever, you can make some pretty good assumptions about how other components are implemented. When you’re faced with a bug in code you don’t have access to, or a behaviour you don’t want, often the solution you’re left with is asking “okay, so what combination of events and timing do I need to force this component into the state I need?”
For example I remember doing this with some third party grid controls. We really really wanted the TAB key to insert a new row at the end. We had to orchestrate exactly the right mix of events and method calls in the right order, with a few BeginInvokes to get the job done.
I wish I had a solution to all of this and other frustrations. It’s all trade offs and a balancing act.
This is why robotic process automation (RPA) is such big business, legacy systems without APIs that need engineered state, race condition detection, all through a black box.
> When reading such a method's source, there was absolutely no way to tell that code was going to get injected.
This clearly makes it pretty awkward to handle. Yet considering that the compiler manages to figure out where code needs to be injected, one can ask: why does the programmer not get to see this information?
This is not a fundamentally unsolvable problem. It is a tools problem. It's easy to imagine a programming environment where you can tell immediately which code gets injected where. Even in a dynamic system you should be able to see, on examining a function, what its current contents including injected code are.
Some programming language features require proper tools support to be useful. This requires languages that want to introduce such features to be opinionated on the matter of what tools you can use.
In the end, it is deterministic, as you say. But it's also quite more involved than you imagine. It's a big pile of interceptors (and their proper ordering), bootstrap classes, autoconfiguration, component scans, layered property sources and some more. Lots of it is not immediately visible and you have to start to read Spring source code. And it gets worse if the project gets set up in a way that makes it hard to deal with. For example, not organizing packages by feature. Relying on component scans at the default package is also not exactly funny.
When developing a Spring project, you indeed start to quickly appreciate the introspection feature that advanced IDEs like IntelliJ IDEA provide. Inspecting the application container at runtime is also possible, but it's a clear sign that your app got waay too complicated and has to be refactored.
I'm sure there are existing systems for which this is quite involved. These are design choices, though. If the compiler can figure it all out and find everything, then so can an IDE, and make everything visible where it needs to be visible.
The issue is that it can become very complicated very quickly, even when the IDE is able to figure it all out and present it in a useful way. Even with the best tooling, nonlocal control flow can't help but increase the WTF per minute[0].
If it does in fact become "very complicated" then maybe you are trying to achieve something that is very complicated.
Good tooling takes the surprise out of nonlocal control flow, and can project it into local control flow on demand.
To take it back to skrebbel's complaint, "methods defined far, far away", "absolutely no way to tell", "very surprising", "no indication whatsoever" -- all things that good tooling fixes because it can show you what gets injected where and why, when you need it (and show you a minimally intrusive reminder when you don't).
Game modding in general tends to strongly resemble AOP. How you do it depends on what the game itself provides you, but it tends to boil down to hooking various parts of the game which may or may not have been intended as extension points and either replacing the default code or adding your own logic.
Game modding also demonstrates the big strengths and weaknesses of AOP. New versions of games tend to break some or all mods until the mods are updated, even if the game developer is trying to support the modding scene and avoid breaking things. When literally every function in your code base is a potential extension point, it becomes impossible to change anything without breaking something else, and you can get insane impossible to debug errors.
Aspect-oriented programming is alive and well and living in the Spring Framework.
While it can be misused (by making unclear what aspects are being applied, as you mention) it can be very useful and avoid a lot of boilerplate code and repeated logic.
Wow, I forgot all about AOP, even though I was, briefly, very excited about it.
On a project where "logging" was budgeted as a short, separate task that could be delegated to a junior engineer at the end, I used Spring's AOP to inject performance and usage logs across entire chunks of the program. It worked great and took way less time than scheduled.
I was convinced there'd be other uses for AOP, and kept an eye out for other opportunities for months. Given that I completely forgot this was a thing until now, I think you can guess how often that happened.
I've used AOP a few times, it's never caused problems.
Other than logging and profiling I've used it in ejb3 app calling old plsql business logic - we had problems with some plsql code handling exceptions and transactions in non-standard way (basically - comiting or rolling back in pl/sql code) which messed up our ejb3 container-managed transactions. We had a template for pl/sql code that should be followed - every function should start with a savepoint, never commit, and only rollback to that savepoint in case of error. But there was a lot of plsql code and some of it didn't followed that rule.
So I added aspect in java that wrapped around any plsql call, created a savepoint before it, and checked after the call if it's still there - if not it would a special exception. That allowed us to quickly find all the places that weren't handling transactions properly in PL/SQL and fix them.
We also used AOP for integration testing with our java business logic and plsql - instead of preparing and using up data during tests we simply wrapped transactions around testing methods and rolled back everything afterwards.
I don't think AOP is bad, even if it's truly COME FROM with a different branding.
> used Spring's AOP to inject performance and usage logs across entire chunks of the program. It worked great and took way less time than scheduled.
There was the guy who used AOP to inject logging and perf, and set it to "instrument every method".
_Every method_.
It was Ok in test, but when the production load rose to the daily peak, the volume of logging data was in itself sufficient to saturate a network and requests got dropped. Including health checks, which caused machines to be taken out of the load balancer, increasing load on the survivors. Autoscaling didn't help, new machines came up with the same issue.
The failure cascade brought down the entire service.
Luckily for my case, some senior engineer was convinced that it was a good idea to make an "abstraction layer" consisting of pass-throughs for every API and external service, and group all the stuff in that layer in a single package. So I did instrument every method, but only in that BS package.
Emacs has an AOP facility as well. It's called advising there. But the manual strongly recommends to only use it as a method of last resort, as performance will eventually degrade. Also, debugging in the presence of these features is not exactly fun.
I absolutely abused this functionality when I first started out as a junior and now I cringe thinking about the poor developers that are stuck with that codebase.
Haha you reminded me of AOP nightmares. It's great used for instrumentation. A big part of why Java has great monitoring tools is runtime code modification with AOP.
But as you said, devs find all kinds of mind melting creative ways of using it.
For those not in the know, AOP is basically the Intercal COME FROM statement (itself a pun on goto, ie it's a jump but in the opposite direction) (think that through a bit). Yes, that's a totally nuts idea, but it turns out that with a bit of effort you can formulate it in such a way that you can do conference talks about it and people nod and think "o wow nice I gotta tell the team".
More concretely, AOP is usually a bit of compiler infrastructure that lets you inject code into other people's functions/methods etc and make arbitrary changes. It's nice for eg inserting logging code at the beginning of each method, with the method name and argument. You'd be able to eg define a query somewhere and say "for all methods with annotation X, inject code Y at the beginning of the method".
But naturally, teams that adopted it always had this one team member who thought they knew better and started using it for injecting all kind of behavior-altering code in methods defined far, far away. When reading such a method's source, there was absolutely no way to tell that code was going to get injected. If the AOP crowd was well behaved there'd be an annotation/attribute/etc that would trigger the behavior, but that's not a requirement. I've seen people inject code that sanitized data, which was very surprising if said function didn't actually take user data and suddenly while debugging a string's content had changed and there was no indication whatsoever what had caused it.