Get Going with Go! : Part 2
Mandar Samant
Head Customer Engineering, Digital Natives- Google Cloud India Pvt.
Going Differently
In Part 1 of Get Going with Go! we covered how Go addresses the Object Oriented Programing paradigm and multi processing. In this second part, I will share how the Go syntax offers very interesting approaches to address common programing patterns.
Lets go..
defer() : defer() allows you to defer running a piece of code till after the current function ends. It is like wrapping the whole function code in a try- finally block where the deferred function executes like a final code block. You can defer multiple blocks of code in the same function, these are invoked in reverse order as the function exits. Besides closing handle to open resources e.g. files/sockets/database I found this interesting application in timing your functions ( see code snippet below). [ I always wanted to write a single line of code to wrap timing functionality. ].
func timeit(name string) func()
? ? start := time.Now()
? ? return func() {
? ? ? ? duration := time.Since(start)
? ? ? ? log.Printf("%s took %s", name, duration)
? ? } //return the function to be deferred
}
func ToBeTimed(sleepForSec time.Duration) {
? ? defer timeit("ToBeTimed")() //Single call to deferred function!!
? ? time.Sleep(sleepForSec)
? ? fmt.Println("Slept for", sleepForSec, " seconds")
}
....
ToBeTimed(1 * time.Second) //Invoke ToBeTimed.
...
Multiple Return Values: Another lifesaver. Go allows functions to return multiple values. So instead of creating kludgy temporary structs or using function parameters ( with pointers) as proxies to return multiple results you can just return multiple values. If the caller needs to ignore some of the return values you can use "_" as a placeholder.
// Returns an int and a string
func returnMultiple() (int, string) {
? ?
? ? return 1, "hello"
}
//...in the caller
var first int
var second string
first, second = returnMultiple() //access both return values
_, second = returnMultiple() //ignore first return value
? first, _ = returnMultiple() //ignore second return value
//...
Handling Errors and Panic!: Errors are part of programming reality ( and life.. ) and understanding the core error handling patterns is important before programming any complex piece of code. Instead of throwing and catching exceptions, Golang functions expose/expect errors in 2 ways: using one of the multiple return values to indicate an error or panic-ing. For errors which the called function expects or non-fatal ones, the functions are called with returnValue, ok = funcName() metaphor. The caller can inspect the value of ok before processing the returnValue ( see code snippet below). For unexpected inputs the called functions may panic() which unwinds the stack ( like when an Exception is thrown in Java/C# ) and terminates the program if it reaches the top of the stack. This unwinding calls the deferred functions along the way up and you can detect and trap the panic using the, aptly named, recover() function in one of the deferred blocks. Sample code demonstrates the concept below. The panic in the IWillPanic function is trapped in the caller's deferred anonymous function and trapped so that the program is not killed.
// Error handling : Checking Errors using multiple return values
data, ok := db[key]
? ? if !ok {
? ? ? ? log.Printf("Unknown key: %q", key)
? ? ? ? return
? ? }
//=================
//Handling Panics
//Wraps a function which can panic
func SafelyCallIWillPanic() {
? ? defer func() {
? ? ? ? if err := recover(); err != nil { //check if you are recovering from a panic
? ? ? ? ? ? log.Println("IWillPanic, panicked, but will recover now: \n", err)
? ? ? ? }
? ? }()
? ?
IWillPanic()
}
func IWillPanic() {
? ? panic("I am good at panicking")
}
...
SafelyCallIWillPanic()
Runes : This one is a interesting fellow. Golang gives the traditional char /wchar an upgrade, rune. A rune type represents a Unicode code point and makes it easier to process a wider character set. The following snippet demonstrates the difference between byte vs rune representation of a string "café" .
fmt.Println(len([]rune("café"))) // will print 4
fmt.Println(len([]byte("café"))) // will print 5
Interesting to note that Golang authors Rob Pike and Ken Thompson implemented the first utf-8 standard for text encoding.
iota : Enumerated constants are created using an?iota?. Enums in Go, which are coded with using a const keyword (!), can be seeded with iota and it automatically increments itself in the context.
领英推荐
const (
small = iota //0
medium //1
large //2
extraLarge //3
)
switch : Switch construct is pretty powerful in Golang e.g. case expressions need not be constants. case blocks interestingly don't fall through automatically ( there is no need of a break ). You can model if-then-else with switch statements ( because.. why not !). Sample snippet below demonstrates 2 styles of using switch.
func SwitchDemo(Age int) {
? ? switch {
? ? case Age < 12:
? ? ? ? fmt.Println("Not permitted")
? ? case Age >= 12 && Age < 18:
? ? ? ? fmt.Println("Permitted with parent consent")
? ? default:
? ? ? ? fmt.Println("Permitted")
? ? }
// 'Traditional' approach
? ? switch Age {
? ? case 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11:
? ? ? ? fmt.Println("Not permitted")
? ? case 12, 13, 14, 15, 16, 17, 18:
? ? ? ? fmt.Println("Permitted with parent consent")
? ? default:
? ? ? ?fmt.Println("Permitted")
? ? }
}
That mostly covers some the interesting unicorns and bunnies you will encounter in the Go rabbit hole. There are a few more and Effective Go is a mandatory and looong, read for anyone who wants to seriously consider adopting 'Go', I would suggest you read it at least twice. Now that the 'What' aspect is covered lets move back in time to briefly understand 'Why' Go.
"WHY" Go
If you are going to adopt a new programming language/runtime then it is good ( ..if not important) to understand 'WHY' was it needed, the raison d'être. One good place to start is this recorded presentation by Carmen Andoh (2017) which goes into the history of Go, a summary takeaway is that the Go authors felt the need for a simpler language to make writing concurrent applications easier ( hence the focus on 'go routines') and benefit from range of multi-core microprocessors which had become affordable and commonplace by then. A more inside view is provided by Robert Griesmer in The Evolution of Go talk at 2015 Gophercon on how the language evolved into what it is today. Gophercon is the annual Go community convention and quick search on your favorite search engine will point to you insightful presentations by Go SMEs.
Conclusion
The language continues to evolve as it sees wider adoption and utility, the recent addition of Generics is a good example. Somewhere I feel the core philosophy of having a 'simple' approach to solving tough challenges is getting tested, but clearly, Go is not different for sake of being different and it makes the programmers life a bit easier.
I hope this gave you a high level view of Go as a programming language, it does take a bit of unlearning to learn to like Go. Taking Go into production involves many more aspects - DevSecOps, team development support, programming standards, tools and tactics and so on. The Go community and large OEM players in the industry have, over last few years, addressed most of the aspects. There are some areas where traditional platforms have evolved further than Go or enjoy more, say, tooling support but I expect Go to catch up in its own unique way. Do leave your thoughts, suggestions in the comments below. Happy Programming and Get Going!!
[The views expressed are my own. I am not affiliated to any paid web publisher.. in case any external links in the article are behind a pay wall.]
Linkedin Top Data Architecture Voice | Mentor freshers, experienced to level up their careers | AWS, Ex (Google, Oracle, IBM) | Public Speaking | Writer, Author for blog publications
2 年Cool thoughts and artifacts to make "programmers life a bit easier". Great write-up!!