
From Java to Go, and Back Again (2016) - amzans
https://opencredo.com/java-go-back/
======
thermodynthrway
Java is crufty, but it's the best general purpose language I know. There's
hardly anything you can't build in Java.

"Casual" Java programmers usually miss the most important feature, what really
makes it possible to do almost anything in Java. Reflection and runtime code
generation. Every time I look at Go this ends up being a problem, worse than
lack of generics. A lot of Java libraries use these features under the hood to
make things like JPA and JSON serialization work.

Nothing I'm aware of comes close to Java in this, where you can redefine the
language and rewrite compiled code at runtime while still maintaining
execution and memory safety. Java agents, annotation processors, and libraries
like JavaAssist, ByteBuddy, and ASM make seemingly impossible things pretty
easy to write.

This is why annotations are used for everything in Java. They're often just
tags used to find targets for rewriting code at runtime.

I was trying to write a thin ORM for Go and quickly found out it was
impossible to do what I needed without blowing a bunch of nasty generated
source files into the build. In Java, I can give the developer a nice clean
interface and just generate the code when the app starts. Go forces you to
expose all this to the user of your library

~~~
vardump
> There's hardly anything you can't build in Java.

I do bare metal/low level embedded, kernel drivers, and occasionally shared
libraries. Java is useless for practically all of my typical use cases.

~~~
repolfx
Technically speaking you can build these things in Java. JNode is an OS
written entirely in Java, including the drivers. You probably meant "drivers
for linux/windows" though and yes, there it's not so useful.

You can also make shared libraries with Java these days. Look at SubstrateVM.
It turns Java code into a .so file that doesn't depend on the JVM, compiled
ahead of time, with a C API exported.

~~~
vardump
Yes, there's very little in software development that's strictly impossible.
Practicality is another matter.

> Look at SubstrateVM. It turns Java code into a .so file that doesn't depend
> on the JVM, compiled ahead of time, with a C API exported.

Thanks, will do. I've been mostly eyeing Webassembly for some "cross-platform
shared object" use cases.

------
nemo1618
I think the author hits the nail on the head here:

>Almost all the tricks I’ve built up over the years for creating embedded DSLs
in Java or Ruby or Clojure are simply unavailable.

>...if there’s one complaint I’ve heard consistently from my fellow developers
over the years, it’s that they can’t understand my code until they also
understand the set of abstractions I’ve introduced

"Tricks" are lovely for personal projects, but no one wants to trawl through
them at 3am when the server has gone down. Go removes a lot of tricks, and
that makes people sad, but it makes total sense for a language design for
large teams. You can still use Go for one-man projects, of course (I do), but
don't expect it to be as whimsical an experience as using Ruby or Lisp.

~~~
pjmlp
In what job does one trawl through code at 3am?

~~~
pdpi
When you’re oncall and something broke.

~~~
watwut
You are on call for coding? What kind of job is that? This sort of things
should be really really rare.

If it happens in any regularity, then tricky abstractions are not the main
problem. Apparent lack of QA and overall quality is.

~~~
pdpi
Point is that it broke and you’re trying to figure out why and how to get it
fixed. Sometimes looking at the code is the most expeditious way to get there.

~~~
coldtea
> _Point is that it broke and you’re trying to figure out why and how to get
> it fixed_

Yeah, but the point was made to support an argument (that code should have
fewer abstractions so that it's faster to grasp any part while reading in a
hurry, as opposed to more abstractions so that it's shorter and more generic,
even if that means having to study the big picture to get what some smaller
part of code does).

Now, if devs routinely had to quickly understand some local code at 3am to fix
bugs, as per the example, then sure, fewer abstractions and more clear local
code would be better.

But since your example is contrived, where does that leave the main argument?

------
tbenedetti10
The post is spot on and certainly mirrors some of my own experiences using Go
and Java.

Java is a wonderful language with a terrible culture. Choosing Go or even
Kotlin over Java is liberating because it allows an engineer to escape the
cultural baggage that comes with Java.

What kinds of baggage? The type of baggage that blindly demands using magical
frameworks like Spring and JPA or worse engages in best practices chants as
they implement 45 interfaces and create thousands of excess classes because
SOLID and DDD said to do so. Yes, I'm exaggerating some but this is a real
problem on Java teams.

I sincerely hope that a culture adjustment is included in the Java 11 release.

