
Best practices for a new Go developer - torrance
https://medium.com/@IndianGuru/best-practices-for-a-new-go-developer-8660384302fc
======
peteretep

        > You will realize eventually that what you first thought
        > was worth criticizing was actually a deep work of
        > genius.
    

Funny, I found the more I used it the less I liked it.

    
    
        > I’ve seen a lot of criticism of Go’s “shortcomings”
        > from people who are true experts in many languages
        > other than Go.
    

Cool, normally I just call those people "experts".

    
    
        > I can’t recall similar criticism from someone who’s
        > worked with Go at a very deep level for a year or two.
    

You would do well to understand the meaning of the term "survivorship bias".

~~~
fauigerzigerk
The thing about Go is that some parts, like channels and goroutines (also some
of the library designs and tooling), are so addictive that pretty soon it gets
hard to imagine ever having to live without them.

But I don't think everyone using Go really believes or supports this "but in
practice it's not a problem and if it's a problem then the problem is YOU!"
kind of response to every criticism.

We live in a world of trade-offs. It's disingenuous to act like that wasn't
the case.

~~~
danieldk
_The thing about Go is that some parts, like channels and goroutines_

I have written a fair share of Go now and have only rarely used channels or
goroutines. In practice, channels are only a good model for a subset of
concurrency scenarios and they come with problems.

Also, those criticizing Go are right (of course). No amount of Go usage will
alleviate its shortcomings. The lack of parametric polymorphism is incredibly
annoying, error handling is mediocre (Rust handles this much better), and the
lack of support for even simple abstractions (algebraic data types) is
jarring.

Nonetheless, I continue to use Go, because they also got a lot of things
right: compile times, tooling, a very-well documented standard library, good
compatibility between 1.x releases, a UNIX feel, low-barrier entry for other
contributors to Go projects, and a thriving ecosystem for a relatively young
language. (Of course, the JVM and Java do as well on these points, except for
UNIX-feel.)

~~~
fauigerzigerk
Where goroutines have helped me a lot is with networking code. The part of the
system I'm writing in Go controls several instances of three different kinds
of networked services, plus one command line tool.

There are hundereds of tasks that request access to these services. The job of
my Go code is to schedule, rate limit and coordinate access by these tasks to
the network services.

I did parts of it in C++ initially (because other parts of the system are
written in C++) using queues and timers (could have done the same in Java) and
then I rewrote it in node.js but wasn't happy with the asychronous design or
with my productivity.

So I think the reason why goroutines and channels have helped me so much is
specific to this kind of project. Go's other language features (or lack
thereof) wouldn't have enticed me into taking a closer look. But now that I
have taken a closer look, I do like the library and tools a lot as well.

To sum it up, if a program is basically a ton of loops doing blocking reads
and writes that depend on each other, then goroutines and channels make the
code look very similar to the idea. Mental load drops. Life is easier.

------
oconnor663
> Because Go gives us interfaces and closures we can write much more elegant,
> generic APIs with a flavor similar to Ruby or Lisp and this is the direction
> the language naturally wants us to take. Personally I like to use the empty
> interface for plumbing and only pin things down to specific interfaces or
> concrete types where I need to for performance or correctness.

That's a lot like using opaque pointers in C. What is it about Go that makes
people assume it's shortcomings are beautiful designs?

I learned yesterday that `x == nil` can return false _even if x is nil_ so
long as x is an interface type. But it depends on whether x is _actually_ nil
or a nil value with a specific type.

