1. Basic application of errors
The errors package is a relatively simple package, including the common creation of an error object or obtaining the text content in the error through methods. In essence, in the builtin type, error is defined as an interface. This type only contains an error method, which returns the error content in the form of a string.
The application code is simple:
// Sample codefunc Oops() error { return ("iam an error") } func Print() { err := Oops() ("oops, we go an error,", ()) }
Through methods, an error object can be created. In the standard library implementation, it corresponds to an entity type called errorString, which is the most basic implementation of the error interface.
2. Comparison of error types
There are often judgments such as err == nil or err == ErrNotExist in the code. For the error type, since it is an interface type, the actual comparison is the address of the interface interface object entity.
In other words, the two repeated new objects with the same text content are not equal, because the addresses of these two objects are compared. These are two completely different objects
// Show error comparison codeif ("hello error") == ("hello error") { // false } errhello := ("hello error") if errhello == errhello { // true }
In normal scenarios, if you can master the comparison of(),() and error objects, you can cope with most scenarios. However, in large systems, the built-in error type is difficult to meet the needs, so the following is the extension of error.
III. Expansion of error
3.1 Custom error
Go allows functions to have multiple return values, but usually you don't want to write too many return values on the function definition (looks ugly), while the built-in errorString type of standard library is obviously limited because it can only express string error information. Therefore, the error return can be extended by implementing the error interface
// Custom error typetype EasyError struct { Msg string // Error text information Code int64 // Error code} func (me *EasyError) Error() string { // Of course, you can also customize the returned string, for example // return ("code %d, msg %s", , ) return } // Easy implements the error interface, so it can be returned in Oopsfunc DoSomething() error { return &EasyError{"easy error", 1} } // Business Applicationsfunc DoBusiness() { err := DoSomething() e,ok := err.(EasyError) if ok { ("code %d, msg %s\n", , ) } }
Now Error code information is stuffed into the custom error type. As business code calls go deeper, when an error occurs in the innermost operation (such as database operations), we hope to carry error information on each layer of the business call chain, just like recursive calls. At this time, the Unwrap method of the standard library can be used.
3.2 Unwrap and Nested error
Once your custom error implementation type defines the Unwrap method, it has the ability to nest, and its function prototype is defined as follows:
// The standard library Unwrap method passes in an error object and returns its embedded errorfunc Unwrap(err error) error // Custom Unwrap methodfunc (me *EasyError) Unwrap() error { // ... }
Although the error interface does not define the Unwrap method, the Unwrap method of the standard library implicitly calls the custom type Unwrap method through reflection, which is also a way for businesses to implement custom nesting. We add an error member to EasyError, indicating the next level error contained
// type EasyError struct { Msg string // Error text information Code int64 // Error code Nest error // Nested error} func (me *EasyError) Unwrap() error { return } func DoSomething1() error { // ... err := DoSomething2() if err != nil { return &EasyError{"from DoSomething1", 1, err} } return nil } func DoSomething2() error { // ... err := DoSomething3() if err != nil { return &EasyError{"from DoSomething2", 2, err} } return nil } func DoSomething3() error { // ... return &EasyError{"from DoSomething3", 3, nil} } // You can clearly see the error message generated on the call chain// Output: // code 1, msg from DoSomething1 // code 2, msg from DoSomething2 // code 3, msg from DoSomething3 func main() { err := DoSomething1() for err != nil { e := err.(*EasyError) ("code %d, msg %s\n", , ) err = (err) // Unwrap that calls EasyError returns child error } }
The output is as follows
$ ./sample
code 1, msg from DoSomething1
code 2, msg from DoSomething2
code 3, msg from DoSomething3
In this way, the error information in the call path can be carried to the bottom of the call stack through nesting in the deep call chain.
For different modules, the error information returned is very different. For example, the network communication module expects the error information to carry the http status code, while the data persistence layer expects to return SQL or redis comment. With the division of modular functions, each submodule may define its own custom error type. At this time, to distinguish different categories of errors in the business, you can use the Is method
3.3 Methods and Error Classification
Taking network errors and database errors as examples, we define two structures that implement error interfaces NetworkError and DatabaseError respectively.
// Error type returned by the network interfacetype NetworkError struct { Code int // 10000 - 19999 Msg string // Text information Status int // http status code} // Error type returned by the database module interfacetype DatabaseError struct { Code int // 20000 - 29999 Msg string // Text error message Sql string // sql string }
NetworkError and DatabaseError both implement the Error method and the Unwrap method, so the code will not be repeated. The division of error types leads to changes in the processing of errors by upper-level services: the business layer needs to know what happened to provide users with appropriate prompts, but they do not want to be too detailed. For example, what users expect to see is "data access exception", "please check network status", and do not want users to see technical error messages such as "unknown column space in field list..." and "request timeout...". At this time, the Is method comes in handy.
Now we add a Code error code to both network or database errors, and artificially divide the error code intervals. [10000, 20000) indicates network error, [20000, 30000) indicates database error, we expect that the error code can be known at the business levelWhether it containsFor network errors or data access errors, Is methods need to be added to two error types:
var( // Reserve 10000 and 20000 for judging error code intervals in Is method ErrNetwork = &NetworkError{EasyError{"", 10000, nil}, 0} ErrDatabase = &DatabaseError{EasyError{"", 20000, nil}, ""} ) func (ne NetworkError) Is(e error) bool { err, ok := e.(*NetworkError) if ok { start := / 10000 return >= 10000 && < (start+1)*10000 } return false } func (de DatabaseError) Is(e error) bool { err, ok := e.(*DatabaseError) if ok { start := / 10000 return >= 10000 && < (start+1)*10000 } return false }
Similar to Unwrap, the Is method is also called implicitly by the method. Let’s take a look at the business code.
func DoNetwork() error { // ... return &NetworkError{EasyError{"", 10001, nil}, 404} } func DoDatabase() error { // ... return &DatabaseError{EasyError{"", 20003, nil}, "select 1"} } func DoSomething() error { if err := DoNetwork(); err != nil { return err } if err := DoDatabase(); err != nil { return err } return nil } func DoBusiness() error { err := DoSomething() if err != nil { if (err, ErrNetworks) { ("Network exception") } else if (err, ErrDatabases) { ("Data access exception") } } else { ("everything is ok") } return nil }
Execute DoBusiness, the output is as follows:
$ ./sample
Network exception
Through the Is method, a batch of error information can be classified and relevant information can be hidden from the application. After all, most of the time, we do not want users to see the error SQL statement directly.
3.4 Methods and error message reading
Now that the classification is implemented through Is, we can determine whether an error is a certain type, but further, what if we want to get detailed information about different error types? When the business layer gets the returned error, it has to obtain the deep error information in the call chain through layers of Unwrap and type assertions. Therefore, the errors package provides the As method. Based on Unwrap, it directly obtains the error interface, which is actually an error of the specified type in the error chain.
So based on DatabaseError, define a RedisError type as the type that encapsulates redis access exception
// The error type returned by the Redis module interfacetype RedisError struct { EasyError Command string // redis commend Address string // redis instance address } func (re *RedisError) Error() string { return }
At the business layer, try to read the details of the database and redis error
func DoDatabase() error { // ... return &DatabaseError{EasyError{"", 20003, nil}, "select 1"} } func DoRedis() error { // ... return &RedisError{EasyError{"", 30010, nil}, "set hello 1", "127.0.0.1:6379"} } func DoDataWork() error { if err := DoRedis(); err != nil { return err } if err := DoDatabase(); err != nil { return err } return nil } // Execute business codefunc DoBusiness() { err := DoDataWork() if err != nil { if rediserr := (*RedisError)(nil); (err, &rediserr) { ("Redis exception, commend : %s, instance : %s\n", , ) } else if mysqlerr := (*DatabaseError)(nil); (err, &mysqlerr) { ("Mysql exception, sql : %s\n", ) } } else { ("everything is ok") } }
Run DoBusiness, the output is as follows
$ ./sample
Redis exception, commend : set hello 1, instance : 127.0.0.1:6379
conclusion
- error is an interface type, which can implement custom error types.
- Error supports chain-like organization, and traversal of error chains is achieved through custom Unwrap.
- Used to determine whether an error belongs to a certain type of error. The classification method can be implemented in the Is method of custom error.
- It can also be used to determine whether an error belongs to a certain error, avoids explicit assertion processing, and returns the details of the error information expressed using this type of error.
- Whether it is Is or As methods, you will try to call the Unwrap method to recursively find the error, so if you have Nesty errors, you must implement the Unwrap method to match correctly.
Through these means, error handling can be enriched without intruding into the business interface, which is the convenience brought by the errors package.
Summarize
The above is personal experience. I hope you can give you a reference and I hope you can support me more.