
Practical Go: Real-world advice for writing maintainable Go programs - ra7
https://dave.cheney.net/practical-go/presentations/qcon-china.html
======
briatx
> Naming the _Config parameter config is redundant. We know its a_ Config, it
> says so right there.

> In this case consider conf or maybe c will do if the lifetime of the
> variable is short enough.

This seems petty. Is it really that problematic to type out a few extra
characters?

~~~
pcwalton
Yeah, I used to go by this advice, and I found the maintainability of my code
dramatically increased when I typed out full names. I don't even use "i" for
loop variables anymore. If the length is a problem, invest in an editor with
autocomplete.

Well-known abbreviations are fine, like "iter" and "prev", but single-letter
variable names notoriously impede readability for me.

~~~
dilap
I feel like I have the opposite problem.

If the name is more than a few characters long, it starts to become non-
instantaneous to recognize it. Things get much easier to follow with visually-
instantly-recognizable symbols.

So in conditions where a variable is used over a short area in the code (or
where it's used _constantly_ over a wide area), I prefer short variables.

~~~
hackinthebochs
Totally agree. If you have to scroll up to be reminded what some variable
means, a longer name is good. But if its usage is a few lines away from its
declaration then there's no reason to add visual noise to your code.

~~~
shaan7
Its easier said than done though. We started following this few years back but
then while it was "few lines away" originally, code evolved and now there are
parts where its 20+ lines away. This means that short var names need to be
continuously "enlarged". Well, then why not start with a "medium" name and
avoid all that headache? ctx is a good enough compromise between c and
context. (c can be client, config, context, certificate ... you get the
point).

~~~
yawboakye
Rarely do you have a case where a variable outgrows its simple name (and
value) and you keep it the same. Even when a function adopts new
characteristics it's advisable to change it's name or make a new one. If you
initially had variable `i`, created and destroyed in 5 lines of code but that
has grown to 20+ lines, I'd recommend you (1) not add the 15+ if it could be
put in a function (you get to name that section of the code), or (2) rename
the variable. But a 5-line code that becomes 20+ lines is both strange and
interesting.

------
letientai299
Came from Java world, I find the Go comment and godoc are really limited. We
can't link between functions, types. We don't have a standard way to declare
input, output, don't have any distinction between a normal word and a Go
identifier Refactoring using tool (e.g: Goland) usually lead to unexpected
text replacements.

Take following functions, for example:

    
    
      // Auth check if user credential is existed in bla, bla...
      // ... (many more explanation)
      Auth(user User, authProvider AuthProvider) bool
    
    
      // AuthDefault check user against the default **AuthProvider**
      AuthDefault(user User) bool {
        return Auth(user, new(DefaultAuthProvider))
      }
    
    

If only the _AuthProvider_ in the second function's godoc be a link to the
first one, we don't have to repeat the explanation for the second one. Dev
will be able to discover the explanation easily via their IDE. This alone will
be very helpful for the maintainability of any big projects.

~~~
TheJazi13
I’d argue there’s a failure of the code to some extent to need such
extsenibility with documentation. I’ve often found when I need more info
outside of Godocs that simply clicking through and diving into the source
provided me with all the answers I needed to know.

~~~
mrbrowning
This attitude, which I'll paraphrase as "needing any feature that I personally
consider unnecessary is a code smell," is endemic to the Go community and a
big reason why I still find myself frustrated by it on a daily basis even
after a month of using it for a greenfield project. I don't like the language
itself, since I'm generally in favor of a language offering more affordances
rather than fewer, but it's fine to write in and I'm ultimately concerned with
making the pragmatic choice. When I go to look for what the idiomatic way to
do something is, though, wow! It's rarely just "this is the way to do it,
since the language optimized for a particular use case by making the tradeoff
of omitting the usual features for this": instead, it frequently goes on to
assert that those features are unnecessary in _nearly every_ case and
languages that provide them are wrong, or old-fashioned, or too Academic, or
the people that use them don't care about Getting Things Done. Also, throw in
a few cargo-culted potshots at Java for good measure! I've used a good number
of languages professionally at this point, and the Go orthodoxy is easily the
most off-putting I've ever encountered. I don't know why it's become so
aggressively totalizing, but it's not a good thing.

~~~
xyzzy_plugh
I couldn't disagree more. I think it comes down to personal preferences,
mostly.

I get so much more done in Go, have fewer maintenance issues, and more
frequently collaborate with other folks/contribute to other projects.

> I've used a good number of languages professionally at this point, and the
> Go orthodoxy is easily the most off-putting I've ever encountered.

I could -- and do -- say the same about the Java ecosystem.

~~~
mrbrowning
For what it's worth, I don't particularly have a cross to bear with respect to
Java. I cited it just because its invocations as a Bad Language bogeyman are
common in the Go-focused writings I've found, and those are far out of
proportion to its inherent flaws (of which I believe there are many!). I
completely agree that it's a matter of preference, which I think is supported
by the fact that I found Go to be the best choice of language in the context
that I'm operating in whatever my reservations.

I'm confused about your assertion that the Java ecosystem has a similar
problem, though, because my sense is both that it has a much less identifiable
orthodoxy by virtue of being so widely used across so many domains, and that
the new guard, such as it is, is very much in favor of creating libraries and
utilities that favor simpler implementations and interfaces contra its
"architecture astronaut" baggage.

~~~
xyzzy_plugh
You make a fair point; I'm not adequately exposed to the Java ecosystem, I
mostly end up reading Apache projects.

I guess my remarks could be rearranged as: In general, I find Java, and the
JVM friend Scala, to worship Abstraction over simplicity. Complexity is
constantly confused for convenience, and that makes me sad. The number of
files I need to read to understand _any_ piece of Go, I could likely count on
one hand, if even. For the JVM-based approach: dozens, if not more.

Abstractions _in theory_ are great: they reduce the cognitive burden, they
simplify behavior, make it easier to rationalize and cast judgement; But in
practice, that just isn't true. You _always_ need to peel away the
abstraction.

In Go, countless times, I've found myself reading the standard library
implementation. Is this good? No, of course not. Ideally, as a consumer, I
never need to look under the curtain. Things should just work. But that tends
to never be true, and looking under the curtain is an important aspect of
computing (c. 1970-1999) that permeates everything we do.

Go makes it really easy to look under the hood, see what's happening, and
more-or-less instantly achieve clarity. The only other languages I've ever
used that came close were C and C++, and history has demonstrated how well
that's worked out.

~~~
geodel
To add to your point last week I cleaned up a part of project in Java
sprawling in 30 or so files with 5 KLOC to 2 Java files and under 1 KLOC of
Java code. Now main clean up was using library properly which was already
present as dependency for years and removing innumerable level of
indirections.

This code was written in really roundabout way in Java tradition where e.g.
setName() would call getName() -> initName() -> initClassName() ->
getDefaultClassName() -> initDefaultClassName() just to set one damn string
value. And believe me this getName() function would get called grand total of
one time in whole project.

And it was possible for me to do so because learning and writing Go made me
confident that straightforward code is not a sign of junior/inexperienced
developer. Because in Java it will be exactly that if you do not bury the
actual logic in ten levels of abstract crap.

------
OutsmartDan
Would love to see better separation on the page between "Bad" and "Good"
examples. While a good read, it's difficult to quickly distinguish between the
two.

For example:
[https://github.com/airbnb/javascript](https://github.com/airbnb/javascript)

~~~
andriosr
true. Google will index it and I can see many people copy and paste bad code
considering the best practice for doing something

------
doodpants
I'm not a Go user myself, but I skimmed through the document. What I found was
a lot of good programming advice that is generally applicable, rather than
being limited to the Go language. (It seems most of the Go-specific stuff is
in section 5.) It's always good to see robust coding practices being promoted,
regardless of language.

------
diegs
About half-way through and I think this is a great article, in particular the
quotes and I also agree that the first 4 sections are generally applicable.

One thing I disagree with is the remark about having fewer, big packages.
Though conceptually I agree that avoiding having too many public APIs that
aren't widely used makes sense, _in practice_ \--at least on the types of
projects I tend to work on--I find that directing people to split things into
a few packages forces them to think about a decoupled design with good APIs
between the components. This could certainly be done with discipline inside a
single package, but unless everyone working on the codebase is very diligent
about this it's easy for abstraction leaks to creep in.

Ultimately it's a judgment call, but I think an earlier paragraph (copied
below) is far more important than optimizing on having fewer packages or fewer
exported types and functions, especially (as is also pointed out in the doc)
you can use `internal` subdirectories to make APIs project-private if you are
writing a library that is consumed by other projects, as opposed a service.

> A good Go package should strive to have a low degree of source level
> coupling such that, as the project grows, changes to one package do not
> cascade across the code-base. These stop-the-world refactorings place a hard
> limit on the rate of change in a code base and thus the productivity of the
> members working in that code-base.

~~~
jerf
I agree with you. I like the way packages are isolated from each other,
meaning that to understand a package it is usually by definition a good start
to simply read what is there. Smaller packages mean more bite-sized chunks of
the program. And I think the discipline of slicing up your program this way is
very, very good for the design, and often makes the tests easier to write to
by significantly shrinking the surface of what your tests have to "fake" in
order to test your code. I think it's just a whole heapin' helpin' o'
benefits.

However, I feel myself to be in the minority on this one. To which I basically
shrug and write my code with lots of relatively small packages. It really only
affects code you're working on, or that your team is working on. Things you
pull in as libraries and have no direct interaction with don't matter too much
on this front.

~~~
bennofs
If packages are too small it can get hard to understand the code if your not
familiar with the structure yet. Working from bottom to top level can work but
might also be different because you are missing context for the low level
packages to make sense.

In general I found larger packages tend to produce more direct / pragmatic
code with less indirection which is usually easier to understand, even though
it also feels wrong to me from a theoretical standpoint.

~~~
jerf
"If packages are too small it can get hard to understand the code if your not
familiar with the structure yet."

I solve that with describing the context of the package in the opening prose
section. I think this section is underused in every language community I've
seen, even though all the automated doc systems support a top-level
summary/contextualization/etc.

I think that an advantage of small packages is precisely that it is easier to
understand if you're not familiar with the structure yet, by isolating how
much structure you have to understand. Large packages, or languages with loose
barriers, force you to eat huge swathes of the project at once to understand
the code. Small packages are both bite-sized on their own, and also the
packages that use the small packages often allow you to gloss over the used
package while you're learning _that_ package.

I don't end up with much indirection caused by the package boundaries. (Where
there are interfaces I would usually have them anyhow for testing purposes.)

