I created this module for Golang to help me with error handling. This idea showed when a colleague of mine was thinking to pass context
from handler to service, to repository to trace the error log more verbose. I disagreed with him because I thought that’s not what context
is for. Perhaps I was wrong or he was wrong, or maybe both of us wrong because this is our first Golang project that deployed on production.
Despite our opinion about context
, we both agreed that Golang’s error handling is too verbose and bulky. It makes us read more error handling more than read the system flow itself. Then I remember when I was coding using Java/Kotlin that I always use throws
, throw
, and try-catch block
to handle any errors.
Hmm…
Why don’t I create it for Golang?
Then I create this try-catch block
module for Golang called taboo
. Because I know this thing creates polemic among Golang developer but, then I thought why don’t I give it a try?
For the design itself, I was inspired by this article but the whole implementation is all adjusted with my current needs. Instead of error
, this module is based on panic
and recover
so it is quite dangerous whenever used in the wrong condition.
Let’s take an example:
package main
func div(a, b int) int {
return a / b
}
func main() {
div(10, 0)
}
When b
is filled with zero
, it causes panic
panic: runtime error: integer divide by zero
goroutine 1 [running]:
main.div(...)
/tmp/anon-org/taboo/cmd/example.go:4
main.main()
/tmp/anon-org/taboo/cmd/example.go:8 +0x12
Process finished with exit code 2
So, we can handle it using taboo
like this:
taboo.Try(func() {
div(10, 0)
}).Catch(func(e *taboo.Exception) {
fmt.Println(e.Error())
}).Do()
taboo
will catch the panic
and try to recover
it and make a stack of error called taboo.Exception
to trace error more verbose. So the program ends like this:
main.div:9 runtime error: integer divide by zero
Process finished with exit code 0
Quite handy right?
Then, what if I want to throw or rethrow the error to the first caller?
package main
import (
"errors"
"fmt"
"github.com/anon-org/taboo/pkg/taboo"
)
func div(a, b int) int {
if b == 0 {
taboo.Throw(errors.New("division by zero detected"))
}
return a / b
}
func callDiv() int {
var result int
taboo.Try(func() {
result = div(10, 0)
}).Catch(func(e *taboo.Exception) {
e.Throw("callDiv rethrow this error")
}).Do()
return result
}
func callCallDiv() int {
var result int
taboo.Try(func() {
result = callDiv()
}).Catch(func(e *taboo.Exception) {
e.Throw("callCallDiv rethrow this error")
}).Do()
return result
}
func main() {
taboo.Try(func() {
callCallDiv()
}).Catch(func(e *taboo.Exception) {
fmt.Println(e.Error())
}).Do()
}
e.Throw(message)
Will wrap the previous exception, and throw it again to the previous caller. So the printed error will be like this:
main.callCallDiv:34 callCallDiv rethrow this error caused by:
main.callDiv:22 callDiv rethrow this error caused by:
main.div:11 division by zero detected
Process finished with exit code 0
It just like try-catch block
in Java/Kotlin I think but with many flaws lol. This module is still an experiment that I myself is not going to using this module in production. Or perhaps should I?
Top comments (2)
This is a common pattern from devs coming from Java/C++/C#/Python/etc. It's not a problem of language design, it's a conflict of expected pattern. The go pattern ends up being fewer lines of code (45 vs 36), even if it handles the error at the top of the call chain instead of the bottom:
The different paradigms from the languages cause us to assume things that aren't necessarily true. Go-style error handling in Java/Python is incredibly verbose, because it's more thorough than the Java/Python pattern. That's neither good nor bad, it's simply a difference of expectations.
Exceptions in Java/Python are considered catastrophic until handled (the infamous "unhandled exception error"), while Go considers them routine until promoted to an exception (panic).
yes, I totally agree with you, because it's natural to be verbose for Go. But since using panic could stop the program itself, it is quite dangerous if the user missed 1 wrapper unless you can provide panicHandler.