Study hard go: 16 Errors and exceptions

Time:2021-12-31

>>Original address

Learn what

  1. What is wrong?

  2. How do I create an error?

  3. How to handle errors?

  4. errorsUse of packages?

  5. What is an exception?

  6. How to handle exceptions?

  7. deferThe role of keywords?

  8. recoverUse of functions?

What is wrong

When writing code, not all cases can be handled. For the logic that cannot be handled, you need to use the error mechanism to tell the upper caller.

In go language, errors are treated as an interface type, which is not used like other languagestry/catchTo capture, simply pass a wrong type variable between functions or methods.

Create error

The creation error mentioned here is actually to implement the error interface. The interface is as follows:


type  error  interface {

 Error() string

}

This interface is built in the go standard package. All error types created need to implement this interface. How to implement the interface? If you don’t understand, see the previous articleInterface

1. errors.New

The go language has a built-in standard package for handling errors. You don’t need to implement it yourselferrorInterface, which has functions to help you handle, as follows:


import  "errors"

var  ErrNotFound = errors.New("not found")

ImporterrorsPackage, callingNewFunction creates an error and saves it toErrNotFoundVariable, the error message isnot found

2. fmt.Errorf

fmtThere is also a function to create errors in the standard packageErrorf, this function can use placeholders to set error messages, such aserrors.NewFunctions are more flexible.

import  "fmt"

var  ErrHuman = fmt. Errorf ("% s does not meet our human requirements", "Lao Miao")

3. Custom error type

If the above two methods are not flexible enough, you can customize the error type.

type  ErrorPathNotExist  struct {

Filename string

}

func (*ErrorPathNotExist) Error() string {

   Return "the file path does not exist."

}

var  ErrNotExist  error = &ErrorPathNotExist{

Filename: "./main.go",

}
  • Customized aErrorPathNotExistStructure, which realizeserrorInterface.

  • Created aErrNotExistWrong type variable.

If you don’t understand how to apply this, don’t worry, look down.

Supplementary knowledge points: if the receiver of the method is not used, it can be directly omitted, for example:func (*ErrorPathNotExist), if not omitted, this is it:func (e *ErrorPathNotExist)Of course, you can also use the underscore “” Replacing “e” is just not necessary.

Print error

In project development, errors are often carried through the return value of functions or methods, and the return position is usually placed in the last bit.

// error/file.go

package main

import  "io/ioutil"

//Read file contents

func  LoadConfig() (string, error) {

 filename := "./config.json"

 b, err := ioutil.ReadFile(filename)

 if err != nil {

 return  "", err

    }

 content := string(b)

 if  len(content) == 0 {

 return  "", errors. New ("content is empty")

    }

 return content, nil

}
  • ReadFileFunction to read the contents of the “config. JSON” file.

  • (string, error)Returns two values, the first is the file content and the second is an error.

  • err != nilUsed to judge whether there is an error, if anyreturnDirect return.

  • string(b)The type of variable B is[]byte, the operation is to[]byteType tostring

  • An error judgment of “empty content” is added, which can also be directly saved to variables and returned.

var  ErrEmpty = errors. New ("content is empty")

func  LoadConfig() (string, error) {

// ...

 return  "", ErrEmpty

// ...

}

Now, assuming that the “config. JSON” file does not exist, callLoadConfigFunction to see the result.

package main

import (

 "fmt"

 "log"

)

func  main() {

 content, err := LoadConfig()

 if err != nil {

        log.Fatal(err)

    }

    fmt. Println ("content:", content)

}

//Output

2021/09/23  16:57:25 open ./config.json: The system cannot find the file specified.
  • WhenerrNot equal tonilPrint error and exit the program.

  • logThe standard package contains a set of functions for printing logs.

  • log.FatalFunction prints the log and terminates the program execution downward.

  • The output error message shows that the specified file was not found.

  • When printing errors, you can also usefmtPackage, for example:fmt.Println(err)No, just output informationlogBao duo.

os.Exit

This function notifies the program to exit, and the logic after this function will not be executed. When calling, you need to specify the exit code. When it is 0, it indicates that you exit the program normally.


