134 comments

[ 2.9 ms ] story [ 166 ms ] thread
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.

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.

Why is having only one implementation for an interface bad? It neatly organizes all your code and doesn't make debugging harder at all.
How does having FooService and FooServiceImpl help with code organization at all? It brings nothing but unnecessary noise.
In my opinion I like hopping through a codebase where everything goes thru an interface. It just seems cleaner to me. Especially when I'm writing new classes I can think about what the interface does and stub the methods out first. So for me it just splits up the workflow in a really nice way and makes things look clean. I see your point though.

Furthermore, when opening a new project you can just read through the interfaces quickly and get a good idea of what the code is doing. You can't just read thru a whole bunch of implementations and get as good of an idea.

Use a better IDE? I've worked on my fair share of Spring projects and I don't miss this one bit. Even though I wade through a lot of open source libraries where the only documentation is the source code.
FooService removes all the noise and makes it obvious the point of the class.
Like header/code in C or C++ ? I'm not a Java programmer but it seems like an abuse of interface if it's done systematically.
It's also silly. If this was the only use-case, you could just generate "interfaces"/headers from the class for the people who want this in e.g. an IDE. No need to liter the codebase with that.
public interfaces help to make dependencies clear and honest.

If the implementation doesn't provide more methods than the interface (a transparent class in GoF terminology) you should not name it.

In Java, the implementation is usually created inside a static method (a static factory) of the interface as a lambda or an anonymous class.

> public interfaces help to make dependencies clear and honest.

Again, moot point if there's only one implementation.

> If the implementation doesn't provide more methods than the interface (a transparent class in GoF terminology) you should not name it.

> In Java, the implementation is usually created inside a static method (a static factory) of the interface as a lambda or an anonymous class.

And how's that not needlessly convoluted and ugly? You're illustrating guitarbill's point about applying design patterns blindly.

It's test-friendly. The code may not have explicitly defined second implementation, but a simple "@Mock FooService" declaration in a test.
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.
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.
Interfaces can also provide read-only references of a instance. Consider interface Foo with accessors only and a MutableFoo implementation with accessors and mutators. The creator of the MutableFoo instance can make all sorts of changes to the instance but then pass it around as a Foo that can't be modified. Another option is to simply put all the state in the constructor so that a Foo never needs to be mutated; however, that might not be elegant in some cases state is incrementally accrued.

I do acknowledge that XImpl is kinda ugly as name.

> The creator of the MutableFoo instance can make all sorts of changes to the instance but then pass it around as a Foo that can't be modified.

Until you cast the object...

You should use truly immutable data types unless you specifically need otherwise, most modern languages and libraries encourage you to do so.

When you only have one implementation, you often have no concept of what a second implementation will look like and you have no idea what concept or task you're delegating to the interface. They just become header files for the single implementation.

Now, you can be good at planning ahead and know what the abstraction should be in many cases. This is still much rarer than all cases. Indiscriminately creating an interface for every class means you're probably not giving the interface much thought.

> 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.

I strongly agree with this point.

I speculate that some people tend to add unnecessary code because they are thinking of all those well-written libraries they use every day, which have evolved to support multiple uses, including the uses that clearly they don't need but they can see are useful to others. Therefore they conclude good software must add features that are useful for other coders!

>Simpler code almost always wins

I strongly agree ! At my current company we are ramping up the team and want to go from 4 very senior engineers to 35. The very complex architecture based on functional programming that we are using will probably be a major hindrance here.

I don't understand your remark on interfaces though. I tend to have many, even for one click method. If I want to have a callback for a list item tap, I create an interface for that very specific item, that way it is very easy to decouple view and business and it also is extremely easy to debug in Intellij. In one click I get from the interface declaration in the viez to it's implemenentation in the business logic.

Why does it hinder debugging in your use case ?

This is also the reason why I prefer Java over C#. Most differences that C# introduces, are quality of life features for devs writing the code. But they make reading the code worse and mean that junior devs have to learn more in order to understand their senior colleagues' code.

Examples of things that C# has over Java:

- Operator overloading: .Add() is really not that much more annoying to write...

- Properties: Shorthand for getters and setters, because auto-completion isn't a feature in IDEs since forever. And, like Java, they've introduced lambdas, but have then also changed the automated code generation of Properties in their IDEs to use lambdas, because apparently even an IDE needs to be lazy and give junior devs a harder time.

