11.1 Error handling

Go's major design considerations are rooted in the following ideas: a simple, clear, and concise syntax (similar to C) and statements which are explicit and don't contain any hidden or unexpected things. Go's error handling scheme reflects all of these principles in the way that it's implemented. If you're familiar with the C language, you'll know that it's common to return -1 or NULL values to indicate that an error has occurred. However users who are not familiar with C's API will not know exactly what these return values mean. In C, it's not explicit whether a value of 0 indicates success of failure. On the other hand, Go explicitly defines a type called error for the sole purpose of expressing errors. Whenever a function returns, we check to see whether the error variable is nil or not to determine if the operation was successful. For example, the os.Open function fails, it will return a non-nil error variable.

func Open(name string) (file * File, err error)

Here's an example of how we'd handle an error in os.Open. First, we attempt to open a file. When the function returns, we check to see whether it succeeded or not by comparing the error return value with nil, calling log.Fatal to output an error message if it's a non-nil value:

f, err := os.Open("filename.ext")
if err != nil {
log.Fatal(err)
}

Similar to the os.Open function, the functions in Go's standard packages all return error variables to facilitate error handling. This section will go into detail about the design of error types and discuss how to properly handle errors in web applications.

Error type

error is an interface type with the following definition:

type error interface {
Error() string
}

error is a built-in interface type. We can find its definition in the builtin package below. We also have a lot of internal packages which use error in a private structure called errorString, which implements the error interface:

In the following example, we pass a negative number to our Sqrt function. Checking the err variable, we check whether the error object is non-nil using a simple nil comparison. The result of the comparison is true, so fmt.Println (the fmt package calls the error method when dealing with error calls) is called to output an error.

f, err := Sqrt(-1)
if err != nil {
fmt.Println(err)
}

Custom Errors

Through the above description, we know that a go Error is an interface. By defining a struct that implements this interface, we can implement their error definitions. Here's an example from the JSON package:

It should be noted that when the function returns a custom error, the return value is set to the recommended type of error rather than a custom error type. Be careful not to pre-declare variables of custom error types. For example:

Using type assertion, we can check whether or not our error is of type net.Error, as shown in the following example. This allows us to refine our error handling -if a temporary error occurs on the network, it will sleep for 1 second, then retry the operation.

Error handling

Go handles errors and checks the return values of functions in a C-like fashion, which is different to how most of the other major languages do. This makes the code more explicit and predictable, but also more verbose. To reduce the redundancy of our error-handling code, we can use abstract error handling functions that allow us to implement similar error handling behaviour:

The above example demonstrate how the data access and template call has detected an error. When an error occurs , a call to unified handler http.Error, returns a 500 error code to the client , and displays the corresponding error data. But when more and more HandleFunc calls are made, so error-handling logic code will increase. We can customize the router to reduce code (refer to the third chapter of HTTP for more detail).

The error handler example above will return the 500 Internal Error code to users when any errors occur, in addition to printing out the corresponding error code. In fact, we can customize the type of error returned to output a more developer friendly error message with information that is useful for debugging like so:

As shown above, we can return different error codes and error messages in our views, depending on the situation. Although this version of our code functions similarly to the previous version, it's more explicit, and its error message prompts are more comprehensible. All of these factors can help to make your application more scalable as complexity increases.

Summary

Fault tolerance is a very important aspect of any programming language. In Go, it is achieved through error handling. Although Error is only one interface, it can have many variations in the way that it's implemented, and we can customize it according to our needs on a case by case basis. By introducing these various error handling concepts, we hope that you will have gained some insight on how to implement better error handling schemes in your own web applications.