Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

What are the error handling methods of Go language

2025-01-18 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

This article mainly introduces the relevant knowledge of the error handling methods of the Go language, the content is detailed and easy to understand, the operation is simple and fast, and has a certain reference value. I believe you will gain something after reading this article on error handling methods of the Go language. Let's take a look.

Rapid comparison with other languages

In Go, all errors are values. Because of this, quite a few functions * return an error that looks like this:

Func (s * SomeStruct) Function () (string, error)

So this causes the calling code to usually use the if statement to check them:

Bytes, err: = someStruct.Function () if err! = nil {/ / Process error}

Another approach is the try-catch pattern used in other languages, such as Java, C #, Javascript, Objective C, Python, and so on. Below you can see the Java code similar to the previous Go example, declaring throws instead of returning error:

Public String function () throws Exception

It uses try-catch instead of if err! = nil:

Try {String result = someObject.function () / / continue logic} catch (Exception e) {/ / process exception}

Of course, there are other differences. For example, error won't crash your program, but Exception will. There are others, which will be specifically mentioned in this article.

Implement centralized error handling

Take a step back and let's see why errors are handled in a centralized place and how to do so.

An example that most people might be familiar with is the web service-if there are some unexpected server-side errors, we generate a 5xx error. In Go, you might achieve this:

Func init () {http.HandleFunc ("/ users", viewUsers) http.HandleFunc ("/ companies", viewCompanies)} func viewUsers (w http.ResponseWriter, r * http.Request) {user / / some code if err: = userTemplate.Execute (w, user) Err! = nil {http.Error (w, err.Error (), 500)}} func viewCompanies (w http.ResponseWriter, r * http.Request) {companies = / / some code if err: = companiesTemplate.Execute (w, companies); err! = nil {http.Error (w, err.Error (), 500)}

This is not a good solution because we have to repeatedly handle errors in all handlers. For better maintenance, errors can be handled in one place. Fortunately, in the official blog in the Go language, Andrew Gerrand provides an alternative that can be implemented *. We can create a Type that handles errors:

Type appHandler func (http.ResponseWriter, * http.Request) error func (fn appHandler) ServeHTTP (w http.ResponseWriter, r * http.Request) {if err: = fn (w, r); err! = nil {http.Error (w, err.Error (), 500)}}

This can be used as a wrapper to modify our handler:

Func init () {http.Handle ("/ users", appHandler (viewUsers)) http.Handle ("/ companies", appHandler (viewCompanies))}

The next thing we need to do is modify the signatures of the handlers so that they return errors. This approach is good because we live up to the DRY principle and don't reuse unnecessary code-now we can return the default error in a separate place.

Error context

In the previous example, we might have received many potential errors, any of which could be generated in many parts of the call stack. That's when things get tricky.

To demonstrate this, we can extend our handler. It may look like this, because template execution is not the only place where errors can occur:

Func viewUsers (w http.ResponseWriter, r * http.Request) error {user, err: = findUser (r.formValue ("id")) if err! = nil {return err;} return userTemplate.Execute (w, user);}

The call chain can be quite deep, and various errors may be instantiated in different places throughout the process. Russ Cox's article explains the practice of avoiding too many of these problems:

"part of the convention for error reporting in Go is that the function contains the relevant context, including the operation being attempted (such as the function name and its arguments)."

The example given is a call to the OS package:

Err: = os.Remove ("/ tmp/nonexist") fmt.Println (err)

It outputs:

Remove / tmp/nonexist: no such file or directory

To sum up, after execution, the output is the called function, the given parameter, and the specific error message. You can also follow this practice when creating an Exception message in other languages. If we insist on this in viewUsers processing, we can almost always identify the cause of the error.

The problem comes from those who don't follow this practice, and you often see these messages in third-party Go libraries:

Oh no I broke

This doesn't help-you can't understand the context, which makes debugging difficult. To make matters worse, when these errors are ignored or returned, they are backed up on the stack until they are processed:

If err! = nil {return err}

This means that when the error occurred has not been passed on.

It should be noted that all of these errors can occur in Exception-driven models-bad error messages, hidden exceptions, and so on. So why do I think this model is more useful?

Even if we are dealing with a bad exception message, we can still understand where it occurs in the call stack. Because of the stack trace, this leads to some parts that I don't know about Go-you know that Go's panic includes stack trace, but error doesn't. I speculate that panic may crash your program, so you need a stack trace, but handling errors doesn't, because it assumes you're doing something where it happens.

So let's go back to the previous example-a third-party library with bad error messages that simply outputs the call chain. Do you think debugging will be easier?

Panic: Oh no I broke [signal 0xb code=0x1 addr=0x0 pc=0xfc90f] goroutine 1103 [running]: panic (0x4bed00, 0xc82000c0b0) / usr/local/go/src/runtime/panic.go:481 + 0x3e6github.com/Org/app/core. (_ app) .captureRequest (0xc820163340, 0x0, 0x55bd50, 0x0, 0x0) / home/ubuntu/.go_workspace/src/github.com/Org/App/core/main.go:313 + 0x12cfgithub.com/Org/app/core. (_ app) .processRequest (0xc820163340, 0xc82064e1c0, 0xc82002aab8 0x1) / home/ubuntu/.go_workspace/src/github.com/Org/App/core/main.go:203 + 0xb6github.com/Org/app/core.NewProxy.func2 (0xc82064e1c0, 0xc820bb2000, 0xc820bb2000, 0x1) / home/ubuntu/.go_workspace/src/github.com/Org/App/core/proxy.go:51 + 0x2agithub.com/Org/app/core/vendor/github.com/rusenask/goproxy.FuncReqHandler.Handle (0xc820da36e0, 0xc82064e1c0, 0xc820bb2000, 0xc5001) 0xc820b4a0a0) / home/ubuntu/.go_workspace/src/github.com/Org/app/core/vendor/github.com/rusenask/goproxy/actions.go:19 + 0x30

