220 lines
8.4 KiB
Plaintext
220 lines
8.4 KiB
Plaintext
[1]Stephen's Tech Blog
|
||
|
||
• [2]Webhook Wizard 🧙♂️
|
||
|
||
Gopher Wrangling. Effective error handling in Go
|
||
|
||
June 19, 2023 · 4 min · Stephen Nancekivell
|
||
Table of Contents
|
||
|
||
□ [3]Guiding principle
|
||
• [4]1. Always handle errors
|
||
• [5]2. Log errors in one layer
|
||
• [6]3. Returning async errors
|
||
• [7]4. Wrapping errors
|
||
• [8]5. Downgrade errors Warnings
|
||
|
||
When programming in Go, the amount of error handling is something that slaps
|
||
you in the face. Most API’s you deal with will expose errors. It can become
|
||
overwhelming, but with a few tips and a guiding principle we can make handling
|
||
errors easy, keep our code clean and give you the confidence that nothing is
|
||
breaking in production.
|
||
|
||
A cartoon of a crazy stressed programmer pulling their hair out in front of
|
||
lots of screens showing a error exclamation marks
|
||
|
||
A cartoon of a crazy stressed programmer pulling their hair out in front of
|
||
lots of screens showing a error exclamation marks
|
||
|
||
Guiding principle[9]#
|
||
|
||
The goal for our error handling strategy is that it should require minimal
|
||
effort and provide an easy way to debug any errors that do occur.
|
||
|
||
We wont cover strategies like retrying because they are less common and also
|
||
expose errors.
|
||
|
||
1. Always handle errors[10]#
|
||
|
||
Always handle errors. Sometimes it’s tempting to skip one, you might not expect
|
||
that error to ever happen. But that’s why it’s an exception! You need to handle
|
||
it so that you can find out clearly if it ever does happen.
|
||
|
||
If you don’t handle the error, the expected value will be something else and
|
||
just lead to another error that will be harder to debug, or worse it could lead
|
||
to data corruption.
|
||
|
||
In most cases to handle the error all you need to do is return it to the caller
|
||
of your method, where they can log it.
|
||
|
||
For example, when refreshing some data you might load it, then save it. If you
|
||
skip the error handling it could overwrite potentially useful data with corrupt
|
||
data.
|
||
|
||
👎 Bad error handling
|
||
|
||
func refresh() {
|
||
bytes, _ := loadData()
|
||
saveData(bytes)
|
||
}
|
||
|
||
👍 Good error handling
|
||
|
||
func refresh() error {
|
||
bytes, err := loadData()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
saveData(bytes)
|
||
}
|
||
|
||
2. Log errors in one layer[11]#
|
||
|
||
You always want to log your errors, ideally to something that will notify you
|
||
about the error, so you can fix it. There is no point logging the error
|
||
multiple times at every layer. Make it the top layer’s responsibility and don’t
|
||
log in any services or lower level code.
|
||
|
||
Make sure your logging framework is including stack traces so you can trace the
|
||
error to its cause.
|
||
|
||
For example in a web app you would log the error in the http handler when
|
||
returning the Internal Server status code.
|
||
|
||
👍 Good error handling
|
||
|
||
func refresh() error {
|
||
bytes, err := loadData()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
saveData(bytes)
|
||
}
|
||
|
||
func (h *handlers) handleRefreshRequest(w http.ResponseWriter, r *http.Request) {
|
||
err := refresh()
|
||
if err != nil {
|
||
log.Error("unexpected error processing request %w", err)
|
||
w.WriteHeader(http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
w.WriteHeader(http.StatusOK)
|
||
}
|
||
|
||
3. Returning async errors[12]#
|
||
|
||
When processing data concurrently using a go-func’s, it can be annoying to
|
||
return the error. But if you don’t your app will be less maintainable. To
|
||
handle async errors, return them via a channel to the calling thread.
|
||
|
||
👎 Bad error handling
|
||
|
||
func refreshManyConcurrently() {
|
||
go func(){
|
||
refresh(1)
|
||
}()
|
||
|
||
go func(){
|
||
refresh(2)
|
||
}()
|
||
}
|
||
|
||
👍 Good error handling
|
||
|
||
func refreshManyConcurrently() error {
|
||
errors := make(chan error, 2)
|
||
go func(){
|
||
errors <- refresh(1)
|
||
}()
|
||
|
||
go func(){
|
||
errors <- refresh(2)
|
||
}()
|
||
return multierror.Combine(<-errors, <- errors)
|
||
}
|
||
|
||
When calling functions that return a value and a possible error using a type
|
||
like Result[T], to wrap the response to pass on the channel.
|
||
|
||
type Result[T any] struct {
|
||
Value T
|
||
Error error
|
||
}
|
||
|
||
4. Wrapping errors[13]#
|
||
|
||
Sometimes you want to add additional context to an error message. Eg to include
|
||
the id of the request that caused the error. You can use fmt.error for this.
|
||
|
||
err := saveToDb(user)
|
||
if err != nil {
|
||
return fmt.errorf("unexpected error saving user. userId=%v error=%w", user.Id, err)
|
||
}
|
||
|
||
Usually this isn’t necessary and its better to just return the error unwrapped.
|
||
|
||
5. Downgrade errors Warnings[14]#
|
||
|
||
There are types of errors that regularly occur during normal operation. The
|
||
system might not be able to prevent them all the time, but they don’t need to
|
||
investigate every time. It is better to treat them as warnings rather than
|
||
errors. These might be for things like timeouts or intermittent connection
|
||
errors.
|
||
|
||
👍 Good error handling
|
||
|
||
func (h *handlers) handleRefreshRequest(w http.ResponseWriter, r *http.Request) {
|
||
err := refresh()
|
||
if err != nil {
|
||
if err == context.DeadlineExceeded {
|
||
log.Warn("Timeout error processing request %w", err)
|
||
} else {
|
||
log.Error("unexpected error processing request %w", err)
|
||
}
|
||
|
||
w.WriteHeader(http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
w.WriteHeader(http.StatusOK)
|
||
}
|
||
|
||
[15]« Prev
|
||
PDFs on the Fly: Programmatically Transforming Webpages into PDFs [16]Next »
|
||
How to Serve Web Sockets with Http4s
|
||
[17][18][19][20][21][22]
|
||
[23]© 2023 [24]Stephen's Tech Blog
|
||
[25][26][27][28]
|
||
|
||
References:
|
||
|
||
[1] https://stephenn.com/
|
||
[2] https://webhookwizard.com/
|
||
[3] https://stephenn.com/2023/06/gopher-wrangling.-effective-error-handling-in-go/#guiding-principle
|
||
[4] https://stephenn.com/2023/06/gopher-wrangling.-effective-error-handling-in-go/#1-always-handle-errors
|
||
[5] https://stephenn.com/2023/06/gopher-wrangling.-effective-error-handling-in-go/#2-log-errors-in-one-layer
|
||
[6] https://stephenn.com/2023/06/gopher-wrangling.-effective-error-handling-in-go/#3-returning-async-errors
|
||
[7] https://stephenn.com/2023/06/gopher-wrangling.-effective-error-handling-in-go/#4-wrapping-errors
|
||
[8] https://stephenn.com/2023/06/gopher-wrangling.-effective-error-handling-in-go/#5-downgrade-errors-warnings
|
||
[9] https://stephenn.com/2023/06/gopher-wrangling.-effective-error-handling-in-go/#guiding-principle
|
||
[10] https://stephenn.com/2023/06/gopher-wrangling.-effective-error-handling-in-go/#1-always-handle-errors
|
||
[11] https://stephenn.com/2023/06/gopher-wrangling.-effective-error-handling-in-go/#2-log-errors-in-one-layer
|
||
[12] https://stephenn.com/2023/06/gopher-wrangling.-effective-error-handling-in-go/#3-returning-async-errors
|
||
[13] https://stephenn.com/2023/06/gopher-wrangling.-effective-error-handling-in-go/#4-wrapping-errors
|
||
[14] https://stephenn.com/2023/06/gopher-wrangling.-effective-error-handling-in-go/#5-downgrade-errors-warnings
|
||
[15] https://stephenn.com/2023/06/pdfs-on-the-fly-programmatically-transforming-webpages-into-pdfs/
|
||
[16] https://stephenn.com/2022/07/web-sockets-with-http4s/
|
||
[17] https://twitter.com/intent/tweet/?text=Gopher%20Wrangling.%20Effective%20error%20handling%20in%20Go&url=https%3a%2f%2fstephenn.com%2f2023%2f06%2fgopher-wrangling.-effective-error-handling-in-go%2f&hashtags=
|
||
[18] https://www.linkedin.com/shareArticle?mini=true&url=https%3a%2f%2fstephenn.com%2f2023%2f06%2fgopher-wrangling.-effective-error-handling-in-go%2f&title=Gopher%20Wrangling.%20Effective%20error%20handling%20in%20Go&summary=Gopher%20Wrangling.%20Effective%20error%20handling%20in%20Go&source=https%3a%2f%2fstephenn.com%2f2023%2f06%2fgopher-wrangling.-effective-error-handling-in-go%2f
|
||
[19] https://reddit.com/submit?url=https%3a%2f%2fstephenn.com%2f2023%2f06%2fgopher-wrangling.-effective-error-handling-in-go%2f&title=Gopher%20Wrangling.%20Effective%20error%20handling%20in%20Go
|
||
[20] https://facebook.com/sharer/sharer.php?u=https%3a%2f%2fstephenn.com%2f2023%2f06%2fgopher-wrangling.-effective-error-handling-in-go%2f
|
||
[21] https://api.whatsapp.com/send?text=Gopher%20Wrangling.%20Effective%20error%20handling%20in%20Go%20-%20https%3a%2f%2fstephenn.com%2f2023%2f06%2fgopher-wrangling.-effective-error-handling-in-go%2f
|
||
[22] https://telegram.me/share/url?text=Gopher%20Wrangling.%20Effective%20error%20handling%20in%20Go&url=https%3a%2f%2fstephenn.com%2f2023%2f06%2fgopher-wrangling.-effective-error-handling-in-go%2f
|
||
[23] https://stephenn.com/2023/06/gopher-wrangling.-effective-error-handling-in-go/#top
|
||
[24] https://stephenn.com/
|
||
[25] https://github.com/stephennancekivell
|
||
[26] https://twitter.com/hi_stephen_n
|
||
[27] https://www.linkedin.com/in/stephen-nancekivell-77003039
|
||
[28] https://stackoverflow.com/users/893854/stephen
|