The original link: www.flysnow.org/2019/01/01/… Wechat official account: Flysnow_org

To Go language (golang) error design, believe that many people have experienced, it is through the way of return values, to force the caller to deal with error, or you ignore, either you processing (processing can also be to continue returned to the caller, for golang this design approach, will be in the code we write a lot of judging the if, To make a decision.

func main(a) {
	conent,err:=ioutil.ReadFile("filepath")
	iferr ! =nil{
		// Error handling
	}else {
		fmt.Println(string(conent))
	}
}
Copy the code

This kind of code, which is very unusual in our code, most of the time error is nil, which means there’s no error, but when it’s not nil, that means there’s an error, and we need to deal with it.

The error interface

Error is actually an interface, built-in, so let’s look at its definition

// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
	Error() string
}
Copy the code

It only has one method, Error, and as long as it implements that method, it implements Error. Now let’s try to define a mistake for ourselves.

type fileError struct{}func (fe *fileError) Error(a) string {
	return "File error"
}
Copy the code

Custom error

A custom fileError type, error interface implementation. Now let’s test it out and see what happens.

func main(a) {
	conent, err := openFile()
	iferr ! =nil {
		fmt.Println(err)
	} else {
		fmt.Println(string(conent))
	}
}

// Just simulate an error
func openFile(a) ([]byte, error) {
	return nil, &fileError{}
}
Copy the code

We run the simulated code and see notification of file errors.

In the actual use process, we may encounter many errors, the difference is that the error message is different, one way is to define an error type for each error, but this is too cumbersome. Error returns a string, so we can modify it to make it settable.

type fileError struct {
	s string
}

func (fe *fileError) Error(a) string {
	return fe.s
}
Copy the code

Well, after this modification, we can declare fileError and set the error text to prompt, which can meet our different needs.

// Just simulate an error
func openFile(a) ([]byte, error) {
	return nil, &fileError{"Error file, custom"}}Copy the code

Yeah, that’s it. That’s what we wanted. Now we can make it more generic, such as changing the name of fileError and creating a helper function that allows us to create different error types.

//blog:www.flysnow.org
//wechat:flysnow_org
func New(text string) error {
	return &errorString{text}
}

type errorString struct {
	s string
}

func (e *errorString) Error(a) string {
	return e.s
}
Copy the code

In this case, we can use the New function to help us create different errors. This is actually the errors.New function that we often use, and we have evolved step by step, now you have a clear understanding of the Go language (Golang) built-in error.

Existing problems

Although the Go language for error design is very simple, but for us developers, it is obviously inadequate, for example, we need to know more information about the error, in what file, which line of code? Only in this way can we locate the problem more easily.

For example, if we want to append more information to the error returned, as in the above example, how do we do this? We can only fetch the original Error message through the Error method, and then concatenate it ourselves, and then use the errors.New function to generate a New Error return.

If we’ve done Java development before, we know that Java exceptions can be nested, which means that it’s very easy to know what the root cause of an error is, because Java exceptions are returned by layer upon layer of nesting, and no matter how much wrapping there is in between, we can look at the cause of the root cause.

To solve the problem

If we want to solve the above problem, we must first expand our errorString and add more fields to store more information. Let’s say we want to log the stack.

type stack []uintptr
type errorString struct {
	s string
	*stack
}
Copy the code

Check out flysnow_org on wechat or www.flysnow.org/ for more original articles.

With the stack field that stores the stack information, we can store the stack information of the call in this field when an error is generated.

//blog:www.flysnow.org
//wechat:flysnow_org

func callers(a) *stack {
	const depth = 32
	var pcs [depth]uintptr
	n := runtime.Callers(3, pcs[:])
	var st stack = pcs[0:n]
	return &st
}

func New(text string) error {
	return &errorString{
		s:   text,
		stack: callers(),
	}
}
Copy the code

Perfect solution. Now what about the problem of adding some information to the existing error? I believe you should have an idea.

type withMessage struct {
	cause error
	msg   string
}

func WithMessage(err error, message string) error {
	if err == nil {
		return nil
	}
	return &withMessage{
		cause: err,
		msg:   message,
	}
}
Copy the code

Using the WithMessage function, a new error can be generated with the information wrapped around the original error.

Recommended scheme

Is the method we adopt familiar to solve the problem above? Especially if you look at the source code, yes, this is the source code for the error handling library github.com/pkg/errors.

Because the errors provided by Go language are so simple that we can’t deal with the problems better or even provide more useful information for us to deal with errors, many error handling libraries are born. github.com/pkg/errors is as simple as github.com/pkg/errors and has very powerful functions. By a large number of developers welcome, many users.

It’s very simple to use, if we want to generate a New error, we can use the New function, the generated error, with the call stack information.

func New(message string) error
Copy the code

If we have a ready-made error and need to rewrap it, we have three functions to choose from.

// Add only new information
func WithMessage(err error, message string) error// Attach only call stack informationfunc WithStack(err error) error// Append both stack and informationfunc Wrap(err error, message string) error
Copy the code

In fact, the above packaging is similar to the Java exception packaging. The error wrapped is actually the Cause of the error mentioned in the previous chapter, which is the root Cause of the error. So this error handling library provides us with a Cause function that allows us to get to the root Cause of the error.

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

	forerr ! =nil {
		cause, ok := err.(causer)
		if! ok {break
		}
		err = cause.Cause()
	}
	return err
}
Copy the code

Use the for loop all the way to the bottom error.

We have all the above errors wrapped and collected, so how do we print out the stack stored in them, the cause of the error, and so on? In fact, the library error types are implemented Formatter interface, we can use fmt.Printf function output corresponding error messages.

%s,%v // function the same, output error message, does not contain stack %q // output error message with quotes, does not contain stack %+v // output error message and stackCopy the code

If there are any loop – wrapped error types, these errors will be recursively printed.

summary

By using the github.com/pkg/errors error library, we can collect more information, which makes it easier to locate problems.

The information we collected can be exported not only to the console, but also to the corresponding Log for problem analysis.

It is said that this library will be added to the Golang standard SDK, and it is expected that, if added, it will complement the errors package currently in the standard library.

This article is an original article. Please scan the public account flysnow_org or asF www.flysnow.org/ to see the following wonderful articles. If you feel good, please click on the lower right corner of the article “nice”, thank you for your support.