Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

It does have it's place. Aspect oriented programming will allow you to write, for example, logging code that are very configurable. It allows you to remove the concern of logging from your classes completely.

In fact, any cross-cutting concerns - like transactions, can be done this way. So you can write code that don't care about transactions, but behind the scenes, it is all within a single transaction, and transaction errors are handled centrally.

It adds runtime costs of course. And the more you add this way, the more complicated it becomes. And certainly can be abused.



Just use a language where it's possible to implement those things in plain old code. When you have higher-kinded types you don't need any bytecode manipulation, you can just have a type that wraps anything that needs to happen in a transaction, and compose together transactional operations in a visible but type-safe way - and you don't even need the runtime cost.

How anyone can look at Java as it's actually used and say Scala is too complex is beyond me.


You can also do it visibly in Java without too much noise IMO. The point of AspectJ is to hide this stuff completely from application code (but it's still visible in its definitions). Whether that makes sense or not I'm not going to debate (I personally don't think AspectJ is particularly great) but arguing that other languages allow you to do that in a more or less visible way is besides the point.

AspectJ is a language, these aspects are visible and have their own language abstractions, just separate from the application code. Just like you have monad implementations separate from their uses. So writing aspects is also "plain code". How the weaving happens (at compile time/load time, on source code or on bytecode, etc.) is an implementation artifact

Also, AspectJ isn't Java and the problems AspectJ tries to solve and the approach apply to a bunch of languages and variants for functional languages also exist.


> AspectJ is a language, these aspects are visible and have their own language abstractions, just separate from the application code. Just like you have monad implementations separate from their uses. So writing aspects is also "plain code".

Code containing aspects is certainly not plain Java code, because the aspect annotations (or, worse, invisible string-name based pointcuts) change the behaviour of the code they're on, breaking the normal rules of the language. You'll see "impossible" behaviour at runtime: call a method and the call stack jumps to a completely different method. A method throws an exception that's thrown nowhere in its body. If you want to say that AspectJ is a language in its own right, then it's a language that breaks all the rules of good language design: it essentially contains COMEFROM as a core language feature. Whereas monad implementations are (generally) plain old code that follows the normal rules of the language.

> Also, AspectJ isn't Java

It (or other libraries that do much the same thing) is the vast majority of Java as it actually exists in the real world. I've yet to see a substantial Java codebase that wasn't using some form of AOP or reflection (even if only indirectly via these big frameworks).


> Code containing aspects is certainly not plain Java code,

I didn't say it was. AspectJ is AspectJ, as you surmised correctly "a language in its own right", and so when you define an `aspect` that's it's own thing, not a `class`. You seem to demand that the language is Java, but that's not really a criticism of AspectJ, the language, then, rather denying that it is a language.

It's like saying a C++ class and some implicit behaviors that change behaviour of user code (via a custom assignment operator override) is crap because it's not C. Maybe it's crap because you don't like it and like C better but it's still not quite fair to demand it be "plain code" when it is plain code.

Please look more deeply into https://en.m.wikipedia.org/wiki/AspectJ. It has its own syntax and is not just some annotations plus a framework you have to run.

FWIW, your arguments against AspectJ work just as well against any language with macros or metaprogramming in general (although you probably don't like those either, and that's fair!).

> If you want to say that AspectJ is a language in its own right, then it's a language that breaks all the rules of good language design

Frankly, the kind of things that AspectJ offers or proposes to use are IMO no less sane than some of the things languages like Ruby (e.g. monkey patching, middleware decorating/nesting calls via including a module, defining methods is a dynamic call that can be intercepted and customized, etc.), Python (e.g. decorators), JS (e.g. messing with prototype behaviors), or even Prolog (e.g. metainterpreters that metalogical predicates to implement different resolutiom strategies) typically encourage.

People have called monads the "programmable semicolon", and so you can do all kinds of things that don't at all represent what a user of the monad would anticipate unless they read the docs/implementation. It could enforce some form of order of evaluation, or store additional state that is hidden, etc. Sure, this is not the same thing as aspects or metaprogramming/macros, but what they have in common is that some external definition defines the exact semantics of a application/user piece of code and has potentially a whole lot of freedom to stray from what may look like it's "obvious".

The goal of AOP is to find a solution to challenges were modularization by typical means hasn't worked. And the typical examples are usually valid. Is it great? Is it needed? I'm not sure either, but your criticism sounds a lot like you only look at it from a lense of a pure statically typed functional programmer. From that POV a lot of language designs probably look impure and crap, whether justified or not.

> It (or other libraries that do much the same thing) is the vast majority of Java as it actually exists in the real world.

Are you specifically talking about AOP or AspectJ? I believe the former. And I don't think it's AOP, you are talking about various frameworks implementing a wild set of changes to the core language via reflection, code generation, or other such things tailored to their specific use cases.

Say what you will but that, while it might fall into the AOP class of doing things, is quite different from AspectJ's goals. The idea is/was that you don't need all these different frameworks with their own conflicting custom implementations. Rather, that using AspectJ should be the common language in which these metaprogrammatic aspect-oriented concerns are defined (for instance by such a framework that's currently using it's owm custom implementation with no common set of rules), which can be analyzed and provide static tool support (e.g. "who"'s amending, wrapping, early aborting behavior for this particular method).

So, again I don't think the criticism of AspectJ is fair (abd that's what I addressed, not the modern state of Java and frameworks in general). If at all, the problem is that AspectJ itself isn't actually used but instead that its superficial ideas have been coopted and turned into a metaprogramming wild west. Maybe it's AspectJ's fault but I think it's just a coincidence. That state of affairs is not unique to Java at all, either.


> I didn't say it was. AspectJ is AspectJ, as you surmised correctly "a language in its own right", and so when you define an `aspect` that's it's own thing, not a `class`. You seem to demand that the language is Java, but that's not really a criticism of AspectJ, the language, then, rather denying that it is a language.

By building on Java AspectJ tries to have it both ways, which I think is a mistake. It's normal, and I'd even say semi-encouraged, to use non-AspectJ-aware tools when working on an AspectJ codebase - and of course an upstream library maintainer may not even know their library is being used in an AspectJ codebase. Which causes real problems because they will refactor according to a different set of rules.

> FWIW, your arguments against AspectJ work just as well against any language with macros or metaprogramming in general (although you probably don't like those either, and that's fair!).

> Frankly, the kind of things that AspectJ offers or proposes to use are IMO no less sane than some of the things languages like Ruby (e.g. monkey patching, middleware decorating/nesting calls via including a module, defining methods is a dynamic call that can be intercepted and customized, etc.), Python (e.g. decorators), JS (e.g. messing with prototype behaviors), or even Prolog (e.g. metainterpreters that metalogical predicates to implement different resolutiom strategies) typically encourage.

Yes and no. You list a bunch of things that are bad to a lesser or greater extent, but the method-name-pattern-based stuff I've seen done with AspectJ is the most cryptic form I've ever encountered. Not only is nothing visible at the declaration site or the call site (not only no decorator but no magic import either), you can't even grep for the method name to find the thing that's messing with that method.

> People have called monads the "programmable semicolon", and so you can do all kinds of things that don't at all represent what a user of the monad would anticipate unless they read the docs/implementation. It could enforce some form of order of evaluation, or store additional state that is hidden, etc. Sure, this is not the same thing as aspects or metaprogramming/macros, but what they have in common is that some external definition defines the exact semantics of a application/user piece of code and has potentially a whole lot of freedom to stray from what may look like it's "obvious".

That's not really true in my experience; monadic composition is programmable only in the sense that an abstract method in an interface is programmable (indeed monad usually literally is an interface with abstract methods, or the closest equivalent in the language you're working in). Syntactically you can see that monadic composition isn't regular composition and so you know that some effect is being invoked, and as with any abstract method you might be able to click through to a specific implementation or you might have to accept that it's just "some unknown implementor of this interface". But in an important sense there's no magic: all your values are just values, all your functions are just functions, all the normal rules of the language still apply. I do object to things like thoughtworks each where a seemingly normal assignment gets magically rewritten into something monadic.

> The goal of AOP is to find a solution to challenges were modularization by typical means hasn't worked. And the typical examples are usually valid. Is it great? Is it needed? I'm not sure either, but your criticism sounds a lot like you only look at it from a lense of a pure statically typed functional programmer. From that POV a lot of language designs probably look impure and crap, whether justified or not.

Well I came to that precisely because of experience with that problem. It was seeing the bugs introduced by AOP that made me look for a better way to do things, and that was how I got into functional programming.

> The idea is/was that you don't need all these different frameworks with their own conflicting custom implementations. Rather, that using AspectJ should be the common language in which these metaprogrammatic aspect-oriented concerns are defined (for instance by such a framework that's currently using it's owm custom implementation with no common set of rules), which can be analyzed and provide static tool support (e.g. "who"'s amending, wrapping, early aborting behavior for this particular method).

