当前位置:网站首页>Golang error handling

Golang error handling

2022-07-01 18:51:00 leeo

To acquaint you with golang error Relevant knowledge .

0. introduction

In this article , I'll show you golang error Relevant knowledge , The general content is as follows :

  1. golang error Comparison with other languages , Advantages and disadvantages
  2. golang Various types of errors in and recommended error types
  3. How to handle errors
  4. Yes go1.13 The error package of
  5. Yes golang error Summarize relevant knowledge 、 carding
  6. Thinking questions

If there are any errors in this article 、 Or you can put forward some good ideas in time , I hope we can make progress together .

notes : All the code in this article is in :https://github.com/driftingboy/advance-go/tree/master/master_go/erroruse

1.Error vs Exception

1.1 Error The essence

  • Error It's essentially an interface
type error interface{
    Error() string
}
  • Regular use errors.New() To return to a error object

For example, in the standard library error Definition , adopt bufio Prefix with context information

var (
 ErrInvalidUnreadByte = errors.New("bufio: invalid use of UnreadByte")
 ErrInvalidUnreadRune = errors.New("bufio: invalid use of UnreadRune")
 ErrBufferFull        = errors.New("bufio: buffer full")
 ErrNegativeCount     = errors.New("bufio: negative count")
)
  • errors.New() It's back error Object pointer

In order to prevent error When comparing , because error The same definition of internal content leads to two different types error Misjudge equality

1.2 Error and Exception The difference between

The evolution of languages :

  • C: Generally, the pointer is passed , By returning int Value to judge success or failure
  • C++: There is no way to know what exception is thrown , Whether to throw an exception ( Only through documents )
  • JAVA: If you need to throw an exception, the owner of the method must declare , The caller must also handle . Processing mode 、 The degree of severity is distinguished by the caller .
  • GO: Do not introduce exception, Return with multiple parameters , Generally, the last return value is error

error or panic: If you did java You will feel go Medium panic Processing is very similar java Throw in exception, Then we are using error still panic?

stay Go in panic Will cause the program to exit directly , It was a fatal mistake , If you use panic recover If you deal with it , There will be many problems 1) Performance issues , frequent panic recover Poor performance 2) It is easy to cause the program to exit abnormally , As long as one place is not handled, it will cause the whole program process to exit 3) Out of control , once panic The processing logic is handed over to the outside , We cannot assume that external packages will be processed When to use panic Well ? For really unexpected situations , Those that indicate unrecoverable program errors , For example, the index is out of bounds 、 Unrecoverable environmental problems 、 Stack overflow , We use panic

Use error Instead of exception The benefits of

  • Simple
  • Considering failure is not success
  • There is no hidden control flow
  • error are value

2.Error Type

2.1 Sentinel Error

ErrInvalidUnreadByte = errors.New("bufio: invalid use of UnreadByte")

Predefined errors , shortcoming :

  • inflexible , Caller use == To compare whether the error values are equal ; Once it appears fmt.Errorf This kind of carrying context information error, It will destroy the equality check
  • Become your public api; such as io.reader,io.copy Such functions need to judge whether the error type is io.eof, But this is not a mistake .
  • Create a dependency between the two packages

2.2 Error Types

Error types Is to implement the error Custom type of interface . for example MyError The type records the file and line number to show what happened :

os Under bag error type :https://golang.org/src/os/error.go

// PathError records an error and the operation and file path that caused it.
type PathError struct {
 Op   string
 Path string
 Err  error
}

func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }

func (e *PathError) Unwrap() error { return e.Err }

Error types advantage :

  • Carry more context

Error types shortcoming :

  • The caller uses type assertions and types switch, Let the customized error Turn into public. This model will lead to strong coupling with the caller , Which leads to API Become vulnerable .
  • share error values Many of the same problems .

2.3 Opaque Error

When you start using errors.Cause(err, sql.ErrNoRows) or xerrors.Is(err, sql.ErrNoRows) when , Means sql.ErrNoRows As implementation details are exposed to the outside world , It became API Part of .

If you only use library code for business development , The judgment after packaging can be understood and accepted .

And for API For the definer of , This problem needs special attention , We need to Opaque error handling . Its advantage is : Reduce coupling between codes , The caller only needs to care about success or failure , Don't worry about the inside of the error

