Administrator
发布于 2023-03-10 / 75 阅读
0
0

Golang---error

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的
image-20230310091746997

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的:

image-20230310092059906

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.

评论