10 ms·
From Java to Go, and Back Again (2016)
- tbenedetti10 8y agoThe 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.
- neonate 8y agoEric 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.
- tbenedetti10 8y agoAgree 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.
- ivan_gammel 8y agoJava 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).
- ivan_gammel 8y agoLiberation 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)?
- stephen 8y ago> 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.
- jrs95 8y agoHonestly, 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.
- hotcrossbunny 8y agoI 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.
- pjmlp 8y agoYou 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.
- brown 8y agoLanguage 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 8y agoIn 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 8y agoWhy is having only one implementation for an interface bad? It neatly organizes all your code and doesn't make debugging harder at all.
- hocuspocus 8y agoHow does having FooService and FooServiceImpl help with code organization at all? It brings nothing but unnecessary noise.
- humbleMouse 8y agoIn 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.
- nemo1618 8y agoI 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 8y agoIn what job does one trawl through code at 3am?
- pdpi 8y agoWhen you’re oncall and something broke.
- watwut 8y agoYou 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 8y agoPoint 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.
- watwut 8y agoIf 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.
- noelwelsh 8y agoWhen 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 8y agoyeah, 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
- daenz 8y ago>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 8y agoWell 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.
- deleted 8y ago[deleted]
- daenz 8y agoNo, it's not generics, but it enables you to write a generic function, which is what the article claimed was not possible in Go.
- stephen 8y ago> 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.
- cdoxsey 8y ago> 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 8y agoClosures is also a big win for passing around functions.
- humbleMouse 8y agoPeople 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 8y agoYeah, 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 8y ago> 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 8y agoYou 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.
- noncoml 8y agoTLDR; writing code in Go is boring and clunky, reading other people’s code is pleasant and easy.
- thermodynthrway 8y agoJava 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
- badrabbit 8y agoI 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.
- cordite 8y agoNative images are getting more attention, but they give up a lot of this runtime magic.
- ivan_gammel 8y agoPython is older than Java.
- thermodynthrway 8y agoThe 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
- aisofteng 8y ago>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
- tapirl 8y agoFrom 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.
- amzans 8y agoWhat do you mean by "warn-up much faster"? Go doesn't have a JIT compiler so not sure what you need to warm up?
- tapirl 8y agoThe time from a program starts to the program fully responses/works, a.k.a., the loading/prapare phase time.
- tomohawk 8y agoAgree - 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.
- abvdasker 8y agoProbably 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.
- tapirl 8y agovgo 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.
- siscia 8y agoHonest 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 8y agoYou'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.
- mark_l_watson 8y agoThere is a bug in a code listing in the article: val exclaimed = strings.map { it + "!" } Should be val exclaimed = plain.map { it + "!" }
- cpt1138 8y agoI’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.
- eternalban 8y agoHe never left Java.
- didibus 8y agoThe 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.