- The out-keyword: Somewhat formalized way of passing along a variable to have it populated via side effects by a method. In Java, you'd refactor your code to not need two return values, or you'd return an object with those values in class variables (or you'd just do this dirty side effect method without telling anyone).

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.

In what job does one trawl through code at 3am?
When you’re oncall and something broke.
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.

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.
If 30 min fix in the middle of the night is your limit, then you better never do any non-trivial project.

But my point is, this should not play like this with any kind of regularity. At all. And it is not happening regularly for mission critical projects. It is not that we write without bugs either, it is more that you have processes and multiple systems to avoid middle of the night quick fixes into production.

>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?

When I use the same expression to describe situations when you need to make changes to code and it is really inconvenient to have to spend time and effort to figure out cleverness or wade through someone else's unfamiliar abstractions.
I wouldn't want to trawl through macro and DSL heavy code at any hour of the day. A very senior engineer once said "do in two lines what you could have done in one line, if you had just made the interface more complicated".
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) 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.)

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
>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.
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.
(comment deleted)
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.
> Really confused by the downvotes

It's not type safe. Any other modern/static language would use generics to ensure inSlice and mapperFn both operate on the same types.

People downvoting you probably see this as obvious for programmers that want/choose a static language like Go/Java/TypeScript/etc.

>It's not type safe.

Yes, I get that, but that's also moving the goalpost. The post claimed that "you can’t write a generic map from an array of T to an array of R", but you can (if you take generic to mean "general" and not "specifically using the mechanism of generics").

I think the author just didn't know that this was possible, or his concern would have been about the lack of type safety with the reflection method.

> Yes, I get that, but that's also moving the goalpost.

no it is not. The whole point of generic programming is compile time type safety for incomplete types.

> but you can (if you take generic to mean "general" and not "specifically using the mechanism of generics").

not in a compile time type safe way. Again, you can't negate the whole point of generics, that's a cop-out. You just wrote Python like dynamic code in Go here, with reflection (AKA runtime behavior) which incures a severe performance penalty.

And let's not get started on the fact that you're returning []interface{} which is often pretty useless when the type of the element matters. You introduced some serious code smell with that function since you threw away type safety.

If you really want to write a generic mapper the only serious way to do that in go is to use reflect.MakeFunc then a function variable of the choosen signature. But again reflection is so slow it's not even worth to begin with.

>no it is not. The whole point of generic programming is compile time type safety for incomplete types.

Except that's not what the post's gripe was about. His gripe was that you need to write a new function for every type you want to map, because there's no way to write one that is generic for all types. He doesn't mention type safety at all, so yes, the solution I posted meets the goal of his complaint.

>And let's not get started on the fact that you're returning []interface{} which is often pretty useless when the type of the element matters. You introduced some serious code smell with that function since you threw away type safety.

Did you miss the point where I said "it's not good or pretty" ? Or do you just want to bitch about everything? Your whole post reads like someone who has never written a container type in Go. Guess how you do it? You use interface{} and cast in the caller!

> Did you miss the point where I said "it's not good or pretty" ? Or do you just want to bitch about everything?

I'm not bitching about anything (you don't need to use this kind of language here, it is not reddit), I'm telling you that piece of code you wrote is pretty useless.

It is not compile time type safe and it is slow due to the use of reflection. This is not an alternative to generics or generic functions in the language. It fixes nothing.

>This is not an alternative to generics or generic functions in the language. It fixes nothing.

When it comes to Go, you don't know what you're talking about. Learn to write a container type and then get back to me. Here's a Heap https://golang.org/src/container/heap/heap.go?s=2105:2138#L5... See the return type? interface{}. This is how it's done in Go. How did you think it was done without generics? IntHeap, StringHeap, FloatHeap, etc?

"You can't do X because Go doesn't have Y" was the original point. My response was "you do X with Z." Whether or not Z is good or bad is completely orthogonal to the original point seeing as how it's idiomatic in Go.

> "You can't do X because Go doesn't have Y" was the original point. My response was "you do X with Z." Whether or not Z is good or bad is completely orthogonal to the original point seeing as how it's idiomatic in Go.

No, it's not orthogonal. The part you cited even mentions strong typing, thereby IMO it is pretty obvious type-safety is a requirement here. Otherwise what would even be the point of the exercise? Mapping of some data to some other data? You can do that in _any_ turing-complete language.

