6 ms·
Yeah, but the amount of boilerplate one must read and write in Go is frustrating, even compared to Java.
by emodendroket 3y ago
Yeah, but the amount of boilerplate one must read and write in Go is frustrating, even compared to Java.
- abound 3y agoMuch less so these days with generics, even in their fairly limited capacity.
- rargon 3y agoHuh? Having defer statements and error returns vs. dealing with exceptions gives Go the win all by itself. And then there is the business of being able to make a type automagically satisfy an interface. (Java may have fixed some of the agony of creating classes just to satisfy a required interface. Last I used Java, it didn't have delegates and whatnot.)
- vore 3y agoGo doesn't really warn you if you forget to handle an error return, which in my experience continuing silently in the face of error conditions is far scarier than crashing loudly.
- xanamander 3y agoThat would not be a compile error, but these sorts of issues are why you should also run `go vet` and `golint` for a healthy codebase
- starttoaster 3y agoCheck out ErrCheck. It's a static analysis tool that detects exactly this. We added it to our common Makefile that we use for testing/building Go projects. Typically I have it test for this among other things before I commit, and then it runs again in CI scripts before a merge to main is allowed. In my experience, if you're not checking errors, then you're often just going to crash loudly. Likely at a similar point that the comparable Python would have had its runtime error. In my experience, if Go would have continued silently, the comparable Python might also just continue silently anyway.
- candiodari 3y agoThe problem with error checking is that Go doesn't cover every case BY FAR. EVERY memory allocation can fail. And I mean EVERY. var x := 5 // where's the error handling? EVERY kernel call can fail. Even this is still not a 100% correct way to call fmt.Printf("Hello, World!"): s := "Hello, World!") writtenSoFar := 0 while writtenSoFar < len(s) { bytesWritten, err := fmt.Print(s[writtenSoFar:]) // and even this is a recent syntax addition. if errno, ok := err.(syscall.Errno); ret == -1 && ok { // error signaled if errno == C.EAGAIN { time.Sleep() continue } else { return err } writtenSoFar += bytesWritten } If you don't do this, you will find, for example, that writing large amounts of data to a network socket suddenly only sends half the output to the other side. Plus anything could set O_NONBLOCK on stdout, which would require this. And time.Sleep() is required in some cases where the program redirects os.Stdout to itself. Even this does not take have proper reactions if an OOM occurs somewhere. So it is still not correct. It's like C. Simple Go looks correct and just chugs along, destroying data instead of crashing. This makes people feel programs run correctly ... but they don't.
- starttoaster 3y agoIn practice, how often does your rant on memory safety really apply though? Because I currently feel that you're inflating the argument substantially to make it seem like a much bigger, much more common problem, than it is. For reference, in 5-6 years of Go programming, memory allocation has been a problem for me exactly one time, and it was because I was a noob and tried to push about 60GB of data into a variable at once on a virtual machine with 32GB of memory available to it. And it wasn't a silent error, the systemd service I wrote for the application was crashing each time it attempted to load up that mega-variable until I rewrote it in a sane way.
- candiodari 3y agoWell that's the problem with correctness. I've seen this fail, in 20 years (that I noticed) about 20x. Let's assume I caught it 1% of the times I actually saw it so ... about once a week. More on slower networks. This issue, not checking the number of bytes written, usually combined with incorrect EAGAIN handling, is a pretty pervasive problem in network programming. You will find the closer you get to 100% cpu usage, the more common this problem becomes, just like threading bugs. It's one of the ways a service goes from handling 5 Gbit at 90% cpu usage, then handling 5 kbps at 95% cpu usage (because everything suddenly errors out, then retries eat all the bandwidth). It's impossible to find if you don't know what you're looking for. It's not this issue specifically: Golang programs, like C programs, are strongly incentivized to just keep going with incorrect data when other languages would crash.
- EnergyAmy 3y agoGo's defer is verbose and unwieldy compared to Python's `with` statement or Rust's lifetime system. In Python, you don't have to manually write any code to close the file, you just do: with open('foo') as f: ... And the file will be closed after that block of code finishes executing. Similarly in Rust if you do "let file = File::open("foo")?;", when the variable drops the file will be closed. If you want to loop over 10k files, both of those handle that just fine, because the active file will be closed at the end of each loop. Go's defer will try to wait until after the loop to close everything. You can make things even more verbose to fix that, but it gets ugly real quick.
- arp242 3y agoI wish defer was block-scoped rather than function-scoped; that would solve the "defer in a loop" problem. Right now I just wrap the lot in a function for that, which works and isn't too bad, but meh. defer is more flexibly and explicit though; I rather like that. It's pretty unclear what exactly that "with" does, what you can and can't use inside "with", you can't "just" run any arbitrary code without creating your own class, and you sometimes end up with 3 or more levels of nested "with"s that could have been one defer. In short, I don't think there's a clear winner. Both are clunky in some scenarios where the other is easier.
- pclmulqdq 3y agoWith things like copilot, the boilerplate (if err != nil sorts of things) comes out really easily, and you can skim past it when reading. Also, I have noticed that a lot of the other stuff that people call boilerplate in Go is actually an artifact of static typing (eg having to unmarshal JSONs before querying them), and is ultimately what prevents you from having Python-style runtime crashes or weird bugs.
- avgcorrection 3y ago> With things like copilot, the boilerplate (if err != nil sorts of things) comes out really easily, and you can skim past it when reading. Easily... but is that simple though.
- pclmulqdq 3y agoIt's very simple. It's explicit and tells you exactly how errors are handled (if you care), and it follows a very common pattern that is easy to ignore when you don't want to know how errors are handled. Simple and concise are two very different things. In comparison, languages with exceptions give you a minefield around every function call.
- avgcorrection 3y agoOk. Then I will spell it out: using an LLM to generate code is not remotely simple.
- pclmulqdq 3y agoI will spell it out for you: Almost every language in the world has some sort of template-based autocomplete for common boilerplate that is often used, from class definition templates to if statements. If you aren't using one of these, you're probably working unproductively by thinking about all of your boilerplate instead of just taking it off the shelf. Copilot and its ilk just take this one step further by effectively giving you the same thing but with the slots in the template filled in. I could have said that "with autocomplete it's easy to write" and it would have the same meaning. The presence or absence of boilerplate (and thus the presence/usability of autocomplete systems) does not imply anything about simplicity.
- starttoaster 3y agoWrite more libraries of common tasks you have to do constantly. If you're not abstracting your chores, you've been doing it wrong.
- throwawa14223 3y agoGo fought me at every step trying to abstract away boilerplate.
- starttoaster 3y agoI'm not sure how you mean it fought you. I've been writing libraries and utilities in Go for something like 5-6 years now though, so maybe what you and I perceive as "fighting" differs.
- eddtries 3y agoGo is so boring that I found AI tooling to start being far more effective, fwiw
- baby 3y agoIt’s must nicer to read IMO than a generic-heavy language. Sure Golang is a bit more of a pain to write because of that but it’s a trade off.
- thadt 3y agoIn a former life I often bemoaned the amount of boilerplate shovel code I was having to write to move data between my underlying data models and the user interface's displaying them. So much repetitive and simple 'pick up this number, format it to two decimals, put it in this display box'. Then technologies came out featuring data binding, and patterns like MVVM to keep me from having to write all that shovel code. Instead I could just specify bindings directly in the declarative UI layer, bind it to my data object (or view object) and be done. All I had to do was learn the binding language, understand how the binding engine worked, understand the corner cases and details like the difference between one and two-way binding, etc. Eventually the code taught me that typing, proofing and and maintaining shovel code is often easier than understanding and maintaining the magic incantations of a more 'elegant' solution. My eyes can quickly scan over boilerplate and glean what it is doing pretty quickly. But magic - magic takes understanding - and I only have so many ready spell slots.
- theshrike79 3y agoYou can see the structure in the boilerplate pretty quickly. And because it's boilerplate, you'll also instantly see when it's missing.
- josephg 3y agoSounds like a waste of time. I’d rather just not have that code in the first place. I don’t want to write it. I don’t want to maintain it. I don’t want to have to scroll past it when I’m working, and have my monitor made artificially smaller because my code has the same repeated error handling code everywhere. What a waste of brain cells.
- theshrike79 3y agoSounds yes, but isn't in practice. You look at patterns every time you read code, if the pattern is clear, it's easy to see from far away. Any disruptions in the pattern stop your progress and you need to zoom in to see what's actually going on.
- josephg 3y agoRight; but the computer is much better at fixed patterns than I am. This is the philosophy behind linters, automatic indenting tools in IDEs and gofmt and friends. Given a choice, I’d rather that the compiler does the work to write “if err != nil …” thing, using any mechanism for it - exceptions, or whatever. You’re right of course. I can deal with patterns. I do so every day. I can type that code over and over and visually learn to spot when I do it wrong. But I’ve got a computer right here in front of me. I’d rather get the computer to do that work and spend my attention focusing on the task in front of me.
- didip 3y agoJava boilerplate is way worse AND hidden behind annotations and so many level of abstractions!
- richbell 3y agoThat's mostly a facet of frameworks like Spring, not something inherent to Java itself.
- josephg 3y agoThere’s a strong culture in the Java community which actively encourages this style of programming. I agree - I don’t think it’s an inherent part of the Java programming language. At least not any more - recent versions of Java have a lot of nice features! But you still see a lot of this overly abstracted, “enterprise” Java code in the wild all over the place. I find it easier to just specialise in other languages.