//  Just return an error without assuming its contents 
func fn()error{
  x, err := bar.Foo()
  if err != nil {
    return err
  }
  // to do something
}

To put it bluntly, it just doesn't pass err To judge various situations , As a caller, I only care about success or failure (err Is it nil)

Assert errors for behaviour, not type

In a few cases , This dichotomy is not enough ( Only success 、 Two states of failure ).

such as net Operations in the package :https://golang.org/src/net/net.go

// An Error represents a network error.
type Error interface {
 error
 Timeout() bool   // Is the error a timeout?
 Temporary() bool // Is the error temporary?
}
if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
   // to do something
}
type temporary interface {
  Temporary() bool
}
//  You can use it directly without importing packages err Related actions
func IsTemporary(err error) bool {
  te, ok := err.(temporary)
  return ok && te.Temporary()
}

Any error can be passed to IsTemporary To determine if the error can be retried .

If the error is not implemented temporary Interface ; in other words , It has no Temporary Method , So mistakes are not temporary .

If the error does happen Temporary, So if true return true , The caller can retry the operation .

The key here is , This logic can define the wrong package without importing , Or know anything about err Based on the type of implementation - We're just interested in its behavior .

I suggest , Try to avoid Sentinel Error、error types, To use the Opaque Error, At least in the library file 、 public api in .


3.Handing Error Gracefully

3.1 Try to eliminate errors handle

1) Without destroying the correctness and readability of the program , Try to reduce error Handle

Patients with a

BadGood
func AuthRequest() error{
if err:= auth();err!=nil{
return err
}
return nil
}
func AuthRequest(r, *Request) error{
return auth(r.User)
}

Example 2

There are often a lot of if error != nil {...} This code , It can be optimized in some special scenarios

Let's start with a crunchy piece of code .

func parse(r io.Reader) (*Point, error) {
    var p Point
    if err := binary.Read(r, binary.BigEndian, &p.Longitude); err != nil {
        return nil, err
    }
    if err := binary.Read(r, binary.BigEndian, &p.Latitude); err != nil {
        return nil, err
    }
    if err := binary.Read(r, binary.BigEndian, &p.Distance); err != nil {
        return nil, err
    }
    if err := binary.Read(r, binary.BigEndian, &p.ElevationGain); err != nil {
        return nil, err
    }
    if err := binary.Read(r, binary.BigEndian, &p.ElevationLoss); err != nil {
        return nil, err
    }
}

We can see that this code is mainly calling binary.Read() Method , We can refer to bufio.Scanner The operation of

scanner := bufio.NewScanner(input)

for scanner.Scan() {
    token := scanner.Text()
    // process token
}

if err := scanner.Err(); err != nil {
    // process the error
}

Errors will be saved to Scanner in , Only make the last judgment . Apply this idea , The optimization code is as follows

type Reader struct {
    r   io.Reader
    err error
}

func (r *Reader) read(data interface{}) {
    if r.err == nil {
        r.err = binary.Read(r.r, binary.BigEndian, data)
    }
}

func parse(input io.Reader) (*Point, error) {
    var p Point
    r := Reader{r: input}

    r.read(&p.Longitude)
    r.read(&p.Latitude)
    r.read(&p.Distance)
    r.read(&p.ElevationGain)
    r.read(&p.ElevationLoss)

    if r.err != nil {
        return nil, r.err
    }

    return &p, nil
}

But this optimization scenario is limited , It can only be applied to the continuous operation of the same business object , For a variety of business objects and functions, we still have to be honest if err != nil

3.2 Annotating errors

In addition to returning the error directly , We often bring more context information to the error and then return , Because enough information can let us solve the problems quickly . The following will give error The operation of adding context is called error notes

at present error notes This is done as follows :

  • fmt.Errorf Get a formatted error, Can carry extra error messages
  • Customize error, By implementing Error() Method
  • Wrong packing , adopt pkg/errors".Wrap

How to choose the right error notes The way ? The following points need to be considered :

  1. Whether it needs to be captured by the caller 、 Handle
  • No need to be captured by the caller 、 Handle ; Use fmt.Errorf
  func Open(filePath string) error {
    _, err := createfile(filePath)
    return fmt.Errorf(“createfile: %s“, err)
  }
  • It needs to be captured by the client 、 Handle

