15 ms·
Definitely underrates the impact of annotations. I'm personally not a fan of the way annotations are used to implicitly wire together applications, but I have t
by dkarl 1y ago
Definitely underrates the impact of annotations. I'm personally not a fan of the way annotations are used to implicitly wire together applications, but I have to admit the impact. Maybe 5/10 is fair in light of the wide range of positive and extremely negative ways annotations can be used.
So many of these features were adopted after they were proven in other languages. You would expect that since Java took such a slow and conservative approach, it would end up with extremely polished and elegant designs, but things like streams ended up inferior to previous developments instead of being the culmination. Really disappointing. Java is now a Frankenstein's monster with exactly as much beauty and charm.
- dhosek 1y agoI used to joke that the direction spring was heading was that you’d have an application be a boilerplate main method with a dozen lines of annotations. Then I actually encountered this in the wild: we had an app that sent updates from db2 to rabbitmq, and the application literally was just configuration via annotations and no actual Java code other than the usual spring main method.
- bdangubic 1y agothis is exactly why spring succeeded. I need to run a scheduled job, @EnableScheduling then @Scheduled(cron = “xxxxxx”) - done. I need XYZ, @EnableXYZ the @XYZ… sh*t just works…
- vbezhenar 1y agoYeah, it works until it isn't. And they good luck debugging it. I'd prefer simple obvious linear code calling some functions over this declarative magic any day. cronService.schedule("xxx", this::refresh); This isn't any harder than annotation. But you can ctrl+click on schedule implementation and below easily. You can put breakpoint and whatnot.
- bdangubic 1y agonever had any issues debugging as I am never debugging the scheduler (that works :) ) but my own code. and what exactly is “cronService”? you write in each service or copy/paste each time you need it?
- alex_smart 1y agoYou can write your own libraries? My goodness. What a question!
- bdangubic 1y agoyou write your own database driver? encryption?
- alex_smart 1y agoWhy would I write my own database driver or encryption just because I wanted to implement my own "cronService"?
- blackoil 1y agoIf you want to build an application from scratch, you must first create the universe.
- alex_smart 1y agoAll this because I told one guy who asked "what exactly is “cronService”? you write in each service or copy/paste each time you need it?" that they can reuse code by writing a library instead of copy/pasting it? If this is the level of incompetence encouraged by a framework, I would avoid using it just to avoid the chance of hiring people like you. Just kidding. Spring boot is great. But yeah, I would fire people with this attitude without blinking an eye.
- bdangubic 1y ago
- skeletal88 1y agoThen you need to deploy it on multiple nodes and neex to make sure it only runs once for each run of the cron, etc.
- bdangubic 1y agowhile not working on out of the box clustered this is trivial issue to address
- didntcheck 1y agoI believe Quartz is the go-to solution for this. It's not part of Spring but it offers a similar annotation-driven interface, but with distributed locking via a database
- bdangubic 1y agoAbsolutely! But Quartz is also quite heavy. If all you need is to ensure scheduled jobs run in a clustered environment there are more “lighweight” options
- happymellon 1y agoLike Shedlock?
- taftster 1y agoAnd then I realize I need to change that schedule. And would like to do it without recompiling my code. Oh, and I need to allow for environment specific scheduling, weekdays on one system, weekends on others. And I need other dependencies that are environment specific. I much prefer Spring's XML configuration from the old days. Yeah, XML sucks and all that. But still, with XML, the configuration is completely external from the application and I can manage it from /etc style layouts. Hard coding and compiling in dependency injection via annotations or other such behaviors into the class directly has caused me grief over the long term pretty much every time.
- nunobrito 1y agoThe only real-world usage I see for annotations are in GSON (the @Expose) and JUnit with @Test. Never really came across with any other real cases where it solves a pressing issue as you mention. Most times is far more convenient to do things outside the compiled code.
- taftster 1y agoAgreed. These are good examples where annotations seem like a good fit. Being able to tell a processor that a method or field is "special". Test methods, serializable hints, etc. It's kind of like, when annotations were delivered to Java, lots of projects thought they were just the next greatest thing. I followed right along at the time, as well. But over time, finding the value in the configuration-as-code approach to application deployment, I definitely feel that annotations have been abused for many use cases.
- bdangubic 1y agoall trivial things you are listing, every single one…
- sass_muffin 1y agoI do realize you were intending to give examples of why you don't think annotations aren't very extensible, but it is an odd example as all those things can still be achieved via annotation, since the annotations can accept values loaded from env specific properties.
- mikelpr 1y agowhy does this sound like systemd?
- 9dev 1y agoIs that strictly bad, though? Being able to run an enterprise service by setting configuration values declaratively, and get all the guarantees of a well-tested framework, seems like a pretty good thing. Yes, it’s weird how that’s still Java, but using standard components and only using code as glue where it’s absolutely necessary seems very similar to other engineering disciplines to me.
- SkiFire13 1y agoI think the general adversity against this specialized configurations is that they often tend to be fairly limited/rigid in what they can do, and if you want to customize anything you have to rewrite the whole thing. They effectively lock you into one black box of doing things, and getting out of it can be very painful.
- 2muchcoffeeman 1y agoSpring boot just provides what they think are reasonable defaults and you provide some specifics. You can always inject your own implementation if needed right?
- _ea1k 1y agoIn theory, yes. In practice, I've found that things get really complicated as soon as you start trying to interact with the spring lifecycle. Figuring out how to customize things and manage priority is the trickiest thing in Spring, IMO.
- lenkite 1y agoPlease use the facilities of the framework for debugging any lifecycle issues. I was surprised when I found out that people did not use `--debug` for example or enabled logging of application startup events. If you prefer GUI, Intellij even has a Spring Debugger: https://www.jetbrains.com/help/idea/spring-debugger.html https://www.jetbrains.com/help/idea/spring-debugger.html
- tormeh 1y agoIt's seriously puzzling. I just don't get how it's possible to look at what so many others have done better, and somehow design something worse. For what reason? Consistency with the rest of the language, possibly? But is that really so important. Do they just not want to tackle certain parts of the compiler?
- dcminter 1y agoSome of the weirder choices have been the result of a desire to avoid making breaking changes to JVM bytecode. Also there was a long period when changes were very lumpy - it could be multiple years for a feature to make it into the release, and anything that might screw up other features got a lot of pushback. Then other conventions/tools emerged that reduced the urgency (e.g. the Lombok stuff) Edit: I should add that it's now on a fixed 6-monthly release cycle which IMO works much better.
- rzwitserloot 1y agoOpenJDK redesigns massive swaths of the compiler every other month. The true explanation, at least the way OpenJDK says it, is that designing language features is more complex than a casual glancer can fathom, and there's 30 years of "Java is in the top 5 most used languages on the planet, probably #1 especially if focussing on stuff that was meant to be supported for a long time" to think about. From personal experience, essentially every single last "Just do X to add (some lang feature) to java; languages A and B do it and it works great!" would have been bad for java. Usually because it would cause a 'cultural split' - where you can tell some highly used library in the ecosystem was clearly designed before the feature's introduction. Even if you introduce a new feature in a way that doesn't break existing code, it's still going to cause maintainability headaches if you've cornered the pillars of the ecosystem into total rewrites if they want to remain up to date with the language. Because they will (or somebody will write an alternative that will) and you've _still_ 'python 2 v python 3'd the language and split the baby in the half. For what its worth, I think the OpenJDK team doesn't take this seriously enough, and a number of recently introduced features have been deployed too hastily without thinking this through. For example, `LocalDate`, which has 'this should be a record' written all over it, is not a record. Or how the securitymanager is being ditched without replacements for what it is most commonly used for here in the 2020s. (To be clear: Ditching it is a good idea, but having no in-process replacement for "let me stop attempts to access files and shut down the JVM, not for security purposes but simply for 'plan B' style fallback purposes" - that's a bit regrettable). I'm nitpicking on those points because on the whole OpenJDK is doing a far better job than most languages on trying to keep its ecosystem and sizable existing codebase on board _without_ resorting to the crutch of: "Well, users of this language, get to work refactoring everything or embrace obsoletion".
- vbezhenar 1y agoI don't really feel that Java uses proven features. For example they used checked exceptions. Those definitely do not seem like proven feature. C++ has unchecked exceptions. Almost every other popular language has unchecked exceptions. Java went with checked exceptions and nowadays they are almost universally ignored by developers. I'd say that's a total failure. Streams another good example. Making functional API for collections is pretty trivial. But they decided to design streams for some kind of very easy parallelisation. This led to extremely complicated implementation, absurdly complicated. And I've yet to encounter a single use-case for this feature. So for very rare feature they complicated the design immensely. Modules... LoL. We will see how green threads will work. Most languages adopt much simpler async/await approach. Very few languages implement green threads.
- rzwitserloot 1y ago> For example they used checked exceptions. Those are from java 1.0 and thus don't appear to be relevant to the part of the discussion I think this part of the thread is about (namely: "Why doesn't java crib well designed features from other languages?"). > Java went with checked exceptions and nowadays they are almost universally ignored by developers. They aren't. Note that other languages invented for example 'Either' which is a different take on the same principle, namely: Explicit mention of all somewhat expectable alternative exit conditions + enforcing callers to deal with them, though also offering a relatively easy way to just throw that responsibility up the call chain. The general tenet (lets lift plausible alternate exit conditions into the type system) is being done left and right.
- jayd16 1y agoI suppose you could actually solve it by having a promise that catches the exception like your suggesting with an either result. The C# Task API can do this. It has it's own headaches where now the developer has to pay attention to observing every exception. Java could do something similar but they have enough promise types already.
- vips7L 1y agoAll modern languages are adopting a checked error system: Rust, Swift, Kotlin, Zig, Gleam; they all have some type of error you must handle. The problem with Java is that they haven’t added the syntax to make dealing with those errors easy. It’s boiler plate hell.
- torginus 1y agowhich is kinda horrifying - it means the framework designers didn't find the language powerful enough to express app logic, and hotglued their own custom arbitrary behavior on top of it. Clear language code should be endeavor to be readable/understandable when printed on a sheet of paper by anyone, acceptable code should be understandable by anyone who knows a bit about the technologies and has some IDE support. Garbage code is what you have when the code in question is only understandable when you actually run it, as it uses arbitrary framework logic to wire things together based on metadata on the fly.
- Groxx 1y agopeople have been gluing other languages on top of languages practically forever - it's a DSL. no single language is ideally suited for every situation, it's not inherently a sign of failure that someone makes a DSL. and since annotations are part of the language, this is still all "the language is flexible enough to build the framework [despite being wildly different than normal code]" so I don't think it even supports that part.
- rzwitserloot 1y agoThat's not how the OpenJDK sees things. They tend to think that the features they deliver are at best mildly informed by other languages. Not out of some sense of hubris, but out of a sense of pragmatics: Simply copy and pasting features from other languages into java - that would produce a frankenstein. For example, java is somewhat unique in having lambda syntax where the lambda *must* be compile-time interpretable as some sort of 'functional type' (a functional type being any interface that defines precisely 1 unimplemented method). The vast, vast majority of languages out there, including scala which runs on the JVM, instead create a type hierarchy that describe lambdas as functions, and may (in the case of scala for example) compile-time automatically 'box'/'cast' any expression of some functional type to a functional interface type that matches. Java's approach is, in other words, unique (as far as I know). There was an alternate proposal available at the time that would have done things more like other languages does them, completely worked out with proof of concept builds readily available (the 'BGGA proposal'). The JVM would autogenerate types such as `java.lang.function.Function2<A, B, R>` (representing a function that takes 2 arguments, first of type A second of type B, and returns a value of type R), would then treat e.g. the expression: `(String a, List<Integer> b) -> 2.0;` As a `Function2<String, List<Integer>, Double>`, and would also 'auto-box' this if needed, e.g. if passing that as the sole argument to a function: ``` void foo(MyOperation o) {} interface MyOperation { Double whatever(String arg1, List<Integer> arg2); } ``` This proposal was seriously considered but rejected. The core problem with your comment is this: Define the terms "polished" and "elegant". It sounds so simple, but language features are trying to dance to quite a few extremely different tunes, and one person's 'elegance' is another person's 'frankensteinian monster'. The same mostly goes for your terms "beauty" and "charm", but, if I may take a wild stab in the dark and assume that most folks have a very rough meeting of the minds as to whatever might be a "charming" language: I know of no mainstream long-term popular languages that qualify for those terms. And I think that's inherent. You can't be a mainstream language unless your language is extremely stable. When you're not just writing some cool new toy stuff in language X - you're writing production code that lots of euros and eyeballs are involved in, and there's real dependence on that software continuing to run, then you __must__ have stability or it becomes extremely pricey to actually maintain it. With stability comes the handcuffs: You need to use the 'deprecation' hammer extremely sparingly, essentially never. And that has downstream effects: You can't really test new features either. So far I have not seen a language that truly flourishes on the crutches of some `from future import ...` system. That makes some sense: Either the entire ecosystem adopts the future feature and then breaking _that_ brings the same headaches, or folks don't use these features / only for toy stuff, and you don't get nearly the same amount of experience from its deployment. Said differently: If java is a frankenstein, so is Javascript, C#, Python, Ruby, Scala, and so on. They have to be. I'd love to see a language whose core design principles are 100% focussed on preventing specifically that. Some sort of extreme take on versioning of a language itself that we haven't seen before. I don't really know what it looks like, but I can't recall any language that put in the kind of effort I'd want to see here. This is just a tiny sliver of what it'd take: * The language itself is versioned, and all previous versions continue to be part of the lang spec and continue to be maintained by future compilers. At least for a long time, if not forever. * ALL sources files MUST start with an indication about which version of the language itself they use. * The core libraries are also versioned, and separately. Newer versions are written against old language versions, or can be used by source on old language versions. * The system's compilers and tools are fundamentally operating on a 'project' level granularity. You can't compile individual source files. Or if you can, it's because the spec explains how a temporary nameless project is implied by such an act. * All versions ship with a migrator tool, which automatically 'updates' sources written for lang ver X to lang ver X+1, automatically applying anything that has a near-zero chance of causing issues, and guiding the programmer to explicitly fixing all deprecated usages of things where an automated update is not available. * The language inherently supports 'facades'; a way for a library at version Y to expose the API it had at version X (X is older than Y), but using the data structures of Y, thus allowing interop between 2 codebases that both use this library, one at version X and one at version Y. That language might manage the otherwise impossible job of being 'elegant', 'simple', 'mainstream', 'suitable for serious projects', and 'actually good'.
- nine_k 1y agoAbsolutely. It seems that the author never touched Spring, for instance, or a dependency-injection framework of any kind. Annotations allow to do things in a completely different way, removing tons of boilerplate. I'd give annotations 9/10 at least. (And I lost the interest in the rest of the article, given such a level of familiarity with the subject matter.)
- Groxx 1y agoand that's before even touching on the compilation steps they can add, which are a pluggable codegen and macro system that is also integrated into IDEs, which is completely missing from almost every other language.
- jayd16 1y agoGood stuff. If that excites you, check out Roslyn source generators too.
- __float 1y agoMy experience using Dagger (2) was so unpleasant that it really soured me on the possible uses of this feature. I understand the benefits of dependency injection, but to be totally honest I'm more likely to take the Go-style approach of wiring it all up manually, even if it's a bit of extra boilerplate. The indirection and abstractions built up in DI frameworks is rarely worth it IMO.
- malfist 1y agoDagger is an absolutely pain in the ass. Its also damn good. Once you understand the archane syntax of their error messages its a lot easier (but still not easy) to use. Harder than spring, but less magic than spring
- vips7L 1y agoDagger is by far the worst UX experience for a DI framework. Really you should try anything else. For compile time DI like dagger you can try Avaje Inject: https://avaje.io/inject/ https://avaje.io/inject/
- jayd16 1y agoThe ratings are really all over the place. Jshell is a 6/10? What is the rubric?
- _ea1k 1y agoIDK, I've worked in projects that didn't use the magic so much and I honestly think it was worse. Instead of a config class and a bunch of apps with @Inject Config config;, we'd have giant *Config classes. Each one would have lots of methods like: @Bean public fooProducer(FooConfig config, BazProvider provider, BarProvider barProvider, SoapClient soapClient) {...} Want to know how they were produced? Find usages on the class' constructor. The magic @Inject and @Autowired annotations don't seem worse than that to me.
- zeroq 1y agoThe list generally oversimplifies a lot of things, but you're on point with annotations. Mandatory personal anecdote: I'm not a java guy, but I've been around java since '99, and few years ago I was moved to a strictly java team. Upon introduction I decided to show them my party trick and implemented live a pseudo "wolf3d" in one day. As usual, java devs were sort of impressed by the fact that you can do graphics and user input in java, because nowadays that's extremely rare for most of them. I got my approval and in return I asked them to give me a quick one day deep dive into Spring. At the end I was presented with a basic hello world project that was comprised mostly of... EMPTY CLASSES I mean literally, class Foo {} END OF FILE! Of course these empty classes had at least 5 lines of annotations on top of class declaration and in the end it somehow pushed the framework into the right direction, but oh my fucking god, I was throwing up in my mouth for the rest of the week.