Basically, error handling code and regular code have different purpose/aspect. So splitting error handling code from regular code is just introducing some type of "goto" structure essentially.
I like handle/check work locally inside function and are resticted not to escape between functions unlike exception are good point. But I think this design proposal may create a very complicated structure。
If there is only one handle per scope ({ }
), it is resonable, but there are many handles and/or have defers in same scope, make code difficult.
If we have to control error handling finely, error handling code and regular code are mixed like bunch of if err != nil
sentences.
Every handler should have return
statement and only one handler works that are defined at last.
func CopyFile(src, dst string) error {
// this handler works if os.Open, os.Create fail
handle err {
return fmt.Errorf("opening %s %s: %v", src, dst, err)
}
r := check os.Open(src)
defer r.Close()
w := check os.Create(dst)
// this handler works if io.Copy, w.Close fail
handle err {
w.Close()
os.Remove(dst)
return fmt.Errorf("copying %s %s: %v", src, dst, err)
}
check io.Copy(w, r)
check w.Close()
return nil
}
It makes clear order between handler and defers and regular code:
- Regular success code executes
- Some regular code executes and fails
- If there are scope nests (
{ }
) and defers between failure function call and the last defined handler, defers execute. - handle executes
- If there are other defers except step 3, they execute.