This tends for me to be one of those places where I wonder if I'm just doing
something really different than most people. Another example is all the many
people over the years who have tried to convince me, with varying level of
politeness, that testing code should only use the external interfaces, or dire
consequences like having to rewrite all the testing code if I tweak the
package will happen. All my testing code uses private interfaces unless
there's a really good reason not to, and maybe once in ten years have I had a
serious rewrite of the test code come up. The threatened problems don't seem
to happen to me. (And I am pretty sure I'd notice them if they did, although I
guess I can't completely discount the possibility that I'm just too oblivious
somehow.)

Certainly, if they cause you trouble, either because your style is different,
or your problem domain is different, or whatever reason, don't use small
packages.

------
bsaul
This has got to be one of the best and most useful post i’ve seen about go
(and code design in general) in a long time.

------
arendtio
> A good Go package should strive to have a low degree of source level
> coupling such that, as the project grows, changes to one package do not
> cascade across the code-base.

I wonder though why there is so little emphasis on how important interfaces
are in Go. I mean, section 4.5 talks about it a bit, but in my experience,
this mistake is made far too often.

~~~
entity345
This is encapsulation, really, and simply makes the point that the principle
applies to packages.

------
argd678
> 2.1. Choose identifiers for clarity, not brevity

This is a real problem in Go these days, people use one and two letter vars
quite a bit which makes reading code you’re not familiar with practically
impossible. On one project we simply switched to semi-java length like names
since our customers couldn’t read the code.

