November 10th, 2019 marked the 10th anniversary of this awesome programming language called Go. The Go community in Bangalore organized a meetup (which was also their 50th meetup) hosted by SAP Labs. The meetup introduced me to gRPC and writing custom plugins for gRPC. The meetup also introduced me to how the Go runtime scheduler works and how it has achieved high performance concurrency. I was just a beginner in Go and starting to fall in love with the language, and knowing the genesis story of Go helped me understand some of the weirdness I felt about the language initially.
After starting with some basic programming in Go (creating a basic http server and some middleware), the language grew on me and quickly became my favorite programming language. The designers of Go set out to create a fast, productive, and fun programming language. That’s exactly what they did. Let’s take a brief tour through some of my learning and experiences.
I have taken less time to learn Go than any other programming language. It has ~25 keywords, but can do so much. It is garbage collected, compiled, statically typed, and has a small build time. It compiles to machine code, so it performs like C.
Many languages avoid enforcing semicolons at the end of a statement. Go, following in the path of C, does enforce semicolons to terminate statements but doesn’t bother developers with it. They insert one when the Go code is compiled and they have defined rules for it. You can add it yourself too, but idiomatic Go avoids it. One consequence of this decision is that braces for control structures (if, for, switch, and select) should be on the same line and not on a new line, which adds to the readability of Go. Programmers do not have to make rules instructing how and where braces are used.
I remember learning to code (in C++), and missing to add break statements in my switch cases and thinking, “that’s not intuitive”. I think, as a beginner to coding, those were the most irritating logical errors that I have had to debug. The fall through behavior that is common in all programming languages (except python, which does not have a switch case) is useful sometimes. However, since it is not intuitive, I guess every programmer would have made the mistake of missing that break statement. Go switch cases do not fall through unless you want to (using the “fallthrough” keyword) and I like that.
Go also breaks convention and renames the while loop. Like the Go tour specifies “C's while is spelled for in Go.” One less keyword to remember.}
Go’s concurrency model involves using Goroutines. Goroutines can be thought of as lightweight threads. They take very little stack space and the stack can grow and shrink according to program requirements. A program may just have one OS thread but a thousand Goroutines. Moreover, if one of the Goroutines is blocking, then the Go runtime moves all the other Goroutines into a newly spawned OS thread. All of this is done automatically by the Go runtime. Goroutines communicate using channels, which are special data structures. Channels can be buffered and Goroutines can push and pull data from it.
Go is not intuitively Object Oriented. One of the main reasons for this is because it does not support inheritance. This is not by accident, but rather an intentional design choice. Go considers inheritance of the classical style fragile. Instead, it uses Composition. In my opinion, Go is responsibly Object Oriented. I have often noticed that programmers write code, using languages like Java and C# where there is only one way (OO way), but still do not achieve OO programs. With Go, you can have methods on any type, not just structures. Go interfaces are just a set of methods and you can compose an interface with multiple types that satisfy these interfaces and any other type that satisfies the whole interface.
Go supports encapsulation at the package level. Exported methods in Go start with a capital letter, and that is one of the things that makes Go so readable. This is perhaps what I like most about Go syntax. Like other languages, we are not burdened with the decision of choosing between camel cases or pascal cases. Go makes it for us.
We (at In Time Tec) decided, after much deliberation and debate, to use Go for one of our data processing microservices (Go concurrency model and performance benchmarks won the debate). We were designing a live data collection agent (for Managed Print Services) which consumed a huge amount of JSON data (about printers and their print count, cost etc.) from multiple paged REST APIs, categorized them based on various parameters, stored them, and presented it. When we did some googling comparing Apache Spark and a custom solution in Go, we found that even though development effort for Spark was less, the Go solution would perform better, was simpler, and more efficient. The first thing that struck me was that Go is just a programming language (Spark is a framework) and it was still easy to build data processing pipelines with just language constructs.
I have come to describe OO Go as “Responsible Object Oriented Programming”, because it has truly enhanced the way I write OO code. When it comes to concurrency, the amazing Goroutines has held me responsible and fearless in my coding. I would make Go my top choice just for its general purposiveness, small learning curve, and its refreshing take on object oriented programming.