Use fmt.Errorf, It will destroy the equality check , As a result, the error message string can only be used to compare whether the errors are equal , Very unreliable ; We can customize the error type , While carrying the context , Provide the caller with a reliable way to judge ; however , Customization errors are quite tedious , We can use pkg/errors This package simplifies operations . as follows :

  // file package
  var FileNotExsist = “file path not exsist: %s“
  func Open(filePath string) error {
    _, err := createfile(filePath)
    return errors.warp(err, "createfile fail: ")
  }

Here is a brief introduction to this bag github.com/pkg/errors, Using it to package downstream information is almost a golang Standard practice in .

It's very easy to use , If we want to generate a new error , have access to New function , Generated errors , With call stack information .

// fundamental is an error that has a message and a stack, but no caller.
type fundamental struct {
 msg string
 *stack
}

func New(message string) error {
 return &fundamental{
  msg:   message,
  stack: callers(),
 }
}

there fundamental Objects are also implemented golang The built-in interface error Of Error Method .

If there is a ready-made error, We need to repack him , There are three functions to choose from .

// Just add new information ,  It is generally used to replace  fmt.Errorf()
func WithMessage(err error, message string) error

// Only call stack information , Generally used for packaging on third-party codes ( Standard library or third-party library ) Call to .
func WithStack(err error) error

// Add stack and information at the same time , Generally used for packaging on third-party codes ( Standard library or third-party library ) Call to .
func Wrap(err error, message string) error

In fact, the package on it , Very similar to Java The abnormal packing of , Packaged error, Is the root cause of this mistake . So this error handling library provides us with Cause Function allows us to get the most primitive error object .

func Cause(err error) error {
 type causer interface {
  Cause() error
 }

 for err != nil {
  cause, ok := err.(causer)
  if !ok {
   break
  }
  err = cause.Cause()
 }
 return err
}

Use for The cycle has always found the most fundamental ( At the bottom ) the error.

We have packed all the above mistakes , I've also collected , So how to put the stack in them 、 The reason for the error and so on are printed out ? Actually , The error type of this error handling Library , It's all done Formatter Interface , We can go through fmt.Printf The function outputs the corresponding error message .

  • %s,%v // Function as , Output error message , Does not contain stack
  • %q // The output error message is quoted , Does not contain stack
  • %+v // Output error messages and stacks

Don't pack incorrectly many times , Stack information will repeat .

If you use it many times WithStack(err), Will stack Print multiple times ,err The information can be very long . It's human flesh check Is it used in the lower level WithStack(err), If you use the lower layer, you don't use the upper layer . But it would add to the mental burden , It's easy to make mistakes . We can use a wrap function , Judge whether it has been carried out WithStack(err). however github.com/pkg/errors Self defined error type withStack Is private type , How to judge whether it has been implemented WithStack(err) Well ? Fortunately StackTrace It's not a private type , So we can use it interface A little trick for , Define one for yourself interface, If you have StackTrace() Method is no longer executed WithStack(err). like this :

type stackTracer interface {
    StackTrace() errors2.StackTrace
}

// once
func WithStack(err error) error {
    _, ok := err.(stackTracer)
    if ok {
        return err
    }

    return errors2.WithStack(err)
}

3.3 Only handle errors once

Handling an error means inspecting the error value, and making a decision.

I quote dave.cheney This sentence of , intend “ Dealing with errors means that you have checked the errors and made a decision ”.

If you make a decision Less than one , So you didn't check for errors 、 Errors are ignored , as follows :

func Write(w io.Writer, buf []byte) {
        w.Write(buf)
}

w.Write(buf) Of error Discarded

If you make a decision about a problem More than one , There are also problems , as follows :

func Write(w io.Writer, buf []byte) error {
        _, err := w.Write(buf)
        if err != nil {
                // annotated error goes to log file
                log.Println("unable to write:", err)
 
                // unannotated error returned to caller
                return err
        }
        return nil
}

In the example above We record the error log , The error is returned to the caller , The returned error may be recorded layer by layer , Up to the top .

Finally, we will get a pile of repeated log information , But at the top, you can only get one of the most primitive mistakes .

We can choose according to different needs error notes ( Check the specific selection method [3.2](3.2 Annotating errors)), Back to the caller