The comment said "you can’t write a generic map from an array of T to an array of R" and, indeed, you can't do that in Go. Your code maps a slice of dynamic values to a slice of dynamic values. It doesn't map array of T to array of R, as requested, because there's simply no way to express that in Go.

Besides, I think think it even actually _is_ idiomatic since you used reflection.

>Besides, I think think it even actually _is_ idiomatic since you used reflection.

https://appliedgo.net/generics/

See point 4 and 5. It is idiomatic.

Are you joking / trolling? The article clearly instructs to _avoid_ refleciton...
That's the author's personal opinion, but it doesn't make it any less idiomatic. There's a difference. And point 4 isn't reflection at all, it's type assertions.
You crossed into incivility repeatedly in this thread. That's not ok on HN, regardless of how little someone knows (or seems to know) about container types. Please (re-)read https://news.ycombinator.com/newsguidelines.html and abide by the rules when commenting here.
Sorry. Every response to me has essentially been "this is useless" when it is idiomatic Go. I'm sure you can see how that can be frustrating. I'll try not to be so snarky.
(comment deleted)
Unfortunately, to do anything with the results you have to iterate over them again to convert back into a slice of non-empty interfaces, making this implementation basically useless.
>basically useless

It is how container types are done in Go.

> It is how container types are done in Go.

You can't define your own compile type safe container types in Go at first place. You demonstrated nothing with your piece of code, you just wrote some dynamic stuff that doesn't care about types, this isn't a container type, you ignore the type of the element.

>You demonstrated nothing with your piece of code

I've demonstrated how you map a function over a list containing any type. How would you do it? Provide a solution.

> I've demonstrated how you map a function over a list containing any type. How would you do it? Provide a solution.

you've demonstrated you are unable to do it while conserving the type of an element.

You iterate over an array with a range loop which allows the code to stay compile time safe. That's the only thing one can call idiomatic go code here. You threw out compile time safety for nothing trying to prove some point that makes no sense to begin with.

> 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.

Closures is also a big win for passing around functions.
All Go functions are closures.
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.

Eric Evans, who wrote the DDD book, is an excellent and tasteful programmer who would never "create thousands of excess classes" or teach anything like that.
Agree with your point and the previous responses. My main point is that the "vocal" Java culture doesn't always treat DDD, SOLID, Spring, etc with a critical eye. My hope is that the culture becomes more pragmatic and open-minded to other approaches.
Java world is the biggest today and we are talking about mass culture, in which average developer isn't a genius with decades of experience and average employer has only resemblance of modern development process. It will never be the culture of best practices only, so it's actually good that big frameworks exist and they are already mature enough to tolerate misuse. It is this environment of uncritical thinking, the real battlefield, in which best practices are being invented, tested and verified on massive scale, outside the sterile conditions of the perfect teams. It also explains why Java sometimes lags behind other languages in adopting new features, but is still much more popular: not all of those features are good enough to be released in the wild (I wish developers of other languages would actually perform "drunken user" test before inventing crazy syntaxes and APIs).
Liberation from SOLID and DDD is like liberation from electricity and healthcare system. Makes things simpler, definitely, but doesn't make life better. Throwing away slightly imperfect tools that compose significant part of modern Java software is also quite interesting idea, but what would you use as a replacement (to achieve at least the same code quality and development productivity)?
> blindly demands using magical frameworks like Spring and JPA

That sounds like ~2005 Java to me, where the ~whole ecosystem was that way, and consequently a lot of Java devs left for Rails.

However, I think in Java's today (since ~2010) there are definitely two (or more camps) of old-school enterprise Java (which is like you say) but also lightweight start-up Java (which is what I'm more familiar with, and matches your "Java can be a great language when used well" comment).

So, anyway, IMO, it's not the entire Java culture anymore, although of course I believe you that that is the one you're currently in.

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.

I agree with your point about the productivity gains you get out of the box, particularly when starting new. The trouble I find in enterprise development particularly around things like Spring is that the features for which you onboard the framework for at inception act like the thin edge of the wedge. A couple of years in, and perhaps a few maintainers later, Spring is somehow running the show, and a huge source of non-essential complexity.

How to limit the scope of use of an on-boarded framework to just its original remit seems to be the challenge.

You are speaking about enterprise culture.

It was already like that with C, C++, Smalltalk, Delphi, VB before Java came around.

