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