
Some Rookie Mistakes in Go - rayascott
http://engineroom.teamwork.com/go-learn/
======
csense
> Go has multiple returns from functions so a very typical scenario is a
> function that returns something and also an error, which would be nil if
> everything worked okay

This is one of the problems I have with C. You basically have to remember to
manually check for an error on every function call, which means (1) an
algorithm with short and simple pseudocode becomes lengthy and hard to read
real code, because you need to add a ton of error handling, (2) it is easy to
forget to handle an error in some place. Exception-based error handling lets
you delegate errors to a top-level handler without cluttering up intermediate
code (i.e. an exception anywhere in webpage rendering logic should be handled
by 500'ing the entire page, which exception-based error handling lets you do
without cluttering intermediate functions between the function producing the
error and the function handling the error with error-handling logic).

I've been thinking about learning Go, but if its only error handling
mechanisms are "pack it into a return value" or "panic() which is like exit(1)
in C" then it's a black mark against the language. It's still a black mark if
the language has exception-like error handling but it's not considered
"idiomatic Go" and the stdlib and a good chunk of third-party libs report
errors in return values.

~~~
jjnoakes
On the other hand, exceptions are implicit control flow, which means you have
to assume every single expression may throw an exception. So if you don't take
extreme care to write strong exception safe code, subtle bugs will exist and
are extremely hard to find.

Explicit control flow is more verbose, but it is also easy to spot when the
control flow passes over something important by mistake. And as pointed out
elsewhere, forgetting to check a return value is a solved problem via static
analysis.

~~~
aikah
> On the other hand, exceptions are implicit control flow

Go doesn't force you to deal with errors either. I see plenty of code ignoring
them with

    
    
       retval , _ := MightYieldAnError();
    

And some languages that have exceptions actually force the client code to
handle them, like Java AFAIK .

Finally, Go "explicit control flow" didn't have to be verbose like it is,
that's a design choice its authors made.

Go is primitive in the sense that it uses C style code patterns , like the
famous :

    
    
          retval = MightYieldAnError( &outError);
    

Edit: I forgot to mention the panic/recover which ultimately means Go
designers implemented some form of exceptions. That basically voids the
usefulness of Go error system, and an admission that it's broken.

~~~
jjnoakes
> Go doesn't force you to deal with errors either.

> And some languages that have exceptions actually force the client code to
> handle them, like Java AFAIK.

But this misses the point. Any error which you have to explicitly ignore is
fine, because then you were aware of it at some point and explicitly ignored
it, and anyone reading the code can plainly see you explicitly ignoring them.

And errors which can only happen at specific points (like function return
values) are also ok, because then you only have to consider error handling and
cleanup at those points.

But if you have neither of the above - if you have the situation where errors
can spontaneously result from any evaluation of any expression with no
explicit notation required - then you have a poor situation indeed.

------
logingone
Why does Rust get articles about eg an operating system written in Rust, but
all the Go posts are how to tie your shoe laces? Somebody explain, please.

~~~
wolf550e
Go is for writing api servers which otherwise should be written in python but
must be fast.

Rust is for the lowest level code which is not assembly, with all the
performance tricks, but that should be secure.

Rust is expensive to develop - if you can afford to use a GC, you should use a
GC.

So people want to rewrite all the world's C and C++ to rust, for security. But
people are less passionate about rewriting all the world's python into go for
performance.

~~~
TheHydroImpulse
> Rust is expensive to develop - if you can afford to use a GC, you should use
> a GC

This has not been the case for many people using Rust, including myself. Once
you learn the language, there is no "cost" to use it. In fact, the compiler
simplifies much of the mental overhead when dealing with concurrency, sharing
data, mutability, etc... Sure the compiler has some strict rules, but it's not
expensive to develop in by any means.

I would also say Rust is in a sense higher-level than Go in terms of
expressiveness and abstractions.

~~~
Retra
Rust can be hard to refactor. Not sure if that's a problem with Go, but it's a
real cost.

~~~
TheHydroImpulse
How so? From my experience, I was able to make massive changes and the
compiler would guide me through to make sure I didn't miss anything. If it
compiles, it'll generally work minus any logical errors/changes.

~~~
kibwen
There are definitely refactoring woes to be had when taking an existing struct
and changing it such that it now stores a reference in one of its fields,
requiring lifetime parameters to trickle down through your program. The
compiler guides you in getting it all right, but it's something that I'd
rather see an IDE do automatically in the future.

------
kough
Found myself agreeing with all of this. I very much second the decision to not
use a framework and instead use the core to libraries and a standalone
multiplexer. Another huge benefit of this is that you can write some glue code
and test your end points without using the http mocking interfaces, which are
absolutely terrible for testing.

------
daveguy
FYI: golang not the game of Go. Recent news has skewed my expectation of go in
article titles.

~~~
qz_
Yep, good news I like both ;)