func Write(w io.Write, buf []byte) error {
        _, err := w.Write(buf)
        return errors.Wrap(err, "write failed")
}

4.Golang1.13 error

Combined with community feedback ,Go The team finished at Go 2 A proposal to simplify error handling in . Go Core team members Russ Cox stay xerrors The content of the proposal is partially realized in . It is used with github.com/pkg/errors Solve the same problem with similar ideas , Introduced a new fmt Format verb : %w, Use Is Judge .:

import (
   "database/sql"
   "fmt"

   "golang.org/x/xerrors"
)

func bar() error {
   if err := foo(); err != nil {
      return xerrors.Errorf("bar failed: %w", foo())
   }
   return nil
}

func foo() error {
   return xerrors.Errorf("foo failed: %w", sql.ErrNoRows)
}

func main() {
   err := bar()
   if xerrors.Is(err, sql.ErrNoRows) {
      fmt.Printf("data not found, %v\n", err)
      fmt.Printf("%+v\n", err)
      return
   }
   if err != nil {
      // unknown error
   }
}
/* Outputs:data not found, bar failed: foo failed: sql: no rows in result set
bar failed:
    main.bar
        /usr/four/main.go:12
  - foo failed:
    main.foo
        /usr/four/main.go:18
  - sql: no rows in result set
*/

And github.com/pkg/errors comparison , It has several shortcomings :

  • Use : %w Instead of Wrap It seems simplified , But the compile time check is lost . If there is no colon , or : %w Not at the end of the format string , Or there is no space between colon and percent , The package will be invalid without error

  • More serious , call xerrors.Errorf Parameters need to be nil Judge . This does not actually simplify the work of developers

here we are Go 1.13 ,xerrors Some functions of are integrated into the standard library . It inherited xerrors All the disadvantages of , And contributed an additional : Call stack information output is not supported . According to the official statement , There is no clear schedule for this function . Therefore, its practicability is far lower than github.com/pkg/errors

Therefore, it is not necessary to use it at present


5.go 2 inspection

https://go.googlesource.com/proposal/+/master/design/29934-error-values.md

go2 Add sum type, That is, a type can represent multiple types ; It's kind of similar protoBuffer Of oneof type ;

var int|error result

So this result Or int, Or error type ;

This also solves go There are some problems with generic proxies in ;

6. summary

Last , Let me help you sort out the key points of this article , That is, what we need to master at present

// TODO picture

The above figure is in the business code and library file respectively 、api The code uses error The advice of

panic

  • When the program starts , If a strongly dependent service fails panic sign out
  • When the program starts , If it is found that the configuration obviously does not meet the requirements , Sure panic sign out ( Defense programming )
  • In other cases, as long as it is not an unrecoverable program error , Should not directly panic Should return to error
  • At the program entrance , for example gin Middleware needs to use recover The prevention of panic Program exit
  • We should avoid using wild goroutine
  • If you need to execute asynchronous tasks in the request , Asynchronous should be used worker , Message notification , Avoid a large number of requests goroutine establish
  • If needed goroutine when , You should use the same Go Function to create , In this function recover , Avoid wild goroutine panic Cause the main process to exit
func Go(f func()){
    go func(){
        defer func(){
            if err := recover(); err != nil {
                log.Printf("panic: %+v", err)
            }
        }()

        f()
    }()
}

error

  1. We use it in our applications github.com/pkg/errors Handling application errors , Pay attention to the Public Library , We don't usually use this
  2. error It should be the last return value of the function , When error Not for nil when , Other return values of the function are unavailable , You should not expect anything from other return values
  3. When dealing with errors, you should first judge the errors , if err != nil If there is an error, return in time , Make the code a smooth straight line , Avoid excessive nesting
  4. When an error occurs in the application , Use errors.New perhaps errors.Errorf Returns an error

