Error is the most basic and important part of Go language development. It is basically the same as the role of try catch in other languages. Think about the fact that in PHP JAVA development, try catch can't be used or used flexibly, so you can't perceive what errors occur in program operation. It's a particularly terrible thing.
Error basis
The error type in Golang is the most basic interface, which defines an Error() method
type error interface { Error() string }
The most commonly used is this
errors.New("error")
In Golang, errors New is defined this way
func New(text string) error { return &errorString{text} } // errorString is a trivial implementation of error. type errorString struct { s string } func (e *errorString) Error() string { return e.s }
In fact, an errorString structure is returned, which implements the Error() method, so the error interface is implemented
Let's see how Error is used in project development?
1. Define the Error variable
Many errors may be returned in a piece of code. How can I judge which error is?
Is that right
var ERR_MSG = "error" if err.Error() == ERR_MSG
In this case, if the error descriptions of multiple third-party class libraries and their own projects are consistent, they cannot be compared. In fact, this should not be the case.
Let's see how orm is defined in beego. From the above basis, we know errors New returns a pointer to errorString
var ( ErrTxHasBegan = errors.New("<Ormer.Begin> transaction already begin") ErrTxDone = errors.New("<Ormer.Commit/Rollback> transaction not begin") ErrMultiRows = errors.New("<QuerySeter> return multi rows") ErrNoRows = errors.New("<QuerySeter> no row found") ErrStmtClosed = errors.New("<QuerySeter> stmt already closed") ErrArgs = errors.New("<Ormer> args error may be empty") ErrNotImplement = errors.New("have not implement") )
In fact, they all use pointers to judge
See how to use it. Here is the pseudo code
err := this.QueryTable(this.table).Filter("id", id).One(data) if err != nil && err != orm.ErrNoRows { return err } return nil
In fact, this method is often used in Golang source code or third-party class libraries. The disadvantage is coupling. When using a third-party class library, the caller needs to know the error types in its code, and also needs to use these error type variables for comparison in the project. It is difficult for developers who use it for the first time to think of using it like this.
2. Customize your own Error
In the past, PHP project Exception defined its own error code.
In Golang, we can also define our own Error type, and then use assertions to determine which Error to obtain more Error data. Take a look at the following example code to understand the simple use of custom Error
type SelfError struct { Code int Err error } func (this *SelfError) Error() string { return this.Err.Error() } func (this *SelfError) GetCode() int { return this.Code } func OpenFile(name string) error { err := os.Rename("/tmp/test","/tmp/test1") if err != nil { return &SelfError{-1001, err} } return nil } func main() { err := OpenFile("test") switch erro := err.(type) { case nil: fmt.Println("success") case *SelfError: fmt.Println(erro.Error(),erro.Code) case error: fmt.Println(erro.Error()) } }
Another usage is to judge whether the error type is user-defined. If so, return the user-defined attribute
func main() { err := OpenFile("test") serr, ok := err.(*SelfError) if ok { fmt.Println(serr.GetCode()) } }
It can be seen that assertions are used to determine whether the error is a user-defined error. If so, the user-defined error's own properties and methods are used.
Coupling, the caller needs to use switch or assertion to use the custom Error attribute.
3. Use of wrap errors
The use of wrap errors should be one of the most used in the project to deal with errors, which can be easily added to the context of use.
Wrap Errors, as the name suggests, is to wrap error layer by layer. The outermost layer gets a stack information of error. According to the stack information, you can always track the first calling code that causes error.
This package is required
github.com/pkg/errors
Take a look at the code example
package main import ( "fmt" "github.com/pkg/errors" "os" ) func ModelFile() error { err := os.Rename("/tmp/test","/tmp/test1") if err != nil { return errors.Wrap(err, "model_rename_fail") } return nil } func LogicFile() error { err := ModelFile() if err != nil { return errors.Wrap(err, "logic_rename_fail") } return nil } func main() { err := LogicFile() if err != nil { fmt.Printf("error:%v", errors.Cause(err)) fmt.Printf("%+v", err) } }
Look at the stack of execution results
error:rename /tmp/test /tmp/test1: no such file or directoryrename /tmp/test /tmp/test1: no such file or directory model_rename_fail main.ModelFile /data/www/go/src/test1/main.go:12 main.LogicFile /data/www/go/src/test1/main.go:18 main.main /data/www/go/src/test1/main.go:26 runtime.main /usr/local/go/src/runtime/proc.go:203 runtime.goexit /usr/local/go/src/runtime/asm_amd64.s:1357 logic_rename_fail main.LogicFile /data/www/go/src/test1/main.go:20 main.main /data/www/go/src/test1/main.go:26 runtime.main /usr/local/go/src/runtime/proc.go:203 runtime.goexit /usr/local/go/src/runtime/asm_amd64.s:1357
Simple rules to use
With so many usage methods, which one should be used? The general suggestion should be like this
- When it is necessary to compare the wrong types, it must be used in the first way, and there is no better way at present
- If you need to add your own project error code or some complex context, you may need to use the second custom error type
- If you need to rely on a third-party class library, which may be unstable, wrap error has obvious advantages. You can print the record stack for easy positioning.
- For some common simple projects, you only need to record the context where the error is triggered, print a log, and directly return error, which is the simplest and most convenient.