This content originally appeared on Level Up Coding - Medium and was authored by Mohsen Shojaee
Features in Go You Will Like
Interesting features in Go which may persuade you to choose it as the next language in your toolbox
Go is a modern and quite young programming language created in 2009 at Google. It has exploited lessons learnt from older popular languages like C to improve on its design. Coming from a mostly Python background (and also some Java and C), I found some interesting features in Go that I believe will make it easier to write clean and maintainable code in Go. I will review these features with an exception: concurrent programming. Go’s concurrency model is quite unique, simple and elegant. However, as it is probably the most famous feature of Go, I will leave it out from this post.
Go Runtime (and other Go tools)
Go is not just a set of language specifications. It comes with a lot of tools and standard libraries: most importantly the Go runtime . Go runtime is a program compiled into Go binaries and does a lot of low-level services like garbage collection or go routine allocation (concurrency support) for you. This is really good news for people like me who always dreaded the error-prone manual memory management (or other low level tasks) in C/C++. This makes Go a high-level language with great development speed which is also blazing fast in runtime, bringing the best of both worlds.
Go runtime takes the approach of Java Virtual Machine a step further. Java code is compiled to a sort of intermediary language (byte code) and you need a separate program (JVM) installed to run the byte code. However the Go runtime is compiled into the Go binary resulting in a single executable. This makes it more straightforward to deploy Go programs as there’s no concern about compatibility between the virtual machine and program, you just run the binary.
There are also many other tools accompanying the Go language making development in Go easier. Some examples are: dependency management, testing, bench-marking, coverage, linting and code inspection.
No Exceptions
There are no dedicated keywords like try, except or catch in Go. Go programmers handle errors with plain if statements and function return values. In Go, functions can return multiple values (like many Python programmers used, I assumed the function returns a tuple but this is not true. They are just separate values). If a function may fail, it should add a value of type error to its output values.
The caller of logic method then should check for the error and handle it, pass it above or stop the program:
But how is this better than exceptions? Firstly, this is explicit, it tells the caller that this function call can fail (similar to throws keyword in Java). Furthermore, it is a compile-time error in Go to define an unused variable. So it enforces you to be explicit about what you are going to do with the error returned by a function. Apart from explicitness, it makes the code easier to read. This is because the error handling code usually is inside an if block and is not at the same indentation level as the main code so the main path and “Oh no!” path are separated nicely.
Go’s style of error handling reminded me of Either data type which is used in functional programming (like in Scala or Haskell) to make error handling pure functional. Although Go’s error model does not provide all features of Either(e.g. these methods) it brings the same effect of explicitness and (almost) compile time safety. (read more about Either vs Exceptions)
defer Statement
There are many situations where you want something to always run at the end of a function, like closing IO or some clean-up. In many languages we use some form of conditional (either if statements or try/except/finally) to handle this. But in Go you can use a defer statement which receives a function (or more accurately a closure) and always runs it the last thing when returning from the function, no matter if it returned successfully or not. Let’s see it in action:
Here we are asking Go to close the file after the logic runs, and it will do this even if we return earlier inside the logic part of code or if the code panics. These are my reasons to like defer over its alternatives like finally:
- No Indent: defer does not create an extra level of indentation and thus keeps the code easier to read.
- No need to repeat: If there are multiple return paths in a function, handling them can get tricky and and usually needs copy-pasting a code block. With defer you only mention the clean-up once and the compiler and Go runtime take care of it.
- Physical Proximity: With defer there is no need to move clean-up to the end of function, so it usually comes just after the resource definition resulting in even more readability.
- Multiple defers: It is possible to stack multiple defers in a function so you don’t need to wrap all clean-ups together (and lose the previous benefits consequently)
Interfaces: Static Duck Typing
While Go’s concurrency model gets all of the publicity, the real star of Go’s design is its implicit interfaces. — [Learning Go, Jon Bodner]
To explain this feature I will first review a well-known principle in software design: dependency inversion principle. If you are already familiar with the concept you can skip the next section.
The Dependency Inversion Principle
This principle is probably the main reason that made object oriented programming so popular. As a simple example consider a service like Medium which offers paid subscription plans. You definitely want to design the subscription module independent from the payment method currently used. This way you can switch different providers and support multiple payment methods at the same time. The standard way to do this is by creating an abstract interface for handling payments and doing the real work in concrete implementations of that interface. (like the diagram below) In a static-typed language like Java, the SubscriptionManager class contains a field of type PaymentHandler which in runtime a concrete implementation like PayPalHandler is assigned to it. Note that in runtime it is the subscription package that depends on PayPalHandler to do its job but there is no code dependency from SubscriptionManager to PayPalHandler. Code dependency is in inverse direction (from details to high-level) thus the name dependency inversion.
It is generally believed that the interface and the caller (i.e. SubscriptionManager ) belong to the same component/package, while the implementation (i.e. PayPalHandler ) belongs to a different package. This makes sense as it is the caller that depends on the interface. Here SubscriptionManager highly depends on the PaymentHandler interface through calling its method and expecting specific results. However, there is also a coupling between the interface and the implementation, a rather strong form of coupling: inheritance. So while dependency inversion has provided a lot of flexibility and done a great job decoupling our subscription manager from details of specific payment methods, it has a few limitations:
- Switching to another implementation requires changing the SubscriptionManager to use another interface or creating a wrapper for the new implementation (especially if the implementation is third party code)
- Changes in the interface can cause unnecessary changes in (or at least recompiling) the implementation
In dynamic-typed languages like Python this coupling does not exist. If an object has the methods the caller expects then it can be used as an instance of the interface required by the caller (duck typing). In our example, the PayPalHandler does not need to know about the subscription component at all, the main of the application is responsible to assemble the right object to SubscriptionManager Here “right” means, having the methods and fields SubscriptionManager expects. Although this decouples the interface from the implementation, it introduces new challenges:
- It is harder to understand and follow the code when reading the SubscriptionManager as you do not know the type of its payment_handler. (I have personally experienced this whenever I joined an existing Python project)
- It is now more likely to mess things when refactoring either SubscriptionManager or a PaymentHandler.Like calling a nonexistent method resulting in a run-time error:object PaypalHandler has no attribute 'method'
Dynamic language developers have exploited test driven development these issues. While TDD is very effective in preventing the run-time errors, the readability issue still exists. This is probably one reason for the introduction of type-hints in Python or creation of TypeScript language.
How Go improves dependency inversion
Go tries to bring the best of both worlds. Interfaces are implicit in Go, to implement an interface there is no need to inherit it. If a type has the methods an interface defines, it implements that interface (and is considered an instance of that interface by the compiler), even if it does not know that interface exists. In fact, inheritance does not exist in Go at all, structural sub-typing is the only form of sub-typing Go provides. Let’s see our subscription example. First from the caller’s point of view:
which both defines and uses the interface. From the implementation’s point of view:
As you see there is no reference to PaymentHandler interface in the implementation code. However because the type PaypalHandler has the methods described in the interface, the compiler considers it an instance of PaymentHandler. This is completely similar to duck-typing, only
- You know exactly what methods your payment handler exposes so you can change the code more easily.
- There is compile-time check if the object exposes the methods with desired signatures
- The Interface Segregation Principle is automatically implemented, as the user code depends only on the interface it defines and does not know about the other methods the implementation has.
- If a third party code already fits the interface you expect, there is no need to wrap it inside an object that inherits your interface. You can just use it as an instance of that interface.
The Pythonic way to do this
Let’s take a small detour and look at Python’s solution to this issues. From version 3.5 Python supports type hints to provide documentation in the code and making it easier to understand or refactor Python code. Consider the subscription example again. We want to initialize SubscriptionManager object with a payment handler. What should we use in the code below as the HandlerTyep ?
While I have mostly seen two solutions with the shortcomings described above being used in Python projects (i.e. defining an abstract base class or using a Union type), the static duck typing does exist in Python too (version 3.8+). Protocol is the data type to describe what methods and/or fields you expect from an object. So we can define a protocol for payment handlers and use that as the HandlerType in the code above. (See an example here. This PEP describes the approach in more detail and compares it with Go’s)
Conclusion
Considering it is only about a decade old, Go has become quite popular among programmers. Some of the most popular technologies in the cloud world like Docker, Kubernetes and Prometheus are written in Go. So if you are looking to add a new tool to your toolbox, Go is probably a good choice. Especially if you’re most used to an interpreted and dynamic language like Python or Ruby, Go can diversify your toolbox as a compiled, static-typed language which runs much faster and has great concurrency support. With multiple options available, you can choose the most appropriate tool for each of your tasks and projects.
Finally, even if you do not code a lot in a new language, I believe the new concepts and patterns you learn in that language can help you code better in any other language.
References
The main resource I used while learning Go was the great book by Jon Bodner: Learning Go. I would highly recommend this book. Also I’ve learnt about Dependency Inversion mostly from Robert (Uncle Bob) Martin’s books and talks.
Features in Go you will like was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Level Up Coding - Medium and was authored by Mohsen Shojaee
Mohsen Shojaee | Sciencx (2022-04-19T14:30:23+00:00) Features in Go you will like. Retrieved from https://www.scien.cx/2022/04/19/features-in-go-you-will-like/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.