Not sure if you should use Go for your next projects? Here are some of the reasons that made Go such a huge success.
If you ask a Go developer nowadays why they chose Go, they’d probably say, “because Go is the go-to backend and DevOps language, everyone around uses it.” But back when Go was young, what were the reasons people would choose Go despite it was not yet as widespread as it is today?
Why would people pick a young language that has obvious DEFICIENCIES?!
Yes you were reading right. When Go 1.0 was released in 2011, the critics raised their voices. According to them, Go ditches every advancement that programming languages made in the last decades: No inheritance, no generics, no fancy language features that support clever coding, no option types, no union types, no this, no that. Go seemed like the most backward-oriented language around. Go was simply boring.
And this actually turned out to be a unique selling point. Boring means no surprises. Boring means readable. Boring means you can look at someone else's code and can actually understand what the code is doing, without looking up a stack of super- and super-super-classes, without examining overloaded operators or diving into the language spec to read about an esoteric language feature that the code author used but you never heard of.
Sure, Go attracted the curious audience with some goodies, like the famous goroutines, of which you can spawn millions without grinding the system to a halt, or Go’s ability to compile static, self-contained binaries, and even compile them for different platforms, without needing platform-specific tool chains.
But to many, the most valuable asset of Go is that Go code is so darn readable. This makes Go the perfect team language. Instead of discussing language peculiarities, or even argue about proper formatting (Go does the formatting for you in a fixed style), development teams can focus on the actual project requirements to implement.
This is a real time saver. For writing new code but also for maintaining existing code.
Let’s look at the factors that make Go so readable and so usable.
The way to write really reliable code is to try to use simple tools that take into account typical human frailty, not complex tools with hidden side effects and leaky abstractions that assume an infallible programmer.
(Joel Spolsky, Making Wrong Code Look Wrong – Joel on Software, 2005)
While the specs of mainstream languages can easily stretch across hundreds of pages, the Go specification requires only around 90 pages. (At the time of writing this article, saving the language specification Web page as an A4 PDF results in 87 pages.)
A small spec means that it is possible for mere mortals to learn and understand all parts of the language. Feature-rich languages, on the other hand, quickly grow beyond any sane level of comprehension. The number of C++ developers who know every single aspect of their language by heart can probably be counted on the fingers of one hand.
As a development team leader, you surely will want that most, if not all, people on your team have an intimate understanding of the language and can reason about their code without much effort. Go with a small-spec language then. Small clearly wins.
Of course, too small doesn't cut it either. A language needs a certain level of expressiveness to allow getting results quickly. A plain Turing machine surely has one of the simplest language specifications but at the end of the day the Turing machine code you created doesn't get much done.
Go has occupied a sweet spot between too simple and too complex.
The Go team deliberately decided against feature bloat. Whenever a new language feature is proposed, the team carefully weighs the pros and cons before deciding to add that feature. Most of the time, they don't. This approach has led to an amazing stability of the Go toolchain. And moreover, there is a strict rule that no change to the language or the toolchain shall break existing Go code written with earlier Go 1.x versions. (A notable exception are security fixes, for good reasons.) This is the Go 1 backward compatibility promise. This promise ensures that developer teams can keep up with the latest version of the Go toolchain and get all the goodies from the latest improvements with very low risk.
Go's built-in concurrency primitives triggered a lot of interest since the beginnings. Building on Tony Hoare's concept of communicating sequential processes, Go provides two fundamental concurrency mechanisms:
The term "goroutine", by the way, is a mix of "Go" and "coroutine", the latter being a standard term for code that runs concurrently, that is, independently from other code.
Few languages had built-in concurrency at the time Go was released. Rather, system threads were exposed to the language through libraries. Hence the language itself did not support concurrency in all consequences. As a result, multi-threading code was a bit awkward to write. Mutexes, semaphores, and monitor are too easy to get wrong. Communication via channels, on the other hand, is much more intuitive and much easier to reason about.
Compiling a medium-to-large project written in a traditional language can greatly increase the caffeine intake of a developer. Simply because compile times were measured in minutes to hours, and this usually means coffee break.
In Go, compile time is measured in seconds to minutes, rather than minutes to hours. Imagine what that does to developer productivity.
Worth noting at this point is that many Python developers love Go not only because of added type safety, execution speed, and single-binary deployment, but also because the Go compiler is so fast it feels like an interpeter.
Go features automatic memory management by utilizing a garbage collector. Automatic memory management is perhaps the most important accelerator for writing code.
Languages without that feature impose a large additional workload to the developer's brain:
It is easy to see that memory leaks are inevitable in languages without automatic memory management. Carefully crafting code without memory leaks is time consuming, and troubleshooting memory leaks is time consuming as well.
In Go, the above problems are none of your business. The Go compiler decides whether a variable stays on the stack or needs to live on the heap (this is called escape analysis), and the integrated garbage collector takes care of releasing unused memory during runtime. As a developer, you can fully focus on the project to implement.
Almost as important as the language syntax and semantics is the availability of a standard library. Go is not stingy here. In fact, the Go standard library is so comprehensive that Go devs have a common advice for newcomers: "Always look in the standard library first. You probably find that you don't need to depend on third-party code."
And this is not exaggerated. About a dozen lines of code with the standard
net/http package yields a basic Web server. Concurrent request handling included for free.
From file handling to database access, from OS interaction to networking, from cryptography to testing and profiling – the standard library has a wealth of packages to get you going.
This feature even appeals to pure users of a Go app or tool. A Go main package compiles to a single, self-contained binary with no external dependencies. No shared libraries, no pre-installed runtime environments are required for running a Go binary. You just pick the compiled binary and push it to the target machine. Or pack int into a Docker container built
FROM scratch. No OS libs required.
As if single-binary was not cool enough already, Go even allows to cross-compile to different target operating systems and architectures without having to install an appropriate target-specific tool chain.
So if you write code on a macOS machine and want to run it on a Rasperry Pi, you just type
GOOS=linux GOARCH=arm go build
and you can push the resulting binary over to your Pi and run it right away.
This works also for OSes like AIX, Darwin (macOS), NetBSD, OpenBSD, Plan9, Windows, and more, and for architectures like 386,AMD64, ARM (32 and 64 bit), RISC-V, or WASM, to name just a few. If you have Go installed, type
go tool dist list to get a list of all supported OS/architecture combinations.
A test framework is an integrated part of the language - get robust code right from the start. With a few naming conventions for file names and function names, paired with a package full of helper functions and types, writing unit tests in Go is so easy that you really do not have any excuses anymore.
Tests are written in plain Go so your brains don't have to switch context when writing a test, but if you prefer a dedicated testing vocabulary, you can choose from a number of third-party test packages.
interface feature makes it a breeze to decouple programming logic from the outside world, and this in turn makes swapping out a bulky database for a slim database mockup a no-brainer. Test-Driven Development (TDD) and Go go indeed well together.
I could go on and on but I know your time is precious and frankly, if the above does not already convince you, then writing more won't either.
Oh, maybe just one more thing. If you want to look at working examples of successful Go applications and tools, pick any of: Docker, Kubernetes, Caddy Web server, virtually all HashiCorp apps (Terraform, Nomad, Consul, Vault, Waypoint,..), CockroachDB, InfluxDB, ImmuDB,
When will you give Go a try?
Applied Go Courses helps you getting up to speed with Go without friction. Our flagship product, Master Go, is an online course with concise and intuitive lectures and many practices and exercises. Rich graphic visualizations make complex topics easy to understand and remember. Lectures are short and numerous, to help planning and saving your precious time, as well as serving as quick reference material after the course. Learn more at https://appliedgo.com.
Categories: The Language