That's fair. But in that case you have to see at AspectJ as a failure in terms of today's Java ecosystem. Even codebases that use AspectJ don't manage to avoid using all those other metaprogramming frameworks as well, and I don't think I've ever heard of a framework deciding to move away from a custom AOP implementation into doing something via standard AspectJ. (And I do think that AspectJ offers too much flexibility to ever make for a comprehensible codebase, even with the help of better tooling support).

> That state of affairs is not unique to Java at all, either.

It's not unique, but it's embraced to an unusually high extent in Java (Ruby would be another example). And I can't help thinking it's because of Java's deliberate, advertised simplicity, because a lot of the aspect-based stuff seems to be there to paper over missing language features (or, even more tragically, to paper over language features that are now there as of newer versions of Java, but that Java programmers have got used to having to step out of the language for).


I think you are being completely unfair here. AOP in Java (and .NET) is a metaprogramming facility that has few equivalents in other languages. The closest thing that comes to it is macros, but those are often compile time modifications.

More importantly, nothing about standard Java development requires The use of AOP.


> AOP in Java (and .NET) is a metaprogramming facility that has few equivalents in other languages. The closest thing that comes to it is macros, but those are often compile time modifications.

You don't need macros to achieve what grandparent was talking about - handling cross-cutting concerns like logging or transaction boundaries without being too intrusive. In a language that lets you do a half-decent monad implementation (which is pretty much any language that has higher-kinded types and first-class functions, although do notation can be a useful enhancement), you can comfortably write this kind of thing in plain old code. (I do those very things - transaction boundaries and logging - in Scala all the time).