(╯°□°）╯︵ ┻━┻

My other pet peeve is that a method with a non-pointer receiver that tries to
modify the receiver object will silently drop those modifications on the
ground, because the object is copied. Which makes some sense, except that Go
likes to convert to pointer receivers automatically, so the caller can't tell
that anything is wrong. The only difference is one character in the method
definition. Everyone I know hits this bug at some point and loses half an hour
before they learn to look for it. You could almost say "all method receivers
must be pointers" except that you need to refer to interface types without the
pointer.

(╯°□°）╯︵ ┻━┻

~~~
eternalban
Go's simplicity is merely syntactical. The semantics can be subtle and not
entirely aligned with intuition honed after x years of working with C-like
language X.

This is an observation and not a critique.

> I learned yesterday that `x == nil` can return false even if x is nil so
> long as ...

In other words:

[http://play.golang.org/p/YV5ylbQN4j](http://play.golang.org/p/YV5ylbQN4j)

Thanks for the heads-up! That was news to me.

~~~
falcolas
Actually, the issue mentioned by the OP is covered here:

[http://golang.org/doc/faq#nil_error](http://golang.org/doc/faq#nil_error)

Basically, if you pass 'nil' of one type into an argument which accepts an
interface of another type, the nil is no longer nil, but an interface wrapper
around a nil (which is not nil).

So:

    
    
        var buf *bufio.Reader // a nil value
    

if this is passed into a function with a signature of

    
    
        func foo(buf io.Reader)
    

then when in function foo:

    
    
        foo(buf) // valid, and buf is nil here
    
        //inside foo:
    
        if buf != nil { // but buf is not nil here, so this is true
            buf.Read() // Panics because nil has no function Read.

~~~
pcwalton
The post you were replying to was talking about whether the behavior is
_intuitive_ , not whether it is documented.

------
johnnydoebk
I'm not interested in C++, not using it, do not care about it. So, I am
neither visiting HN links that are about C++ nor taking part in discussions
related to C++.

And I'm really curious what drives people in this thread that are coming to
say they do not want to use Go, do not like it, and stuff like that?

~~~
lobster_johnson
Go's surge in popularity — and with it, the surge of evangelism — compels
people to take a stand, for better or worse. (If Go weren't popular, nobody
would bother.)

I suspect it's also partly due to Go's curious mix of maturity and immaturity,
in an overall promising package with lots of momentum. Here's a young language
that on the one hand is productive to work with and simple to understand, yet
so obstinately awkward and inelegant in several other respects, and
represented by a team of designers who aren't always saying what many want to
hear.

It's clear that Go was, first and foremost, designed for Google and their need
for a natively-compiled, concurrent language that steers the user to a very
specific path, largely defined by _limitations_. Its approach prevents
language-theoretic cleverness that can complicate a codebase (think Boost or
the STL), and it prevents a lot of foot-shooting and optimistic overdesign
that newbies are prone to.

But while that's wonderful for Google, that's also a frustrating to developers
who don't need this invisible hand hobbling them, and sees Go's potential
subverted by the stringent design principles. I admit I'm one of those
frustrated by Go's type system, for example, or its heavy-handed approach to
error handling.

Part of the frustration is the lack of a clear alternative: Nimrod doesn't
have the mindshare, Rust is overly complex and slow to compile, Elixir doesn't
have the performance, C++ is, well, C++. Go, for better or worse, fills a kind
of sweet spot that, unfortunately, also has a dash of sour. And that kind of
frustration easily leads to debate whenever Go comes up.

~~~
spronkey
Lack of alternative++. Python is too dynamic and slow, gradual typing will
help but it's still slooooow. For web work, PHP and Hack are popular but often
very amateur, node.js works best with microservices and can quickly become
difficult to manage. Clojure, Scala, and Java suffer from idiosyncrasies
around the JVM. Clojure's lispy syntax can be a drag, Scala gets very complex
very quickly, and Java is oh-so-verbose.

C# is about as close as it gets to the sweet spot in the middle, IMO. But .net
not being a first class citizen on all platforms kills that option. Yikes.

~~~
lobster_johnson
Agreed on all points. C# does indeed look like it hits the sweet spot, at
least in terms of feature set, but I wish it were wholly independent of .NET.
Also, certain ugly Windowsisms have snuck into the language, such as
CapitalizedMethodNames (but snakeCaseVariableNames, for some reason).

A whittled-down, cleaned up Scala without the JVM might also have hit that
sweet spot.

I'm hoping Swift will be a contender once it's open-sourced and becomes
multiplatform.

------
danieldk
_Learn about stack versus heap, and recognize that Go treats the stack
differently from other languages._

Note that the Go language spec does not even contain the terms 'stack' and
'heap. Whether something is stack or heap-allocated isn't always clear. E.g.
consider:

    
    
        foo := &Foo{} // or: new(Foo)
    

This could be stack or heap-allocated (in the most popular Go implementation)
depending on whether the compiler thinks it escapes or not.

~~~
pcwalton
> Learn about stack versus heap, and recognize that Go treats the stack
> differently from other languages.

Yeah, this comment strikes me as deeply confused. Go does not give you precise
control over what goes on the stack versus the heap: it's even in the FAQ.

There's a common misconception that stack vs. heap and value types vs.
reference types represent the same distinction; they do not. For example, note
that it is possible in unmanaged languages to have dangling references into
the stack.

~~~
masklinn
Unless I'm mistaken it is technically true though, AFAIK other languages fall
in two broad groups

* heap-based languages (vast majority of the high-level ones), everything is treated as heap-allocated, may be stack-allocated as an optimisation (while ensuring/conserving heap semantics)[0]

* stack/heap languages, strict semantic separation between heap allocation and stack allocation, the developer decides whether values get heap-allocated or stack-allocated

Go has the semantics separation of the latter (whether something is heap-
allocated or stack-allocated had strong semantic significance and is not
dependent on the thing's type) but the control of the former (the runtime
decides and doesn't tell you)

[0] a subset of these languages may have "value types" which have stack
allocation semantics, that's linked to the type itself and the semantics are
stable

~~~
pcwalton
To use your set of categories, Go is a fully heap-based language. Everything
is semantically on the heap. Stack allocation semantics have to do with when
the object is _destroyed_ , which Go does not have any way to control in the
language. Value vs. reference types are totally orthogonal. To see how this is
true, try creating a value type (such as a struct or an array) in Go and
capture a reference to it in an (escaping) closure to defeat the escape
analysis optimization. The object will definitely be allocated on the heap.

~~~
masklinn
The issue I was referring to is is if you don't

> capture a reference to it in an (escaping) closure to defeat the escape
> analysis optimization.

the object absolutely doesn't behave as a heap-allocated value. So everything
is _not_ semantically on the heap.

~~~
pcwalton
Three points:

1\. There are many other things that can cause escape analysis to fail. You
can also capture references to a value in an opaque function, send it over a
channel, etc. etc.

2\. There is no _semantic_ difference between stack and heap (modulo when
finalizers run). The only thing the language says is that object is guaranteed
to be destroyed when it is no longer referenced. The compiler can fulfill this
obligation sometimes through stack obligations, but that's done through a set
of heuristics. The _language_ says nothing about this (and the FAQ states this
quite explicitly).

3\. Java does the same thing. The HotSpot JVM will use escape analysis to
perform SROA (which is an important optimization). Note that escape analysis
is _independent_ of value vs. reference types; Java has much fewer value
types, but it can _still_ do escape analysis. This is because value/reference
and stack/heap are orthogonal.

------
drakenot
I was hoping this article would have more substantive advice for new Go
developers. It seemed to mostly have very general advice that applies to most
other languages like: "Don't try and write language X in language Y. Keep
complexity down by not over using complex language features."

I'm writing my current hobby project, a podcast fetcher, as my first project
in Go.

The project has been going generally well but there have been a few annoyances
so far:

* Why are you not able to easily version git dependencies? Go's solution to this problem is to tell you to create an entirely new git repository for each major version. Really? If they didn't want to go full blown dependency versioning with something like CocoaPods, they could at least let you specify a git branch or tag.

* The db.Sql abstraction does not support multiple result sets. Therefore database drivers, like the popular mysql driver, don't support multiple result sets. This really limits the kinds of stored procedures you can call.

* The debugger support is bad. I have to fall back to using print statements for most of my debugging.

~~~
robryk
> Why are you not able to easily version git dependencies? Go's solution to
> this problem is to tell you to create an entirely new git repository for
> each major version. Really? If they didn't want to go full blown dependency
> versioning with something like CocoaPods, they could at least let you
> specify a git branch or tag.

Take a look at gopkg.in[1]. This is a service that makes branches in other
repositories go-gettable. Eg. go getting gopkg.in/foo/bar/v5 will download
branch v5 from github.com/foo/bar. This seems to me to be a way of dealing
reasonaly well with the issue -- the go get tool itself doesn't need to
understand anything at all here.

[1] [http://labix.org/gopkg.in](http://labix.org/gopkg.in)

~~~
gcv
A nasty workaround for a problem which should not exist in the first place.
Ditto dependencies on private repositories.

~~~
IvarTJ
I personally don't see having the version in the package address particularly
more nasty than specifying it in the project file, as in Maven, which I have
been dealing with lately. One problem I imagine is that when migrating to a
new version of a library, the address has to be updated in each file the
library is used in, with potentially catastrophic consequences if any one file
is forgotten. A grep seems wise in that case.

Would you argue that there are more robust solutions in the Haskell ecosystem?
What scheme do you prefer?

~~~
drakenot
I don't think the issue was with specifying the version in the package
address. It is that you need to use a 3rd party, remote service, to redirect
your requests to the appropriate git repo and branch.

This is because Go doesn't support specifying branches/tags itself when
specifying a git dependency.

This workaround is contingent on gopkg.in people keeping their service
maintained and running. If they went down, all your dependencies would break.

------
stonewhite
I was expecting an empty page with a huge "don't" in it. But instead there was
interview excerpts from Go developers and a ambiguous list of "best
practices".It would be more meaningful if this was backed with some actual
practices with some code examples.

I just don't see what people think others would understand by teleologic
statements like: "write Go the way it wants to be written"

~~~
rakoo
> I just don't see what people think others would understand by teleologic
> statements like: "write Go the way it wants to be written"

To me it means "Read what the creators thought about when creating Go and how
they view it, and do the same". But really that applies to any craft that
exists: instead of trying to apply and adapt what you already know, clean your
mind and start from scratch.

~~~
lhc-
Wait, the advice is "ignore your past knowledge and experience and start from
nothing" ? That strikes me as terrible advice.

~~~
rakoo
More like "Don't try to _shoehorn_ your existing knowledge into that thing you
are trying to learn and use; it may or may not fit but isn't supposed to".
That absolutely doesn't prevent you to _use_ your knowledge as stepping stones
to understand the new thing.

------
kasey_junk
> I resisted the recommended workspace configuration, as described in How to
> Write Go Code. Don’t bother, especially in the beginning

I've had the same dev folder structure across platforms, languages, jobs and
decades. I basically had to abandon that structure when starting Go. I fought
& fought and at the end of the day it is just easier to use their expected
workflow. It was (is?) galling but it was the only way to stop fighting the
tools and get work done.

~~~
XorNot
The intellij go plugin is your friend. Per project go-paths. It's also a good
use of make.

------
jerrac
So, if Go is as bad as the comments so far make it out to be, what are some
alternative languages? Specifically, a compiled language that can be deployed
without worrying about dependencies. That's the feature that has had me
looking into learning Go. I want to be able to just copy one file to my server
and run it, no need to install anything extra on the server to make my program
work.

Actually, can Go even do that? I think it can...

~~~
jerf
"So, if Go is as bad as the comments so far make it out to be, what are some
alternative languages?"

While a good question anyhow, it's not as bad as the comments make it out to
be. Anything can be made to look bad by only looking at the negatives,
anything can be made to look good by only looking at the positives. And the
balance shifts depending on what sort of application you're writing. There's a
reason that Go has seen a lot of success writing network servers. There's a
reason why Go has no penetration into the scientific computing community, and
I tend to warn away anybody even thinking about it.

In particular, a lot of the people screaming about Go do not consider some of
the positives. For instance, thanks to the implicit satisfaction of
interfaces, I find it one of the easiest mainstream imperative languages to
still create a separation between IO and pure code, by wrapping all IO behind
an interfaced object, one that I may not even have to create (i.e., Files
already implement io.Reader and io.Writer, io.Reader & io.Writer also already
have several test implementations available in the stdlib and it's easy to
adapt them to a few more), which then allows me to write some _really_ good
testing code, almost as good as Haskell. (In fact, swapping in alternate
implementations is probably easier than in Haskell.) This _can_ be done in
other languages, but it often involves jumping through hoops to create your
own objects that mirror other object's methods so they can implement an
interface or something; in Go it's trivially easy. Between that and the way
errors are handled, I find it relatively easy to write very bullet-proof code.

And while I also consider it annoying that Go lacks half of generics
(interfaces are actually half of generics, but it's missing generic types),
there are also often ways of spelling your APIs so it matters less. My code
actually doesn't end up with very many interface{}s in it, and many of the
ones that do are "real", in that the code in question really _doesn 't_ care
what's there.

If you don't put those positives onto the balance, you don't get a proper
view.

It doesn't help that, frankly, this was not a very good article and I strongly
agree it has tone issues. This brought out a lot of people who might otherwise
have just clicked over without commenting.

~~~
pcwalton
> For instance, thanks to the implicit satisfaction of interfaces, I find it
> one of the easiest mainstream imperative languages to still create a
> separation between IO and pure code, by wrapping all IO behind an interfaced
> object, one that I may not even have to create (i.e., Files already
> implement io.Reader and io.Writer, io.Reader & io.Writer also already have
> several test implementations available in the stdlib and it's easy to adapt
> them to a few more), which then allows me to write some really good testing
> code, almost as good as Haskell. (In fact, swapping in alternate
> implementations is probably easier than in Haskell.)

I'm confused. How does not having to write "implements Reader, Writer" (25
characters to type) have anything to do with purity and IO effects?

> interfaces are actually half of generics

Interfaces aren't generics at all. Java had interfaces and Object as a
supertype before it had generics, and it still had zero support for generics.

I do agree with you that Go has maybe 50% of the use cases for generics
covered, but not because it has interfaces. Those don't count. It's because it
has maps and growable arrays built in.

~~~
jerf
"Interfaces aren't generics at all."

It's a definition game. One component of "generic" is "generic algorithm". Go
has that covered; the Sort package provides a "generic" algorithm via an
interface specification. A lot of C++ template code is built around
implementing generic algorithms at compile time rather than Go's run time, for
instance.

If you choose to call that "not generics", that's a valid choice of
definition, and certainly makes sense in a Rust context, but it's not
universally valid.

For context, I'm not trying to bend a definition to "defend Go"... I generally
have a low opinion of software engineer's ability to create universal
definitions of terms that apply across all languages, and I _observe_ that
pretty much any term you can imagine varies across language communities. I'm
not the one creating terminology vagueness. And I have observed that every
time someone contradicts me and insists some term really is rigorously defined
and agreed to by all major language communities, you can _count_ on two or
three disagreements from not-me in the replies...

------
jheriko
its not all go specific, despite the comment about 'C style' heap allocation
and pointer usage, you will find your C code will get better if you do not do
this as well. the heap is a last resort, not the first.

~~~
scott_s
I found that comment strange as well - I consider "put everything on the heap"
to be more of the default attitude in dynamic languages (Python, Ruby) and in
the statically typed managed languages (Java, C#). In C and C++, I default to
the stack, and I don't think I'm alone.

------
forgotAgain
I take it these are the idiomatic best practices.

