In an article titled The Feel of Java (PDF), James Gosling, one of the language’s creators, explains the design choices which went into the making of that language. He states frankly that Java was not meant to be innovative, but instead tried to synthesize a few carefully chosen long existing ideas to form a coherent whole. Some of those ideas are: a portable runtime, distributed objects, object orientation with late binding, exception handling, and a virtual machine focussed on safety. History tells us that Java went on to occupy a very different computing niche from what its creators intended, but on the whole, the “feel” of the language as Gosling describes was true for its time, and I have felt the same after many years of programming in it. He says Java feels playful, flexible, deterministic, non-threatening and rich — you feel confident that you can just sit down and write the code. It is important to reiterate that the specific design choices which gave Java that feel reflect the state of commercial programming at the time. A different era would have necessitated different choices for the same feel.
In a similar vein, Rob Pike, one of the creators of Go, writes about the motivations behind the language in an article titled Less is Exponentially More. His recounts how his misgivings about the complexity of C++ boiled over into frustration one day when he found out that even more features were being added to it. Since Ken Thompson (one of the creators of C) was fortuitously also working for Google at the time, they joined forces to build a simpler language from scratch. The article is notable for its long list of simplifications from C++ that Pike and his team chose to make. Recent history has shown that while Go has achieved tremendous popularity, its initial user base consisted largely of former users of Python — and some Ruby — as acknowledged by Pike himself. It is possible that a good amount of — misapplied? — C++ code is being re-written in Go, but I doubt any that relied on its famous “zero-cost abstraction” philosophy. Go also powers much of cloud infrastructure (Kubernetes, Docker etc), where the code deals with intricacies of computers rather than whatever business your company happens to be in.
It was this last point which struck me the most when I started dabbling in Go myself at Uber, where it is widely used and well supported. My only other experience with Go till then had been a couple of projects in grad school, where again the focus was on the hardware underlying computing (or on specialized algorithms). While Go might make a great fit (in today’s time) for software that deals with configuring hardware and deploying other software, I fail to see any particular advantage over Java when it comes to building business applications. And I feel that on average it is equivalent to Java. Some features are more nifty (type inference, multiple return values, named return values, type aliases and the resultant ability to add methods to primitive types) and the standard library has benefited from the intervening decades of accumulated programming wisdom since Java was created. But there are noticeable regressions as well. The lack of generics and exceptions leads to tedious and repetitive code. There’s hardly any acknowledgement of the importance of immutability.
What of the much hailed concurrency primitives built into the language? Sure, you can launch a set of communicating “processes” and make them synchronize easily enough given that the language runtime includes more machinery to enable such a set up, but I doubt the average business application is going to contain much such code.
Where Go goes better is memory usage. If you create a struct/class that contains other struct/class fields in it, Go can store them all in one big contiguous chunk of memory, while Java will need to allocate a separate chunk of memory for each of the inner fields, and then tie them together in the outer object by using an additional set of pointers (one for each nested object).
I can’t help but notice that it is only in such nuances am I able to bring out stark differences between the two languages, thus pointing to a large area of overlap. If you want to argue that one compiles to machine code and the other needs a virtual machine to run, in practice this distinction gets obscured by good deployment tools (which, incidentally, are being actively developed in Go as discussed earlier). Speaking of tools, Java has more mature IDE support, build and dependency management — and probably, even runtime instrumentation and profiling. But this is more a factor of its age and Go may catch up.
Since neither Java nor Go claim any great innovation in language design, extended comparisons between the two are not enlightening. The real head scratcher is why anyone would choose Go over languages with more facilities for abstraction and better type systems. Here I am talking about Scala, OCaml, Haskell etc. And here’s my very speculative take on it. Since software is still in an expansionist phase where it is being applied to many new domains, there is a continuing need for programs that can be developed cheaply and quickly to assess a fit and gain market share, leaving enough time later for improving upon the original design. Advanced programming features provided by languages like Scala only come into play late in this type of product lifecycle and one may not see merit in learning them. Even if the initial design happens to be poorly thought out, software based products are lucrative enough that companies can put up with the overheads imposed by a few suboptimal choices.
Does that mean that if competition in software intensifies leading to decreased margins, then a better programming language could make a significant difference to a company’s fortunes? I doubt it. Again I will speculate that existing languages fall short in significant ways. For instance, I don’t know how any language can help in coming up with a good system architecture, understanding data flows between various components of the business or in identifying potential performance issues. These are not faults of a language per se as some of these are ecosystem features. But still, the net effect is the same.
Therefore, one can argue that in essence, the design of Go is a timely admission of humility in the face of untamed complexity in commercial software, with a willingness to tide over technical shortcomings with more money and manpower, just as Java did once. While every programmer would love nothing better than to build an elaborate tower of abstraction, such towers can come crashing down if a hundred of them are to be built at the same time, and expected to lean on each other as they are being worked on.
All this does not rule out the possibility that small teams of highly skilled programmers can build intricate structures more quickly and robustly in advanced programming languages. But whenever it’s required — but not possible — to assemble such a team in large numbers, the safe decision might be to choose Java. Or Go. As I said, I find them equivalent, on average.