So I'm just getting my feet wet with Go, and I'm trying to cleanly handle errors in the Go-way. One result is that having a type Movie struct
with methods to update the record, and sync to a data store. So an example method:
func (movie Movie) SetTitle(title string) : error {
prevTitle := movie.Title
movie.Title = title
json, err := json.Marshal(movie)
if (err != nil) {
movie.Title = prevTitle
return err
}
err = db.SetValue(movie.id, json)
if (err != nil) {
movie.Title = prevTitle
return err
}
return nil
}
The issue above is the 'cleanup' for a failed operation is to save the previous state, and restore as appropriate. But this feels very unmaintainable - it's so easy for a new error check to be made in the future, only to return without the proper cleanup. This would get even worse with multiple such 'cleanups'.
So to contrast, in other languages, I would wrap the whole bit in a try/catch, and handle cleanup just inside a single catch block. Here, I can:
- Create a copy of
movie
, do all the stuff on the copy, and then only copy back to the original object once everything is successful - Change the logic to:
if json, err := json.Marshal(movie); err == nil {
if err = db.SetValue(...); err == nil {
return nil
}
}
movie.Title = prevTitle;
return err
Which works, but I don't love having a level of nesting per check.
- Change the error return to indicate it was updated locally, but not saved
- Do it as described above
- Break out the save logic into an
func Update() : err
function to minimize the number of checks needed (just thought of this - think I like this one)
I feel like none of these are perfect; am I missing something obvious?