> More importantly, nothing about standard Java development requires The use of AOP.

And yet, in real-world Java development, people feel the need to use AOP. In my 10+ years of JVM work in companies large and small, I don't think I saw a single one that didn't use some form of AOP. That should tell you something.


If I give you a jar (or equivalent) of my compiled app, can you still apply cross cutting concerns (over compiled code you may not own)? Because that's what AOP and bytecode transformation can do.


You can't insert them at completely arbitrary points in third-party code. But libraries can be written generically (in terms of e.g. a general monad) so as to propagate cross cutting concerns from your callbacks to the right place, even when those concerns are implemented later on and the library doesn't know about them. Look at e.g. fs2 and http4s: everything's written generically in terms of some F[_] type which the user supplies when they use the library, and it will all be plumbed through correctly. So if you want logging, you use something like treelog as your F[_] type; if you want transactions, you include a transaction type in there. And so on.

In my experience that covers almost all the use cases, while it means that library code can still behave predictably and be understood and tested (e.g. you don't have to worry that a library upgrade is going to break your application, which you do if you've used AOP to define pointcuts deep in its internals).


I'm not familiar with scala but will take a look. Completely agree over your last point. I very much prefer libraries over frameworks, and as you said pointcuts are a bit fragile, but my main point is that they behave differently because of bytecode transforms.


I have done a LOT of development using Spring and have very rarely used AOP. I don't think it is a complex technology to work with though.


AOP is doable in most languages that allows very basic levels of runtime instrospection and manipulation of objects and types, and is easy in a large number of them.

In Ruby, for example, "alias_method", "define_method" and "send" is sufficient to implement cross-cutting in a handful lines of code, and at least one generic gem exists that provides basic cross-cutting in a generic way.

But it's very rarely done because it's generally cleaner to pass down code for the library to explicitly call.

I used to be very excited about AOP, but in effect while AOP can be great as a debugging facility (e.g. ability to inject logging at any point for example), when you have control over the design it tends to be better to explicitly build code so that it is designed to accommodate user provided functionality. In Ruby that tends to involve e.g. the ability to pass in blocks a lot of places.


> Java .. Scala

To be fair, there's something to be said about a seasoned/documented library versus the cooked up micro-framework/function that a random coworker invented in Scala to do the same thing, and which might be implemented differently in each project in your company!

Source: I'm about a year into Scala after many years of Java. I like a lot about Scala, but 4/5 places where we have "plain old code" where in Java we'd have used an open source library, the "plain old code" is painful to read/reason about.


I'm all for using standard and documented libraries provided they follow the rules of the language, and I don't think Scala goes against that - if anything "microlibraries" that do one thing and do it well seem to be more common in Scala than in Java. What I'm objecting to is frameworks that violate the rules of the language and require their own special understanding, even if those frameworks are well documented.


How would you then, say, do a profiled run of the application, to help find the choke points in your application? You'd have to make sure that you apply the exact same "trace the runtime of this function" wrapper around every single function call, then make sure you haven't missed anything anywhere, then have to maintain such usage, and then mentally ignore it everywhere when you're trying to read through the code.


Profiling isn't really a good example; I don't actually mind doing it via language-level magic, since it doesn't affect the actual behaviour of the application - every function still executes the same and still returns the same thing, we just observe it in more detail from the outside.