os.Exit(0)

Do not actively call this function, that is, when the program ends naturally from the main function, the default exit code is 0. When using the writing tool, you may see the successful exit code information. For example, I use Golan. After executing the code, the following information will be displayed at the end of the output result.


Process finished with exit code 0

If you exit abnormally, the exit code is non-0, and 1 is usually used to indicate an unknown error.


os.Exit(1)

in uselog.FatalFunction is called internallyos.Exit(1)

Error processing

1. Wrong splicing

When an error is returned, if you want to carry an additional error message, you can usefmt.Errorf, modify nowLoadConfigFunction.

func  LoadConfig() (string, error) {

 filename := "./config.json"

 b, err := ioutil.ReadFile(filename)

 if err != nil {

 return  "", fmt. Errorf ("error reading file:% v", ERR)

    }

 // ...

}

%vThe placeholder represents the value of the obtained data. In this section, it represents an error message. The use of the placeholder will be explained in detail later.

Now re execute the above proceduremainFunction, or assume that the “config. JSON” file does not exist.

// ...

content, err := LoadConfig()

if err != nil {

    log.Fatal(err)

}

//...

//Output

2021 / 09 / 24 11:37:33 error reading file: open/ config. json: The system cannot find the file specified.

LoadConfigFunction returnserrThe variable carries an additional error message, but there is a problem. The additional error and the original error message are mixed together, so they cannot be separated.

2. Error nesting and errors Unwrap

The above-mentioned “mixed error messages lead to inseparability” problem. If you don’t understand it, you can take another look at this one and you should suddenly be enlightened.

Study hard go: 16 Errors and exceptions

Error nesting is similar to the figure above,err1Nested classerr2err2You can also continue nesting. If you want to start fromerr1Get inerr2Just peel one layer, like onions, and look inside layer by layer.

How to implement this nested relationship, or usefmt.ErrorfFunction, just use another placeholder%wwMy full English name iswrap

Continue to modifyLoadConfigFunction, as follows:

func  LoadConfig() (string, error) {

 filename := "./config.json"

 b, err := ioutil.ReadFile(filename)

 if err != nil {

 return  "", fmt. Errorf ("error reading file:% w", ERR)

    }

 // ...

}

Do it nowmainFunction, that is, callLoadConfigThis function and prints the error.

2021 / 09 / 24 18:07:14 error reading file: open/ config. json: The system cannot find the file specified.

Is it found that the error result has not changed, then modify itmainFunction.

package main

import (

 "errors"

 "fmt"

 "log"

)

func  main() {

   content, err := LoadConfig()

   if err != nil {

      log.Fatal(errors.Unwrap(err))

   }

   fmt. Println ("content:", content)

}

//Output

2021/09/24  18:11:09 open ./config.json: The system cannot find the file specified.

In case of printing error, an error is addederrors.UnwrapFunction, which is used to take out nested errors, and then look at the output results. The additional error message “error reading file:” it’s gone.

3. Custom error type

We talked about how to customize error types. Now we talk about how to apply your customized errors. Next, we willLoadConfigThe error with empty content in the function is changed to a user-defined error type.

func  LoadConfig() (string, error) {

 filename := "./config.json"

 // ...

 if  len(content) == 0 {

 return  "", &FileEmptyError{

            Filename: filename,

            Err:      errors. New ("content is empty"),

        }

    }

 return content, nil

}

FileEmptyErrorIs a custom error type, the same implementationerrorInterface.


type  FileEmptyError  struct {

Filename string

    Err      error

}

func (e *FileEmptyError) Error() string {

 return fmt.Sprintf("%s  %v", e.Filename, e.Err)

}

Call nowLoadConfigFunction. Now assume that the “config. JSON” file exists, but the content is empty. The results are as follows:

content, err := LoadConfig()

if err != nil {

 if  v, ok := err.(*FileEmptyError); ok {

        fmt.Println("Filename:", v.Filename)

    }

    log.Fatal(err)

}

//Output

Filename: ./config.json

