Skip to content

Instantly share code, notes, and snippets.

@jfeng45
Last active November 9, 2022 11:44
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jfeng45/416d4ecb42a4df50bebf86ce1c41c668 to your computer and use it in GitHub Desktop.
Save jfeng45/416d4ecb42a4df50bebf86ce1c41c668 to your computer and use it in GitHub Desktop.
A simplified Go 2 error handling solution with no handler

I propose a simplified Go 2 error handling solution with no handler, which can solve 80% of the problems.  Here is an example of the current error handling code:

// Top layer
func main() {
   src :="test"
   f, err := funcLevel1(src)
   if err != nil {
     fmt.Println(err)
   }
 fmt.Println(f.Name())
}

// first layer
func funcLevel1(src string) (*os.File, error) {
    f, err := funcLevel2(src)
    if err != nil {
      return nil, err
    }
    return f, nil
}

// second layer
func funcLevel2(src string) (*os.File, error) {
    f, err := funcLevel3(src)
    if err != nil {
      return nil, err
    }
    return f, nil
}

// last layer
func funcLevel3(src string) (*os.File, error) {
    f, err := os.Open(src)
    if err != nil {
       return nil, err
    }
    return f, nil
}

There are 4 layers of it. The last layer is the one that generate the error and the top layer is the one handles the error, the second and the third are the middle layers in the calling chain. "Go 1" solution are fine with the top layer, which needs to return meaningful message to the user, and the last layer, which generates the error.

The problems we need to solve is in the middle layer, which is not related to the error (which is generated by last layer), but they are forced to check the error and handle them. Because they don't know much about the error, usually they have no valuable information to add. Most of the time, they just propagate the error to the upper level.

The following are the proposed solution:

func main() {
    src :="test"
    f, err := funcLevel1(src)
    if err != nil {
      fmt.Println(err)
    }
    fmt.Println(f.Name())
}

func funcLevel1(src string) (*os.File, error) {
    f, err := funcLevel2(src)?
    return f, nil
}

func funcLevel2(src string) (*os.File, error) {
    f, err := funcLevel3(src)?
    return f, nil
}

func funcLevel3(src string) (*os.File, error) {
    f, err := os.Open(src)
    if err != nil {
      return nil, err
    }
    return f, nil
}

I didn't change the code in top and last level, only changed the middle level. The simplest solution is to add "?", which is better then "check", but if you insist on "check", I can live with it. The key is to remove the error handler. I don't need error handler here, the "?" will check the results (of the function call), if there is an error, it returns the error to the calling function and set the other values in the return parameters to be nil or default value; otherwise, it will go to the next statement. Depending on the coding style, removeing error handle part could be very important. I usully write code with small functions. In that case, the current draft doesn't necessarily make the code simpler.

The benefit: 

  1. It is compatible with the current solution ( Go 1). If you don't use it, you still have your current code.
  2. It will compatible with future error handling solution ( Go 2) if we add them later. You can add error handlers later to catch errors, if there is no error handler, then it returns errors to the calling function.
  3. It solves most of my error handling problems. Because even with the current draft, it probably won't help much for the top level and last level, which do need error handling. And most of the middle layer problem will be solved by this solution. 

Why is "?" better than "check"?

  1. The code flows better. Because "?" is suffix (rather than prefix), then at the end of the function call, you get the return and decide whether to return the error to the upper level or go to the next statement ( you don't do that at the beginning of the function call).
  2. It is easier to distinguish it with the surrounding texts, just compare the two:
    funcLevel3(src)?
    check funcLevel3(src)
  3. It is more concise then "check"

We can get this simplified solution out the door first, then take time to craft the more complete solution. Actually, I probably will be fine even just with this simple solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment