Welcome back to our six-part series on Golang Pros and Cons for using Go in a DevOps development cycle. In this part, we discuss interface implementation (the pro) and public/private designations (a real con).
Be sure to read up on the last post about goroutines and panics/errors if you missed it, or subscribe to our blog updates to be notified when the rest of the series is published. (We are doing these about every other week.)
Go’s automagic interface implementation is simply amazing. The number one reason we love it is because it saves us from dependency hell.
Unlike most languages, interfaces in Golang are implemented automatically by structs that match the interface definition. Here’s a simple example. Notice the distinct lack of the implements keyword on the Dog
and Cat
struct.
package main
import (
"fmt"
)
type Animal interface {
Speak()
}
type Cat struct {
}
func (this *Cat) Speak() {
fmt.Println("meow")
}
type Dog struct {
}
func (this *Dog) Speak() {
fmt.Println("woof")
}
func main() {
var pet Animal
pet = &Cat{}
pet.Speak()
pet = &Dog{}
pet.Speak()
}
Most major languages (C++, C#, Java, PHP, etc.) require classes to specify which interfaces they implement. Golang takes a different approach. As the saying goes, if it quacks like a duck, it is a duck. Sounds like Python’s or Ruby’s duck typing, yes? While it seems the same on the surface, Golang adds one critical advantage: it still works with a compiler. You don’t have to put run-time checking in your code to see if the item passed the quacks-like-a-duck test.
Now, on to qualify my claim that this sort of interface magic avoids circular dependencies. The easiest way to explain it is that it reduces the number of dependencies in general.
Think about the database libraries in your favorite language — I’ll use Java as an example. JDBC has the following interfaces in the java.sql package that all concrete implementations must implement: ResultSet, Connection, Driver, Statement, and 18 more.
Now, consider the MySQL, SQLServer, Oracle, Postgres libraries. I haven’t written them or checked to see if they use any of the concrete classes or enums defined in java.sql. At bare minimum, however, these vendor libraries wouldn’t need to depend on the JDBC packages to use the interfaces named above.
The monitoring agent that we’re building at Blue Matador has a lot of packages and modules. Each module can be enabled by itself, or they can all be enabled. Right now, it’s Lumberjack, our centralized log management product, and Watchdog, our systems’ vitals monitoring tool. Additionally, there’s a base agent that is in charge of registration and module management. As you can imagine, we have a good amount of common code, shared packages, and dependencies.
I searched every interface in our agent codebase (and every instance of using those interfaces) to find a good example to share with you. None of them made a good example. All I could do is paste our import statements and say, “See how it doesn’t say XYZ?” It’s not very convincing or interesting to look at. So, take it from me, it’s nice and most welcome compared to the alternative.
On the other hand, I have pulled out my hair because of the insanity that is changing the access modifier of a function or variable in Go. I don’t know why the Golang developers chose to do this, but instead of using public
and private
keywords to dictate access, they use the first character of variable and function names. If it’s uppercase, it’s public. If it’s lowercase, it’s private.
First, this leads to semi-unreadable code. The whole reason coding conventions exist is to make code more uniform and readable. Along with whitespace (don’t get our dev team started), naming conventions are the most hotly debated topic for conventions. Golang has essentially said, “Saving seven characters is more important than readability.”
You might ask, “Well, doesn’t that solve the convention issue?” In a way, yes. It definitely solves the need for programmers to argue. What it doesn’t do is make the code more readable, which was the purpose behind the code convention in the first place.
You might say, “Well, that’s easy enough for me to read it.” Well, your eyes are better than mine.
Second, and far more importantly, when you want to change from private to public or vice-versa, it’s no longer a single keyword. Now, it’s a distributed entity with references either throughout the package (with a private → public transition) or, worse, throughout your entire codebase (public → private).
Because names can be duplicated (e.g., key
, id
, etc.), embedded (e.g., user
inside of abuser
), and immediately followed by operators (e.g., user:=1
), no amount of full-word, case-sensitive searching will find all the correct instances of the name you’re trying to change! The only way to change it is to change the source, and then compile/fix your way to glory.
Public/private keywords don’t bloat the size of the resultant binary. What the Golang developers chose to do was sacrifice our speed of refactoring for a couple kB of text that we automatically ignore unless we need it.
If you add up all the times I’ve done this, it’s a non-trivial amount of time. Given the benefit of saving a minute amount of disk space, I declare it’s a terrible decision and a severe downside of the language.
Blue Matador provides proactive AWS application monitoring that installs in minutes with no configuration required to integrate with the 13 AWS services that we support. It's worth checking out.