------
otraore
I personally use gin [https://github.com/gin-
gonic/gin](https://github.com/gin-gonic/gin) and have never looked back

~~~
marcc
+1 here. Gin is a pretty reasonable web framework that sticks to the core go
patterns and practices, but provides some good built in helpers. It includes
http router (a high performance route handlers and dispatcher), and the
implementation of middlewares is nice.

We've tried with Revel, and I completely agree with the post here. We could
use net/http directly, but gin is working super well for us right now.

------
fpoling
It is spot on observation about rather bad rules in Go about := and scopes
that hit me as well. For example, the following compiles without warnings and
even runs the first invocation of test successfully,
[https://play.golang.org/p/tYQSNjudGT](https://play.golang.org/p/tYQSNjudGT) :

package main

func test(n int) { for { n := n - 1; if n == 0 { break } } }

func main() { test(1); test(2) }

~~~
Veedrac
That seems somewhat natural to me, to be honest. The same could happen in
Rust, or most languages with shadowing.

IMO, allowing it is the lesser evil.

~~~
fpoling
The issue here is not shadowing in general but shadowing withing a single
statement like in n := n + 2. A sane rule would not allow to refer to the
declared name in the right-hand side expression.

------
shwouchk
Most of these mistakes are mistakes for any language you write in.

~~~
reitanqild
"Don't use a web framework" definitely isn't something I would tell
developers.

I really think good web frameworks is one of the reasons why the web has
progressed so quickly the last ten years.

~~~
jerf
In Go, "don't use a web framework" translates to something like "net/http is
already a 'web framework' much like web.py or other 'minimalistic'
frameworks". Unlike, for instance, Python, where you "have to" use a web
framework because the standard library doesn't ship with much (http.server is
too basic on its own [1]), the base net/http is enough for many non-trivial
usages. (Of course Python has a few dozen good 3rd-party choices.)

[1]:
[https://docs.python.org/3/library/http.server.html](https://docs.python.org/3/library/http.server.html)

------
azinman2
I'm not sure that using a custom panic filter is idiomatic...

~~~
HeyImAlex
It's used in the stdlib I think, you just need to recover the panics at your
api boundaries.

~~~
sagichmal
Only for very specific use cases, like the recursive descent parser in
encoding/json. It's not appropriate as a general pattern.

~~~
azinman2
That was my understanding. You're just suppose to handle/return errors
normally right?

~~~
sagichmal
Yup!

------
knughit
Golang, not Baduk :-(

~~~
Natsu
I know. I was waiting for some life or death problems...

------
pm90
I was intrigued about the part about not being able to ready the response body
more than once. Can anyone more experienced explain why this decision was made
in the core libraries? And if there is a good reason to not read the body more
than once, is the author making a mistake by trying to get around it?

~~~
jerf
Go's core library specifies several interfaces for reading, writing, and
closing streams. Here's Reader, for instance:
[https://golang.org/pkg/io/#Reader](https://golang.org/pkg/io/#Reader) Those
interfaces do not promise seeking, so they apply to as many things as
possible. (See
[https://golang.org/pkg/io/#Seeker](https://golang.org/pkg/io/#Seeker) .)

The HTTP body is provided as an "io.Reader" which actually backs to the
network stream, and since TCP streams can't be seeked, it's an error to think
you can. This means that native Go HTTP handlers can handle a multi-gigabyte
upload or download without consuming all that RAM at once (assuming there is
some way to stream), but you lose seeking. You can easily recover read-only
access by using io.ReadFull to fully consume a reader into a byte buffer, but
then you pay the price of consuming all the RAM, of course.
([https://golang.org/pkg/io/#ReadFull](https://golang.org/pkg/io/#ReadFull) )
Whether or not it's a mistake depends on your situation. Fortunately, in the
HTTP context, you have a Content-Length that you can examine to make decisions
if you need to; the generalized io.Reader does not have that, either.

This all comes from the nature of HTTP itself. I tend to consider it a basic
requirement for any web development environment that there be a way to
correctly deal with an HTTP upload as a stream, and that the environment not
"helpfully" unconditionally load the entire stream into memory for me.

------
znemz
Can someone follow up with more details on vendor for those not using go
currently? What is the best practice for locking versions of libraries down. I
am comparing this to gemfile.lock and shrinkwrap.json . Ty

~~~
pm90
There are multiple ways to do this. Personally, I've used and liked govendor
[https://github.com/kardianos/govendor](https://github.com/kardianos/govendor).

As of go 1.5, its supported by the language itself:
[https://github.com/golang/go/wiki/PackageManagementTools](https://github.com/golang/go/wiki/PackageManagementTools)

------
saturn_vk
actually, for error handling, panic is a lot more like exceptions than exit.
much like java, panic acts like throw, and defer/recovery like try/catch. and
you can use it like that in the depths of your library, recovering from panics
in your exported functions.

------
pka
I am constantly amazed at how people will find a way to fly to Mars in a
bucket just so they don't have to learn how to operate a rocket :)

~~~
jshen
I'm constantly amazed at how people find a justification to fly a rocket just
to get to the McDonalds a block from their house.

~~~
hguant
Obviously you need to spend more time playing Kerbal Space Program. Rockets
are their own justification.

On a more serious note, I think that's just human nature - if you're invested
in something - a technology, a stack, a business model, a car - you value that
investment more than its worth, because it is yours. I think there's also an
element of "I put in the effort to learn this, so damnit I'm going to use it".

Or just people like challenges.

------
matiasb
Loved the article!