Rest assured there will be GoEE and GoSpring if enterprise architects ever put their hands on Go for the same kind of scenarios.

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?

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.
> 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.

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.
TLDR; writing code in Go is boring and clunky, reading other people’s code is pleasant and easy.
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

I think it's the crappy jvm's that keep giving java a bad name. If only you could compile native executables independent of a runtime/jit/bytecode.

On the contrary java has been around since 1994. Who knows how mature golang and python will get after aging and maturing like java.

Native images are getting more attention, but they give up a lot of this runtime magic.
The JVM is a double edge sword. It makes all of this possible. Not only can you generate code at runtime, Java will optimize it just as well as if it was originally compiled in the application.

I'm not sure why people harp on the JVM so much. JavaScript uses a runtime with even more magic that uses 5-10x as much RAM. I think the crappy JVM's of the past along with bad memories of Java Applets tarnishes its reputation unfairly

A major pain point with JVM even today is the startup time. This is especially true for command line tools. I use hdfs tools and it is really slow to start. Someone wrote a Go replacement that is 100X faster to start. None of the JVM sophistication means anything if it cannot solve simple common world problems. The java community has failed in this regard in my opinion.

[1] https://github.com/colinmarc/hdfs

This is actually being addressed, eventually. There's work being done to lower the startup time in Java 9-10 but I'm not sure what state it's in. Basically cache everything from the first run to make future launches almost instant
I played around a bit with that, i.e. app class-data sharing and AOT compilation. It turns out Java 9 comes with an enormous regression in launch performance, so that even with app CDS enabled it is slower than Java 8 to start.

Java 10 is slightly better but still slower than Java 8 by default. Enabling app CDS gets you back to comparable performance to Java 8. With AOT enabled (in addition to app CDS) it might be faster, but that just crashes on machine (and only works on Linux anyway).

So don't get your hopes up too much.

Totally agreed that java fails hard for use cases involving small, lightweight binaries that are low memory and fast to start. This is an area where golang shines on the other.

People are starting to experiment with AOT compilation via graal to make headroom in this space, but the techonology being used feels young, nascent, and experimental or immature:

https://medium.com/graalvm/instant-netty-startup-using-graal...

https://www.innoq.com/en/blog/native-clojure-and-graalvm/

Interestingly most high-level runtimes also have slow startup, including python, ruby, and node.js, but java's feels worse because people seem to want to use heavyweight frameworks like spring or aspectj that absolutely murder startup time, that for instance generate bytecode on startup or use classpath scanning to discover dependencies.

Yeah it's a bad trade-off developers don't realize they're making sometimes. Spring is Gigantic. Application containers like Tomcat aren't much better. It's easy to write huge apps in Java that still perform well so there's not much pressure to modulize huge monolithic libraries. So we all just deal with the 200+ deep stack frames.

Newer frameworks like Vert.X and Dropwizard address this pretty well. We have a fairly large Dropwizard project that still starts in ~5 seconds. Also some written in Spring MVC that take 2 minutes...

Also, God AspectJ let's you create horrible abominations. And it feels like it was designed to encourage you to do so. A great example of why you shouldn't always use self modifying code even if it's easy to do.

Keep your eye on Graal - specifically its native-image component.

Not only do you get instant startup, you also get a statically linked binary that doesn't need a JRE present to run. Throw it a JAR - get a native binary for your system.

https://github.com/oracle/graal

I would think the vast majority of uses of the JVM aren't for command line tools / anything where startup time even matters.

> None of the JVM sophistication means anything if it cannot solve simple common world problems.

It means plenty for all the companies that use it.

You can since around Java 1.2 time actually, the problem is that most devs don't want to pay for commercial tools.

Almost all third party commercial JDKs have some form of AOT support.

> Nothing I'm aware of comes close to Java in this,

C#. No Type Erasure. More powerful reflection with less code. Not to mention the unbelievable power of Roslyn and being able to programmatically generate code with little effort.

If you love Java reflection, your mind will be blown by C#'s.

I came from the C# world :). And yes, it's even more powerful, plus reified generics.

The huge problem is that it's not that much better than Java which has ~50X more open source software written for it. The day MS makes a bytecode transpiler so I can use my Java libraries in C# I'll never touch Java again. The languages are so similar I have no idea why they haven't done it yet

"The huge problem is that it's not that much better than Java"

I always see the .net fanboys tout how much better C# is than Java with generics, await/async, etc. but at the end of the day it's not that much better when you can achieve the same thing in Java with better open source support and across oses.