~~~
jrs95
Honestly, Spring and JPA are largely what brought me back to Java. There's
complexity there for sure, but those are very productive tools that are also
very flexible. I don't waste much time on configuration, testing (even testing
Spring Data JPA repositories) is easy and requires almost no configuration or
external tooling, essential things like JSON serialization, request
validation, fine grained control over error responses, database transactions,
async tasks, and message queues all pretty much work out of the box and just
let me get shit done. There might be a lot of overengineering going on under
the hood, but as far as my code is concerned there's little to no inheritance
and even interfaces typically aren't needed. It's mostly just object
composition.

And if I didn't want the magic, projects like Dropwizard and Vert.x are there.
But I'd rather have the most productive out of the box experience possible.

------
siscia
Honest question,but am I the only one who find really hard to read and follow
quite big golang projects?

Maybe is true that I can understand what is happening in every single line of
code in my screen, what I found really hard is to understand how those line
interact with the rest of the code base.

I honestly believe that there are some part of the code base that should
manage the low level stuff like managing files, writing to buffers or interact
with the file system and other part that should compose/manage/move those low
level parts.

Using go and its focus on "simplicity" it feels like these basic abstraction
fall apart and that in every part of the code base I can find something doing
quite low level stuff.

I find go quite good for small, self contained projects but honestly I believe
that big projects like docker could benefit more from a more structured
approach.

Am I the only one feeling this pain?

~~~
dnomad
You're definitely not the only one. It's a very strange phenomenon: Golang
developers insist their language is boring and uber-simple but every non-
trivial Go codebase I've encountered has been anything but. It seems like the
end result always involves a bad reinvention of exceptions using
panic/recover, code that isn't type safe that has too many curly brackets...
and lots and lots of code generation. Combined with immature tooling the
result is horrifying. I'm by no means a Go expert and perhaps I'm just unlucky
but that's been my experience. I certainly no longer volunteer dive into
strange Go codebases like I might do with Java.

At this point I have to wonder if in 10 years the Golangers will actually find
themselves in a good place. I don't see these codebases aging well at all.

------
brown
Language wars aside, I liked this:

 __ _Returning to Java (and Kotlin) development after a couple of months of Go
development, [...] I discovered the value of a certain sort of "plain
speaking" in code._ __

This resonates with me. I 've spent 1 year+ in each of C, C++, C#, PHP, Java,
Python, and JS. There are obvious benefits to taking advantage of a platform's
strengths, but the "plain" code has merits too. Easier to reason about, often
more maintainable, faster to ramp up new team members, etc.

~~~
guitarbill
In my experience, Java suffers from this the most because often CS teaches the
tools/techniques, but not how/when to use them. For example, the number of
times I've seen an interface with only one implementation is ridiculous. (Yes,
I know interfaces have other advantages, no I don't think they're worth the
trade-off of making debugging harder until you get to boundaries where that
sort of abstraction is a real win.)

Simpler code almost always wins. Predicting the future is hard, refactoring
simple code that does the minimum it needs to is comparatively easy. Don't
write what-if code, YAGNI.

~~~
humbleMouse
Why is having only one implementation for an interface bad? It neatly
organizes all your code and doesn't make debugging harder at all.

~~~
hocuspocus
How does having FooService and FooServiceImpl help with code organization at
all? It brings nothing but unnecessary noise.

~~~
ivan_gammel
It's test-friendly. The code may not have explicitly defined second
implementation, but a simple "@Mock FooService" declaration in a test.

~~~
guitarbill
This is a fair point, however in practice there's solution for that (Mockito,
as long as the class isn't final). Which is how it should be. Your code
shouldn't be untestable, on the other hand you shouldn't have to go out of
your way to make it testable by adding more cruft.

~~~
ivan_gammel
I don't think the design should be based on the requirement to use Mockito
library and it's not the only and not always the most convenient way to
provide a test implementation. So, no, it's not how it should be.

------
noelwelsh
When I was a Java programmer I used to build my own abstractions. I can
understand the complaint

> ...if there’s one complaint I’ve heard consistently from > my fellow
> developers over the years, it’s that they can’t > understand my code until
> they also understand the set of > abstractions I’ve introduced"