func (u *usecese) usecase1() error { money := u.repo.getMoney(uid) if money < 10 { errors.Errorf(" The user balance is insufficient , uid: %d, money: %d", uid, money) } // Other logic return nil } 5. If there is an error calling other functions of the application , Please return directly , If you need to carry information , Please use errors.WithMessage

func (u *usecese) usecase2() error {
    name, err := u.repo.getUserName(uid)
    if err != nil {
        return errors.WithMessage(err, " Additional information ")
    }

    //  Other logic
    return nil
}
  1. If it is to call other libraries ( Standard library 、 Enterprise public library 、 Open source third-party libraries, etc ) When getting an error , Please use errors.Wrap Add stack information

  2. Bear in mind , Don't use it everywhere errors.Wrap It only needs to be done when the error first occurs errors.Wrap that will do

  3. Judge whether it is necessary to swallow the original errors of other libraries according to the scene , For example, you can put repository Layer database related errors swallow , Return the business error code , Avoid our subsequent segmentation of micro services or replacement ORM You need to modify the upper code when Library

  4. Note that we are in the basic library , Third party libraries introduced in large numbers are generally not used when writing errors.Wrap Avoid stack information duplication

  5. It is forbidden to log every error , Just use it at the beginning of the process %+v Print uniformly , for example http/rpc Service-Oriented Middleware

  6. Wrong judgment use errors.Is Compare

func f() error {
    err := A()
    if errors.Is(err, io.EOF){
        return nil
    }

    //  Other logic
    return nil
}
  1. Wrong type judgment , Use errors.As Assign a value
func f() error {
    err := A()
    if errA := new(errorA) && errors.As(err, &errA){
        // ...
    }

    //  Other logic
    return nil
}
  1. How to determine whether the wrong information is sufficient , Think about whether your error information can help you locate the problem quickly when there is a problem in your code that needs to be checked , For example, we usually output parameter information in the request , Used to assist in judging errors

  2. For business errors , It is recommended to create an error dictionary in a unified place , The error dictionary should contain the wrong code, And print it as a separate field in the log , It is convenient to judge the business alarm , Errors must be clearly documented

  3. No need to return , The ignored error must output log information

  4. It should only be handled once , Output error log is also considered as processing , Once the function is determined / Method will handle errors , Mistakes are no longer mistakes . If the function / Method still needs to issue a return , Then it cannot return an error value . It should only return zero ( such as Degraded processing in , You returned degraded data , And then you need to return nil).

  5. For the same type of error , Use the same pattern , For example, parameter error , Don't return 404 Some return 200

  6. When dealing with mistakes , Need to deal with allocated resources , Use defer Clean up , For example, file handles

  7. Try to avoid Sentinel Errorerror types, To use the Opaque Error, At least in the library file 、 public api in .

Recently, I am also designing 、 Reconstruction of the company's microservice error 、 Log related infrastructure , If you have the time , I will also write practical articles .

7. Thinking questions

  • When we operate the database , such as dao When a sql.ErrNoRows When , Whether it should be Wrap This error, Throw it to the top . Why? , Please write the code for what you should do ?

  • Why is it not allowed to use everywhere errors.Wrap ?

  • errors.wrap/ WithMessage What's the use , Why not use the standard library fmt.Errorf("%w")

reference

https://blog.csdn.net/u012516914/article/details/110848774 https://zhuanlan.zhihu.com/p/328591249 https://github.com/xxjwxc/uber_go_guide_cn

https://time.geekbang.org/column/article/330207?utm_source=u_nav_web&utm_medium=u_nav_web&utm_term=u_nav_web

https://dave.cheney.net/2012/01/18/why-go-gets-exceptions-right https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully https://dave.cheney.net/2015/01/26/errors-and-exceptions-redux https://dave.cheney.net/2014/11/04/error-handling-vs-exceptions-redux https://rauljordan.com/2020/07/06/why-go-error-handling-is-awesome.html https://morsmachine.dk/error-handling https://blog.golang.org/error-handling-and-go https://www.ardanlabs.com/blog/2014/10/error-handling-in-go-part-i.html https://www.ardanlabs.com/blog/2014/11/error-handling-in-go-part-ii.html https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully https://commandcenter.blogspot.com/2017/12/error-handling-in-upspin.html https://blog.golang.org/errors-are-values https://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package

https://www.ardanlabs.com/blog/2017/05/design-philosophy-on-logging.html https://crawshaw.io/blog/xerrors https://blog.golang.org/go1.13-errors https://medium.com/gett-engineering/error-handling-in-go-53b8a7112d04 https://medium.com/gett-engineering/error-handling-in-go-1-13-5ee6d1e0a55c

go2

原网站

版权声明
本文为[leeo]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/182/202207011731470994.html