After 6 years of using Go professionally I think it's a good time to reflect about my experience using it as my default programming language for backends.
How well a language is fitted for a task is highly contextual, so before jumping into the results, let me explain the context in which I've been using Go.
The context
Even though I have 20+ years of experience as Software Engineer, both as an individual contributor and as Tech Lead, I only started using Go professionally when I joined the company I'm currently working for.
I'm part of a team that is in charge of developing internal tools that aim to reduce the burden of securing products supported by all the engineers in the company. In order words, it's in charge of automating as much as possible the tasks related to securing the development and operation of the products within the company.
Our potential costumers are all the engineers working in the company which is in the low thousands.
When it comes to tech we have a very heterogeneous ecosystem, not only in terms of languages and frameworks, for instance we have systems written in: Java + Spring, Kotlin, Scala, C# and ASP.NET, PHP + Symfony and many more, but also in terms of architecture and operating principles, we have everything, from stateles microservices deployed in K8s to monoliths deployed in pet instances, using both big cloud providers and on prem data centers.
The team is composed of individuals with different backgrounds. Some have more experience in the information security area, others in infra and others, like myself, in software engineering. Nonetheless, it's expected that everybody should be eventually able to own a component or system end to end, that is: from developing and deploying it to being on call for it.
Finally, the engineering culture has been evolving during the years, but the basics are still the same:
- Tipical workflow of PR's and code reviews
- Immutable infra
- You build it you run it
- Voluntary on calls for the required services
- Don't be dogmatic (right tool for the job)
- Microservices(-ish) architecture
How was the ride?
TD;DR: Even though it's impossible to know exactly what's the importance of Go in the overall experience, I believe Go played a significant role in the success we had from the software enginering point of view, concretely in the following aspects:
- Onboarding people who didn't know Go was very easy, much easier than with other languages.
- Not having to spend too much time in improving the performance of the code in order to meet our requirements.
We of course also experienced some drawbacks:
- Bigger upfront investments when developing simple CRUD components than with other languages.
- Some initial "language fighting" for people, like myself, that were more experienced developing in more object oriented languages.
- Some troubles with a few gotchas.
Ok that was the TL;DR, now let me explain a little bit in more detail both the good and the bad points.
The good
Quick onboarding people
I already knew Go when I started working in my team, but I hadn't built anything professional, just some small experiments and pet projects. During those years almost nobody who joined the team had professional experience with it, and some of them even didn't know anything about Go.
Despite not having experience, as far as I remember, everybody was able to contribute to our code base in less than about 2 weeks. In my experience, that's an extremely short time to be productive. Even for someone with knowledge and experience with the languages and frameworks used by a team the expectation of the time needed to start having meaningful contributions is in the order of months, not weeks.
Although It's impossible to know exactly the actual contribution of Go in this
quick onboarding
process, it's true that Go is known for having less
features that the majority of the other "mainstream" languages,
so I think it's fair to assume Go is easy to learn and read, at least for new
commers, and that, in turn, can explain at least partially why we enjoyed those
fast onboarding times.
Meeting our performance requirements
As I explained in the context, some of the products we've built help with securing a quite big number of other products, that means they have to support a non trivial level of workload.
We've found that the components written in Go scale much better out of the box than the components written in orther languages, for instance Ruby or Python. It is of course true that it would have been possible to meet our requirements using those other languages, but for me, the key point is that Go allowed us to met them without having to spend much time investigating and fine tuning our code.
Our workloads tend to be more I/O bound than CPU bound, so I think is also fair to assume the ability of the language for running thousands of goroutines, and the tools it provides for building concurrent programs are big contributors to this "simple scalability".
The bad
Upfront investments for simple CRUD components
Go is designed to be explicit, that means that the ability to build frameworks that speed up the creation of simple applications is severely limited by the lack of features that allow to introduce some kind of "magic" in the code.
It's not that such frameworks can't be built in Go, there are indeed quite a few web frameworks out there, but I think the reason they are not very popular among the gophers is that it's kind of awkward and usually not very productive to use them. That's not the case for instance with RoR that actually delivers when it comes to speeding up the development of a CRUD.
Even if this is clearly a drawback initially, I would say that we found, or at least that's my perception, that even for simple CRUD components the ones written in Go scaled much better out of the box and were easier to maintain than the ones written using other tech stacks. So, unless there is a specific reason to use another language, we also use Go for CRUD applications.
Some initial fighting with the language
As Rob Pike (one of the designers of the language) stated, Go it's not designed to program with types, on the contrary you'll find the types while you program. That means that it's usually not a good idea to start thinking upfront which types you should create to code your program. It is better to start writing functions and find the types you need while writing them. In other languages, like the ones that are more object oriented, that's usually the opposite, so, if you come to Go from those kind of languages, you can find yourself struggling a little bit because the language simply is not designed to be used in that way.
I think this is also the reason that many people finds Go "boring". I kind of agree with them, I don't have so much fun coding in Go compared, for instance, with Rust. In any case, this could also be a good thing when your objective is more to solve a concrete problem instead of having fun, as you can focus more in the problem and less in which features of the language you are going to use while implementing the solution of the problem.