I think this may be something that has been overlooked in the design of Go-not all languages will ignore it.

If we use Java as a random example, one of the stupidest mistakes people make is not recording stack traces:

LOGGER.error (ex.getMessage ()) / / do not record stack trace LOGGER.error (ex.getMessage (), ex) / / record stack trace

But Go doesn't seem to have this information in its design.

In terms of obtaining context information-Russ also mentioned that the community is discussing some potential interfaces for stripping context errors. It might be interesting to know more about this.

Solution to the problem of stack tracing

Fortunately, after doing some lookups, I found this excellent Go error library to help solve this problem and add a stack trace to the error:

If errors.Is (err, crashy.Crashed) {fmt.Println (err. (* errors.Error). ErrorStack ())}

However, I think this feature will be an improvement if you can become a class citizen first class citizenship of the language, so you don't have to make some type changes. In addition, if we use a third-party library as in the previous example, it may not use crashy-we still have the same problem.

What should we do about mistakes?

We must also consider what should happen when something goes wrong. This must be useful, they don't crash your programs, and they usually deal with them immediately:

Err: = method () if err! = nil {/ / some logic that I must do now in the event of an error!}

What happens if we want to call a large number of methods that generate errors and then handle all the errors in one place? It looks like this:

Err: = doSomething () if err! = nil {/ / handle the error here} func doSomething () error {err: = someMethod () if err! = nil {return err} err = someOther () if err! = nil {return err} someOtherMethod ()}

This feels a bit redundant, and in other languages you can deal with multiple statements as a whole.

Try {someMethod () someOther () someOtherMethod ()} catch (Exception e) {/ / process exception}

Or simply pass an error in the method signature:

Public void doSomething () throws SomeErrorToPropogate {someMethod () someOther () someOtherMethod ()}

Personally, I think these two examples achieve one thing, except that the Exception pattern is less redundant and more resilient. If anything, I think if errands = nil feels like a template. Maybe there's a way to clean it up?

Deal with errors in multiple failed statements as a whole

First of all, I did more reading and found a more pragmatic solution in Rob Pike's Go blog.

He defines a structure that encapsulates the wrong method:

Type errWriter struct {w io.Writer err error} func (ew * errWriter) write (buf [] byte) {if ew.err! = nil {return} _, ew.err = ew.w.Write (buf)}

Let's do this:

Ew: = & errWriter {w: fd} ew.write (p0 [a: B]) ew.write (p1 [c: d]) ew.write (p2 [e: F]) / / and so onif ew.err! = nil {return ew.err}

It's also a good solution, but I feel like there's something missing-because we can't reuse the pattern. If we want a method that contains string parameters, we have to change the function signature. Or what happens if we don't want to write? We can try to make it more generic:

Type errWrapper struct {err error} func (ew * errWrapper) do (f func () error) {if ew.err! = nil {return} ew.err = f ();}

But we have the same problem. If we want to call a function with different arguments, it won't compile. However, you can simply encapsulate these function calls:

W: = & errWrapper {} w.do (func () error {return someFunction (1,2);}) w.do (func () error {return otherFunction ("foo");}) err: = w.err if err! = nil {/ / process error here} this article ends here. Thank you for reading! I believe you all have a certain understanding of the knowledge of "what are the error handling methods of Go language". If you want to learn more, you are welcome to follow the industry information channel.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report