
The Zen of Go - sagichmal
https://dave.cheney.net/2020/02/23/the-zen-of-go
======
pansa2
The article mentions “Simple is better than complex”, but not the next line of
the Zen of Python, which I think tells us a lot about that language’s
philosophy: “Complex is better than complicated”.

Looking closely, that line says “(not simple) is better than (not easy)”, or
more clearly, “easy is better than simple”. Python definitely lives up to this
- it’s easy to get started with, but if you look deeply it’s a very complex
language.

Go’s philosophy is probably the opposite - that simple is better than easy.
This is similar to the philosophy of Clojure, as explained by Rich Hickey in
“Simple Made Easy” [0].

[0] [https://www.infoq.com/presentations/Simple-Made-
Easy/](https://www.infoq.com/presentations/Simple-Made-Easy/)

~~~
masklinn
Don't forget this absolute banger:

> Obviously Go chose a different path. Go programmers believe that robust
> programs are composed from pieces that handle the failure cases before they
> handle the happy path.

A function which only returns an error can have its result ignored without any
warning.

~~~
throwaway894345
I don’t think that paragraph was referencing compiler guarantees.

~~~
coldtea
It should refer to them or address them.

Since, given this, Go is no better than a language with unchecked expressions
nobody handles...

~~~
stouset
There’s other reasons too! I’ve written the following:

    
    
        if v, err = func(); err != nil {
            nil, err
        }
    

… and then went on to use `v`. Thanks to `v`s zero value being legitimate (and
not nil like a pointer would be), the program continues on as if everything is
okay. In case you didn’t catch it, I forgot the `return`.

Rust takes a _much_ better approach with Result, where the return value is
_either_ `Ok(v)` or `Err(e)`, and there’s _no way_ to access a meaningless
value for the other possibility.

~~~
coldtea
Yeah, and this is so simple a change (compiler wise) and far more stronger a
guarantee I don't see why Go didn't implement it...

At least then errors as return values would be solid.

Of course now they make programmers do all the extra error wrapping thing in
1.14 to pass "richer" errors...

~~~
pm90
Eh. It’s verbose but I like it. It makes me think about the code a bit when I
have to write a descriptive error wrap. Kind of annoying I guess... I haven’t
written ultra large go codebases though so ymmv.

~~~
coldtea
> _It’s verbose but I like it. It makes me think about the code a bit when I
> have to write a descriptive error wrap._

Having the compiler force you, as is my suggestion, would make you think even
more -- or not be able not to think and skip the error check or miss it.

------
SPBS
> If you can, don’t reach for a goroutine, or a channel, or embed a struct,
> anonymous functions, going overboard with packages, interfaces for
> everything, instead prefer simpler approach rather than the clever approach.

This reminds me of an interview I had with a company recently. I mentioned
that although I was using go for my current pet project, I had hardly touched
goroutines because net/http automatically spins off a new goroutine for every
request so I don't have to think about it. The interviewer was incredulous and
said that every database call should be put behind a goroutine so that it's
not slow. He said the point of go is to leverage its powerful concurrency
mechanisms. What I wanted to say was that I believed go's selling point was
its simplicity and not its concurrency mechanism, and that it would be more
idiomatic to keep it simple first and refactor only if there's performance
issues. But he was the senior and I was the new job seeker, so I just ended up
agreeing with him.

~~~
dana321
The multithreaded nature of the go http library makes it look simple, but in
fact, its not. It won't show up problems until you use a specific tool to send
concurrent requests.

Just a gotcha i want to mention to anyone reading. ApacheBench is a good first
start.

~~~
sagichmal
ab is broken by design.

wrk, h2load, boom, vegeta are more sound load testing tools.

~~~
dana321
Its not broken, it does make concurrent parallel requests.

Whats with the nitpicking on hacker news?

~~~
sagichmal
ab uses a single OS thread. Broken by design.

------
dmit
Go was so obviously designed by Googlers, for Google. After the initial
release, outside adopters would frequently report issues where Go undermined
their productivity. And every time the core team's response was the same -
complete disbelief.

"Dependencies? What? Do you not vendor every single library you depend upon in
your monorepo?"

"Advanced type system? Why? Do you not hire thousands straight out of college,
and expect employees to stay put for 2-3 years (give or take)?"

Every single issue that was raised in the past 10 years was met with such
incredulity.

Rob Pike, Robert Griesemer, Russ Cox - they've done a tremendous job. A great,
stunning achievement for their employer. The more of the infrastructure
transitions to Go, the less money Google will need to spend on people in the
long term. Those three have earned billions for their company, no doubt.

And none of that means Go is a great language for your 12-person team working
on a CRUD web app.

The Zen of Go is not reducing your problem to "can it be put in an array?" or
"can it be put in a hashmap instead?"

The Zen of Go is looking at individual persons and deciding whether it's net
positive to cut them now, or 6 months from now, after their next performance
review.

~~~
hellcow
Your comment is unfair.

I've been programming for more than 20 years. I've learned many languages
including C, Objective C, Java, Python, Ruby, JS, and some Haskell. I've never
worked at Google.

Go is my favorite language of them all and has been since I started using it
almost a decade ago. I use it for personal projects, and I use it at my
company.

It helps me and the teams on which I've worked write correct code quickly the
first time. The code we write is _incredibly_ reliable in production at scale.
It's easy to read and debug years later. Binaries can easily be compiled and
deployed on any system without messing with dependencies or cross-compiler
toolchains.

The same properties of the language which helped Google have helped me and
many thousands of others as well.

~~~
dmit
> The same properties of the language which helped Google have helped me

To be fair, I don't think anything I wrote contradicts that. If your goals
align with Google's, great.

~~~
0xFACEFEED
Then why couple those goals to Google instead of ascribing them to a broader
philosophy that Google happens to be adopting (among many others)?

Your original comment read like you were salty that Go didn't adopt ideas that
enhanced yours and others productivity. Instead of writing it off as a
philosophical difference (that can be attributed to _any_ language with
philosophical ideals), you have to take it to the next level with some kind of
"Go devs only care about Google's needs" conspiracy. I've seen this kind of
thing a lot over the years. Why can't people just agree to disagree?

~~~
monocasa
Because the entire governance board of golang is Google employees, and if your
use case is different than there's they become outright hostile?

~~~
0xFACEFEED
You'll have to back that up. Unless you interpret every rejection of an idea
as hostility.

~~~
stouset
I think it’s more “the rejection of every idea”.

------
tsimionescu
> The verbosity of
    
    
        if err != nil {
            return err
        }
    

> is outweighed by the value of deliberately handling each failure condition
> at the point at which they occur. Key to this is the cultural value of
> handling each and every error explicitly.

I've talked about this before, but it annoys me a lot that checking if an
error exists and returning it is referred to as "handling the error". You are
not handling anything - you're passing on responsibility up the stack just as
clearly as if you had thrown an exception.

You're just doing it manually instead of letting the runtime do that for you.
You're also making it much harder to tell the code that handles errors apart
from the code which doesn't.

~~~
sagichmal
The snippet used in these examples is almost never what you actually write in
real code. You will almost always annotate the error, e.g.

    
    
        result, err := computeVector(input)
        if err != nil {
            return nil, fmt.Errorf("error computing vector: %w", err)
        }
    

But that's a bit unwieldy to write in an article or talk.

~~~
monocasa
You've just removed all of the machine parsable context of the error,
guaranteeing that downstream code can't make an intelligent decision.

~~~
sagichmal
No, I didn't.

[https://golang.org/pkg/errors](https://golang.org/pkg/errors)

~~~
monocasa
I mean, that's a not in the standard library, and it's a library that's
literally already in maintenance mode.

Edit: you also deleted the context of your call. You didn't create an error
type for Unwrap to match on. That means that downstream code only has
extremely primitive errors to match on, with the context of the call gone.
Just because you can track down the EOF error, doesn't mean that code calling
this can do anything useful with that. 'Socket closed unexpectedly' is way
less useful than 'Unable to update user record'.

~~~
sagichmal
Package errors is indeed a part of the standard library.

~~~
tsimionescu
Yes, they were wrong about that.

But they were right about it losing error context. Basically errorf can only
help you bubble up the lowest level error in a machine readable way. You have
to write your own error type if you want to let code handle the higher level
errors.

------
zxcvbn4038
This is a misnamed article. There is nothing zen about this article. A better
title would be “I’m mad that no language is perfect” because that is really
the gist of the article. Author has nit-picks with every contemporary computer
language. “If only someone would write the one true language, here is why
every other language fails to be that, blah blah blah.” Pure opinion piece.

~~~
raister
"The only Zen you can find on the tops of mountains is the Zen you bring up
there." Robert M. Pirsig

~~~
zxcvbn4038
“He who makes mountain out of mole hill should open ski resort in Kansas” A.
Nonymous :)

------
jpgvm
These are great idioms for writing code in any language.

I'm no longer a fan of Go but still a fan of many of the people the work with
it because they also value many of these things.

~~~
mongol
Did you use to be a fan? What happened?

~~~
jpgvm
I was a fan in the past and still think the language has niches it's good at.
I just no longer think it's a good general purpose language - more correctly I
think there are languages that are better than Go in every metric that counts
for general purpose use cases.

I invested a lot of time and work in the language (about 4 years
professionally) but become disillusioned with it over time.

The big reasons for no longer liking the language stem from decisions made
around the type system (this being the main reason), tooling (namely
packaging, vendoring/modules bleh) and weariness with the verbosity and logic
per line density.

So when would I still pick it?

I would pick Go if I specifically need to shuffle bytes between sockets with
high I/O concurrency and don't need access to C libraries or any complex logic
at all. The moment there is business logic I don't want to be writing Go
anymore.

~~~
apta
> The moment there is business logic I don't want to be writing Go anymore.

What do you prefer for writing business logic type apps then?

~~~
jpgvm
For me right now it's Kotlin. I believe it picks a good balance of
simplicity/readability vs expressibility/power.

In particular I find it's generics, data classes, infix operators and
extension functions very useful for expressing business logic concisely.

These features let me write half (or less in many cases) the code I might need
to in Go.

More importantly is Kotlin is much easier to refactor. Part of that is there
is just less code to refactor, the other important piece is tooling and IDE
providing awesome automatic refactoring tools.

------
saagarjha
> In Java, and Ruby, and Smalltalk, the core value that everything is an
> object drives the design of programs around message passing, information
> hiding, and polymorphism.

This is certainly not an accurate description of Java…

~~~
skrebbel
Why not? Seems apt to me.

~~~
saagarjha
Java has Simula-style OOP. The characteristics used there are often used to
describe Smalltalk style OOPs: note that Java does not have message passing,
and it has primitive types.

------
mxschumacher
The principles and origin of Go are dear to me, I feel philosophically
aligned. Focus on: Dev tooling, simplicity, large scale collaboration,
networked services and readability

~~~
apta
> large scale collaboration

What does golang have to facilitate large scale collaboration that isn't done
better in languages such as Java or C#?

~~~
tsimionescu
Even more, what does Go have better in terms of dev tooling than Java or C#?

~~~
kasey_junk
Go fmt

Code formatters are available in Java/C# but not ones that have a default set
in stone.

~~~
tsimionescu
Ok. But you get to stop discussing some style choices, and instead you lose a
decent debugger, a decent (open source) IDE, any graphical profiler, good
package management that is separate from your source control tool, and
probably a few others I'm missing.

And if you care about style trivialities ,you'll still need an external
linter, since go fmt doesn't enforce anything about variable names , line
length, function length, comment placement, line breaking and many other
things some people like to obsess over.

~~~
andreygrehov
The beauty of Go is that it's so simple that you don't need a debugger. Do you
need a word dictionary to read this comment? No, because this comment is
simple. Same with Go. But if you need a debugger, there is Delve [1], though I
only used it once to debug a dynamic programming algorithm.

[1] [https://github.com/go-delve/delve](https://github.com/go-delve/delve)

~~~
tsimionescu
I know about delve, but it's an extremely basic debugger. You can't even pause
program execution on demand or execute an arbitrary function while stopped at
a breakpoint.

Beyond that, the complexity of a language has nothing to do with how much you
need a debugger. If you are writing a complex program, you sometimes need a
debugger to quickly figure out what is going wrong, instead of endless theory
-> change/log -> rerun cycles.

~~~
andreygrehov
> Beyond that, the complexity of a language has nothing to do with how much
> you need a debugger

I disagree. There is a higher chance you need a debugger when trying to
understand, say, Scala code versus Go.

> If you are writing a complex program [...]

Complexity of a program depends on its author. Even a stupid simple function
can get complex if the programmer is less experienced. Go has a philosophy to
simplify things. The community tries to follow this philosophy as much as
possible, which is why a lot of Go code is easy to read regardless of how
complex the logic is.

In the majority of cases, a need for Go debugger means you are dealing with
bad written code. Heck, this idea can be extended to pretty much any language.

~~~
tsimionescu
I think the general agreement is that there exists essential complexity - the
complexity of the problem domain you are trying to solve - and accidental
complexity - extra complexity introduced into code code by the tooling or by
shoddy design.

Simple code often doesn't need a debugger to be understood. Complex code
sometimes does. But code can be complex simply because the problem domain is
complex.

Also, a simple tool can very well introduce accidental complexity than a more
flexible (and therefore complex) tool. Go is famous for doing so with many of
its design choices.

A very good example is if you want to perform a set union of two collections.
In many languages, you could simply create a set from each collection and then
run the union operation on them. In Go, the easiest way to achieve the same is
to use maps and rely on the fact that the keys of a map happen to form a set,
while ignoring the value part of the map entirely. This is not only inelegant,
it is also unintuitive and it wastes memory, but is at least slightly less
complex than writing a set data type for your struct by hand.

------
jb3689
> I think that we can all agree that when it comes to Go, simple code is
> preferable to clever code

What even is "simple code"? The problem I have with Go is when you need to do
anything non-trivial the intent of the code quickly becomes lost in lots of
for loops. You can try to abstract that away so that your program reads
better, but in my experience that has always been a poor decision. I get the
impression Go wants me to write big for loops that do lots of work and have
lots of scope and that it doesn't want me to break my problems up into steps
(like I might in a functional style by separating each stage of the
computation). Go wants me to have mutable variables and mutable references.
State management in Go is questionable and really left up to the programmer
(mutable references and data hiding are too easy compared to functional
languages where immutability generally forces you to think harder about your
data's structure). Sure, this is usually going to lead to a more performant
end-result, but it's not going to lead to the "simpler" result (in my eyes)

There's a lot about Go that I do like - the toolchain and docs are
particularly good, and the standard libs are modern yet succinct. It is low-
level enough when I want it to be and relatively high-level (nits above
aside). The support for concurrency and distributed systems is also good. I
can see how "simple" is exhibited there

If I had to guess at what "simple" means to Gophers (well, the Golang team at
least) it would be using the fewest number of language primitives/features to
get done what you want to get done. There are a lot of peculiar ways channels
are used for example where other languages might prefer things like condition
variables. The range keyword is overloaded and used different in several
contexts but it does keep the number of keywords low. Most Go tends to look
the same (low-to-the-ground, verbose)

Go is great until it isn't and when it isn't you have little choice but to
brute force around some of Go's clunky pieces. Extracting high-level meaning
out of Go source can be difficult because of how verbose it can be. I'm glad
it's around and it's a good choice for a lot of projects, but I really wish we
could have simple things like generics so we can write cleaner code

In short, Go as a language may be simple (and I think it is) but I need to
solve complex problems - either Go helps me with that complexity or it leaves
me on my own. Go has chosen the latter of those options and I'm not convinced
that our Go code can be simple for many non-trivial applications

------
superkuh
Like others that have been downvoted to invisibility in this thread I'm a bit
upset about the namespace conflicts between the new computer language and the
old but highly active game. This "Zen of Go" has to be an intentional
overloading of the traditional meaning.

~~~
dkarl
Also, the use of "Zen" to refer to anything intuitive or aesthetic.

Zen itself is pretty fascinating. It sucks to see it invoked as an excuse for
"I'm going to go on at great length and at the end still ask you to take
everything I said on faith because it's simply too profound for argument."

------
ed_balls
> Avoid package level state Seek to be explicit, reduce coupling, and spooky
> action at a distance by providing the dependencies a type needs as fields on
> that type rather than using package variables.

I can't parse it.

------
Koshkin
Not sure if it was the intent, but Go's mascot has always reminded me of "When
a man's got to go, the man's got to go."

------
stevewilhelm
Any suggestions of open source Go projects/services that exemplify the Zen of
Go?

Bonus points if they are using Docker / Kubernetes for deployment and hosting.

~~~
bigato
The standard library

~~~
monocasa
I get told all the time about how the standard library isn't a great example
of go code, because of the backwards compat they have to do.

~~~
sagichmal
stdlib was once an exemplar of good, idiomatic Go code, but a lot of the
idioms it uses haven't withstood the test of time, and it's less and less
appropriate as an example.

~~~
0xFACEFEED
This is true, but it's still worth poking around for pre-existing solutions to
problems. My editor will jump to stdlib definitions so I've learned a lot just
diving into various stdlib packages (I remember the JSON parser being a good
one).

------
georgehm
>What if, instead, we take a position that rather than enhancing components,
we replace them. Then the best way to know when something needs to be
replaced, is when it doesn’t do what it says on the tin.

This is something that I have had trouble reaching a balance. Replacing
components could be costly and therefore it makes sense to design components
that are extensible (and seem over engineered) especially when the
requirements aren’t known completely? What are your thoughts on this

~~~
njharman
YANGNI

You've replaced a "could be costly" (rewrite if we need to) with a always
costly (over engineer for expansion).

Also, when you don't know the requirements completely is the prime time to
do/write as little code as possible. You can't know future and guesses are
more often wrong.

You should really push back on teams managers that ask you to write code
without telling you what that code should do.

Learned over 25+ years of professional dev xp. Coding for 39yrs.

~~~
georgehm
Thanks @njharman , how does one consider between rewrite vs expand when it is
known that some requirements will be true not for the current release but 2 or
3 releases down the line. Would you still prefer rewrite vs expansion? The
reason I ask is I got severe pushback recently where I proposed building
generic enough components and infra to go along with it that could have
satisfied the requirements for the next 2 or 3 releases

~~~
bigato
In my experience, reality is unpredictable enough that no one actually know
what the requirements will be two versions down the road. In practice you end
up with code that is more complex than what the current problem needs, trying
to solve a future need that you presume will happen but you don’t really know
what it is and when it will happen.

------
getpost
Not Zen! Go Kant!

------
stickfigure
Here's a hot take from a polyglot who does a fair bit of professional work in
Go:

Go is a low-blub language whose advocates are proud of the fact that they
never made it past 200-level CS courses. It's the computing equivalent of the
blue-collar anti-intellectualism that is rampant in politics these days.

Yes, Go the language is simple - it pushes complexity off to your programs
instead! Instead of functional expressions, Go programs are pages and pages of
iterative loops and variable manipulation. Without generics, you can't use
even the most basic functional constructs like map and reduce. The most boring
mainstream languages like Java and Javascript are adopting functional
paradigms, whereas Go is actively hostile to functional programming.

Nothing is more tiresome than the continuous prattle about exceptions. "How
can you possibly write code that is robust in the presence of errors when you
don’t know which statements could throw an exception?" _Easily_. That's the
great thing about exceptions, you don't need to know about them to write
robust code.

In general business processing, an exception thrown across a transaction
boundary rolls back the transaction. In most cases that's pretty much all you
need to know. In the case where you need to explicitly rollback a state change
(extraordinarily rare in business processing), you add a try/catch/throw, and
you don't even need to look at the exception!

The fact is, 99% of programs need only one exception handler. In webapps, it's
the http processor that returns a 500 error. In GUI apps, it's the main
execution loop that shows an error dialog.

Go's error handling not only requires endless tedious "if err != nil return
err", but each one of those statements _destroys stack information_. Folks
like to berate Java for long stacktraces, but those stack frames are valuable
for debugging. By comparison, Go's errors are an opaque bit of text. Third-
party libraries can help by wrapping errors in other errors (hand-building
stacktraces), but this won't help you when using third-party libraries that
don't use these wrappers.

I'll say something positive about Go, and why I still use it: It's a better C.
For low-level-ish code that needs performance and fast startup time (CLIs,
simple GAE services that scale to 0 and need immediate startup), Go fits the
bill. Having concurrency built into the language is nice, although most other
common languages have equivalent facilities in their libraries.

But as a language to take over the world: Not a chance.

~~~
Aloha
As someone who fits this description "never made it past 200-level CS courses"
how do I make the leap from interpreted basically procedural programming to a
compiled language like Go, or C, or whatever else - the mental model seems
very very different to me, and I have trouble understanding what the program
is doing when I try to just read along.

~~~
dleslie
The lower you get the more your model of computation must understand the host
system; but be careful, even C statements often compile into surprisingly
unexpected machine code.

If you want to explore this route I recommend picking up an embedded hobby
project; Ie grab an Odroid Go and write some fun little arduino games in C++
using the Esp-Idf toolkit. It's got two cores, some IO, and a slowish LCD; and
so you'll have to understand async programming and have a mental model of the
device's memory to get any satisfaction.

But that still doesn't require much beyond most 200 level computing knowledge.
:)

~~~
Aloha
this has been my issue, is my model is very much higher than the hardware

