Thiago Cafe - Programming is fun !  
Created by thiago on 2019-06-14 21:05:08

Manual error handling

A common problem of Object oriented languages is the overuse of Exception. You never know which exceptions can be thrown and the error handling is super hard. How many times we see catch blocks that catch everything (Exception or Throwable) and do nothing specific ?

How Go solves this problem?

Go solves this problem having no Exception. However it has it's own problems because of that. Let's see a piece of code that is very error prone

func printLines(writer io.Writer) error {

	var e error
	_, e = fmt.Fprintln(writer, "Line 1")
	if e != nil {
		return e
	}
	_, e = fmt.Fprintln(writer, "Line 2")
	if e != nil {
		return e
	}
	_, e = fmt.Fprintln(writer, "Line 3")
	if e != nil {
		return e
	}
	_, e = fmt.Fprintln(writer, "Line 4") // Oops!
	_, e = fmt.Fprintln(writer, "Line 5")
	if e != nil {
		return e
	}

	return nil
}

It is quite easy to miss something. Also, the code is bloated with lots of repetition. I think it's common sense that repetitive code easily leads to bugs when one of the repetitions is forgotten.

Important context: I am super super new to Go.

We can see one pattern from this piece of code - and quite common in many other code blocks. There is a sequence of similar functions that only will be executed if all previous calls suceeded.

How to solve Go error handling problem

I had this problem today and I found a great way to solve. Monads to the rescue!

Long story short, a Monad is a structure that receives a function, data and returns another monad containing the result of this computation.

If you are curious, take a look on the Wikipedia definition for Monads

To start, I need a structure that holds previous errors that maybe happened. The writer property is there just to improve the syntax - and due to the poor Go type system

type printer struct {
	writer io.Writer
	err    error
}

And now we need something that checks if previous errors happened, executes a function, stores the result and returns itself.

Note that when err only is mutated once. If err is not nil we just bypass.

func (w *printer) Ln(a ...interface{}) Printer {
	// Let's only execute if no previous errors were seen
	if w.err == nil {
		_, w.err = fmt.Fprintln(w.writer, a...)
	}

	return w
}

// Returns the error set during the executions
func (w *printer) Err() error {
	return w.err
}

And now we can use the function

func printLines2(writer io.Writer) error {

	printer := NewPrinter(writer)
	printer.
		Ln("Line 1").
		Ln("Line 2").
		Ln("Line 3").
		Ln("Line 4")

	return printer.Err()
}

It is quite annoying that we cannot make something more generic and convenient as no meta programming is supported, but it is much much better than before.

Source code: monadic_writer.go

...

Tell me your opinion!


Please prove you are human.
What is the result of zero * three ?


Other comments

Bruno Leandro de Lima
on 2019-06-21

Excelent article, I've never used Monads before, I'll try to use it more on my future projects.

sofia
on 2019-06-15

I love it