Now I mostly use Scala in an FP style I find I reuse abstractions. The
abstraction I want almost always exists in a library. Monads, applicatives,
monoids, etc. actually work for code reuse, and this makes it much easier to
read code. When I look at a code base such as http4s
([https://http4s.org](https://http4s.org)) I find I can read it without much
difficulty even though I'm not familiar with the code.

So my contention is you can reduce expressivity to make code easier to read,
or you can increase expressivity so that truly reusable abstractions can be
created. (You basically need higher-kinded types in addition to the usual FP
abstractions.)

~~~
dionian
yeah, it's not "either C# with a better language and less ecosystem, or
java"... you can use scala and get something more powerful than C# and still
get the java ecosystem. I suspect I will be using scala as my main language
for some time to come

------
tapirl
From Java to Go, and never back, for Go programs use much less memory, warn-up
much faster, need much less code lines to implement and compile much faster.
Beside happier developer experience, my App Engine apps also save much money
by using Go.

~~~
abvdasker
Probably the number one thing I have always hated about Java is its build
tools. Maven has never really made any sense to me and I simply don't want to
have to deal with XML in 2018. Gradle requires me to use a totally different
programming language just to write configuration. Ant is somehow more
confusing than Maven but less standardized (and is almost useless without
Ivy). It's been a few years since I wrote any Java so maybe there is something
newer and better, but I just can't bring myself to go back to the misery of
reading through that shit just to get a project to compile.

With Go it feels like all you need is a dependency manager and maybe a
Makefile for more complex builds.

~~~
vorg
> Gradle requires me to use a totally different programming language just to
> write configuration

There's an alternative to using Apache Groovy for writing Gradle config
scripts: [https://blog.gradle.org/kotlin-meets-
gradle](https://blog.gradle.org/kotlin-meets-gradle) . If you also use Kotlin
instead of Java for your software on the JVM then you can stick to one
language for everything.

------
aisofteng
>I found myself more willing to consider that the concrete and immediate
expression of some piece of program logic might serve better than trying to
pull out a more generic version of the same logic that might never be re-used
in practice.

This is the YAGNI principle in XP - “you ain’t gonna need it”:
[https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it](https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it)

------
humbleMouse
People are always saying that spring boot has too much "behind the scenes
magic". What behind the scenes magic? Spring boot barely does anything fancy.

It builds an xml dependency tree of your classes. That's not hard to
understand.

Everything it does is well documented on their site as well.

Is @component and @restcontroller and @requestmapping really that hard to
understand?

~~~
jrs95
Yeah, it's a little weird. Metaprogramming is apparently only a virtue if
you're using a dynamic language. Personally I like being able to throw
@Repository on an Interface and get paginated CRUD operations for free. I like
being able to take any JPQL query, put it in an @Query annotation, and if I
decided I want sorting and pagination later, I just change the return type to
Page<T> and add an extra parameter for the pagination info. I like being able
to write unit tests against those repositories by just adding a @DataJpaTest
annotation to my test class. This stuff saves a considerable amount of time,
and it's certainly better than the experience I've had in Go or even in Python
with Flask.

~~~
bradleyjg
> Personally I like being able to throw @Repository on an Interface and get
> paginated CRUD operations for free.

If it doesn’t work how you expect / want it to work, now what? With a library
I can go the declaration and see what’s going on and what I might be able to
do. With annotation magic I have no choice but to go to google where my
choices will be diving into extremely verbose and not particularly well
written documentation or cargo culting based on stack overflow answers.

~~~
jrs95
You can still just configure and use Hibernate or JDBC directly. Although I
wouldn’t be surprised if it was also possible to add your own custom
implementation with different methods on the repository. It’s by far the most
flexible and extensible framework I have worked with.

------
didibus
The part I'm not sure about is easy to read and understand. Like the map
example is much easier to read and understand in my opinion.

So I wonder, having a smaller vocabulary, it might make learning the language
easier. And maybe your first pass on a new piece of code is quicker to read.
But after that first pass, or once you've learned the expanded vocabulary,
wouldn't it now be even easier to read? And not using it would just make
things redundant and appear like noise.

It's like reading: "He felt a deep attachment that was unique and stronger
then any other to her." And then you learn about "love". Now from that point
on, I'd rather read "He loved her." Seem just easier to read once you know the
concept.

------
cdoxsey
> While Go does support passing around functions (and this is occasionally
> useful), it doesn’t really support the use of functions to build up
> composable abstractions that feel like extensions to the language. It
> favours the concrete, inline expression of logic, with function calls acting
> primarily as a mechanism for structured programming.

I see what the Author is getting at, but he's missing the primary mechanism Go
uses for this: Interfaces. This is hard to explain in the abstract, so here's
an example I gave in a talk once: (bit.ly/1mBisa6)

Suppose we are building a new podcast platform which dynamically splices
advertisements into mp3s. We will do this with an HTTP server. In Go you do:

    
    
        f, err := os.Open("somefile")
        ...
        http.ServeContent(w, r, "", time.Now(), f)
    

ServeContent takes an io.ReadSeeker. Now we build a Splice function:

    
    
        func Splice(
            src io.ReadSeeker, 
            splice map[time.Duration]io.ReadSeeker,
        ) (io.ReadSeeker, error)
    

Used like this:

    
    
        f1, _ := os.Open("/tmp/1.mp3")
        f2, _ := os.Open("/tmp/2.mp3")
        f3, _ := os.Open("/tmp/3.mp3")
        final, _ := mp3.Splice(f1, map[time.Duration]io.ReadSeeker{
            time.Second * 5:  f2,
            time.Second * 10: f3,
        })
    

Splice uses two additional functions. First the MultiReadSeeker, which simply
concatenates ReadSeekers:

    
    
        func NewMultiReadSeeker(
            readSeekers ...io.ReadSeeker,
        ) io.ReadSeeker
    

Next the SectionReadSeeker, which gives you a section of a ReadSeeker:

    
    
        func NewSectionReadSeeker(
            src io.ReadSeeker, 
            offset, length int64,
        ) io.ReadSeeker
    

So it ends up being:

    
    
        final := NewMultiReadSeeker(
            NewSectionReadSeeker(f1, 0, A),
            f2,
            NewSectionReadSeeker(f1, A, B-A),
            f3,
            NewSectionReadSeeker(f1, B, C-B),
        )
    

So it's pretty straightforward. There are some really powerful ramifications
from these abstractions:

1\. The file is never built in its totality in memory. Rather we are
representing the operations in a lazy fashion that only get executed when you
use them.

2\. The ReadSeeker supports seeking, which means we can resume from the
browser, or seek to the end and skip the middle bits. With the go HTTP library
we basically get this for free.

3\. Lots of things use these interfaces. For example if I want to write this
to disk, I can just io.Copy it. Or suppose I want to use this as another
source for a second splicing. It's ReadSeekers all the way down, so I can do
that with no changes. With some tinkering I'm pretty sure you could hook this
up to S3 too.

~~~
pstuart
Closures is also a big win for passing around functions.

~~~
tapirl
All Go functions are closures.

------
daenz
>Go’s lack of generics means that you have to write mapStringToString as a
mapper between collections of two fixed types – you can’t write a generic map
from an array of T to an array of R, as you can in almost any other strongly-
typed modern language.

That's not true. Here's their example written to be more generic:

    
    
      func mapThingToThing(inSlice interface{}, mapperFn interface{}) []interface{} {
      	mapper := reflect.ValueOf(mapperFn)
      	input := reflect.ValueOf(inSlice)
      	output := make([]interface{}, input.Len())
      	for i := 0; i < input.Len(); i++ {
      		item := input.Index(i)
      		output[i] = mapper.Call([]reflect.Value{item})[0]
      	}
      	return output
      }
    

and calling:

    
    
      fn := func(item string) string {
      	return item + "!"
      }
      res := mapThingToThing([]string{"a", "b", "c"}, fn)
      for _, item := range res {
      	fmt.Printf("item = %+v\n", item)
      }
    

I didn't say it was pretty or good, but it can be done. Really confused by the
downvotes.

~~~
dagss
Well technically this is reflection which is something else than generics. You
say "that's not true" but it IS true that go lack generics (or metaprogramming
in general). You point out that it supports reflection which is something
else... it is a bit like coding in Python, you loose compile time type safety.

~~~
daenz
No, it's not generics, but it enables you to write a generic function, which
is what the article claimed was not possible in Go.

------
eternalban
He never left Java.

------
mark_l_watson
There is a bug in a code listing in the article:

val exclaimed = strings.map { it + "!" }

Should be

val exclaimed = plain.map { it + "!" }

~~~
cpt1138
I’m glad you confirmed this. I don’t know much about Go but I do know
programming. My gut told me there was a mistake but imposter syndrome kicked
in and I assumed Go had some non-intuitive proximity rules like ‘it’ refers to
the last iterable in scope or something.

I have mentioned this before but the reason I like Java so much is the mature
and cohesive ecosystem. Every single problem has been solved in Java and all
the solutions look similar so you can readily pick them up. I don’t find that
to be the case in other languages.

------
noncoml
TLDR; writing code in Go is boring and clunky, reading other people’s code is
pleasant and easy.

