I have been working as a consultant and trainer on Go programming language and distributed systems architectures, in India, and guided a lot of software development teams to adopt Go for building their systems. I have worked with product engineering start-ups, mid-size organisations and larger enterprises. Except working with some larger enterprises (not with everyone, but majority of times), the adoption path to Go has been very pleasant experience. When I mentor fresher software engineers, they quickly adopt the Go language and then working on product engineering, like experienced engineers do. Because Go is a very simple and minimalist language, the adoption path to Go is very easy, which is really helpful for product engineering start-up companies. The benefit of many lesser experienced software engineers, is that they are coming with less baggages from their previous programming background so that they are ready to accept and adopt what they have learned from an experienced practioner.
The dangerous attitude of adopting Go with the Java baggage
In my consulting experience, sometimes, mentoring larger enterprises on Go, was a bit difficult job. The main reason is their baggages from previous programming background. Most of these teams are coming from an Enterprise Java background, where they are simply looking for Go equivalents for what they had done in Java — in the language as well as the way they constructed Java applications. I have had experiences where teams are first looking for an inversion of control (IoC) container in Go or a Spring Boot framework equivalent, even before learning the basics and design philosophy of Go programming language.
A lot of these Java engineers, especially very experienced people, have been first looking for how to write Gang of Four design patterns and enterprise patterns in Go. They want to write Go code in the exact way, which they had written in Java. These people have been adopting Go for the sake of adopting a new mainstream language, but not for understanding what Go does and does not have. They don’t take it as a new language. They adopt Go without having a passion towards Go. Within their organisation, someone else decided to use Go for some new projects, or client would like to use it. Or some hype is happening towards Go, so that someone else thinks that just use it for projects. That’s it. It’s just for the adoption for the sake of adopting something new. This attitude is very dangerous. The decision makers might be choosing Go for all good reasons, but when a larger team is adopting a new language without understanding its soul, it does make lot of problems. Even when I conduct a programming workshop on Go, some of these people (Few people who are having an attitude problem) are not ready to adopt things in Go way by writing idiomatic Go code. They want to write Go code in Java way.
When you are looking for each Java equivalents in Go, it does make lot of problems. When you are looking on Go’s user-defined type system with a Java baggage, it does make lot of problems to you. When you learn Go interfaces, which are implemented implicitly, you can take it as a great opportunity, or you can think it as a problem by having a Java baggage of interfaces are implemented explicitly. If you are simply looking for another Java in Go, it’s crystal clear that you adopt Go for all wrong reasons, because for your way of thinking to writing software systems, you can focus on Java instead of moving to Go. Every programming languages are having its own strengths and weakness, where you choose a language based on your attitude and philosophy about programming.
Understanding the Go philosophy
Programming languages, tools, frameworks or even architecture approaches, are designed for solving some kind of problems. Many of these languages and tools are evolutionary things, which were evolved from our past experiences and from the challenges we had faced to solve some kind of problems. So when new things are designing, we, of course, consider the problems we had in the past experiences. Everything in this universe is evolving. There is nothing permanent — which is true in software engineering as well. Things will come and go.
Go is designed for simplicity and pragmatism, which doesn’t care about programming language theories (PLT) and intellectual thoughts. In order to master on Go programming language, the most important thing is to unlearning and take it as a fresher programming language. When you take it as a fresher programming language without having a baggage on some of your past experience, you see lot of opportunities everywhere.
When I adopted Go six years ago, I was a veteran C# developer and architect, who has been using Microsoft .net since its beta version. When I looked into Go without having baggages from C# and .net, I was really excited about this pragmatic programming language. After writing systems with Go for several years, now, I confess that I did lot of over-engineering when I architected systems with Microsoft .net technologies.
Go’s user-defined type systems and concurrency primitives are most exciting things on Go programming language. States and behaviours are isolated in Go’s user-defined types, where you simply define states in struct body, and then behaviours are added later on by writing functions specifying with receiver types. Go interfaces are implemented implicitly. When you look these pragmatic features with a fresher mindset, you may feel lot of opportunities
Keep in mind that Go is not a conventional object-oriented programming language. Thus, some of the design patterns designed for the era of conventional object-oriented programming languages, may not be the right way to implement in Go, just for the sake of implementing those patterns in Go. I have seen that lot of Java developers have been looking for a Go way for writing many conventional patterns which they had written in Java. When you are looking for patterns in Go, things would be different in Go world. Because of the simplicity of Go, sometimes, you may need more copy/paste of code. For example, as of Go 1.13, Go doesn’t support Generics, which means that you may need some copy/paste of code in some specific scenarios. Here you prefer simplicity and better performance by losing a feature like Generics. Understanding a language is very important before you write software systems with it.
Focusing on good software engineering principles
When I mentor fresher software engineers, I always tell them that do focus on good software engineering principles rather than focusing on language syntax and grammars. Programming languages are just providing a mechanism to write code by using its grammar and semantics. Even by using a good programming language, you can write very bad code if you don’t have good knowledge. I have observed that some people are tied up to language syntax and named features, which make lot of problems to them understanding things in right manner. I tell people that “Don't focus on the syntax, it is the biggest trap that prevent you from getting into the soul of a thing or a capability”. When you look into Go interfaces with a syntactical barrier, you, of course, miss its soul. Otherwise interface become a magical tool for writing great software systems based on testability and extensibility.
If you focus on good software engineering principles, you can write good code in any language once you understand the design philosophy of that language. Understand what does by the language which you are using, and then incorporate good software engineering principles with it. Do learn SOLID principles very well and then learn about building software systems based on a clean architecture. When you work on a product, your primary objective is to solve the problem domain, not implementing language features or patterns. You use language features or patterns to solve your problem domain by writing idiomatic code. So first understand the problem domain, then think about the language and its features to solve the problem.
When you adopt Go, learn to write software systems based on SOLID principles with Clean architecture, by writing idiomatic Go code. Writing Go code in Java way, doesn’t make any sense, for which you can use Java instead of Go. When you write applications with Go, don’t use global variables. If you write free functions that operates on package level/global variables, then better to make a struct where define all states and dependencies, and then write methods to that struct instead of writing free functions. This allows you to isolate dependencies, which is also gives you better testability. The most important thing is to isolate dependencies in your systems, for which you can define Go structs in which you can make Explicit Dependencies through property parameters and write methods on it. You must removes the dependent logic from the composition process. You can make dependencies by specifying on interface contracts.