The web content provides insights into idiomatic Go development practices, emphasizing the importance of adopting language-specific patterns and idioms for writing Go code that is both efficient and native in style.
Abstract
The article "Be in the flow with idiomatic go development" discusses the significance of writing Go code that conforms to the language's nuances and idioms. It encourages developers to embrace Go's duck typing philosophy, utilize single-method interfaces for simplicity, avoid unnecessary structs by leveraging Go's type system, and implement package initialization for singleton patterns without relying on the sync package. The author, likely with an object-oriented programming background, advises on choosing appropriate integer types, using value receivers for methods when possible, avoiding else blocks for better readability, and employing closures as a workaround for the absence of generics in Go. The article aims to help developers write Go code that is concise, maintainable, and performant, reflecting the language's design philosophy.
Opinions
The author expresses a strong preference for Go's duck typing over traditional class-based inheritance, suggesting it simplifies code and design.
Single-method interfaces are praised for their ease of use and their ability to make any type conform to a required behavior with minimal effort.
The author advocates for a judicious use of structs, recommending that developers avoid wrapping simple types unnecessarily and instead directly define methods on the types themselves.
The use of package initialization for creating singleton objects is presented as a simpler alternative to using sync.Once, showcasing Go's built-in mechanisms for initialization.
The author emphasizes the importance of selecting the appropriate integer type based on the use case, which can lead to more efficient CPU usage.
The choice between value and pointer receivers for methods is discussed, with a recommendation to use value receivers for smaller, immutable structs to avoid unnecessary copying.
The article suggests that omitting else blocks can improve code readability, especially in the context of Go's error handling patterns.
Closures are highlighted as a powerful feature in Go, enabling developers to write generic-like code despite the language's current lack of generics.
The author encourages reader interaction and support for their work on Medium, indicating a desire for community engagement and feedback on their content.
GO DEVELOPMENT PATTERNS
Be in the flow with idiomatic go development
Develop code which looks like Go keeping in mind some simple rules
Every programming language has it’s way of being different. Golang is no different.
Every language has some nuances that if implemented, makes the code look like native to that language. I am sure we all have been in situations, where we move to a new language and write a lot of code in the new language in the form of old language. I have seen lot of libraries in Golang in the wild, which is really not needed because Golang provide far more easy and manageable way of doing it.
Here in this post, I will go through a few ways of writing Golang code, which makes the code really idiomatic and go like.
This post is not about how to write Golang or what are the best practices to write super fast and optimized code.
🖖 Embrace duck typing
Starting off with the most obvious and most common one.
If it looks like a duck, quacks like a duck, it’s a duck.
If you are coming from a language like Java, this might be a bit strange at first time. But it is a really intuitive feature of Go I am really fond of. Other languages like Python or JavaScript already have duck typing.
It basically means, I am not concerned if a type(A) I am working with is a subtype of T. If A has all the behaviors(methods) that T is demanding, then A is type of T.
This feature is one of the most fundamental one, in the Go libraries. We should use them everywhere.
👍 Use single method interfaces, it’s easy and everywhere
I am really fan of single method interfaces. Here are a few most used ones:
When an interface is like this, it is very easy to add it to any other type. With the power of duck typing, we can make any type a Reader by implementing just one method, for example.
🤙 Don’t create a struct when a simple type would suffice
Coming from a OOP background to Golang, I had written code like this earlier:
don’t do this, do this instead
The reason for doing it was, I was trying to encapsulate the pool with a wrapper object, which is not necessary in Golang.
Go types can be anything, an int, a bool, a map, pointer to a val, even a function. Idea is to define the type and add features to it by adding method receivers.
✌️ Use package initialization to create a singleton object, without using sync package
It is another pattern I have used sometimes with added complexity, which it need not be.
I used sync.Once several times to make a singleton object. It not be this complex, as go packages are initialized only once, I can easily declare a exported variable and have it contain an struct which conforms to the interface I am exporting from the package.
Consider this piece of code:
versus this piece of code:
Let’s analyze:
The first form is trying to make a singleton using the sync.Once type in the ObtainStrategy and filling a private variable instance. This type conforms to the Strategy interface. So anytime we call we will get the same instance always.
The second form is just filling an exported variable STRATEGY once and can be used from anywhere outside. This type also conforms to the Strategy interface.
Both the above code do the exact same thing, but the second form is more simple and does not use the sync package to make a singleton.
🤚 Use only the int type needed, don’t just always use the default
Go has basic int types defined in it’s standard library which servers different use cases depending on the need.
Coming from another language(Though there is support for smaller or lager sized primitive int types), often we use the default and call it a day.
It is always a great practice to use only the size of int we need.
Our CPU will bless us for selecting a smaller int, when it is enough.
🤞 Use value receivers also
If we are not modifying the struct and our struct is small, to which the method receiver being added to, a value receiver is enough. Use it.
addProject does change an employee so it is a pointer receiver
numberOfProjects does not have to be a pointer receiver, as it is not changing the employee, it can be a value receiver.
We have to remember the copying cost while using value receivers, if the struct is small and copying cost is small, then only we should use value receivers as for every value receivers, the original value will be copied.
🤚 Avoid using else block
Though this true for a lot of other languages, in Golang it is of a great need, I believe personally.
One of them most distinguishing feature in Golang and almost tiresome in most of the cases is the error check. They are infested everywhere. As a result, we get to see code like this everywhere.
Consider this:
Now if I write the same code like this:
I leave it to you to decide, which one is easy to read and understand.
In the first example, if an error happens, I know the code will not flow and can stop reading. The code flows linearly. But in the second case, we introduce complexity as we have to check the else block, what is going on.
🙏 Use closures
As of the time of writing this article, we don’t have generics yet in Golang. But we do not have to wait. We can use closures to achieve to write some generic code in Golang. Consider this code:
Let’s analyze:
We have 2 services SubscriberDetailsService and SubscribeEntitlementService who returns a type of ServiceCallback.
These ServiceCallbacks can be processed separately or with a load balancer and job scheduler like: rio
Closures when in the time of execution, gives the parent object a context to run in its way even when called from somewhere else.
In our example, the service callbacks can be called in anywhere but when it is being called, it gives the parent object control the calling mechanism and as they are of the same type ( ServiceCallback) they can be collected, prioritized or serialized as the developer wants to.
Closures can be used in so many various ways to write generic code or more versatile code.
If you like this post, please share or comment on the post. Any sort of interaction is highly appreciated. If you would like to support me and get access to all great things in medium, please join and become a member. Here is my referral link: