Lombok makes Java cool again
bytes.grubhub.com
bytes.grubhub.com
Kotlin has data classes which auto generate sensible 'toString' and 'hashCode' methods which is a massive time saver.
Really, if you are considering introducing Kotlin to a Java project I'd recommend you just take the plunge. It's pretty much just a minor change to your pom file (if you're using Maven). Kotlin has Java interop as a primary feature and is flawless from my experience.
Unless you're using arrays. They went with "predictable" over "sensible" here.
> is flawless from my experience
As a heavy Kotlin user, I'd say it's far from flawless (not sure if you mean interop or the entirety of the language, but applies to both). I'm often creating and starring YouTrack issues. But yes, it's still a highly recommended alternative to Java.
0 - https://gist.github.com/cretz/2a49514b18914ef09b7c518db6db11...
It may be easy when your engineering org is 5 persons. It's much harder, genuinely harder, when it's 250 engineers, and an hour of downtime costs hundreds of thousands. Adoption of another language, even clearly superior and interoperable, becomes a large and costly undertaking.
So "just use Kotlin" is not always possible, and I'm thankful to Lombok's authors for their work.
The hard truth is that Scala was not designed with tool support in mind and it probably never will be. For example hierarchical implicit conversions, while powerful, are terrible for tooling.
You should pay attention to what's going on in the ecosystem. Dotty comes with LSP support. The Scala Center's main focus is tooling. JetBrains has been pretty committed and cooperating as of late. And several projects are building serious alternatives to SBT.
Because it does a pretty good job with implicits now: https://blog.jetbrains.com/scala/2018/07/25/intellij-scala-p...
Most libraries — SBT is one example — have shifted away from the use of excessive overloading.
Additionally the JVM wasn't enough for JetBrains and code written for multi-platform has slightly different semantics (see Kotlin/Native and immutable data).
Kotlin is a good alternative on Android due to a frozen Java 8.
Against Java 12 not so much.
It is very hard to displace platform languages when others just feel like guests.
* Excellent null safety enforced by the type system
* Data classes reduce boilerplate and bug sources
* Extension methods
* Declaration site variance removes a lot of the ? extends T
* Everything being an expression (if, switch/when) is powerful
* Smart casts are useful, especially when combined with subtypes and when (switch)
* Removal of checked exceptions
* Inline functions, a good way to work around runtime type erasure
* Properties, no more getX or setX
None of these are the killer reason to go over to Kotlin or another JVM language, but all combined they make building software much more efficient. Java could get all of these, but they've tried to increase feature delivery speed multiple times by adjusting release schedule and failed so far.https://trends.google.com/trends/explore?geo=US&q=java,kotli...
Specially since JetBrains decided that they not only want to go against Java, they are also positioning Kotlin agoainst JavaScript, TypeScript, WebAssembly languages, C, C++, Rust, .... Ergo choosing multiple fronts to focus on.
Which already has the consequence that Kotlin/Native code does not fully follow the same semantics.
Also regardless of what people in HN think, Eclipse still rules the enterprise and the Kotlin plugin is a bit meh playing catchup with latest features. Most IT departments won't sign off InteliJ licenses just because some devs think Kotlin is cool.
It is not language feature bullet points that make an eco-system, specially for those that don't own platforms.
With ChromeOS and Fuchsia on the game as well, it remains to be seen who will win Google's internal OS wars.
So far Android seems to be the one taking them all, though.
Outside Android I don't really see a major benefit switching whole teams away from Java, Eclipse/Netbeans and rewriting/wrapping libraries to be idiomatic Kotlin and such.
- supports both dynamic and static typing - it works just as well for bash level scripting as it does for full application development. For example, you can use it exactly like an interpreted language (no compilation needed).
- seamless Java integration. I know many JVM languages say that, but Groovy actually makes it one of its core features. It has a lower impedance mismatch than any other JVM language I've tried (and I try them all).
- partly because of the dynamic typing, it is able to support the most concise, powerful options in its syntax of any of the JVM languages. So you get more done with fewer, simpler lines of code
- because of the static typing and low impedance mismatch with Java, it can achieve very high performance. I was not able to get Scala to perform as well, for example, because of type conversions needed to invoke underlying Java library calls (unless you abandon idiomatic Scala).
- The amazing SQL library - HTML builders (there are XML builders too) - Literal collections (lists and maps)
This causes the program to run in dynamically-typed mode, much sloooower, so why do it? You'd have to add @Static annotations everywhere to get anything approaching Java-like speeds. One of the Apache Groovy project managers has long since wanted to make Groovy statically compiled by default, but that's never going to happen because they're running an unofficial go-as-slow-as-possible in the official upcoming upgrade to Groovy 3.
Magic always seems cool when you add it to a project - hey, look - I don't have to do <tedious thing> anymore.
Then time goes on, and you (or your successor) opens up the project to track down a bug. Only there's no logical flow - things just seem to happen as if by magic.
Lombok strikes me as magic in these ways.
I actually had to go to the project home page to figure out what problem it solves:
The examples refer to a specific kind of boilerplate, namely the kind that comes from treating classes a dumb bags of properties. Getters directly returning private fields. toString methods, etc.
An alternative approach to Lombok would be to think about how the project ended up with so many dumb data classes. How could the project be refactored to eliminate them, for example?
Doing so would avoid the need for magic and produce a code base in which effect followed cause.
On the other hand, when built into the language, this kind of explicit but automatic code generation can be awesome (as in Rust's #[derive()] traits [3]) but I think the difference there is the compiler/language vendor agree to support it indefinitely.
[1] https://developer.apple.com/library/archive/documentation/Co...
[2] https://developer.apple.com/library/archive/documentation/Co...
[3] https://doc.rust-lang.org/rust-by-example/trait/derive.html
Mapping entities to/from data transfer objects is a well established technique, as using dumb POJOs to drive serialzation/deserialization.
If you throw CQRS into the mix you can get a POJO/endpoint ratio that is greater than 1:1.
Do you want to write bug-prone boilerplate code for all those POJOs you are forced to write? Or do you prefer to simply add lombok to the project and add a single class annotation?
It's disingenuous to blindly criticize the extensive use of POJOs and DTOs as if it was code smell. In some applications it's quite the opposite. There is a ver good reason why lombok is very popular, as are other projects designed to complement it. In .NET Core land, where POCOs are used extensively, AutoMapper is also a must-have just because the need to handle POCOs without having to write all the boilerplate code is very real and pervasive.
Sure, I've seen Spring/J2EE projects that used Lombok... some 10 years ago, but the idea seemed to have died down, maybe it's my own bubble.
Automagic is ok for a specific type of problem (Rails for webaps?), Lombok however reminds me a bit of the promises of AOP which failed to gain traction outside frameworks.
There are other highly opinionated frameworks/stacks that have mixed adoption in the java world -- jhipster seemed to gain some traction, but people seem to just use bare spring boot nowadays(which is quite opinionated)
I don't have absolutely anything against small projects, I'd love to work more on smaller projects, but 18000 lines of generated code is NOTHING, try to convince a medium team to use Lombok, you'll very soon end up with people loving or hating it, removing it for debugging purposes, creating their own generators and very soon it's a real mess. But all that is just my experience, I'm not trying to dismiss Lombok or any approach, if it works for you, great!
You've must have heard about Spring projects. I'm yet to come across a Spring project that doesn't use lombok.
> Sure, I've seen Spring/J2EE projects that used Lombok... some 10 years ago, but the idea seemed to have died down, maybe it's my own bubble.
Perhaps it's my bubble as well but I'm still seeing Spring projects in production today that feature lombok extensively.
Not all organizations are willing to burn through cash to jump to the flavor of the month every couple of months. If you deployed a system and the system works, you need to discover a very good reason to throw a revolution and start from scratch with a new tech stack.
RubyMine had no idea about more than half of the Ruby code being written in the project. I couldn't grasp the concept of symbols in Ruby reliably enough. E.g. methods used either symbols or strings - I couldn't discern a pattern for which to be used when. This was the magic part of it - things seemed to work seamlessly but when they wouldn't, I had no idea where should I even begin to look.
Also every file had to be annotated for the strings to be treated as immutable. No ways to set it globally, project-wide.
It felt like a mess. On the plus side, there were gems.
Sure, it's very easy to add Jackson, but people severly underestimate the problem of JSON <-> POJO mapping(multiple ways of doing it and different programmers wanting to do it in different ways). So when you start it's very easy to just add some Jackson annotations and you think it's going to be smooth sailing from now on; add some complexity down the road and you just use some stackoverflow recipes to do the thing I want (without any in-depth Jackson knowledge) and it still is ok-ish; add some more complexity down the road and then you feel the frustration of using Jackson.
Add in some web annotations, security annotations, ORM annotations, logging, test annotations and so on and it's the same story -- you are in fact doing something very complex and when you depart from the happy path of using the functionality via annotations, it gets quite frustrating.
I really don't know anything "better"(time wise) to accomplish the same thing though.
I even have this experience on my own projects and with much shorter span than a year.
When I go back to a code base I worked just 3-4 month ago -- I have very hard time following Dagger injections. Braking one of the Dagger providers (I use dagger 2) creates 10s and 10s of compilation errors.
So I have been slowly removing dagger from my code bases. As I just cannot follow it, I do not know it well enough either.
I stopped using Lombok for same reason, although I found Lombok is much more intuitive.
I thought about going to Kotlin, but it was just too much of cognitive overload (in this particular instance both for backend and android UI).
Instead, I am finding the use of rxjava and friends a lot more intuitive and helpful for the type of things I need help with (interconnecting components on different threads, with different lifecycle and data structure semantics, with lots of asynchronous behavior options).
And the same rx-<> library is available to many other languages, therefore, the skill I am building up is sort of 'cross-language'.
On top of that it creates huge ass stack traces that makes finding your code difficult unless your debugger can filter out specific packages:
https://github.com/ReactiveX/RxJava/issues/4834
Kotlin on the other hand is a fairly clear language!
This.
I use a different but similar analogy borrowing from DnD.
- Java and especially annotations need wisdom (WIS)
- E.g., Clojure needs intelligence (INT)
This holds even for syntax where homoiconicity requires just so much less "experience".
It is difficult to avoid Getter/Setting in Java due to how popular framework works.
@ToString are useful for debugging. Why hand code pages of println if it can be generated?
I seemed a cool idea, however I thankfully no longer need to fix bugs in such projects.
ba dum tish
Currently I'm building my SaaS + Selfhosted software licensing product with this Spring Boot + Lombok + Postgresql combo and Annotations are helping me out for :
* @Bean, @Autowired, @Profile,... for configuring dependency injection with multiple profiles (saas vs selfhosted)
* @Entity, @Table, @Repository, @Enumerated, @Embedded for mapping my classes to Database tables
* @Api, @ApiOperation, @ApiParam,... for generating Swagger documentation for my endpoints
* @GetMapping, @PostMapping, @PreAuthorize for configuring and authorizing my endpoints
* Lombok @Data, @Value, @Builder for tedious boilerplate generation
* @NotNull, @NotBlank, @Email, @PhoneNumber, @Pattern(regex), @Length(max=12),... for validation of form input, api params and database models.
* @JsonProperty, @JsonSerialize, @JsonIgnore for configuring json deserializing for my DTO's
* @Value("${my.config.key}") for decoupling configuration from my code and injecting values at runtime
I probably forgot a few more use cases. All of these are pretty straightforward, are defined alongside the code they are modifying or interacting with and would lead me to a lot of lost time and a lot more code if I were to implement them myself, or configure them by hand "in real java code".
Honestly, I prefer to debug with annotations and a debugger over a manually coded configuration class with hundreds of lines of wiring components together.
And for removing the other annotations: Why should I decouple apidocs from my code? They are litterally annotating the source code so it makes sense to have them close.
Stuff like the validators and JSON properties and configuration also makes sense to have them close to the fields that they are about instead of in a separate JsonSerializer class or whatever.
The main issue with these annotation hacks is that they fail in the area of “means of combination” and “means of abstraction”.
For example if you have: @X @Y class Something{}
You cannot tell if X & Y can be combined (or how it’s combined), or you cannot easily create an abstraction Z with X and Y (you may be able to do that but it requires framework support and usually is painfully impractical)
It seems to be a small issue compared to the added value, but to me is not. As the code base grows the maintenance and debugging issues grow too... in the end probably is better to use Kotlin or Scala.
[1]: https://mitpress.mit.edu/sites/default/files/sicp/full-text/...
Makes clear and explicit the queasy feeling I get from over reliance on Java annotations.
Lisp macros, for example, fare much better in the "means of combination" and "means of abstraction" tests, I think.
i.e.
@GET(url="http://host/users/$userid") public abstract User getUser(String userid)
isn't that much easier than the python requests version but much harder if you need to add a custom dynamic header to the call.
That seems like an easy problem to solve. Don't use annotations.
Snark aside, it can be a bit of a tricky balancing act. When there are more annotations than actual code, that's a code smell in my book and something needs to change. Separating concerns usually does the trick. I'm heavy on code generation, so anything that's boring enough to benefit from oodles of annotations will probably end up getting generated based on a 2 line config file or something.
Switching to / mixing in Kotlin is worth it just for the data classes at this point (and you get some type-level nullability and `val` instead of `final var` to boot).
Then you can create a standard immutable class with a constructior, and Jackson's object mapper can then serialize and deserialize.
The only catch is that you must add -parameters to your javac args.
Compile time dependency injection (Micronaut framework).
Not something I have a use for but it's clever.
Its just: @RequestHeader("Header") added to the param list.
>@Context HttpHeaders headers
I'm not sure if there's some kind of regex support or anything like that. Looping through the headers seems simple enough.
This is similar to the situation with design patterns. When design patterns came along, Java folks talked about them as though they were a wonderful new invention. But they only exist because Java is so clumsy that it needed crutches to do things that had been easy and natural in other languages for years; someone merely came along and gave the crutches names.
More generally comparison and hashing methods seem like small potatoes compared to locking primitives, portability, and a large, well-documented class library that have made Java ideal for a very large fraction of business applications. There are many things to criticize in Java but the points you list don't seem to be especially important compared, say, to the relatively steep learning curve/complexity vs languages like modern Javascript.
Many of the popular design patterns are simply lambda. If you have first-class functions, they are trivial. But Java doesn't have first-class functions; instead you have to write a giant thing that looks like a class, and because that feels like a lot of work, it seems to deserve a fancy name like Observer or Strategy. But it's all fabricated work; the only reason you have to do it is that the language has this stupid weakness.
It's as though you were working in a language that didn't have lists. And one day you realize, hey, it's useful to be able to manipulate variable-length collections of things. So you look at a problem and say "Aha, this is exactly the place that we should use a Collection design pattern" and you go write the entire implementation of lists from scratch and feel satisfied that you did a good day's work. You tell all the programmers you know that there is this great design pattern called a "Collection" that they should all start using. You write books about it. You give workshops. You get really good at writing list implementations, because every time you need one you implement a new list from scratch.
When you tell a Python programmer this, she looks at you like you have lost your mind, writes a function that takes a list, solves the problem in 5 minutes, and gets on with her day.
Qt was also born in that era, and look at how strikingly similar its standard library is to Java's in many cases, and how unlike STL and Boost.
I cannot speak for Boost, but STL is also not free of OOP as such, iostreams, data-structures, allocators.
ATL was all about using templates, as optimization or not, one is on template land when using ATL, it is even part of the name.
Design patterns are a useful way of thinking about any sort of design, not even just in programming. Novels and paintings have them, too, so does your favourite language. There will always emerge from experience particular ways of using your medium, and having names for those patterns can actually be pretty useful for thinking about them and structuring them into a good design (like how SICP talks about knowing a spirit's name gives you power over it). You can't avoid using design patterns; someone just might not have come along and named them yet, or you might not be aware of them.
The GoF book itself actually discusses this in the first chapter a bit, here's a relevant part:
Point of view affects one's interpretation of what is and isn't a pattern. One person's pattern can be another person's primitive building block. For this book we have concentrated on patterns at a certain level of abstraction. [...] Our patterns assume Smalltalk/C++-level language features, and that choice determines what can and cannot be implemented easily. If we assumed procedural languages, we might have included design patterns called "Inheritance," "Encapsulation," and "Polymorphism." Similarly, some of our patterns are supported directly by the less common object-oriented languages. CLOS has multi-methods, for example, which lessen the need for a pattern such as Visitor.
i would think javascript is the language with which the mistakes wasted the millions in man hours, not java.
Sorry, but this argument doesn't pass muster anymore, when there are abundant examples of strongly typed object oriented langs which don't exhibit these flaws. C# avoids getter and setter boilerplate with `get` and `set`. Kotlin and Swift, unlike Java, actually are strongly typed, because objects aren't implicitly nullable. And the many conveniences afforded by modern language features add up to improved productivity, provided the programmer using the language doesn't rigidly refuse to learn to use them.
It's about having non-nullables. It's about having value types. It's about controlling mutability. It's about the evils of (especially mutable) statics and life-before main. It's about having closures (at least recent Javas have them!)
None of this goes against strong typing or object orientation. Java just is... not so great. Of course, everything about this is hindsight: Java was also a revolution: generics (slightly flawed and bolted afterwards, but anyways), garbage collection, memory safety, JIT in an usable package.
Java has shown it's age and some people are aware of it.
See, for example, F#, Scala, or Haskell.
All in all, I am not a fan of OO anymore. I like the computer science principles behind it, but practical programs never use them. They always get Liskov Substitution backwards. People always use subclasses for cases like "give me the superclass, but with these conditions" which is the exact opposite of what you're "supposed" to do. Problem is making your subclass less restrictive is largely useless, what people really want is copy-paste without having to maintain two copies of the code. So there is some mismatch to what programmers and computer scientists call classes, and the result is that you get a mess. I'm not sure that's Java's fault or its largest problem, but it isn't helping.
And that's why I just can't love Java these days. Java was great for 20 years ago, but, by modern standards, it's sub-par in both departments.
Meanwhile, I'm sure in Java that you have to implement some class that is the visitor, which means you have to create a new file, and probably install some VisitorUtils for making file visitors easier to implement, ... that is where it's gone off the rails. (But I am perfectly willing to believe that the exact same API exists somewhere in Java too, and most people browsing directory trees don't have 13 classes to print the names of the files in a directory. But at the same time, I feel like there are pressures pushing you in that direction. That's where the complaints come from.)
The ironic thing is that golang doesn't have an even better construct for this kind of pattern, namely pattern matching.
Your history is wrong. Design patterns became popular with the release of the GoF book, which was written using C++ and was published in 1994 -- before Java was even released.
"Design Patterns", both as a book and a concept, is about the recurring patterns found in object-oriented software; it is about the problems that lead to those patterns, and the solutions to them. Although the book itself is rather dated, the patterns described in it remain relevant to most modern languages. Some of those patterns include: Builder, Singleton, Proxy, Decorator, Iterator, Visitor, Prototype (c.f. JavaScript prototypes), Observer, Chain of Responsibility, and more.
Many languages employ these patterns in the design of their standard library. E.g. the Iterator pattern shows up in a large number of language libraries (Python, Ruby, JavaScript, Swift, Rust).
If you search for "Design Patterns in X", for programming language X, you can find quite a lot of literature about how to employ them in any language. For example, see "Python 3 Patterns, Recipes and Idioms" [1], which describes how to employ many design patterns in Python, or "Design Patterns in Ruby" [2]. A lot of modern literature for many programming languages relies on these patterns either implicitly or explicitly.
Most of these patterns do not exist because of language weaknesses, although I would agree that this is true for some of them, such as the Visitor Pattern. Use of the Visitor Pattern is often better replaced with pattern matching in programming languages that have it. However, this isn't true for all of the patterns (e.g. Chain of Responsibility, Facade, Proxy); and even the Visitor Pattern remains relevant to many languages, especially dynamic languages that do not support pattern matching.
Many of these structural concepts were not new, in the sense that they did not originate with the book; but the GoF took the time to assign names, characterize them, and describe the trade-offs involved in using them.
[1] https://python-3-patterns-idioms-test.readthedocs.io/en/late... [2] https://bogdanvlviv.com/posts/ruby/patterns/design-patterns-...
... in statically typed languages of that time (like C++ and to some extent Java). At least some patterns solve problems that are not (that kind of) a problem in, e.g., dynamically typed languages.
They are/were a bit over-hyped and sometimes overused inside the Java community, that's true.
> But they only exist because Java is so clumsy that it needed crutches to do things that had been easy and natural in other languages for years;
Sure, some design patterns work around language deficiencies or are that commonly used to justify including them into the language. Java sadly did not do this enough in the past, that's right. But if you look through the list of design patters only a minority fall into this category.
> someone merely came along and gave the crutches names.
That's basically the whole point of design patterns. It's assigning a name to things already widely used to improve communication between developers and to provide learners a point to look up a concept.
Go dig out a book about Boochs, CORBA, DCOM/MTS,...
Is there anyone here with Lombok experience that wants to share any issues they've run into while using it? All I can think of right now is the fact that the source code isn't compatible with Java.
Some people are uncomfortable with the extensive bytecode manipulation that it does. While Lombok provides annotations, it is not your normal annotation processor. From http://notatube.blogspot.com/2010/11/project-lombok-trick-ex...:
> Project Lombok hooks itself into the compilation process as an annotation processor. But Lombok is not your normal annotation processor... The trick is that Lombok modifies the AST. It turns out that changes made to the AST in the Annotation Processing phase will be visible to the Analyse and Generate phase. Thus, changing the AST will change the generated class file.
That said, we never encountered any Lombok-related problems when running services in the cloud or locally. And the Lombok plugin for IntelliJ is good enough in that the auto-complete will "see" the Lombok-modified version of the file. For example, using the @Value annotation creates an immutable value type, which among other things a) makes every field private and final, and b) generates a getter method for each now-private field. With the Lombok plugin, IntelliJ auto-complete will a) not auto-complete the composed fields which are now private, and b) auto-complete the generated getter methods.
I highly recommend looking past the voodoo bytecode manipulation and using Lombok. The @Value annotation alone is worth the price of admission and made me a more productive programmer.
The premise is fine...I have no problem with it. But the default generation of @EqualsAndHashcode literally pulls in the WORLD to generate the output.
The real world scenario we had was this. Lots of POJOs were created, many were simply but a non-trivial number were NOT. Those POJOs could have dozens and dozens of fields. And if you have a key abstraction with say, 86 fields, things get interesting.
Suppose you don't use @EqualsAndHashCode on one of these POJOS with lots of fields, ALL 86 fields are included in the default equals and hashCode methods. They didn't realize this, or didn't care, and as a result, had some serious performance issues because trying to run hashCode on insert to a map when you're hashing 86 fields together might actually take some time inserting 100,000 records... ugh
So in short, it's OK and useful, but you have to understand the side effects of everything to know if it's the right thing for you.
SIDE NOTE: A POJO with 86 fields can be common in financial services when you are representing various kinds of financial trades where gazillions of things are tracked on them...interest rates of note, ratings, security characteristics, etc. That in and of itself isn't necessarily poor design, although these choices predated me at this company.
I also don't like that the generated code is not checked in or visible. It makes code review harder. In theory you can have magic change to all Lombok classes just by upgrading the library.
Also it doesn't pass the cost/benefit test for me. Adding Lombok adds complexity to your code, build system, and IDE. What do you get? Slightly shorter classes? Less characters? Most of this code can be generated by the IDE.
I'll give Lombok one win. It will keep equals and hashcode up to date if you add properties. That's a pretty common error.
As others have said, Kotlin is the best alternative. But even without Kotlin I skip Lombok happily.
If you check in generated code, you have to prevent anyone from editing it (so it doesn't become something you have to start testing and reviewing) and include some kind of summary so nobody wastes time looking at it.
I love it though, think I've been using it for at least 8 years now in just about every Java project.
If you work on a lot of different projects with different versions of Lombok, do you have to have multiple versions of the IDE plugin installed / is that even possible?
Actually, anything involving default values seems to be very brittle and difficult to work with -- especially when deserializing objects from JSON.
The @Wither annotation results in methods that do not always make a copy, and sometimes use == instead of .equals when comparing class members. If you call one of the Wither methods and assume you have a copy of the original object, and then you modify that "copy", you might have just created a very subtle bug.
If you are using IntelliJ you can use the Refactor > Delombok menu option to show you the code that Lombok generates. I've been told that does not actually invoke the same code that the annotation processor invokes at compile time, so the results of Delombok might be misleading.
A builder is just a fancy alternative to a constructor. It is possible to set the value of a final field in a constructor, and it is also possible to have another constructor in the same object that sets a default value for that field. I expect the same flexibility from a builder.
For example:
public class Person {
private final String name;
public Person(String name) {
this.name = name;
}
public Person() {
this("Bob");
}
}
Now you can get immutable Person objects: Person p1 = new Person();
Person p2 = new Person("some other name");
But if you do this with Lombok: @Value
@Builder
public class Person {
@Builder.Default("Bob")
private String name;
}
You cannot do this: Person p = Person.builder().name("Some other name").build();
The name is stuck at the Builder.Default value. In that sense it is not a default, rather it is the only possible value. A default is supposed to be something that can be overridden.I could use @Data instead of @Value but then the resulting Person objects would be mutable which I don't want.
Also as it is compiler magic, it can be a bit confusing to developers who haven't used it before.
We also know from Google’s paper on software practices that software naturally gets rewritten over time, at a cadence that makes it acceptable to switch languages. So there is really no reason not to have a plan for your business to migrate languages periodically.
Unfortunately the industry seems to be stuck in a rut, and we’re forced to retrofit ancient compilers like horse drawn buggies with plate armor and machine guns on them.
New tooling often follows a commom cycle of being light weight because they ditched the stuff that looked unecessary. Then slowly a whole ecosystem of plugins and libraries springs up to rebuild the missing tools. Like the bottom bracket tool in a bicycle tool box. I have never used one, but when I need one no other tool would work.
I do truly believe that there is mastery in advanced, long lived tooling and efficiency may not lie with a new language but in truly understanding a comprehensive set of ordinary tools.
Languages also tend to be just the building blocks for the real tools, which are the frameworks and libraries built with them. So I hesitate to throw away my hard earned knowledge only to re-learn another artisan MVC framework written in a prettier language.
I concede that the parade must go on though, and ultimately I have to pay the bills. If precompiled serverside React is where the money is then... so be it.
Why do you think people in the industry are doing this? There must be multiple factors behind this.
[0] http://worrydream.com/refs/Brooks-NoSilverBullet.pdf
[1] https://www.cs.utexas.edu/users/EWD/transcriptions/EWD10xx/E...: the business community, which, having been sold to the idea that computers would make life easier, is mentally unprepared to accept that they only solve the easier problems at the price of creating much harder ones
[2] See why Martin Fowler stresses Technical Excellence basically every time he get's on stage: https://www.youtube.com/watch?v=G_y2pNj0zZg
Naturally it only works in new markets, because for existing products the market is already saturated.
Also some kind of improvements are just impossible in existing languages either due the language semantics and libraries, or community resistance.
Secondly, education is focussed on what’s new, not on what has been well engineered.
ahem
XML bean configuration files. Annotations. Implicit code generation. Property and YAML files. Looking for files and resources in "default" locations. Executing code at runtime as the result of adding a dependency, without it ever being explicitly called anywhere.
The result is, you can never really predict what the hell your program is going to do when its running in production. Certainly not by carefully reading and inspecting the Java code you write.
My experience doing Web development in Java started with joining a team that was ramping up a project on WebSphere 5.1, alongside a migration from BEA WebLogic.
I went through a couple of WebSphere, JBoss and TomcatEE versions up to JEE 6, was the technical lead for an in-house JSF framework built on top of RichFaces, and on very last JEE project used PrimeFaces.
All the employers that I worked for, mostly used JEE stacks for their Java projects. Spring was the exception, rather than the norm.
Additionally, even if a bit slowly, many of the Spring improvements ended up in JEE and Spring also supports the JSR related to JEE anyway.
Nowadays I live in the .NET world, and the Java teams next door are happily delivering JEE based solutions.
So if I was suddenly asked to architect a Java based solution, naturally it would be JEE based.
Those decisions were less to do with personal freedom or program simplicity, and more to do with the realities of large scale enterprise development. You end up having to adopt patterns that make individual programmers replaceable, and allow for a standardized pool of candidates with experience in $X framework. The reality is that most companies building Java software just need to be able to hire an average Java programmer to show up every day and manage their huge tangle of business logic.
This is a re-occuring pattern.
I have seen it happen with C, C++, TP, Clipper, Java, JavaScript, C#, VB, ...
The enterprise environment is quite "interesting", specially when politcs get intermixed with technology.
(Of course, there is very little new under the sun so I'm not claiming Java was the first to do anything in particular)
The original collections library in Java was poor due to lack of generics. Again, there were better models out there and some of them were even in fairly wide use at the time (C++). Java is only recently catching up to better models for collections with streams that many other languages have been doing in some form for a long time.
I don't have much of an opinion on Java IO or how it compared to contemporary alternatives but most languages have pretty robust support for basic IO in their standard libraries. What about Java do you think was particularly good for the time?
One thing that I think Java deserves some credit for popularizing is reflection, although the Java model of dynamic reflection isn't what I'd pick if designing a language from scratch today.
Some languages do at least one thing particularly well, setting the standard in that regard. Some introduce or popularize new concepts that slowly see adoption elsewhere. Several make you think about programming in a different way. Some just have an overall elegance, unity, consistency or simplicity of design that make the whole greater than the sum of the parts.
Languages that check at least one of those boxes for me include C, C++, Lisp, F#, Haskell, Lua, Python, Elm, APL, SQL, ISPC and Rust (I don't claim to be an expert in all or even most of those languages). Other languages that seem interesting / different but I know almost nothing about include Erlang, Clojure, Smalltalk, Prolog, Forth, Coq, Idris and Kotlin. By contrast I've not really seen anything in Java that checks any of those boxes and isn't done better elsewhere (admittedly sometimes by languages that came later like C#).
Java's biggest mistake IMO is checked exceptions.
Java is more notable for the things it left out than what it added. It was designed for success and achieved more than most of the languages you list.
I don't love Java. However there are many cases in which it's still the best option.
I'm from the games programming world where C++ still dominates for performance reasons. Where GC is widely used with C# in Unity, Unity are building their own C# compiler without GC for performance critical code (the Burst compiler) and GC is a major cause of performance issues in existing Unity games.
C++ and another non GC language (Fortran) are also the most used languages for HPC ( High Performance Computing) in scientific computing.
C and C++ also dominate in performance sensitive and resource constrained embedded programming.
I personally don't find GC very useful nor do I think it's a particularly good idea and it's great to see new languages like Rust taking new approaches to memory management but I recognize that it's now the default in the majority of applications that don't prioritize performance or deal with severe resource constraints and that many programmers find it valuable. There are actually several languages I like that rely on a GC, Java is just not one of them.
Why? It adds more time to the build, it requires plugins to work effectively, and it confuses the hell out of people when they expect to see Java. If you really want a better syntax, use Scala or Kotlin (I'm a huge Scala convert).
Is it not the most popular JVM language?
In my professional bubble Lombok is completely gone. I'll go as far and say that modern Java does not use Lombok. Smaller, focused libraries like Immutables or AutoValue, solve the problem of boilerplate for data classes.
Lombok tries to do too much across many concerns, in a fairly opaque way, and makes the code and tooling around it more magic than it needs to be.
Skip Lombok. Modern Java is better off without it...
favoriteFoods = new java.util.LinkedHashSet<String>(
this.favoriteFoods.size() < 1073741824
? 1 + this.favoriteFoods.size() + (this.favoriteFoods.size() - 3) / 3
: Integer.MAX_VALUE
);
favoriteFoods.addAll(this.favoriteFoods);
Why not just? this.favoriteFoods = new java.util.LinkedHashSet<>(this.favoriteFoods);The infrastructure team of the main company forced us to include Lombok and use it and the code was a mess. Too much automagically generated code that was hard to debug.
My company managed to deliver the microservice with enough quality, but I really hated the experience with Lombok, vanilla Java with the libraries needed for the task is more than enough (more control over your code).
Since Lombok is "just a library" I've managed to sneak it in almost every project I've worked on. Deftly used, I swear it cuts the LoC in a project by 30%
What would've made them good would be a static analysis of some sort that could divine all possible exceptions thrown by a method call and its dependents based on its code, but in Java a class can always override another class and do something entirely different so that's not possible.
Checked exceptions are (for the most part) the mirror image of checked returns! Both describe and circumscribe your expectations for what comes back from the method, the only difference is one is happier than the other.
They follow the same kinds of type-rules, and the real problem is entirely on the human end: It's harder to imagine failure scenarios, features are prioritized over error-handling, and people don't like to do it if they can avoid it somehow.
> What would've made them good would be a static analysis of some sort that could divine all possible exceptions thrown by a method call and its dependents based on its code
Again, ask yourself "why don't we already do this but with return types?" Probably because it's horribly impractical in most cases and and outright impossible in others. For example, when it comes to interfaces, abstract-methods, or overridden methods... All of those involves concrete implementations that haven't been made yet.
Heck, they might not get created until months or years after you ship your .class files. Then some random guy you've never met says: "Oh, hey, I can fix my problem by creating my own implementation based on their interface..."
So unless your compiler can literally see into the future, you've got to set down your type-expectations first.
> but in Java a class can always override another class
No, even if all classes were marked "final", you'd have the same problem when it comes to using interfaces.
Furthermore, checked exceptions force you to bubble up implementation details all the way up to the interfaces. For example, if any implementation of an interface might throw IOException, you need to put that in the method signature in the interface. You can be clever and wrap it in a custom exception that exposes the same level of abstraction as the interface, but that's not a whole lot better than just using RuntimeException, and it causes people to try to cast the exception cause.
In both cases it's about constraining and describing the results from a method. The only difference is that most developers are so much more accustomed to thinking and coding along the happy-path, where they will willingly do all the exact same architectural tasks.
Seriously, read these three variations aloud and see how they sound:
>> return-types force you to bubble up implementation details all the way up to the interfaces
>> if any implementation of an interface might return Foo, you need to put that in the method signature in the interface
>> You can be clever and wrap it in a custom return-type that exposes the same level of abstraction as the interface, but that's not a whole lot better than just using Object
I'm willing to bet you can think of a slightly-incredulous answer to each of those, right? Perhaps something along the lines of "no they don't", "yes that's normal" and "WTF of course you should."
If every kind of exception was checked, then every method in Java, including constructors, would need to declare that it throws RuntimeException and IOException and NullPointerException and NoClassDefFoundError and InterruptedException because those things could happen in any method.
So we settle for declaring the type a function is expected to return, with the understanding that if something exceptional occurs a magical thing called an Exception will be thrown and it's not handled the same way as a return value. This lets us get our jobs done. It's engineering, not science.
When you solve the halting problem we can talk about formalizing every possible failure state as a return value. Until then we will get on with our lives.
Hold it: I said nothing remotely like that. You have begun ranting against an absolutist straw-man of your own creation.
What I DID say is that:
1. Type-checked exceptions are just as valuable as type-checked returns.
2. The "problems" you listed with checked-exceptions are baseless: Each of them is identical to an architectural decision or task you already regularly complete for happy-path return-types.
3. The fundamental driver of complaints is that people don't like to spend time on the unhappy-path, and aren't as rewarded for it either.
While they may not have been the wrong decision, empirical evidence continually shows they add a syntactical burden many devs just work around (e.g. just wrap in a RuntimeException anyways). It's understandable why most modern languages don't consider predictable exceptions as part of the function signatures (though many discourage exceptions altogether in all but the most extreme cases).
Error conditions captured in the type system are a vital part of making code robust. The fact that some developers don't know how to properly handle these error cases doesn't change anything to the soundness of checked exceptions.
It's not like saying they shouldn't return them (unchecked exceptions exist), it's like saying they shouldn't be forced to check the error code or declare that they will return that error code themselves. Checked exceptions aren't sound when in practice the same ones are reused to mean many things. And we shouldn't pretend deciding whether an exception should or shouldn't be checked is an objective choice applied the same way across the ecosystem.
What's not to like about that?
Contrast with all the other approaches (runtime exceptions, return codes, Either/Result, etc...) where the developer can happily ignore errors, the code will compile, and then crash at runtime.
list.stream().map(doStuff)
might throw whatever doStuff might throw, but instead map is defined so that doStuff can never throw any checked exception, which requires smuggling out every real-world failure mode.One of the parts of Lombok that really works well for me is that its notations are completely optional and fit many different levels of granularity as you see fit. Don't want sneaky exceptions? Don't use them!
I mostly end up using it to make my Java code feel like C# though...
EDIT: There's also the fact that Java includes unchecked exceptions, so you can already subvert that part of the type system.
I don't really write much Java these days, but I've use at least a few libraries which use checked exceptions in a way that really sapped the fun out of the experience. (And personally I feel it is important for writing code to be fun, because little else of working with computers is.)
"Forces API clients to catch exceptions you can't handle."
My observation is that obfuscation frameworks throw a bunch of checked exceptions which are unrecoverable. Which says more about the frameworks than the language.
Hibernate: RDBMS obfuscation framework
Spring: Flow of control obfuscation framework
JAXRS: Exception obfuscation framework
On the other hand, it's impossible to scale Java down to the point where it would be sensible choice for what would otherwise be a bash script.
Counterfactuals can be cool, but you have to explore them, not pre-suppose them. Pre-supposing them is definitely not cool, which calls into question the ability of our interlocutor to recognize what is and (more pointedly) what is not.
Off the top of my head; PostSharp (although not free) would get you much of this. Also might be achieveable via Roslyn? And there is another .NET AOP framework whose name eludes me at the moment... EDIT: Fody.
Is this still true when you consider available libraries, performance, concurrency, build and dependency tools, development environments, and deployment options?
I've found F# to be pretty good in all of the areas you mentioned. Although not enough to literally provide an order or magnitude boost like the post you were replying to suggested.
I think people use "order of magnitude" a little too freely. Developing with Java (and to a lesser extent, C#) feels slower than with F#, but not 10 times slower.
Five times in six their grocery search - literally the core of their product - 502s on me. In the remaining 1 of 6, if I just keep repeating, I get random subsets of the data I should actually be getting. It's amazing to me that I haven't seen a fail whale yet.
I refuse to believe that Instacart has a large practical server load.
These are not the people we should be listening to.
This is a convention that emerged in the late nineties where people used a naming convention for setters and getters to expose properties. Lots of frameworks (hibernate, jackson, gson, etc.) rely on discovering such properties via reflection. Unfortunately this results in a lot of boiler plate and code that does nothing interesting. Worse, a lot of these frameworks expect mutable classes, which should rightfully make you feel dirty every time you write one.
Lombok helps with that and you can sort of fake having quasi immutable classes. There's a price to pay unfortunately: it depends on generating code at compile time. This makes it necessary to use plugins for your favorite IDE to not get confused about all the non existent methods you are calling to access these getters, setters, builders and all the other boiler plate Lombok generates. Unfortunately these plugins make intellij more likely to get confused, which is something that can only be fixed with lengthy rebuilds, cache clearing, and the occasional restart. I've wasted no small amounts of time in trying to convince Intellij to get rid of phantom compile errors.
For this reason, I'd never introduce it in any project I'm in charge off. It's just not worth the pain and I'd rather solve the problem of not having to have stupid code like that to begin with by using something that does not require it. IMHO code generation is a bit of an anti pattern. It's kludge for having flawed languages and broken frameworks. There usually is a better solution than generating a lot of source code.
These days, Kotlin is a vastly superior way (and I say that with close to 25 years of Java experience) of avoiding that type of code and modern frameworks like spring boot work fine with it. Also worth pointing out that Java is slowly absorbing new features. Type inference is there now and they are working on data classes and a few other features. It might eventually catch up with less than half of what Kotlin does today in a decade or so. But why wait? IMHO Java is no longer a defensible choice on most new projects.
Converting Java to Kotlin with the built in conversion tool in intellij is not flawless but it gets you quite far and you can go class by class. I actually have one project where I've been gradually moving to Kotlin for over a year now. Every time I touch some old code, the first thing I do is convert it to Kotlin. Only takes a few minutes once you get the hang of it and it's a great way to explore the language. Mostly you spend this cleaning up the things it got wrong regarding nullability, funky generics, or just using a bit more idiomatic constructs (e.g. using sequences instead of streams), etc .
To quote @Retra in this thread: "I feel it is important for writing code to be fun". IMO, programming is fun when you are solving "business" problems, not writing boilerplate or handling a language's myriad edge cases.
NB: Groovy might be the closest thing to "LombokScript"
/s (just a bit)
Honestly, a lot of the best TypeScript features could be described the way you describe it.
A few annotations don’t seem that much more radical than stuff like AspectJ.