
Go’s Features of Last Resort - kristianp
https://www.arp242.net/go-last-resort.html
======
leetrout
As a very opinionated user of Go for ~6 years now...

interface{} is almost always a code smell. I can judge a book by its cover
with a grep and line count for interface{} and reflect. If it's more than
20~30 for just about any small-medium size code base I dig deeper and look at
why. Usually it is people being lazy and refusing to accept what is my twist
on "idiomatic Go": a little bit of copying is better than a whole lot of
abstraction[0]. Also devs telling me interface{} and type switches make it
polymorphic are the ones who will always complain yet not leave the Go
ecosystem. Hopefully generics in 2 solve this for everyone.

panic is great when you really want to crash. There are times to do this. I
have only used it once in a batch job runner.

init() is a sin ESPECIALLY when you combine init() with the `.` (dot) imports.
Pulling packages around for their magic side effects suck. Also the lack of
some library maintainers to acknowledge init() can be poisonous... [1]

[0] [https://go-proverbs.github.io/](https://go-proverbs.github.io/)

[1] [https://github.com/lib/pq/pull/455](https://github.com/lib/pq/pull/455)

~~~
ben_jones
Two valid use cases I've found for interface{}:

Handling large arbitrary JSON objects where I need to operate on their keys
but don't need to work with their values by unmarshaling to
map[string]interface{}. The alternative would likely be code generation of
struct definitions with JSON bindings, which is likely preferable if you
already have code generation in your project, but can be an overhead in
smaller ones.

It can also be used to create your own syntactic wrappers around API's which
consume interface{} such as encoding/json and database/sql:

    
    
      Unmarshal(r *http.Response, dst interface{}) ([]byte, error)
    
    

and

    
    
      BulkInsert(tableName string, columns []string, values [][]interface{}) (time.Duration, error)

~~~
RSZC
I use it for shutdown channels. It doesn't matter what you're putting in the
channel if you're never putting anything in the channel

~~~
zeeboo
Using chan struct{} is more efficient, and guarantees there is nothing being
put into the channel. That's why you see it used on the Context type's Done
method, for example.

[https://golang.org/pkg/context/#Context](https://golang.org/pkg/context/#Context)

------
papito
It's very interesting to me how bad design decisions in Go are accepted by the
users with little protest. There are other things - the Go path, error
handling, surprising conventions in a language with no "magic", etc.

Yes, it was created by behemoths of computer science (but more accurately in
this case, dinosaurs), and they clearly have not been writing and debugging
large production systems for decades now. That's why the whole thing is
clearly missing the lessons learned from modern language design.

~~~
SEJeff
Well they did port dl.google.com from a very large C++ codebase to go. Do you
have any apps you've worked on that are higher traffice than dl.google.com?

~~~
azth
That's not an argument. There are many websites that are higher traffic, yet
written in slow languages like Ruby and Python.

~~~
SEJeff
Sure, instagram is a giant django app running ontop of many postgres
databases. But I was pointing out that Go can be used for very large codebases
running high traffic sites.

~~~
papito
It compiles to bytecode, of course it is fast - we are specifically discussing
_language_ features and developer experience.

------
grenoire
Struct tags in Go always seemed like a very bad and _hacky_ way to add
metadata to fields. I really hope that's something they'll look into in Go 2.

~~~
seiferteric
Currently I am facing an issue where I am unmarshalling json into a protobuf
struct. Unfortunately the json tag names are a bit different than the protobuf
struct field names. I would love to be able to use struct tags here, but to my
knowledge protobuf definitions don't allow this, and I don't think I can put
them in after the fact. So I am stuck loading the data into an interface{} and
manually pulling out the fields.

~~~
tynorf
One technique I use is to create a struct that mirrors the layout of the
target struct, then just cast between them. So if you have a

    
    
      type Person struct {
        FirstName string `json:"first_name"`
      }
    

And you get json like `{"name": "Alice"}`, you can decode it something like
this:

    
    
      var doc struct {
        FirstName string `json:"name"`
      }
      json.Unmarshal(data, &doc)
      person := Person(doc)
    

I have not looked at the assembly so I can't say whether this incurs extra
overhead or not.

~~~
seiferteric
I thought of this, but I would have to do this for every single protobuf
message. Also, anytime the protobuf changes, you would have to update the
code.

~~~
tynorf
Your protobuf messages are changing that much? My condolences.

ETA: Also, you already are needing to add code to every encode/decode point
for the `map[string]interface{}` handling. :P

------
gautamcgoel
I think the fallthrough keyword also belongs in this category. It's a
surprisingly ugly feature of the Go language, and I recall an interview where
two of the three language designers said its their least favorite part of the
language.

------
ArtWomb
>>> pass a map[string]string to json.Marshal()

This is my goto speed hack when prototyping. It always works. You don't have
to worry about deep nesting. And it frees you to design the data model as you
prototype. It's also less punishing than reflecting on every node in a list.

And yes, golang / json (and go / node) interactions need to evolve better
developer patterns and runtime performance ;)

------
frou_dh
Say you put a single type Foo in its own package to ensure that nothing ever —
intentionally or unintentionally — touches its unexported (private) fields.

If "Dot Imports" are frowned upon, it means that your other code using that
type is always going to have to refer to it as the stuttering "foo.Foo" rather
than "Foo". That's not great.

~~~
LukeShu
Do that, then do a type alias in the package you would "like" for it to be
imported from:

    
    
        mypkg/internal/foo/foo.go:
            type Foo struct { … }
    
        mypkg/mypkg.go:
            import "mypkg/internal/foo"
            
            type Foo = foo.Foo
    

Then, it can be imported from "mypkg` and used as `mypkg.Foo`.

~~~
frou_dh
Neat, you win at puzzle solving! I forget that the user-level type alias
feature exists... maybe since it was introduced relatively late in the Go 1.x
timeline.

------
luord
____

Exceptions are generally considered to not be worth the cost

____

Why is exception handling being added in 2 then? Sure, they're calling it
check/handle instead of try/except(catch) because Go ought to be hip and
different but if it moves like a duck and quacks like a duck...

~~~
Carpetsmoker
check() and handle() have rather different semantics; it's still just error
returns, just with a bit of syntax sugar.

It's also just a proposal which received a lot of feedback/criticism, and
eventually they decided to go with another try() proposal which ended up being
abandoned after criticism. I'm not quite sure what the current status is here.

So nothing is "being added" in Go 2 thus far.

------
varshithr
I am not very familiar with Go but panic() and recover() seem like neat
features.

~~~
paol
It's just exception throw/catch. The only difference to other languages is
that you're not supposed to use it, as a matter of principle.

~~~
aikah
You're not supposed to use struct tags, type aliases, "interface {}" or
reflection either... that's a lot of features Go developers "aren't supposed
to use"... Why are they here then?

~~~
sturgill
Frequent use of these patterns is a good smell test that one should reconsider
design choices. Are there times when usage is justified? Yes. Should it be the
first / default mechanism? No. Learn the rules first, and then it will be more
clear when you should break them.

I have no problem with the general rule nor a built-in mechanism for breaking
them.

Simple example: if I ever have a row lock I wrap the corresponding block in
recover to ensure that I never have a deadlock. But I certainly don’t wrap
every function in recover...

------
hacknat
I would add the sync package to this list. Maybe not the same extreme, but if
you force yourself to use channels you’ll end up writing more maintainable
code.

~~~
SamWhited
I would have said the opposite. Channels are great on occasion, but half the
time I see them used in places where a simple mutex would be much cleaner and
easier to read or where a mutex could be hidden from the user entirely but a
channel is used instead and has to be exposed to the user. They each have
their place. There are specific things in the sync package I see abused
frequently, but I wouldn't call use of the sync package itself code smell.

------
fortytw2
empty interface{} and struct tags really shine when used as a library author,
or authoring a bit of extremely reusable code that’s pervasive throughout a
codebase.

Think things like json encoding (entirely réflection based), or writing a set
of functions that can operate on something more generic. It’s not scary and
it’s not really that slow, you just need to be very particular and choosy in
your application of it.

------
NicoJuicy
I'm always wondering how .net developers feel towards Go. Anyone can relate?

~~~
pjmlp
It is a good replacement for C like programs, that is all.

~~~
SamWhited
And also Python like programs. And probably Node.js like programs. And really
anything where you have lots of I/O to do.

~~~
pjmlp
I would rather use PyPy in Python's case.

~~~
SamWhited
I would not because I still have to deal with the language and tooling, even
if the implementation is much faster.

~~~
pjmlp
Dealing with the language is exactly why I would rather advise to use PyPy,
instead of completely change ecosystem.

Go would not even be on my list for Python developers looking for performance.

There are much better AOT compiled languages with a feature set similar to
Python.

~~~
SamWhited
Such as? Go is what Python should have been in my mind, although I would love
a more expressive type system. But overall it generally replaces Python for
me.