2021/09/27  14:36:40 ./ config. JSON content is empty
  • takeerrThe interface type of the variable is inferred as*FileEmptyErrorType and outputFilenameField.

  • Print custom error content.

If you want to useerrors.UnwrapFunction, you need to implement itWrapperInterface,fmt.ErrorfIn function%wThe placeholder bottom layer implements this interface.


type  Wrapper  interface {

   // Unwrap returns the next error in the error chain.

   // If there is no next error, Unwrap returns nil.

   Unwrap() error

}

We just need to achieve itUnwrapMethod is OK.


func (e *FileEmptyError) Unwrap() error {

 return e.Err

}

Come down and experiment by yourself. I won’t be wordy here.

Wrong judgment

For a function or method, the error returned is often more than one error result. If you want to have different processing logic for different error results, you must judge the error results at this time.

// ...

var (

 ErrNotFoundRequest = errors.New("404")

 ErrBadRequest = errors. New ("request exception")

)

func  GetError() error {

 // ...

 //Error 1

 return ErrNotFoundRequest

 // ...

 //Error 2

 return ErrBadRequest

 // ...

 //Error 3

 path := "https://printlove.com"

 return fmt.Errorf("%s:%w", path, ErrNotFoundRequest)

 // ...

}

GetErrorThe function does not write specific logic, but only shows the return of three errors. Let’s see how to judge these situations.

1. Simplest “= =”

The simplest way is to use “= =” to judge the wrong result.

err := GetError()

if err == ErrNotFoundRequest {

 //Error 1

} else  if err == ErrBadRequest {

 //Error 2

}

There is a problem with this way of judgment, “error 3” does not meet these two requirementsifJudging, but in terms of misclassification, it belongs toErrNotFoundRequestError, only the requested address data is splicedpath, look down at another way to judge.

2. errors.Is

useerrors.IsFunction to solve the judgment problem of “error 3”. Why?

Placeholder used in error 3%w, it willErrNotFoundRequestError embedded in it,**IsThe function is used to split and judge the errors layer by layer * * until they are successful or there are no nested errors. If you don’t understand, you can combine the above figure.

The function is defined as follows:


func  Is(err, target error) bool
  • errThe parameter is the error to be judged.

  • targetThe parameter is the wrong object to compare.

Use the following:

err := GetError()

if errors.Is(err, ErrNotFoundRequest) {

 //Error 1, error 3

} else  if errors.Is(err, ErrBadRequest) {

 //Error 2

}
  • When we call a function or a method in a specific project, we do not know whether the underlying layer is nested correctly. If it is not clear, we will use it uniformly.IsFunction.

  • Error 1andError 2It can be judged without nesting.

3. errors.As

This anderrors.IsFunctions are similar, except that the function only judges the error type, anderrors.IsThe function judges not only the type, but also the value (error message).

// error/as.go

package main

import (

 "errors"

 "fmt"

)

type  ErrorString  struct {

s string

}

func (e *ErrorString) Error() string {

 return e.s

}

func  main() {

 var  targetErr *ErrorString

 err := fmt.Errorf("new error:[%w]", &ErrorString{s: "target err"})

    fmt.Println(errors.As(err, &targetErr))

}

//Output

true

targetErrVariables have several requirements:

  • No initialization is required.

  • Must be a pointer type and implementerrorInterface.

  • AsFunction not acceptednilTherefore, it cannot be used directlytargetErrVariable to use its reference&targetErr

What is an exception

The error is the above, and its occurrence will not cause the program to exit abnormally.

So what happens when you quit abnormally? For example:

  • Subscript out of bounds

  • Divisor is 0

  • wait

Usually, the exception is thrown when you don’t notice the code problem. Of course, you can throw it on your own initiative.

panic

usepanicThe function can actively throw exceptions. The format of the function is as follows:


func  panic(v interface{})
  • vIf the parameter is an empty interface type, it means that any type of data can be accepted.

Example:

Panic ("I'm an exception")

//Output

Panic: I'm an exception

goroutine 1 [running]:

main.main()

        C:/workspace/go/src/gobasic/panic/panic.go:4 +0x45

exit status 2

