Tigraine

Daniel Hoelbling-Inzko talks about programming

Why I love Go error handling

Posted by Daniel Hölbling on April 21, 2022

One thing that Go almost forces you to do is to explicitly handle each and every error that any random part of the system might create. This has one very obvious side effect of making even simple code quite long and peppered with if err != nil statements:

func writeFile() error {  

   fileName := "test.txt"  
   f, err := os.Open(fileName)  
   if err != nil {  
      return fmt.Errorf("failed to open file %s: %w", fileName, err)  
   }  
   defer f.Close()  

   d1 := []byte("hello\ngo\n")  
   _, err = f.Write(d1)  
   if err != nil {  
      return fmt.Errorf("unable to write to file %s: %w", fileName, err)  
   }  
   return nil  
}

As you can see a simple open-file-and-write requires 2 error checks that add almost 50% of lines of code to this rather simple method. Because of this most Java/C#/C++ people you show Go code to almost always react with aversion and distaste and never give the language another chance (although I think this is now changing gradually).

But I actually think this is Go's biggest strength and a boon to developers. By having errors be so "in your face" - you have to do something about exceptions. In languages with Exceptions traversing up the call stack it's all too easy to just expect someone up the food chain to catch your exception. All too often that doesn't happen or if it does it's a very generic "catch-everything" block that can only log the problem without having any chance to actually recover from it.

Go in contrast makes you think about every error in detail and how it affects the current control flow. A classic example of this would be a for loop that calls some method. I've seen all too often bugs because people didn't put a try catch inside the loop, so the first problem that arises (and most likely it's a very rare thing that happens) stops execution of the loop and you are then wondering why you're missing half your data or something like that. If you have to really think hard about each error, you're much more likely to also think about how it's affecting the code you're currently writing, so in Go I find myself writing continue and break a lot more frequently than I usually do in Java/Kotlin.

Another case where conscious error handling is very handy in my opinion is when making an application resilient to failures downstream (see my recent post on Bulkheads). Only if you have useful error handling in place on all levels of the application can you start building logic that responds to these errors (without having to go on a archeological excavation of the whole call stack).

Obviously you have to have some discipline in your errors, just doing return err won't do you any favours here. But I find the way Go requires error handling also tends to promote more deliberate throwing of errors that carry actually useful information up the call stack (because you need that info to handle them up there). If that's then in place you can also make much better decisions on how to treat these errors in failure scenarios and when deciding if a CircuitBreaker should trip or not etc.

Filed under golang, errors
comments powered by Disqus

My Photography business

Projects

dynamic css for .NET

Archives

more