I know there's .net core now but I am not going near it until it matures.

I miss all that from C#, some of it a lot. But Java has a community so large that they've basically hacked these things into Java anyways.

LINQ -> streams, QueryDSL, RxJava, JOOQ etc

Async/await -> Comsat/Quasar

The type system is lightyears better in C# but it was rare that I needed those features. Usually you can hack around the limitations and end up with something (slightly cruftier) that does the same thing in Java. Mockito, Retrofit, and RxJava are good examples of hacking about the type system until you have something good enough. That said, there are definitely times I still miss C#'s stellar type system. Easily the best of any language I've touched besides maybe Typescript (which is designed by the same guy)

There is nothing particularly outstanding about C#'s type system. It has reified generics and some basic type constraints, but so what? No option type, no ADTs, no pattern matching... Working with Typescript at the same time, I feel crippled every time I need to do something in C#. Not that Typescript is perfact, but its type system (especially with `strict`) is considerably more sophisticated.
It has basic pattern matching now, nullable is the option type, which is being extended to reference types in the next version. It's no Scala, but it's getting there.
If you really care about all those features then you would just use kotlin. It works almost seamlessly with Java and has the best ergonomics of any language I have ever used. All those festures are in kotlin and then some.
It's weird, I started in C# but haven't touched anything Microsoft for roughly 10 years. That's how long they've been asleep, maybe longer. I would have killed for official cross-platform C#, if only for a decent UI toolkit (Mono was too bigger pain), and I'm sure C# would have significantly dented the popularity of e.g. Python. Instead C# never outgrew Windows, while many developers and companies did. Meanwhile, Java has kept evolving and taking chances, so much so that Java 8+ is pleasant enough. Only now has Microsoft realized the error, but way too late.
It's a damn shame really. MS killed the golden goose by tryiing to squeeze too much money out of it years ago. C# is an excellent langauge, the design and type system are just incredible. But they missed the open source boat by about a decade and it's yet to be seen if their attempt to rally is too late
It's distinctly possible that Unity3D will "rescue" this aspect if C#, although from what I saw of the community it's not there yet. Probably the lack of a good package / dependency manager - oh, and the lack of up-to-date C# support (last I checked you were stuck way back with 3.5).

Yadda yadda, if I wanted to "fix" the lack of an open source community in C#, I'd start with the Unity3D community.

In the latest Unity version (2018) you have access to C# 6 (stable) and .NET 4.6.
The problem with that is, there are now open source game engines coming on the scene that are promising (in the long run, and yes it's a BIG maybe) to upset Unity's stranglehold, that support Python and in the future C# to lure away Unity devs. I'm speaking of Godot. I'm not sure how successful they'll truly be but I, for one, am hoping they bring a solid rivalry.

Python as a scripting language makes sense, in this arena, anyways, as it's the scripting language for Blender. A single scripting language for 3D software and game engine in a game studio just makes sense.

Only makes sense in modern game engines if it comes with a JIT.

I can go for a coffee when Blender does some kind of data transformation, I can't do that in a middle of a rendering a frame 60 times per second.

It can also do .net standard 2.0, which is great for library compatibility, and the experimental (but working) incremental compiler is Roslyn based and can do c# 7.2.
Oh, horray! I stopped working with Unity3D, and so haven't been in the loop
I don't think that's accurate, we use opensource .net dev stack since 5/8 years, and since 3 it can be deployed on win, linux and osx.
That's unfortunate, since ms opensource journey started 10 years ago :D Java as a language stagnated 10 years compared to anything (example lambda). .net has been the source of many technologies, CQRS/ES, Async/Await, Reactive Programming, etc.
Woah there. I think a lot of these need citations. While it's true that .NET had async/await in 2007 (F#, AFAIK), Microsoft open-sourcing things isn't that old. From what I can see, their most popular open-source projects are TypeScript (Oct 2012, so 5 years ago), and VSCode (Apr 2015, so 3 years ago). .NET Core was Nov 2014, also 3 years ago. Cynically, you might say they released VSCode and .NET Core because VS and .NET were dying slow deaths, maybe the same can be said for TypeScript, because JavaScript/web dev cannibalized Microsoft's markets.

Meanwhile, the interesting technologies, especially relating to web development, ops/deployment, or data storage and processing are first-class citizens in Linux (and macOS gets a free ride, because POSIX and desktop). Most of them don't use Java , but enough do: Lucene/ElasticSearch, Cassandra (and probably DynamoDB, even if it isn't OSS), Hadoop, Neo4j, Kafka, and more.

