error is a type—the error type
the error
is a type ,like int 、string、bool
func calcRemainderAndMod(numerator, denominator int) (int, int, error) {
if denominator == 0 {
return 0, 0, errors.New("denominator is 0")
}
return numerator / denominator, numerator % denominator, nil}
type error interface {
Error() string
}
The Go compiler requires that allvariables must be read.
By making errors returned values forces developers to either check and handle error conditionsor make it explicit that they are ignoring errors by using anunderscore (_) for the returned error value.
create an error from a string
The first is the errors.New function. It takes in a string and returns an error.
The second way is to use the fmt.Errorf function. This function allows you to use all of the formatting verbs for fmt.Printf to create an error.
errors.New("denominator is 0")
fmt.Errorf("%d isn't an even number",i)
我们在这里,先看下golang中,如何定义errors的
package errors
// New returns an error that formats as the given text.
// Each call to New returns a distinct error value even if the text is identical.
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
}
接下来,我们再看看golang中,是如何定义fmt.Errorf的:
Sentinel error
Sentinel errors are usually used to indicate that you cannot start or continue processing.
For example, the standard library includes a package for processing zip files, archive/zip. This package defines several sentinel errors, including ErrFormat, which is returned when data that doesn’t represent a zip file is passed in.
package main
import (
"archive/zip"
"bytes"
"fmt"
)
func main() {
data := []byte("this is not a zip file")
notAZipFile := bytes.NewReader(data)
_, err := zip.NewReader(notAZipFile, int64(len(data)))
if err == zip.ErrFormat{
fmt.Println("told you so")
}
}
输出
told you so
我们看下上面的zip.ErrFormat,是什么?
var (
ErrFormat = errors.New("zip: not a valid zip file")
ErrAlgorithm = errors.New("zip: unsupported compression algorithm")
ErrChecksum = errors.New("zip: checksum error")
)
how to check sentinel error?
How do you test for a sentinel error? As you can see in our code sample above, use == to test if the error was returned when calling a function whose documentation explicitly says it returns a sentinel error
custom error with status code
package main
import (
"errors"
"fmt"
)
type Status int
const (
InvalidLogin Status = iota+1
NotFound
)
type StatusErr struct {
status Status
msg string
}
func (se StatusErr) Error() string {
return se.msg
}
func loginAndGetData(uid,pwd,file string) ([]byte,error) {
err:=login(uid,pwd)
if err != nil {
return nil, StatusErr{
status: InvalidLogin,
msg: fmt.Sprintf("invalid credentials for user:%s",uid),
}
}
data, err := getData(file)
if err != nil {
return nil, StatusErr{
status: NotFound,
msg: fmt.Sprintf("file not found:%s",file),
}
}
return data,nil
}
func login(uid,pwd string) error {
return errors.New("test login error")
}
func getData(file string) ([]byte,error){
return nil,errors.New("test getData error")
}
func main() {
_, err := loginAndGetData("1", "abc", "西游记")
fmt.Println(err)
}
输出
invalid credentials for user:1
这个示例,返回的error,同时包含了 string 字符串 和 错误码,表明了这个错误的具体类型和原因。
就是利用了StatusErr这个struct,并且让这个StatusErr实现了error接口
custom error—don’t return an uninitialized instance.
func GenerateError(flag bool) error {
var genErr StatusErr
if flag {
genErr = StatusErr{
Status: NotFound,
}
}
return genErr}
func main() {
err := GenerateError(true)
fmt.Println(err != nil) // true
err = GenerateError(false)
fmt.Println(err != nil)} // true
The reason why err is non-nil is that error is a interface.
As we discussed in “Interfaces and nil”, for an interface to be considered nil, both the underlying type and the underlying value must be nil.
Whether or not genErr is a pointer, the underlying type part of the interface is not nil
There are two ways to fix this.
Either explicitly return nil when no error occurs or define the variable to be of type error
func GenerateError(flag bool) error {
if flag {
return StatusErr{
Status: NotFound,
}
}
return nil // Either explicitly return nil
}
func GenerateError(flag bool) error {
var genErr error // define the variable to be of type error
if flag {
genErr = StatusErr{
Status: NotFound,
}
}
return genErr
}
errors.As
上面的示例中,返回的类型是error,而error是一个接口。如果我们自定义了一个StatusErr类型,这StatusErr中包括错误码,但是现在返回一个error接口类型的值。我们没办法,直接用这个返回值,调用StatusErr的方法,取出错误码。
所以,我们需要将error类型的值,转化为StatusErr类型的值。前面,我们学过了type assert 和 type switch ,但是针对于error这种场景,这2个并不好。这里介绍一个新的方式:errors.As
The errors.As function allows you to check if a returned error (or any error it wraps) matches a specific type.
It takes in two parameters.
The first is the error being examined and the second is a pointer to a variable of the type that you are looking for.
If the second parameter to errors.As is anything other than a pointer to an error or a pointer to an interface, the method panics.
If the function returns true, an error in the error chain was found that matched, and that matching error is assigned to the second parameter.
If the function returns false, no match was found in the error chain.
err := AFunctionThatReturnsAnError()
var myErr MyErr
if errors.As(err, &myErr) {
fmt.Println(myErr.Code)
}
You can pass a pointer to an interface to find an error that meets the interface.
err := AFunctionThatReturnsAnError()
var coder interface {
Code() int
}
if errors.As(err, &coder) {
fmt.Println(coder.Code())
}
wrap error & unwrap error
When an error is passed back through your code, you often want to add additional context to it. This context can be the name of the function that received the error or the operation it was trying to perform.
When you preserve an error while adding additional information, it is called wrapping the error. When you have a series of wrapped errors, it is called an error chain .
how to wrap error?
There’s a function in the Go standard library that wraps errors,and we’ve already seen it. The fmt.Errorf function has a special verb, %w.
how to unwrap error?
The standard library also provides a function for unwrapping errors, the Unwrap function in the errors package. You pass it an error and it returns the wrapped error, if there is one. If there isn’t, it returns nil.
package main
import (
"errors"
"fmt"
"os"
)
func main() {
err := fileChecker("not_here.txt")
if err != nil {
fmt.Println(err)
fmt.Println("------------")
wrappedErr := errors.Unwrap(err)
if wrappedErr != nil {
fmt.Println(wrappedErr)
}
}
}
func fileChecker(name string) error {
f, err := os.Open(name)
if err != nil {
return fmt.Errorf("in fileChecker %w", err)
}
f.Close()
return nil
}
输出
in fileChecker open not_here.txt: The system cannot find the file specified.
------------
open not_here.txt: The system cannot find the file specified.
调用一个方法,得到了一个error,除了上面说的,将error wrap一下,填充一些额外的信息,得到一个新的error。 还可以,直接创建一个error,不wrap 原始的error
If you want to create a new error that contains the message from another error, but don’t want to wrap it, use fmt.Errorf to create an error, but use the %v verb instead of %w
err := internalFunction()
if err != nil {
return fmt.Errorf("internal failure: %v", err)
}
errors.Is
前面,谈到了,一个error类型的值,不仅仅包含error msg,还可能wrap了很多的原始error,背后能牵扯出一个error chain。
那么,我们怎么能判断出,这个error chain中,是否包含某个指定类型的error呢?
这就需要用到errors.Is
了。
use errors.Is, To check if the returned error or any errors that it wraps match a specific sentinel error instance
It takes in two parameters, the error that is being checked and the instance you are comparing against. errors.Is returns true if there is an error in the error chain that matches the provided sentinel error.
package main
import (
"errors"
"fmt"
"os"
)
func main() {
err := fileChecker("not_here.txt")
if err != nil {
if errors.Is(err,os.ErrNotExist) {
fmt.Println("this file do not exist")
}
}
}
func fileChecker(name string) error {
f, err := os.Open(name)
if err != nil {
return fmt.Errorf("in fileChecker %w", err)
}
f.Close()
return nil
}
输出
this file do not exist
custom error—work for errors.Is
implement the Is method on your error
type MyErr struct {
Codes []int
}
func (me MyErr) Error() string {
return fmt.Sprintf("codes: %v", me.Codes)
}
func (me MyErr) Is(target error) bool {
if me2, ok := target.(MyErr); ok {
return reflect.DeepEqual(me,m2)
}
return false
}
circumstances for errors.is & errors.as
Use errors.Is when you are looking for a specific instance or specific values.
Use errors.As when you are looking for a specific type
use Defer wrapping multiple errors with the same message
func DoSomeThings(val1 int, val2 string) (
_ string, err error) {
defer func() {
if err != nil {
err = fmt.Errorf("in DoSomeThings: %w", err)
}
}()
val3, err := doThing1(val1)
if err != nil {
return "", err
}
val4, err := doThing2(val2)
if err != nil {
return "", err
}
return doThing3(val3, val4)
}
panic
panic是什么呢?和error是什么关系呢?
其实在Java中,异常,分为检查异常 和 运行时异常。比如IOException就是检查异常,而java.lang.ArithmeticException: / by zero
就是运行时异常。
同样的,对应到go中,error就是检查异常,而panic就是运行时异常。当发生运行时异常后,当前方法的调用栈会马上结束,并且继续向上抛出异常
Go generates a panic whenever there is a situation where the Go runtime is unable
to figure out what should happen next.
This could be due to a programming error (like an attempt to read past the end of a
slice) or environmental problem (like running out of memory).
As soon as a panic happens, the current function exits immediately and any defers attached to the current function start running.
When those defers complete, the defers attached to the calling function run, and so on, until main is reached.
The program then exits with a message and a stack trace.
package main
import "fmt"
func main() {
fmt.Println("main start-----------")
calculate(5, 0)
fmt.Println("main finish-----------")
}
func calculate(a, b int) int {
fmt.Println("before cal------------")
i := a / b
fmt.Println("after cal--------------")
return i
}
输出
main start-----------
before cal------------
panic: runtime error: integer divide by zero
goroutine 1 [running]:
main.calculate(0x5, 0x0)
E:/ProfessionalDoc/GoProject/raw-demo/src/petshop/PetShop.go:16 +0xe5
main.main()
E:/ProfessionalDoc/GoProject/raw-demo/src/petshop/PetShop.go:8 +0x65
上面的例子中,当panic发生时,后面的代码fmt.Println("after cal--------------")
,都不执行了,直接退出这个calculate函数的调用栈了
在业务代码中,当我们想要退出当前调用栈,抛出panic怎么做呢?
直接调用panic方法即可,如下:
package main
import "os"
func main() {
doPanic(os.Args[0])
}
func doPanic(msg string) {
panic(msg)
}
输出
panic: C:\Users\weipeng.b\AppData\Local\Temp\GoLand\___go_build_testError_go.exe
goroutine 1 [running]:
main.doPanic(...)
E:/lagouRes/Go/goBorn/demo1/src/wperror/testError.go:11
main.main()
E:/lagouRes/Go/goBorn/demo1/src/wperror/testError.go:6 +0x45
recover &defer function
The built-in recover function is called from within a defer to check if a panic happened. If there was a panic, the value assigned to the panic is returned. Once a recover happens, execution continues normally.
package main
import "fmt"
func main() {
nums := []int{1, 5, 0, 6}
for _, val := range nums {
div60(val)
}
}
func div60(i int) {
defer func() {
v := recover()
if v != nil {
fmt.Println(v)
}
}()
fmt.Println(60 / i)
}
输出
60
12
runtime error: integer divide by zero
10
You must call recover from within a defer because once a panic happens, only deferred functions are run.
use a recover to convert the panic into an error, return it, and let the calling code decide what to do with them
so, recover function provides a way to capture a panic in order to provide a more graceful shutdown or to prevent shutdown at all.
circumstance for panic & recover
Reserve panics for fatal situations and use recover as a way to gracefully handle these situations.
If your program panics, be very careful about trying to continue executing after the panic.
It’s very rare that you want to keep your program running after a panic occurs.
If the panic was triggered because the computer is out of a resource like memory or disk space, the safest thing to do is use recover to log the situation to monitoring software and shut down with os.Exit(1).
If there’s a programming error that caused the panic, you can try to continue, but you’ll likely hit the same problem again.
In the sample program above, it would be idiomatic to check for division by zero and return an error if one was passed in.
The reason we don’t rely on panic and recover is that recover doesn’t make clear what could fail. It just ensures that if something fails, we can print out a message and
continue.
Idiomatic Go favors code that explicitly outlines the possible failure conditions over shorter code that handles anything while saying nothing.
There is one situation where recover is recommended. If you are creating a library for third parties, do not let panics escape the boundaries of your public API.
If a panic is possible, a public function should use a recover to convert the panic
into an error, return it, and let the calling code decide what to do with them.