Hacker News new | ask | show | jobs
by erik_seaberg 2869 days ago
The flipside of easy-to-learn is there's no payoff for getting better with the language. Your code will always be exactly as tedious as novices' code because they'd rather conserve compiler cycles than spend them to amplify programmers' work.
5 comments

On the bright side, your code will always be as easy to understand as a novice's code too, so it conserves other programmer's mental cycles as well.
This is the root of the Go confusion.

The code will indeed still be easily understandable on a line-by-line basis, but larger units (functions, groups of functions, modules) then become harder to read and understand due to large amounts of noise. As you progress along to more advanced, larger codebases, with novice-style code you just pile boilerplate on top of boilerplate.

Larger Go codebases remind me of old versions of Java where they went as far as to embrace all the boilerplate and call it 'design patterns'. It's sad how Go designers recognized that Java code tends to be hard to read and generally bad, but apparently weren't really able to discern why that is. They decided to just blame it all on inheritance and exceptions.

(FTR I'm not advocating for inheritance or exceptions here, but believing that those are the root of Java's problems and simply omitting them will somehow magically make a language better is just naive & shortsighted.)

> your code will always be as easy to understand as a novice's code too

Yeah, that seems about perfectly correct. Have you looked at novice's code? Is it easy to understand?

Yes, we have adopted Go for a lot of things at work and I have reviewed code of many of our devs who are more or less Go novices. It has indeed been pretty easy to understand - much easier than other languages (notably Java in this regard, but Python falls afoul of it a bit too) where people tend to write things in quite different styles and/or with excessive abstraction that made it much harder to understand what was going on.
That's a feature, not a bug. It means I don't have to deal with anyone's 'clever' code.

It's not about saving the compiler work at all, it's about saving the hundreds of humans who have to read your code after you the work of understanding the abstractions you created.

Saving the work of understanding abstractions usually means you'll pay the cost of sieving through explicit duplication.
A little duplication is better than the wrong abstraction, and I've seen far more subtly wrong or obfuscating abstractions than duplication in code I have to manage. Go is definitely not perfect, and sometimes it's plain wrong about this (I don't particularly like the go error handling and hope it improves), but there is a reason for discouraging certain types of abstraction and encouraging verbosity and boring code instead, and it's not to save the compiler time.

https://www.sandimetz.com/blog/2016/1/20/the-wrong-abstracti...

Abstractions like map and filter that have decades of use and countless pages of research behind them are not the wrong abstraction. You are more likely to get the wrong abstraction by forcing programmers to create their own abstractions instead of letting them use well-known ones that have been refined over many years.
I'm not sure anyone was opposed to map (that wasn't under discussion), not all abstractions are bad, however a flexible language makes code easier to write but harder to read, a rigid language makes code harder to write but easier to read. I prefer ones that are easier to read, even at the expense of a little verbosity.

I'm not saying Go is the best of all possible worlds (I would like to see generic functions like map too, or things like sum types for errors), just that there are good reasons for the decision to exclude some opportunities to build abstractions (for example I'm happy go eschews inheritance), and abstraction is not an unmitigated good. I've seen far more bad abstractions built than code duplicated when reading code in any language, so limiting abstractions is not always a bad thing.

> A little duplication is better than the wrong abstraction

Besides the notion of "The wrong abstraction", which is sometimes used as a proxy for "Abstractions I don't want to learn", we're discussing about language level abstractions here. The article you quote criticizes user level abstractions.

Language-level abstractions have a decent enough track record that we can assess them. Go even uses some of them: GC is, after all, an abstraction.

There would also be a lot to say about what the "little" in "A little duplication" means.

This. Either you check in a DSL, or you try to compile the DSL to boilerplate in your head and check that in, then everyone has to try to read the boilerplate and try to infer what the DSL would have said.
It's always strange watching programmers defend go's obvious deficiencies. I mean, this sort of "appeal to simplicity" could be used to defend anything.

The reality is most go programs are (1) very difficult to understand because error handling swamps their logic and (2) end up reinventing exceptions anyways, albiet poorly and (3) inevitably end up leaking resources because go's "error handling strategy" doesn't ensure resource cleanup.

We can observe this and measure this quite clearly in non-trivial go codebases.

Eventually the go dictatorship will relent and provide exceptions. At that point all the people who praise the existing broken model will happily praise the new approach and denounce the existing brokenness.

I wholeheartedly agree with you. Go got a lot right (concurrency, deployment), but some parts of Go's language design are missing the last two decades of programming language history. To me, arguments supporting Go's error handling approach alway seem a little bit like people are rationalizing a horrible mistake.
That's precisely why Go is loved.
Consistency in reading a code base amplifies work.
"The flipside of easy-to-learn is there's no payoff for getting better with the language. Your code will always be exactly as tedious as novices' code because they'd rather conserve compiler cycles than spend them to amplify programmers' work."

In my years of experience with the language, this is, bluntly, untrue. Go, used properly, is slightly more verbose than most comparable code in Python or Perl. If someone is writing code that is shot through with boilerplate in Go, then I would say that they may be using "Oh, Go just needs lots of boilerplate" as an excuse.

The problem isn't that Go lacks abstraction mechanisms; the problem is that you need to learn how to use the ones that are there and not sit there pining for the ones that are not. I find this to be almost exactly like learning Haskell; you need to learn to use what is there, not sit there pining for what you don't have. Also like Haskell, there are some particular points that it all comes together at once and hurts you, but, then again, there's some places in Go where I've had big wins using the language features too. It does cut both ways. (I've done some fun things with interfaces, and the pervasive io.Reader/Writer support, while not necessarily a feature of the language, can make certain things amazingly easy to do while still retaining incredible flexibility.)

As one example I went through personally, while by the time I learned Go I had a lot of non-OO experience, so I wasn't as stuck on inheritance as someone who only did OO-languages for the last 10 years would be, I still had to adjust to using a generally-OO language (by my standard of the term) that did not support inheritance. It has now been literally plural years since I missed inheritance in Go. (In fact, quite the opposite; I miss easy composition in my other OO languages! Yes, Virginia, it is possible to miss features Go has when using other languages, despite what it may seem like if you only read the criticisms.) But my first couple of months were a bit rougher before I internalized how the composition works and affects the design of your code.

Complaining that Go code is all boilerplate is like someone who tried Haskell but complains that it's just an especially inconvenient imperative language and you end up doing everything in IO anyhow. Nope... you have not yet gotten past your "Writing X in Y" phase. That's fine; there's a ton of languages and platforms and libraries in the world. If you didn't get a short-term payoff from using it, go ahead and move on. But you haven't attained enough mastery to go around slagging on the language/platform/library yet.

(And, again, let me say that, yes, it is somewhat more verbose that Python or something. If you've shrunk your Go down to that level, you probably went too far and are doing something ill-advised. But I find that in practice, for most tasks, it is not that much more verbose. There are exceptions, like heavy duty GUI code or (IMHO) scientific code; the solution is not to use Go for those.)