Several points can be seen from the output results:

  • Print out the specific exception location, which is called stack information.

  • The program terminates with exit code 2.

Handling exceptions

Whether it is actively throwing exceptions or passively throwing exceptions when your program has bugs, these are very serious problems when we write the project, because it will cause our program to exit abnormally.

In other languages, bytry/catchThe mechanism can catch exceptions to ensure the normal operation of the program, which is used in go languagerecoverFunction caught exception.

Before learning this function, you should understand another keyworddefer

1. defer

It’s not a function, it’s just a keyword. The statement followed by this keyword will be executed late. It will be executed in advance when the function or method ends normally or there is an abnormal interrupt.

package main

import  "fmt"

func  main() {

 defer  func() {

        fmt.Println("defer")

    }()

    fmt.Println("main")

 panic("panic")

}

//Output

main

defer

panic: panic

...
  • deferKeyword is followed by an anonymous function call. Of course, functions with names can also be used.

  • encounterdeferKeyword, the subsequent statements will delay execution, so the “main” string is output first.

  • panicThrow an exception, so execute before exitingdefersentence.

  • If calledos.ExitFunction,deferThe following statement will not be executed.

If an error occurs in a function or methodMultipledeferStatement, that will adopt the first in, last out principle, that is, the first onedeferStatements are executed after, and those that appear after are executed first.

Example:

package main

import  "fmt"

func  Fun1() {

    fmt. Println ("guess when I'll output?")

}

func  main() {

 defer  func() {

        fmt.Println("defer")

    }()

 defer  Fun1()

    fmt.Println("main")

 panic("panic")

}

//Output

main

Guess when I output?

defer

panic: panic

...

NowdeferThe concept of keyword may not be known where it is used in practice. Let me give an example.

// defer/main.go

//Copy file

//Copy srcname path file to dstname path file

func  CopyFile(srcName, dstName string) (written int64, err error) {

 src, err := os.Open(srcName)

 if err != nil {

 return

    }

 defer src.Close()

 dst, err := os.Create(dstName)

 if err != nil {

 return

    }

 defer dst.Close()

 return io.Copy(dst, src)

}

There are two in the codedeferKeyword. The following statements are used to close files and release resources.

If you do not use this keyword, close the fileCloseThe function must be written inio.CopyFunction, because the function also uses file resources, close it in advance, and it’s over. So, usedeferKeyword will delay execution, so there is no need to consider when the file is used.

Of course, this is not the only case. Just remember that any statement you want to process before the end of a function or method can be useddeferkeyword.

2. recover

I seedeferKeyword, before an exception occurs in the programdeferStatement is executed first, so indeferThe following statement can intercept exceptions in advance.

// panic/recover.go

package main

import  "fmt"

func  main() {

 defer  func() {

 if  err := recover(); err != nil {

            fmt. Println ("I captured:", ERR)

            fmt. Println ("I'm fine")

        }

    }()

 Panic ("I'm an exception")

}

//Output

I caught: I am abnormal

I'm fine
  • recoverReturn exception information.

Whether you throw an exception actively or passively,recoverFunctions can capture, so as to ensure the normal operation of the program.

Suppose functionAMany functions are called, and many of these functions are called again. As long as the following called functions are abnormal, the functionAMediumrecoverFunctions will be able to capture. But there is one exception,GoroutineThe exception in cannot be caught and must be in the newGoroutineReuse inrecoverFunction, which is an expected knowledge point. I’ll talk about it later.

summary

This chapter explains two concepts, “error” and “exception”. These two concepts are very important and must be understood. Another point is that when dealing with errors, we sometimes find that the wrong results are useless to us, so we can ignore them at this time.

When I write, I also want to make it as clear as possible. Some places will be wordy.

This work adoptsCC agreement, reprint must indicate the author and the link to this article

Handsome brother and Miao

Recommended Today

aggregate

aggregate 1: Set concept Object, which implements common operations on objects, similar to array functions 2: Differences between sets and arrays: The length of array is fixed, and the length of set query is not fixed Arrays can store basic and reference types, while collections can only store reference types Location: java Util* Collection system […]