But to answer the question, I'd use a monad, something akin to treelog ( https://github.com/lancewalton/treelog ). Apply the wrappers at the points where you want them (and they'll be visible in the type), and then the tracing is naturally threaded through in a way that's visible but not intrusive (basically just the difference between <- and =, so when you're reading the code you can see it but it doesn't get in the way of reading the business logic). Maintaining it is easy because it's just part of the type of everything, so automated refactoring in you IDE will do the right thing; testing is easy because everything's still just a function and a profiled computation is still just a value, but you have access to the profiling trace in a normal way as a first-class value in the language.


Well, the whole point of AOP is to handle these "not good examples". Now, certainly if you use the wrong tools for the wrong job, you'll shoot yourself in the foot. But there are many powerful tools that we shouldn't eschew just because we fear using them wrong. And then there are many cases where you shouldn't use these tools just because you can.


> Well, the whole point of AOP is to handle these "not good examples".

I don't have a problem with AOP that doesn't change the functioning of the code. I have a problem with AOP that changes the functioning of the code, which is the overwhelming majority of AOP that I've seen in real life.

> Now, certainly if you use the wrong tools for the wrong job, you'll shoot yourself in the foot. But there are many powerful tools that we shouldn't eschew just because we fear using them wrong.

But you don't need any of the dangerous functionality to do profiling. So that's not a valid argument.


I mean, you can do anything you want, it's just code, but I would argue that I can get more maintainability and better results with less code by using AOP to implement application-wide profiling.


For cases where you need to change the behaviour of the function, you get more maintainability and better results by doing it in a way that's visible in the code. For profiling, you get more maintainability and better results by using something less powerful than full AOP (e.g. stack sampling or tracing). There is certainly no case on the JVM where AOP leaves you better off than not having AOP, and I'd be surprised if there were such a case anywhere else.


For those of us hazy on the AOP concept... Is it an artifact stemming from restrictions of static languages, or would it be a valid concept with dynamic languages too?


In the dynamic languages world this is done with monkey patching. If you invent a declarative way to monkey patch your code, you've got AOP as done in Spring.


I believe emacs lisp's advice system is an AOP system in a highly dynamic language, and doesn't make much use of monkey-patching.

Advice/AOP is a pattern that can fit (if you want it) in dynamic language as well.


What about this Clojure library?

https://github.com/technomancy/robert-hooke


Don't forget AOP can work on top of compiled classes, not sure I'd there is an equivalent in python or ruby.


I don’t thin AOP is specific to static languages. From my understanding, the “aspect” part implies the ability to “hook into” and extend an application at specific “cut points”, which are typically pre/post function/method calls. This is done in Java by modifying byte code to insert proxies. I imagine something similar could be developed with other languages. It might just be easier to do in Java because the type signatures are static.


It is very close to python's @ syntax on top of methods definitions.

Like:

@get('/posts')

mymethod

  ...


? no, it's not.

look at https://python-aspectlib.readthedocs.io/en/latest/ for an example of using aspect with python


Python decorators are a (limited) form of AOP: they let you transform functions in-place to implement cross-cutting concerns. Aspectlib allows you to do a more intrusive form of AOP.


Decorators and AOP are very similar concepts in my opinion then, if you prefer.


> It allows you to remove the concern of logging from your classes completely.

You say that like it's a good thing. When there's a bug to look into grepping strings from the log is one of the first steps, the first step if you don't have a stack trace, I want that string to be where the problem is. Having the logging in with the rest of the code is an inevitability anyway and unless you only want logging at function boundaries.

> In fact, any cross-cutting concerns - like transactions, can be done this way.

That's another thing I want to be explicit, if I have some code that is going to to do a bunch of updates I want it to demand a transaction as part of it's interface. I don't want it hoping it gets executed in the scope of a magic layer that will handle the transaction.

The common theme with AOP is that it takes stupid simple code into an opaque mess spread across a dozen classes that's far harder to maintain.


I agree, things should be straightforward, readable and logical.

The Java people are describing here is alien to me, and I code Java for a living at the moment.


> The Java people are describing here is alien to me, and I code Java for a living at the moment.

you're looking at enterprise java, not regular java


I'm certainly not! You take that back! Yuck!

I'm writing microservices using a relatively lightweight, straightforward and explicit web framework (sparkjava, which is more of a library than a framework really), modern language features and no AOP...

Enterprise... pah!

(Oh, unless you mean the Spring crowd are enterprisey, in which case I take that all back.)


Modern OSes don’t log to simple text files you can grep, tail, watch, open in a text editor, etc.


The requirement was the other way around, that the source can be grepped for an error message and the result be meaningful.


Unix was an improvement on all its successors.


And then it went downhill about 5 years ago as logs started being stored in binary and hidden by default




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: