The way you use the core inversion-of-control framework people often just call "Spring" is you take your homegrown, doesn't-depend-on-Spring business logic, you make a config file, and if you're not executing it a servlet container, you make one entry-point class to start it all up [1].
There other projects under the Spring umbrella, and some of them are meant to be called from your code. Like Spring JDBC for database access, whose framework-like nature is apparent, yet in terms of its usage pattern, it resembles a library [2].
[1] https://docs.spring.io/spring/docs/current/spring-framework-... [2] https://docs.spring.io/spring/docs/current/spring-framework-...
Part of the blame I think falls on Spring's DI container, which is "too good". It makes it easy to pull in any bean, even the wrong one, which means developers have to be disciplined managing how beans depend on each other, and you end up with controllers returning entities. Of course, any codebase turns into a mess if you're not disciplined, but my impression is that developers tend to be less careful when working within the confines of a framework like Spring because it gives them the false sense that they can do no wrong.
Recently I started a side project with Spring Boot (after all I like the framework), trying to organise it following Uncle Bob's Clean Architecture approach, which aims to isolate the core business logic of an app from its implementation details like database code and external interfaces. The core turned out pretty well isolated, but the rest is all Spring. Database access? Spring Data. External interface? Spring MVC. Communication with external services? Spring RestTemplate. I'm not saying it's bad, it's actually awfully convenient, but it's not so easy to swap out - say - Spring MVC for http4k, so you end up with Spring being everywhere. Which again, not a bad thing, just something to consider.
External interfaces are just isolated interfaces, which end up being tied to a framework as frameworks do most if not all of the heavy lifting.
Just because you picked Spring Database to implement your persistence layer it doesn't mean you are bound to Spring to add a webapi or a web app, though.
If for some reason your frameworks are leaking out of any of your external interfaces then that's an issue with how you designed your app, not the frameworks you used.
As an example, some Clean Architecture examples using ASP.NET Core decide to implement their persistence layer passing around Entity Framework classes as their interface. That, obviously, tightly couples the whole app to Entity Framework in particular and ASP.NET Core in general. This coupling in turn is completely eliminated by passing a generic repository interface, but using Entity Framework's convenience often forces us to ignore that.
I agree, you're definitely not bound to, but again, in theory. For what I've observed in practice, factors like convenience and friction in going off the beaten path mean that when you pick Spring your app ends up being 90% Spring stuff. Which is not necessarily bad, there are certainly many good things about using the well supported, robust, and predictable set of Spring components. But I wouldn't say that Spring is the kind of framework that merely helps you tie together your app, that otherwise stays out of the way, and that you can swap out at any time.
You can choose to import facade-style Spring libraries, and write your code against them -- like Spring JDBC, Spring JMS, TaskScheduler -- if you want something that does some heavy lifting for you, but doesn't tie you to a vendor implementation directly.
Meanwhile, Spring Boot is a fully-opinionated framework based around the combination of defaults and on-the-fly auto-configuration, and giving you a single runnable uber-JAR at the end. And you're right about it: if you're using Spring Boot, it makes sense to depend on other Spring libraries, because (1) the docs guide you into them, and (2) the two will interact to auto-configure. For example, you can just run your DB code, and it can auto-configure an in-memory instance as you're developing [1]. Then, when you've gotten the real database, just specify the real config.
Spring Boot really shines in bootstrapping a greenfield project to get going quickly, especially if you're willing to compile its annotations into your code. You can then go back and incrementally override behavior and configs once you realize you want them a certain way.
[1] https://docs.spring.io/spring-boot/docs/2.2.7.RELEASE/refere...
To be fair, at least Spring and Lombok don't require me to maintain loads of yaml/XML files to describe the DI stuff, but annotations in java aren't always the most intuitive thing.
Requiring a class representation of literally everything is a huge limitation, and I wish there were other alternatives to this style of DDD that abstracts everything to an absurd degree.
The Service class holds an instance of the Caching class and the Database class, each of those hold an instance of clients with connections, for example.
Can you explain how language support would remove this pattern?
I'm currently working on Scala services, and we don't have any DI, which is actually kinda nice, but it just means we "new-up" all these classes manually in our Main class and pass them through as arguments one to the next.
So far I like how there is no "spring magic"; however, in the end the pattern of dependencies is the same.
There was maybe one instance where I had to deal with obtuseness like that. Definitely not the norm.
Spring
JVM
Container
OS
hypervisor
x86
Real CPU instructions
Electrons
Unknown
EDIT: formatting
I think one could argue that containers aren't much of an abstraction for the running program itself, though, as you say. The place where containers provide an abstraction is at deployment time/process management time; the program itself has (essentially) the same API whether it's running in a container or not.
The only question is if you can afford hiring a few gods for the task or you are rather satisfied with whatever falls off a truck and in reality is completely uncapable of inveting universe (or even capable of baking).
At the end the customers digestive problems are those that do matter. And you can get sued for food poisoning.
I personally love apple pies and am just not satisfied with plastic taste of supermarket frozen, microwave oven heated "products".
“When you want to hurry something, that means you no longer care about it and want to get on to other things.”
― Robert M. Pirsig, Zen and the Art of Motorcycle Maintenance
I actually "finished" it to the point of a working system for the customer with real customers in about 5 weeks by ignoring most of what they had done - required a handful of classes and JSPs. Probably distinctly under-architected but it worked and was built on for a number of years after.
It was quite complex (message queues, multi-threading, async etc.) so that it would be 'scalable' yet crushed under the slightest load, customers were experiencing delays during peak hours.
We removed about a quarter of the code (some intermittent queues & components), nothing changed in terms of functionality. Now we're removing more code and switching over to less complex data structures to fix the delays.
Maybe a complete rewrite would have been better after all, who knows.
I'm not a very experienced developer. I like to ship stuff and get projects done in reasonable time and a "YAGNI" attitude. Let's just rewrite stuff and introduce more abstractions when it actually hurts and a refactor is in scope.
On the other hand, I see a tendency that if you give projects (it could be a greenfield project or some maintenance) to a team of, say, 6 developers, then they will just 'create work' for themselves, filling up the todo list with items which may lead to an over-engineered project that could have been easily be done by 3 people within the same timeframe.
If you have some good reads/stories on this topic, patterns to watch for, I'd appreciate if you shared them.
This is a very good attitude, but I would like to tack on a second part to it: Instead of designing your code to be infinitely extensible in all directions, make it easily replaceable instead.
It's impossible to anticipate every future requirement, but certain choices will make it easier to deal with a new requirement as it pops up. One of these things is unit tests. If you have some gnarly business logic with tons of edge cases, you really really want a good test suite because it enables you to add new edge cases with confidence.
It does require a lot more thought and discipline, because it's not just a case of constructing an epic hierarchy of abstractions, or stubbing/mocking the hell out of your code in the tests to swap things around.
I've been on too many projects which tried to build the perfect abstraction right out of the box - as if the problem domain fully known.
That's because people in general write to think.* The level of deep thought achieved through writing is harder to achieve beforehand.
In the same sense, the level of deep thought achieved through writing code, is hard to achieve beforehand. It's actually worse because your engineers, dangled in this web of classes, won't be able to think about the big picture for themselves.
An Architect thinks in terms of what sounds good, what is beautiful in OOP-land not in terms of what is easy to write, and what's performant to implement.
One poignant example is the interviewer who wants you to implement chess pieces as classes. which sounds good in the architects' world but is blatantly insane if you think about the actual code and the actual challenges you're facing.
* writing to think and writing to be read are conflicting goals that why editors exist.
I stopped the work, broke the team up (and let a few of them go), re-assigned the goals of the project to 3 people who, working half-time, from scratch, and in Python, rebuilt the entire thing in 3 months, delivered it and it worked (and continues to work). "Builds" are now an scp to a VM, and a restart of a flask application.
My wife recalls a similar experience where a complex application she worked on (and ran reliably on very little infrastructure and with an entire team of 4), and serviced thousands of simultaneous users, was replaced (when a new CTO came in) by a giant, consultant driven Java project that required approximately 6x the infrastructure, 3x the staff to keep it going, and at launch could only serve 9 to 12 users at once. It was a debacle. But sunk cost fallacy forced her company to throw millions of dollars of consultants at it until it achieved some minimal level of barely available service. She and her boss left around that time.
She heard that a couple years later the CTO was let go, the consultants fired and it was all replaced by a team of 5 rewriting it all again from scratch (in Java) but like you also without all the architecture barf dropped in from orbit, and immediately went back to servicing the thousands of users again.
I'm convinced that this is the fault of some kind of consultant industry that seems to infest enterprise software circles that is really only good at inventing ways of designing systems that require more of their services, but never seem to actually deliver working systems.
So it's not always "those architecture astronaut guys used the company's resources to study their webscale fantasies and I came to save the day", but rather, "they did as well as they could using the choices and resources they stuck with and I happened upon the project with 20/20 hindsight".
Where I think I part ways with you is with this "they did as well as they could using the choices and resources they stuck with and I happened upon the project with 20/20 hindsight". One of the big lessons we've learned as an industry involved in software development is to start small, iterate often, get feedback from users. What I see time and again with these types of overblown space elevator projects is a kind of fundamental...immaturity -- the people who run these don't seem to understand how to achieve results with the minimum required to do it.
They've never started small and iterated to large. The projects they've worked on have all been so enormous, and have taken so long, that they have very few data points to draw lessons from. Each iteration and growth cycle in a mature project creates lots of information that can be reapplied elsewhere. But for people who've only ever grown up in large enterprise software projects, that's the only approach they know, and there's only a handful of lessons they've learned.
It's not only architecture astronauts, but the entire ecosystem that supports "enterprise" software engineering: vendors, consultants, scaled agile experts, design tools -- even university programs that churn out enterprise ready engineers. The appearance of these things, and how widespread they are in certain circles, demonstrates how those circles have regressed and tossed into the rubbish bin the lessons that we've learned. The penalty that's being paid is that these same lessons have to be continuously relearned again and again, but instead of pushing against outside models of how to do things (e.g. trying to adopt physical engineering approaches to software), we have to push against our own industry.
When you go against billion dollar enterprise software industries, you end up sounding like a heretic. It's really only when you get a long portfolio of success stories can you succeed. But that's very hard to get in today's climate where people spend their entire careers trying to kill ants with nuclear weapons dropped from the orbit of Mars.
I just didn't put the blame on the previous team, maybe I should have for sticking with it for two entire iterations, still not entirely convinced. I think they couldn't help it and the decisions were already made for them by large faceless corporations. I guess what's the point of calling themselves engineers if they don't feel like they are in a cockpit pressing lots of buttons right from the start?
By profession, I call myself a computer programmer, not an engineer or architect in order to remind myself that I should weigh problems by starting from fundamentals such as a state machine, an integer constraint system or a breadth first search, before setting up service discovery or training neural networks.
getInstrumentableClassLoader()
Return a ClassLoader that supports instrumentation
through AspectJ-style load-time weaving based on
user-defined ClassFileTransformers.
Oh man... I admire the people who have invented this. But at the same time I feel sorry for those who have to use this in order to earn their living.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.
How anyone can look at Java as it's actually used and say Scala is too complex is beyond me.
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.
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).
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.
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).
More importantly, nothing about standard Java development requires The use of AOP.
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.
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).
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.
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.
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.
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.
Advice/AOP is a pattern that can fit (if you want it) in dynamic language as well.
Like:
@get('/posts')
mymethod
...look at https://python-aspectlib.readthedocs.io/en/latest/ for an example of using aspect with python
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.
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 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.)
- paid per class
- sought to reduce the number of executable lines per class to 1.
Nope, that's OOP taken to extremes rather. Java does that with enforcing things to be classes.
It's the unruly mind coming up with generalised words.
The class loader is an important entity for a Java app. And you can have more than one (although this is advanced voodoo already). So, an instrumented class loader.
This is more than infuriating, it can actually cost you a lot of time if you're not careful about evaluating the framework.
"Right. Fuck you. Fuck your lack of hammers. Fuck your factory factory factories. And fuck your store. If you decide to pull your head out of your ass, I'll be over here, duct-taping a rock to a stick so I can actually hammer a actual nail into a actual piece of wood."
I'm reminded of the HGttG scene regarding the display department.
Note to anyone actually trying this: if you have a drill, drilling a hole in a piece of wood and sticking the stick (preferably dowel) through works better; if you need more weight, attach the rock to one side of the head, and a coin or other patch of metal to the other.
Also (if you have the space for it): adopt a lathe/smelter/set of chemical-refining glassware/etc today!
The conclusion was "the rails-not-java club is best" and then everyone worked backwards to find believable reasons why. At the impromptu pitch meetings "minimalism vs. complexity" took off.
Now that the rails club is out of fashion and the k8 club is in. Well, we'll invent reasons that over-engineering is better than deployment automation that "just" sticks a .jar in a .deb and apt-get upgrades a handful of non-virtualized servers colocated at a local datacenter.
Spring used to be a bad example yes, but not anymore. They really put the thought and work into fixing its design. Right now I can write an app __completely__ independent of Spring. In fact that's what I'm usually doing. You can do Clean Architecture with Spring now. Please keep your facts straight.
This claim is so bad.
You could look at the name AbstractDestinationResolvingMessageTemplate and the useful information they would be gaining is that it's a message template. They would still not know how to use it; they'd still have to do an additional step like checking the documentation on its usage etc. The amount of useful information to text is small and it doesnt help.
I'm 95% sure that all those people complaining about Spring never read the reference documentation. I did, and whenever I bump into a problem I have an idea about how to solve it. Besides reading it only takes a few days and you're set for years.
Why do anybody expect to be able to work with a technology if they don't bother reading the documentation? This is like complaining about not being able to drive a car when you sit down in the seat without training.
You're throwing out some very large assumptions as well.
Inheritance: You must predict how people will use your class. Make a mistake and they're screwed and need to resort to all kinds of hack. (Like frameworks)
Composition: People will use your class if it fits their needs. They can easily wrap the code, tweak or simply discard it. (Like libraries)
Anyone know what I mean?
Edit: to explain my position a bit, what I observe with people that are easily offended is that they can't seem to separate what the character is saying from what the overall piece is saying.
Just for example, think of The Office. Michael Scott says a lot of horribly sexist and homophobic stuff right? And we as the audience frequently laugh at it. But I don't think anyone in their right mind would say that The Office is sexist or that the audience that watches it is. Because we understand that the humor is that Michael Scott is saying something really inappropriate but he's also clueless and naive.
In this piece, the salesman character makes a dark joke. That is part of the character of the salesman, along with other aspects that are revealed. If you personally don't find it funny -- maybe you just don't like dark humor -- hey, I won't tell you you're wrong, humor is always subjective, but I don't see anything offensive about this. It's not advocating for violence it's simply adding a weird dark personality quirk to an already very weird character.
What I find with the easily offended crowd is all they look for is "bad thing involving a protected group in here? OFFENSIVE!" and completely ignore the actual context of everything else. It'd be like if you took a "that's what she said!" joke from The Office and completely ignored the context of Michael Scott's character.
You don’t have to be a bad person to have a dark sense of humor and you don’t have hate women to find the joke in the hammer factory story funny.
But I recently saw "Dont Fck With Cats" on Netflix and if had been a joke about animals I'd also been jared (because in my mind it would no longer be absurd).
So I guess it depends on what media makes us fearful off right now.
An AbstractEmotionSensorSensorFactory would be required to unwrap that.
Moreover dark humor has its place. Indeed 2 of the biggest comedies of the last 20 years used exactly this kind of humor (Seinfeld and The Office). Even to this day some of the most acclaimed comedies have humor in this vein (Veep and Curb Your Enthusiasm for eg).
It's that kind of joke that at best will make you look like a moron and ruin the mood... It's especially sad since the post is otherwise interesting.
You’ve got it backwards. No “psycho” would consider it funny; they would just consider murder something normal one could conceivably do with a hammer. There is nothing funny about it, to them; it’s just normal.
The murder joke was only funny to those people who consider murder something utterly unthinkable. It is its very outlandishness which creates the humor.
If it is no longer considered funny today, it can mean a few things: Either more people now believe that there are more “psychos” than they thought previously, or an extraordinary number of people have turned into “psychos”. Or possibly both.
This is why I think that masking outrageous things as humor is a losing strategy. Either it is funny, in which case the thing being masked with humor is so outrageous as to be completely niche, making the secret signal irrelevant, since so few people recieve it. Or, the thing is such a relatively common stance to make it no longer absolutely unthinkable, which makes it no longer funny, which removes the mask.
(Also, how do “white supremacists” fit into this? The original joke was about murdering an ex-girlfriend. This might possibly be generalized into misogyny, but I fail to see the connection to white supremacy. Please don’t fall into thinking that all people who hold opinions which you don’t like also hold all other opinions which you don’t like.)
The one thing not mentioned in the article is that the above line of thinking is almost guaranteed to lead to an insane level of abstraction, which was parodied in this classic article from nearly 15 years ago:
http://web.archive.org/web/20141018110445/http://discuss.joe...
(Sadly, that site is gone, but the memories live on...)
Addendum: to see this in practice, one needs to look no farther than Spring, the famous Java framework:
https://docs.spring.io/spring-framework/docs/current/javadoc...
https://docs.spring.io/spring-framework/docs/current/javadoc...
https://docs.spring.io/spring-framework/docs/current/javadoc...