Comment by piva00
5 years ago
> but not in the granddaddy of OOP languages: Java. Still to this day, I have absolutely no idea how people can write Java for line-of-business applications (its bread-and-butter!) and remain sane from having to manually manage data-flow, implementing Java Beans, and manually writing by-hand getter-and-setter methods...
Because... We don't. IDEs and code generators have replaced a lot of the more stupid boilerplate. Not that there isn't a lot of stupid boilerplate in Java but it's been greatly reduced by tooling.
Still, I don't work with Java because I like it, I work with Java because it works and has a great ecosystem, the tooling around it make it bearable and without it I'd have definitely have jumped ship a long time ago.
I'm not only a Java developer, I've worked with Go, Python, Clojure, Ruby, JavaScript, Objective-C, PHP and down to ASP 3.0. Java is still the language that employed me the most and the longest, I have no love for it apart from the huge ecosystem (and the JVM gets some kudos) but it works well for larger codebases with fluid teams.
Ward Cunningham once noted that because a programmer can input incomplete, or minimal, code, then push a few buttons to autogenerate the rest, that there's a smaller language inside Java. Since he made that remark a number of other JVM languages have sprung up that try to be less verbose. One of them is Groovy, which uses type inference and other semantics to reduce duplication (or "stuttering" as Go calls it").
The issue now with Java is that it's such a big language and it's accumulated so much from many different paradigms that experience in "Java" doesn't always transfer across different companies or teams. Some teams hew closely to OO design and patterns, others use a lot of annotations and dependency injection, still others have gone fully functional since Java 8.
And then there are shops like one of my employers, where a large codebase and poor teamwork has resulted in a mishmash of all of the above, plus some sections that have no organizing principles at all.
> Some teams hew closely to OO design and patterns
I maintain that design-patterns are just well-established workarounds for limitations baked into the language - and we get used to them so easily that we rarely question why we’ve even built-up an entire mechanism for programming on-top of a programming language that we never improve the underlying language to render those design-patterns obsolete. (I guess the language vendors are in cahoots with Addison-Wesley...)
For example, we wouldn’t need the Visitor Pattern if a language supported dynamic double-dispatch. We wouldn’t need the adapter, facade, or decorator patterns if a language supported structural typing and/or interface forwarding. We wouldn’t even need to ever use inheritance as a concept if languages separated interface from implementation, and so on.
Better answer: the only real OOP system is Smalltalk.
> design-patterns are just well-established workarounds for limitations baked into the language
Strict functional programmers have been saying that for years. They may be workarounds, but as patterns they have value in allowing one programmer to structure code in a way that is recognizable to another, even months later. You could say that a steering wheel, gas pedal, and brakes are workarounds for limitations baked into the automobile we wouldn't need if cars could drive themselves, but still value the fact that the steering wheel and the rest of the controls for a driver generally look and work the same across vehicles.
6 replies →
What resources would you recommend for learning more about design patterns that are essentially missing language features?
2 replies →
I'd agree with that statement. I feel that I have settled in a few frameworks that I consider modern but mature and stable, with a good job market. For that I had to learn these smaller languages inside Java.
And not only smaller languages, the tool set as well. Maven and Gradle for a start, and Gradle is its own Universe of little quirks and "what the fuck" moments. IDEs have a learning curve (but I learned vim and emacs before using any IDE so knew how steep learning curves work) and if you manage to use their features it can help immensely your productivity. Frameworks such as Spring have enough pull that it's easy to find interesting projects with it, I think that the direction of Spring Boot is pretty good for modern tech enterprises, at least for part of the stack.
Boring technology has its place, it's a hassle you have to learn a pretty big set of tools to be productive in Java but when you do you can actually accomplish a lot with multiple teams and a scale of hundreds to thousands of engineers.
You can also shoot yourself on the foot pretty easily, in massive scale, if the people taking decisions are architecture astronauts and not battle-hardened engineers who have suffered through incomprehensible code and piles over piles of mishmash of technologies and failed frameworks. Keeping the tech stack simple, boring and focused on a small set of tools has its benefits on scale.
Groovy is a nightmare. We now reject candidates who advocate for it in production during interviews and we warn those who do say they d never dare there but use for testing.
Who cares if you have to write setters and getters by clicking a button in IntelliJ, or have to explicit your types rather than ask every subsequent readers to use their brain as a type inference compiler.
Typing clear code isn't a problem Groovy should have solved by making it all implicit, at the cost of having non compiled production code. A code should never fail at runtime because you made a function name typo that it couldnt tell you about any other time for free.
A lot of this also applies to other interpreted languages widely used in production, such as Ruby, Python and Clojure.
You don’t like interpreted languages in production, fine. But rejecting candidates who think differently from you just creates a monoculture and reduces the chance that you learn anything new, beyond reinforcing your own convictions.
Millions of lines of production Python code do just fine by doing the thing you should have been doing all along: testing.
How rational this is depends on your testing culture. With a comprehensive test suite, the guarantees offered by a compiler are not very important. The software goes through all its runtime paces before production anyway.
If you’re not going to write any tests, then obviously compile time is a crucial line of defense.
Most shops will be somewhere in the middle where compiler guarantees offer a real but marginal benefit, to be weighed against other tradeoffs.
4 replies →
> A code should never fail at runtime because you made a function name typo that it couldnt tell you about any other time for free.
So you also reject all dynamic and weakly typed languages? No JavaScript, Objective-C, PHP, Python, Ruby, Lisp, or Tcl? No type coercion at runtime?
> at the cost of having non compiled production code
Groovy can be compiled to the same bytecode as Java.
10 replies →
I see where you're coming from, having used Groovy for a few years (in Grails, mostly) but I think you're also overstating your case.
Groovy is very quirky, and it's good at turning compile errors into runtime errors (@CompileStatic negates many of its advantages like multiple dispatch) and making IDE refactoring less effective. But the again, so is Spring. This got better when Java configuration was introduced... and then came Spring Boot, which is a super leaky abstraction, with default configuration that automatically backs off depending on arbitrary conditions (!). And yet people find it valuable, because it reduces boilerplate.
These days, I use Groovy mostly in testing (and Gradle), and it can really make tests more expressive.
What is “a code”?
i have never seen repeating type names on lhs / rhs referred to as stuttering. nice.
> IDEs and code generators have replaced a lot of the more stupid boilerplate. Not that there isn't a lot of stupid boilerplate in Java but it's been greatly reduced by tooling.
I've been dealing with Java for about four years in an academic setting, not professional. When observing Java code in workplaces, the code bases have universally been bloated monstrosities composed mainly of anti-patterns - but that was hopefully down to the era these applications were written in (J2EE, Struts, etc).
My experience is that:
* Auto-generated code is still visual (and debugging) noise
* Annotations like Lombok's are great, but are "magic" abstractions, which I find to be problematic. They add cognitive load, because each specific library has its own assumptions about how you want to use your code, as opposed to built-in language constructs.
* Especially for Lombok, I can't help but think adding @Getter and @Setter to otherwise private fields is a poor workaround for a simple language deficiency: not having C#'s properties { get; set; }. I feel the same about libraries that try to circumvent runtime erasure of generic types.
* Compared to C#'s ASP.NET (core), I find fluent syntax configuration with sane defaults and "manual" DI configuration much more manageable and maintainable than auto-magic DI as in Spring, because at least it's explicit code.
* Java tooling (and if previous points weren't, this is definitely a subjective stance) just seems inferior and to set a low bar in terms of developer experience - maven or gradle compared to nuget, javac compared to dotnet CLI, executable packaging... As to other tooling, I suppose you're mainly referring to the IntelliJ suite?
I think C# (and by extension, Kotlin) had the right idea in seeking a balance through removing as much boilerplate as possible in base language constructs. Adding libraries is fine, but shouldn't be a workaround to evolving the language.
I agree with all of your points, including C# and Kotlin approach.
At the same time, a lot of that are artefacts of decisions made early in Java such as backwards compatibility.
The same for tooling, a lot of tools are results of its time, Ant, Maven and now Gradle.
Lombok has its pros and cons, I use it sometimes but had issues with Lombok and Java upgrades due to how Lombok writes out its bytecode.
I haven't touched javac in more than a decade so I don't really care about it, it's been abstracted away by my build tools (and I agree, the build tools are less-than-optimal).
Again, I agree with all the critics, at the same time just given how large the Java installation base is and by having to go through the Python 2 vs Python 3 migration path, debacles, etc., I still prefer to have all this cruft that is known, well discussed online, has documentation about its quirks rather than a moving target of language features over the last 20-25 years.
Java is too big due to all the evolution it went through, it could have taken different paths and modernised the language? Yes, at the expense of some core design decisions in the beginning. Do I agree with all these decisions? Nope, but who agrees to all design decisions made by their programming language designers?
> Compared to C#'s ASP.NET (core), I find fluent syntax configuration with sane defaults and "manual" DI configuration much more manageable and maintainable than auto-magic DI as in Spring, because at least it's explicit code.
Fluent-syntax, as it exists today, needs to die in a fire. It's horrible. It's abusing a core key computer-science concept (return values) and turning it into something that exists to only save a few keystrokes.
1. You have no way of knowing if the return-value is the same object as the subject or a new instance or something else.
2. It doesn't work with return-type covariance.
3. You can't use it with methods that return void.
4. You can't (easily) save an intermediate result to a separate variable.
5. You can't (easily) conditionally call some methods at runtime.
6. There is no transparency about to what extent a method mutates its subject or not. This is a huge problem with the `ConfigureX`/`UseY`/`AddZ` methods in .NET Core - I always have to whip-out ILSpy so I can see what's really going on inside the method.
Some libraries, like Linq and Roslyn's config use immutable builder objects - but others like ConfigureServices use mutable builders. Sometimes you'll find both types in the same method-call chain (e.g. Serilog and ImageProcessor).
What languages need is to bring back the "With" syntax that JavaScript and VB used to have - and better annotations or flow-analysis so that the compiler/editor/IDE can warn you if you're introducing unwanted mutations or unintentionally discarding an new immutable return value.
> exists to only save a few keystrokes.
It does that, but it also makes your code read more like natural language. Perhaps I was careless in my wording, as I meant to point to manual, explicit configuration rather than fluent syntax per se.
As to your bullet points: I can see where you're coming from. I still think it's better than the invisible side effects and invisible method calls you get with annotations.
> What languages need is to bring back the "With" syntax that JavaScript and VB used to have
As far as I know, With... End With is a weird cross between "using" in C# and object initialisers. How does that help prevent mutations? One of the code examples (0) even explicitly mentions:
I honestly don't see the big difference with either:
or:
[0] https://docs.microsoft.com/en-us/dotnet/visual-basic/languag...
AutoValue makes a lot of this easier, but you're right that vanilla Java is quite terse, much more than needed.