~~~
entity345
Since when are variables names a language issue?

There is no such thing as "Java length like names", or whatever. There are
good practices and bad practices and they are universal.

~~~
croshan
It's really more of a language community issue, but communities of a language
are commonly referred to by the name of the language itself.

~~~
entity345
I'm sure there are many actual professionals using Go. Not just script
kiddies.

I happen to be learning Go coming from a C background. I find "The Go
Programming Language" by Donovan and Kernighan exemplary and the decades of
experience that went into the language really show.

~~~
croshan
No need for insinuations, that's not a point of contention.

If you look past the book, and directly at the STL, you'll find a common
example: the "fmt" library, shortened to save three letters. Or compare the
verbosity of these two examples from language docs, and the length and
descriptiveness of variable names in them:
[https://docs.oracle.com/javase/tutorial/essential/io/cl.html](https://docs.oracle.com/javase/tutorial/essential/io/cl.html)
to
[https://golang.org/pkg/bufio/#example_Scanner_emptyFinalToke...](https://golang.org/pkg/bufio/#example_Scanner_emptyFinalToken)

I agree, it's a great language. These naming conventions are part of its
developers and community.

~~~
entity345
Naming conventions are part of your own practices and processes.

There is no issue with having short names to describe well-known entities part
of the language, or trivial code.

Your example from pkg.bufio is trivial and but still the variables names are
meaningful. There is a difference between clearly and meaningful, and verbose.

Your example from Java is not very different.

I feel that the point is not being understood here.

------
twentythree
> 2.3. Don’t name your variables for their types

What's the best way to handle a situation where you use two different types
for the same data? e.g.

    
    
      var usersMap map[string]*User
      var usersList []*User

~~~
samstokes
How about describing the intended use of the collections? E.g.

    
    
        var usersByUsername map[string]*User
        var usersToEmail []*User

------
Groxx
[https://dave.cheney.net/practical-go/presentations/qcon-
chin...](https://dave.cheney.net/practical-go/presentations/qcon-
china.html#_never_start_a_goroutine_without_when_it_will_stop)

> 8.3. Never start a goroutine without [knowing] when it will stop.

100% agreed with the concept, but even the final example is flawed. That'll
unblock and continue _immediately_ after `close(stop)`, without being able to
do two important things: it can't tell you when it's done shutting down, and
it can't tell you if it encountered an error. Fixing this makes it even more
complex.

Both of those are common and often necessary things to do - e.g. if you need
to drain traffic, you need to wait until it's actually done before killing
your process. Same goes for closing files (you might need to flush / sync
first), OS resources that might survive your process, etc. This example will
just kill your process as soon as everyone has been _informed_ that it should
stop, not when they're _done_.

\---

Yeah it's a bit nitpicky, but it's even called out as "asking a http.Server to
shut down is a little involved, so I’ve spun that logic out into a helper
function". Except that the helper function can't ensure the involved server
shutdown process _actually shut down the server_. That's a dangerous pattern
to encourage, and it's an over-simplification that's absolutely rampant in go
tutorials and code I encounter.

The workgroup lib he links doesn't (arguably?) have this flaw, but it has some
strange / incomplete error handling details... E.g. if you Add 5 funcs, you'll
only receive the error result of _the first func to complete_ , not the first
err or anything more predictable. If the first succeeds but all the rest fail,
you'll see no error. Go's errgroup does "first non-nil error" at least, but
you can still only get one:
[https://godoc.org/golang.org/x/sync/errgroup](https://godoc.org/golang.org/x/sync/errgroup)

~~~
coder543
> 100% agreed with the concept, but even the final example is flawed. That'll
> unblock and continue immediately after `close(stop)`, without being able to
> do two important things: it can't tell you when it's done shutting down, and
> it can't tell you if it encountered an error. Fixing this makes it even more
> complex.

The final example actually does both of these things already, but maybe you're
confusing the purposes of some of the channels?

The loop at the end of main will wait until it receives one (possibly nil)
error value from every task that was launched. Upon receiving the first error,
it will close the done channel, which will then cause any remaining tasks to
run their Shutdown function, which will attempt to gracefully shutdown the
server. The error of each shutdown function is then returned and printed if
non-nil.

There is no immediate termination, and all errors are communicated. It doesn't
let the process die until every task has sent its error value.

Obviously, there are two semantics not enforced at compile time: each task
must send exactly one error value, and each task is responsible for its own
shutdown. If a task sends more than one error, there will be early
termination. If a task doesn't shut itself down properly, that is its own
fault.

~~~
Groxx
Shutdown blocks until draining is complete, but ListenAndServe returns
immediately when Shutdown is called:
[https://golang.org/pkg/net/http/#Server.Shutdown](https://golang.org/pkg/net/http/#Server.Shutdown)

> _When Shutdown is called, Serve, ListenAndServe, and ListenAndServeTLS
> immediately return ErrServerClosed. Make sure the program doesn 't exit and
> waits instead for Shutdown to return._

So no, this writes to the "done" channel essentially immediately once
`close(stop)` is called, and ends the process prematurely.

------
gigatexal
This is so fortuitous as I started writing my first real Golang service and as
a python dev I have no idea what I am doing.

But one refreshing thing is how opinionated the language and the frameworks
are refreshing as there’s only one acceptable way to do many things.

~~~
hashhar
I agree so much with this. I recently attended a Golang meet up and since I
was one of the few who had been using the language regularly for over 2 years
now i was asked to let the newcomers know my favorite "feature". I replied
with the same point you make above and was met with a look of refreshment on
people's faces.

Go lets you write One True Code for the most part.

------
tarasmatsyk
Good collection of best practices overall. I would not say they are Go-only,
as I read about them 5 years ago in Clean Code by R. Martin and another part
can be found in Code Complete by Steve McConnell.

Once again, good collection if you have no time to read the book. Anyway, it
does not cover other parts like contracts/interfaces, the absence of comments
(which can be a good sign) etc. Would recommend checking both CC books if you
want to write better and maintainable code.

PS. Anyway, Dave, thank you for the popularization of best practices.

------
leshow
I've always found Go's 'guiding principles' to be highly subjective, the
annoying thing about it is that they are masqueraded as objectivity.
Simplicity and readability for whom?

If you've been writing C and Java for years, all simple/readable means is
'familiar'. There are other definitions of simplicity

~~~
fastbmk
Go developers have a sectarian mind set. They obey the cult of The Three
Creators, and some other minor advocates/evangelists like this australian
dude.

------
shoo
> That’s a lot of repetitive [error handling] work. But we can make it easier
> on ourselves by introducing a small wrapper type, errWriter.

> errWriter fulfils the io.Writer contract so it can be used to wrap an
> existing io.Writer. errWriter passes writes through to its underlying writer
> until an error is detected. From that point on, it discards any writes and
> returns the previous error.

this is a cute trick.

In some cases it might produce a great result. For example, floating point nan
values work roughly like this: a special error values included in the type
that can be computed upon without needing to write special control flow until
you explicitly need to check the final computed result to see if you got an
actual value or the nan value.

but I fear the applicability of this trick in go is limited as it relies on a
heap of idiosyncracies belonging to this example:

The chain of potentially error-ing operations that are being performed all
involve a value of the same type that can be wrapped. In a less contrived
example there would be a variety of different types involved in the sequence
of possibly error-ing operations.

The code that is processing the wrapped error-hiding type roughly must not
perform side effects when processing an apparently non-erroring computation,
since now the error wrapper type is stopping the code from aborting early.
It's completely non obvious that this transformation will be correct (ie
result in a program with equivalent behaviour to the original program) without
reading exactly through the implementation of everything that touches the
error wrapper. This isn't helped by go not having a way to tag functions as
pure.

The new code now superficially looks like it is wrong as it isn't bothering to
check error values of the functions it is calling in the usual way. This will
probably cause a spray of linter warnings about failing to handle possible
errors in your CI build.

In other languages a much more general way to address this kind of problem
could be:

error monads ; or exception handling

Which can be used to short circuit the entire normal control flow.

~~~
masklinn
> In other languages a much more general way to address this kind of problem
> could be: error monads ; or exception handling

errWriter is quite literally "let's do monadic error handling, except not just
without monads but without generics or reified errors". And thus it can only
handle a single source or error type.

Also FWIW monadic error handling != error monads. For instance Rust does the
former, but its type system can't express the latter (so there is no monad or
functor abstraction on top of Result).

------
johnisgood
I don't understand something.

> 5.1. Consider fewer, larger packages

> 5.2. Keep package main small as small as possible

If you have one package, which is the main package, then how are you supposed
to keep it small, and at the same time not creating more packages because
somehow that is the work of the devil? He is telling me to try to fit
everything into one package, but at the same time keep it really small. It is
not going to work. Maybe I misunderstood?

~~~
dub
In golang packages named "main" are special. They're not importable and they
correspond to a single executable.

If you want to be able to reuse code in different packages in the future or
have more than one compiled binary, you won't be able to put everything into a
single "main" package

~~~
johnisgood
Right now I use many internal packages. It makes my code more organized. By
many, I use 4-5 and the number probably is not going to change. I dunno if
this is a good idea, but it seems like the best I have got that suits me.

------
jstewartmobile
Love Mr. Cheney, hate stuff like this.

Someone who has put the hours in will probably stray from all of this advice,
and still end up with something beautiful. Meanwhile, for all of the other
schmoes who haven't put the hours in, this is just more fuel for screeching
about how this name isn't right or that comment is too long.

Dijkstra said GOTO was bad, and now we have callback hell and 10-layer
inheritance hierarchies instead.

~~~
AnimalMuppet
If you're writing the kind of thing where you need callbacks, GOTO isn't going
to make it _more_ clear what's going on...

~~~
jstewartmobile
Never said it would. I did imply that things haven't significantly improved.

Throw away goto and globals, replace them with anonymous functions and
closures, and the new thing looks an awful damn lot like the old thing.

All I know is that we used to have retrained housewives writing physics
programs. Now we expect 10,000 hours to write a halfway decent web form, and
halfway decent doesn't happen nearly as often as it should.

------
tschellenbach
This book is a great read for writing maintainable code:
[https://www.amazon.com/Clean-Code-Handbook-Software-
Craftsma...](https://www.amazon.com/Clean-Code-Handbook-Software-
Craftsmanship/dp/0132350882/ref=sr_1_2?s=books&ie=UTF8&qid=1550785898&sr=1-2&keywords=beautiful+code)

------
dana321
My advice would be this: If you have a specific reusable component.. Make a
package to contain it.

Use init() to compile any regular expressions and store them as variables
within that package, so that you don't

Split out related code in a folder, remembering that the included code is done
in alphabetical order - so shared functions within the same package you should
alphabetically make it the first to be included.

You can register components of a larger system by using the init() function to
call a function in the base package, and within the calling package (usually
main) import the "plugin" using the underscore space prefix (sql database
drivers use this method)

Use the new go modules system; its great. But sometimes it doesn't include the
latest packages, just modify the go.mod file and anything after the package
name remove it and just put master instead.

One last thing with error handling. Everybody does if err!=nil. I tend to go
the opposite way, if err==nil and nest these, if err isn't nil i drop out and
deal with the error. If it is ok, it will return ok.

~~~
undreren
> One last thing with error handling. Everybody does if err!=nil. I tend to go
> the opposite way, if err==nil and nest these, if err isn't nil i drop out
> and deal with the error. If it is ok, it will return ok.

Don't you get "staircase of doom"?

    
    
        err := unsafe1()
        if err == nil {
            err = unsafe2()
            if err == nil {
                err = unsafe3()
                if err == nil {
                    err = unsafe4()
                        if err == nil {
                        err = unsafe5()
                        if err == nil {
                            // ...
                        }
                    }
                }
            }
        }

------
krat0sprakhar
Slightly offtopic: Does anyone know which template was used to generate this
document? I'm assuming the source in markdown and I really like that the
generated html has table of contents, support for footnotes and is responsive!

~~~
andrenth
It’s asciidoctor, and it’s indeed really nice.

[https://asciidoctor.org/docs/user-manual/](https://asciidoctor.org/docs/user-
manual/)

------
holografix
Nice write up, could use a little simple editing / spell check!

------
fastbmk
One more advice - implement Generics in your programming language, it's 2019
already, not 1970s!

------
EngineerBetter
Really glad to see more pragmatism and less dogma in the Golang community.

------
amedvednikov
Great article, as always, by Dave Cheney.

I took a lot of his advice when designing V [1].

It's very similar to Go, but it has

\- No global state

\- Only one declaration style (a := 0)

\- No null

\- No undefined values

\- No err != nil checks (replaced by option types)

\- Immutability by default

\- Much stricter vfmt

\- No runtime

\- Cheaper interfaces without dynamic dispatch

[1] [http://vlang.io](http://vlang.io)

~~~
wuschel
Exciting!

How long was this language in development? In what language was the compiler
originally written in?

~~~
amedvednikov
It was written in Go, my favorite language at the time :)

About 2 months later I rewrote it in V. Bootstrapping is fun. Did it 4 times
(V1 => V4).

------
ilovecaching
So I don't know how to fly a commercial airliner, but I could probably figure
my way around a small single prop airplane. That's basically the difference
between Go and a language like Rust or C++ or any language that requires a lot
of up front investment, but then let's you work at power level 9000.

So yeah, Go will get you there. It will take you a lot longer to get there
when you aren't going 600MPH, and you can carry a lot less people, but at
least you aren't driving a car like the people using Python. Sure it's
probably not going to be as rigorously inspected as the Boeing borrow checker,
but Go is at least getting an inspection, whereas your Python code is just
going to break down on the side of the road when the problems surface itself
because you didn't check the oil light.

To me, Go is a better Python. It's easier to learn, faster, more scalable, and
safer, and just as easy and quick to write. It's just not a language for big
projects.

~~~
platz
But Go will never be a data science language like python, because Go is too
limited to express that domain.

Python may leave you in the lurch when it comes to maintainability, but for
some domains python is infinitely better than Go.

~~~
chewxy
Hmm. Weird. Almost my entire data science pipeline is in Go.

~~~
platz
Data engineering is not data science

