> Go was probably the key technology that migrated server-side software off Java bloatware to native containers
Interesting point of view - Golang might be pithily described as "Java done right". That has little to do with "systems programming" per se but can be quite valuable in its own terms.
Java has a culture of over-engineering, to the point where even a logging library contains a string interpolator capable of executing remote code. Go successfully jettisoned this culture, even if the language itself repeated many of the same old mistakes that Java originally did.
> Java has a culture of over-engineering [which] Go successfully jettisoned
[looks at the code bases of several recent jobs]
[shakes head in violent disagreement]
If I'm having to open 6-8 different files just to follow what a HTTP handler does because it calls into an interface which calls into an interface which calls into an interface (and there's no possibility that any of these will ever have a different implementation) ... I think we're firmly into over-engineering territory.
Java is a beautiful and capable language. I have written ORMs in both Java and Go, and the former was much easier to implement. Java has a culture problem though where developers seem to somehow enjoy discovering new ways to complicate their codebases. Beans are injected into codebases in waves like artillery at the Somme. Errors become inscrutable requiring breakpoints in an IDE to determine origin. What you describe with debugging a HTTP handler in your Go project is the norm in every commercial Java project I have ever contributed to. It's a real shame that you are seeing these same kinds of issues in Go codebases.
> But don't those exist primarily for unit testing?
I believe that's why people insert them everywhere, yes, but in the codebases I'm talking about, many (I'd say the majority, to be honest) of the interfaces aren't used for testing because they've just been cargo-culted rather than actually considered.
(Obviously this is with hindsight - they may well have been considered at the time but the end result doesn't reflect that.)
It's indeed horrible when debugging. OTOH, there's merit to the idea that better testing means less overall time spent (on either testing or debugging), so design choices that make testing easier provide a gain -- provided that good tests are actually implemented.
Agree. Open source OAuth go libraries have this too. It's like working with C++ code from the bad old days when everyone thought inheritance was the primary abstraction tool to use.
Interface, actual implementation, Factory, FactoryImpl, you get the idea.
Java lends itself to over-engineering more than most languages. Especially since it seems that every project has that one committer who must be getting paid per line and creates the most complex structures for stuff that should've been a single static function.
Nice. My reply would have been something like: it combines the performance of Lisp with the productivity of C++. These days Java the language is much better though, thanks to Brian Goetz.
The performance of the JVM was definitely a fair criticism in it's early years, and still is when writing performance-critical applications like databases, but it's still possibly the fastest managed runtime around, and is often only a margin slower than native code on hot paths. It seems the reputation has stuck though, to the point that I've seen young programmers make stock jokes about Java being slow when their proposed alternative is Python
Yes it's possible to write Java without any boxing of primitives or garbage collection, but one can't use any of the standard libraries and it's not really Java one is writing but a very restricted subset. I don't think these benchmark are particularly indicative of real world performance. But of course Java is still hundreds (thousands?) of times faster than Python.
It’s okay to do some allocations - it can be stack replaced, and even if it’s not, the cost is very negligible. The problem is mindless allocation, not allocation itself.
> it combines the performance of Lisp with the productivity of C++
Is that supposed to be a jab? Because IME SBCL Lisp is in the same ballpark as Go (albeit offering a fully interactive development environment), and C++ is far from being the worst choice when it comes down to productivity.
Hopefully you agree Lisp is more productive than C++? Lisp is however not quite fast or efficient enough to displace C++ completely, mainly because, like Java and Go, it has a garbage collector. C++ was very much the language in Java's crosshairs. Java made programming a bit safer, nulls and threads not withstanding, but was certainly not as productive as Lisp. Meanwhile Lisp evolved into Haskell and OCaml, two very productive languages which thankfully are inspiring everyone else to improve. Phil Wadler (from the original Haskell committee) has even been helping the Go team.
I consider that a bad practice, because it doesn't make things obvious. I guess it works so well in Go, because the language itself is small, so that you don't have to remember much of these "syntax tricks". Making things explicit, but not too verbose, is the best way in my opinion. JetBrains has done amazing work in this area with Kotlin.
for (item in collection) {
...
}
list.map { it + 1 }
fun printAll(vararg strings: String)
constructor(...)
companion object
I like the `for..in` which reads like plain English. Or `vararg` is pretty clear - compare that to "*" and the like in other languages. Or `constructor` can not be more explicit, there is no need to teach anyone that the name must be the same as the class name (and changed accordingly). Same is true for companion object (compare with Scala).
I've always found it eye rolling how often this is given as some sort of "mic drop" against Java. Yeah it's a little weird having to have plain functions actually be "static methods", but it's a very minor detail. And I really hope people aren't evaluating their long-term use of a language based on how tersely you can write Hello World
From looking at what the Go team had to say about Go in its earliest days, Go had very little to do with Java, and they weren't very concerned with fixing Java's issues.
The "Bloated Abstractions" issue in Java is more of a cultural thing than an issue of the language. You could even say it's partially because early Java (especially before Java 1.5) was too much like Go!
Java used to have the same philosophy around abstractions, and Sun/Oracle were pretty conservative about adding new language features. To compensate for lack of good language-level abstractions, Java developers used complicated techinques and design patterns, for example:
1. XML configuration, because there were no attributes.
2. Attributes because there were no generics and closures.
3. Observers/Visitors/Strategies/etc. because there weren't any closures.
4. Heavy Inheritance, because there was no delegation.
5. Complicated POJOs and beans, since Java didn't have properties or immutable records.
Many other software development cultures lived through similar limitations without evolving to the levels of abstraction astronaut achievement award of Java.
Very true, and Go (or bash for that matter) are proofs that language limitations do not mandate complexity. Complexity is mandated by perceived need and culture. You can easily see how complexity would play out in Go by looking at Kubernetes. Most projects did not fall into the complexity trap, but there are many differences in form, functions and context:
- Early Java projects before Java EE (like Applets and early GUI apps) did not have this level of complex abstraction. The code wasn't great (old Java APIs like StringBuffer and Date were often quite horrible), but it was simple.
- Java started getting complex with J2EE. J2EE was strongly motivated by enterprise requirements for interoperability and dynamically configurable and interchangeable components.
- Another source of complexity was the popularity of XML and the widespread belief (back in the early 2000s) that moving part or all of your business logic to XML was a good thing.
- And then there was the design patterns obsessions, where design patterns transformed from being a common pattern observed in code into something that should be emulated.
- Most of the early J2EE complexity is dead (EJBs, XML configuration, CORBA, SOAP), but many users don't want to give up component interoperability and powerful dynamic configuration. That's why Java frameworks like Spring, Java EE and even modern frameworks like Quarkus or Micronaut have all their annotations and implicitness.
- Go was luckier(?) to be born in a different time and popularized in a different context.
- There was no top-down enterprise-oriented backing for Go (even within Google, it was a bottom up project).
- The Go compiler, runtime and libraries were fully open-source from day one, under a permissive license. Third party libraries were also almost universally open source. The existence of open source culture made concerns about vendor lock-in moot, and interchangeability was not a thing.
- The XML hype died and there was a consensus that component wiring should be done in code.
- Many of us got sober on design patterns. Peter Norvig's critique[1] gained traction outside of the LISP community and the anti-design pattern view became dominant within the dynamic language community as well, even in strongly OO languages like Ruby. This is the community that was feeding Go.
- Most servers written in Go were just smaller in scope than the equivalent Java projects. This has many causes (less top-down big-rewrite enterprise projects, microservices gaining popularity, UI logic usually moving to front-end or mobile app).
Interesting point of view - Golang might be pithily described as "Java done right". That has little to do with "systems programming" per se but can be quite valuable in its own terms.