It's not even close. Please understand I'm not hating on C#, or saying Java was why these things are successful (definitely not), but I think it helps to understand the eco-systems surrounding both C# and Java. C# may be a nice language, and may be able to run on Linux. In 2005 (C# 2.0) it was almost revolutionary, but now it's just another language. Meanwhile everybody else has been learning and using the other languages to build great things - distinctly without Microsoft.

Yep you are right, i rounded too much, mvc is from 2009 : https://stackoverflow.com/questions/734892/asp-net-mvc-is-no... But the bulk of it started a bit later with the whole .net ecosystem.

I agree with all you said, but i'm referring to the previous comment where ms has been all but asleep in the last 10 years :D Anyway talking about language and dev-environment are two completly different conversations, and i'm not wrong saying that Java as language stagnated far too long. I never said the .net eco system thrived meanwhile :D

> 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.

Your comment seems largely unnecessary.
I get the sense you're being contrarian. Op isn't saying it's the best choice, but being in that field, you should know that even those things people have tried to do and sometimes done with Java. For example, Java-based smart cards or Java Micro Edition.
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.

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.

It's really a matter of preference, but Go encourages you to expose more to the user. It tends to discourage heavy abstraction. You don't really need runtime code gen (Go actually does have reflection) and it's certainly quite possible to write clean implementations of most things without it.

Java's real issues like in two aspects: 1) Bad Java programmers have built a culture of writing "overpatterned" enterprise code (with FactoryBuilderFactorFunctions) 2) Horrible GC/iffy performance for a modern language

#1 is reason enough to avoid Java like the plague. #2 makes it totally unsuitable for heavy numerical computation or systems level applications. Nothing like huge GC spikes to liven up your day.

1) I mildlt disagree with this. There are lots of amazingly written Java libraries, and not all teams or people write “over enterprises code. I’ve seen dubious code quality on both ends of the spectrum in many languages. I do not think this is limited to Java, but more of a fact that Java is very popular and has been around for a long time, and thus has a lot of both good and bad history with it. I feel that modern Java practices have really improved.

2) Without a doubt native shines here. I’ve also been a victim of those massive GC spikes in the past. But I think saying the JVM has horrible GC seems a bit hyperbolic as it’s quite matured and more than adequate for most usecases.

> 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

Speaking as someone moving from Ruby to Go, I actually like the strong stance that Go takes against magic. The original article acknowledges that Go works the way that it does by design, and acknowledges the reason very fairly: the language is firmly on the side of making code easy to read, even at the expense of making it less elegant to write.

The biggest problem with Java is the culture. All modern languages are Turing complete and roughly equivalent in the their expressiveness (Lisp, APL and Forth notwithstanding). But only Java people seems to have an AbstractAbstractFactoryFactoryWidgetGadget fetish.

When I have to do Java, I keep recalling Brazil[0].

[0] https://www.imdb.com/title/tt0088846/

More modern Java libraries and frameworks don't have this issue.

Sure, if you look at JEE stuff, you'll run into it...

I think you're right, but for me Java lacks compile-time features sorely (JavaPoet looks alright, but suffers from not being an official component of the JDK). Thinking about the user I'd much rather have that processing done at compile-time than startup-time. I guess this is part of where the appeal of Scala and Clojure lies.

Rust and Nim provide the sort of thing I mean, hygienic macros - they're nicer than reflection because you keep type safety and you keep run-time performance without generating a bunch of transient source files. Nim's also got a nice RTTI interface, too.

>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

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.
What do you mean by "warn-up much faster"? Go doesn't have a JIT compiler so not sure what you need to warm up?
The time from a program starts to the program fully responses/works, a.k.a., the loading/prapare phase time.
Agree - developed in Java for many years, and would never consider going back. Also agree on the resource use of Go vs Java. Having watched other Java devs make the switch, the big thing seems to be to stop over thinking things.
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.

vgo is great, which will be released along with Go 1.11 soon.

Go tools are not intended to be a full build system, so for more complex builds, Makefiles are still needed.

> 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 . If you also use Kotlin instead of Java for your software on the JVM then you can stick to one language for everything.

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?

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.

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

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

Should be

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

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.

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.