By Gavrie Philipson | 2015-05-13
I have been programming in Python in one capacity or another since around 2000. That’s almost 15 years. Over those years, I’ve come to know the language fairly well. I originally came to Python from C and Perl, and it was quite refreshing to come to a language that is simple to use, has a consistent syntax and library, and is quite powerful.
I’ve worked on several small to large applications in Python since then, and reached a point where its limitations became quite visible. Ultimately this led me to Go. Below I’ll describe the process that led me there.
Taking a Break From Python
At my current job I started work on developing an automated testing system from scratch. One of the first questions that came up was which programming language to use. I was quite certain that Python would no longer cut it, being simply too painful to develop large applications in. It took me a while to admit this to myself, having long been a staunch supporter of dynamic languages and having taught a long series of Python courses in which I defended the Python approach to Java and C++ programmers. But once I ceded this point, I went back looking for a statically typed language.
Dynamic typing is wonderful and great, but in a large application the codebase simply becomes too complicated to understand and navigate. Python has a lot of “magic” features behind its deceptively simple façade, and it becomes tempting to do a lot of metaprogramming. One constant point of frustration was the large gap between the textual representation of the code and the runtime memory model: There is no direct mapping. It’s simply impossible to tell your editor to “go to definition” of a function or method and be sure that it reaches the correct target. Note that recent developments, such as PEP 484 – Type Hints and mypy, may change this in the future if they catch on.
Another pain point is concurrency: A modern application need to do several things concurrently. Raw multithreading with mutexes and semaphores is an old and rusty programming model, which in any case is not very suitable to Python with its GIL limitation. And Gevent is very nice, but not a standard part of the language and very hard to debug.
Yet another one is deployment: Python, and any other dynamic language, has a lot of baggage when deploying. It needs a runtime environment with all dependencies present. Yes, we have virtualenv and it’s great, but things could be made simpler.
So, we’re looking for a statically typed language with good support for concurrency and easy deployment. But which one?
At this point, I had read a lot about Go and played with it a bit. It promises a simple and modern language with static typing, great concurrency support, easy deployment and a large and exponentially growing community. Sounds almost too good to be true. Does it deliver?
I accepted the challenge to convince my colleagues to give Go a try. Several alternatives were suggested. To begin with, several other languages were already in use at out company, including C, Java, Haskell, and Ruby. It would make sense to see if one of those would fit before adding yet another language to the pack. In addition, some colleagues suggested Scala and C++.
For me, the matter was simple: Ruby is too similar to Python with its advantages and disadvantages. C, while being great for a high performance software core, is too low level for developing a whole automation solution. C++ is much too complicated a beast, and I don’t like it. Java sounds like an obvious contender, but it’s getting long in the tooth and I wanted something more modern and less “enterprisey”. Haskell sounds wonderful, but has a reputation of being hard to get right. Its acceptance in the industry is also less than I would hope for. Scala sounds nice, but would mainly benefit projects that already use the JVM.
Of course, all of the above are my opinion only and I have no intention at this point of convincing anyone. I just wanted to reach the initial conclusion that giving Go a try might be a good idea.
What I liked about Go
So, I started learning Go in earnest and writing some real code in it. There were several things that I liked a lot:
- Syntax is simple and consistent. It feels somewhere between Python and C and level of abstractness. It reminds me of Pascal in several places: Declaring variables with
:=operator, the much stronger typing than C. Pascal was a language that I adored as a teenager, having done wonderful things in Borland’s Turbo Pascal (yes, I’m disclosing my age here).
- High level constructs such as slices and maps are built into the language with their own syntax.
- High order functions and closures are supported.
- Consistency everywhere: The syntax is consistent, as is the standard library. There is a consistent coding style with well-defined rules. While this may sound like a headache that limits expressivity, it makes third party code a joy to read and understand.
The toolset is absolutely wonderful. Following the UNIX philosophy, the Go community has created several strong tools that each does one thing very well. Those tools integrate with the command line and with you editor to deliver a great development experience:
gofmtformats code consistently
gocodeautocompletes your code by analysis of actually built code so its results are precise
gorenamehandles renames in a type-safe way
godeffind the definition of every construct you point it at
oracledoes amazing code analysis that gives you insight into the flow of your code.
Combine all those with
vim-go which makes all those tools accessible from Vim with a keystroke, and you have the ideal developer setup.
Concurrency! Channels are Go’s way of handling concurrency and they’re marvelous. Go is the only language I’ve worked in so far that does not need to distinguish between asynchronous and synchronous code: You simply write as if everything is synchronous. This reminds one of Python’s
gevent, but in Go it’s native to the language and used everywhere. There is no need to manually manage futures/promises/threads/locks/mutexes, or to carefully consider if code should be asynchronous or not. This reduces the maintenance burden significantly.
What took time to get used to
Some things in Go took time to get used to, and I’m not sure yet whether I like them or not:
Error handling is explicit. Errors are returned as values from functions (using a special
error type). Together with multi-valued returns, this is much better than C’s error handling, but much less intuitive than Python’s exceptions. You are forced to consider every error at its site of origin. Tools like
errcheck help ensure that you don’t miss any. While it’s much easier to write code using exceptions, it’s nice to be forced to copy with errors and not forget about them until your code breaks. I believe this leads to more robust code, which is a worthwhile price to pay. One exception is “end user” code such as tests that begin to look very tedious with explicit error handling. It may be worthwhile to use
recover for such code.
Yes, everyone says it so I will too: Go doesn’t support generics for custom data types. While its built-in maps and slices are generic, you can’t write your own
set type and have it support sets of
int or of
string without duplicating code. In practice this has not been a great concern for me so far, apart from having to occasionally supply multiple versions of
Shuffle functions with identical logic for different types. There are several approaches for generics support that make use of interfaces or of code generation, and one of those may become an accepted norm. Even if not, the current situation is quite bearable.
Banner image by crapeye