Golang-正常处理错误 - Go语言中文社区

Golang-正常处理错误


尽管go具有一个简单的错误模型,但是乍一看,事情并没有那么容易。 在本文中,我想提供一个很好的策略来处理错误并克服您在过程中可能遇到的问题。

首先,我们将分析go中的错误。

然后,我们将看到错误创建和错误处理之间的流程,并分析可能的缺陷。

最后,我们将探索一种解决方案,使我们能够克服这些缺陷而不会损害应用程序的设计。

Go发生什么错误

查看内置错误类型,我们可以得出一些结论:

// 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
}

我们看到错误是实现简单方法Error返回字符串的接口。

此定义告诉我们创建错误所需的全部是一个简单的字符串,因此如果我创建以下结构:

type MyCustomError string
func (err MyCustomError) Error() string {
return string(err)
}

我想出了最简单的错误定义。

注意:这仅是示例。 我们可以使用go标准软件包fmterrors创建一个错误

import (
"errors"
"fmt"
)
simpleError := errors.New("a simple error")
simpleError2 := fmt.Errorf("an error from a %s string", "formatted")

一个简单的消息是否足以优雅地处理错误? 最后,让我们回答这个问题,探索我将提供的解决方案。

错误流

因此,我们已经知道什么是错误,下一步是可视化他的生命周期中的流程。

为了简单起见,并且不重复自己的原理,最好一次对一个错误采取措施。

让我们看看为什么给出以下示例:

// bad example of handling and returning the error at the same time
func someFunc() (Result, error) {
result, err := repository.Find(id)
if err != nil {
log.Errof(err)
   return Result{}, err
}
  return result, nil
}

这段代码有什么问题?

好吧,我们正在处理错误,方法是先将其记录下来,然后再将其返回给该函数的调用者。

您的团队同事中可能有一个将使用此功能,并且当错误返回时,他将再次记录该错误。 然后在系统日志中发生错误的噩梦。

因此,假设我们在应用程序中有3个层级别,即存储库交互器Web服务器

// The repository uses an external depedency orm
func getFromRepository(id int) (Result, error) {
result := Result{ID: id}
err := orm.entity(&result)
if err != nil {
return Result{}, err
}
  return result, nil 
}

按照我之前提到的原则,这是通过返回顶部来处理错误的正确方法。 稍后将其记录下来,将对Web服务器的正确反馈全部集中在一处。

但是前面的代码有问题。 不幸的是,内置错误并不能提供堆栈跟踪。 除此之外,该错误是在外部依赖项下生成的,我们需要知道项目中的哪段代码对此错误负责。

github.com/pkg/errors解救了。

我将通过添加堆栈跟踪并添加消息说明存储库无法获取结果来重做以前的功能。 我想这样做而不损害原始错误:

import "github.com/pkg/errors"
// The repository uses an external depedency orm
func getFromRepository(id int) (Result, error) {
result := Result{ID: id}
err := orm.entity(&result)
if err != nil {
return Result{}, errors.Wrapf(err, "error getting the result with id %d", id);
}
  return result, nil 
}
// after the error wraping the result will be 
// err.Error() -> error getting the result with id 10: whatever it comes from the orm

该功能的作用是包装来自ORM的错误,构建堆栈跟踪而不会影响原始错误。

因此,让我们看看其他层如何处理该错误。 首先是交互者:

func getInteractor(idString string) (Result, error) {
id, err := strconv.Atoi(idString)
if err != nil {
return Result{}, errors.Wrapf(err, "interactor converting id to int")
}
  return repository.getFromRepository(id) 
}

现在是顶层,Web服务器:

r := mux.NewRouter()
r.HandleFunc("/result/{id}", ResultHandler)
func ResultHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
result, err := interactor.getInteractor(vars["id"])
if err != nil {
handleError(w, err)
}
  fmt.Fprintf(w, result)
}
func handleError(w http.ResponseWriter, err error) { 
w.WriteHeader(http.StatusIntervalServerError)
log.Errorf(err)
fmt.Fprintf(w, err.Error())
}

如您所见,我们只是在顶层处理了错误。 完善? 否。如果您注意到我们总是返回500作为HTTP响应代码。 除此之外,我们总是记录错误。 诸如“未找到结果”之类的错误只会给日志增加噪音。

我的解决方案

我们在上一主题中看到,在处理顶层错误时,字符串不足以做出决策。

我们知道,如果我们在错误中引入新内容,则会以某种方式在产生错误的地方以及最终解决错误的时间点引入依赖性。

因此,让我们探索定义3个目标的解决方案:

  • 提供良好的错误堆栈跟踪
  • 记录错误(例如,Web基础结构层)
  • 必要时向用户提供上下文错误信息。 (例如,提供的电子邮件格式不正确)

首先,我们创建一个错误类型:

package errors
const(
NoType = ErrorType(iota)
BadRequest
NotFound
//add any type you want
)
type ErrorType uint
type customError struct {
errorType ErrorType
originalError error
contextInfo map[string]string
}
// Error returns the mssage of a customError
func (error customError) Error() string {
return error.originalError.Error()
}
// New creates a new customError
func (type ErrorType) New(msg string) error {
return customError{errorType: type, originalError: errors.New(msg)}
}

// New creates a new customError with formatted message
func (type ErrorType) Newf(msg string, args ...interface{}) error {
err := fmt.Errof(msg, args...)

return customError{errorType: type, originalError: err}
}

// Wrap creates a new wrapped error
func (type ErrorType) Wrap(err error, msg string) error {
return type.Wrapf(err, msg)
}

// Wrap creates a new wrapped error with formatted message
func (type ErrorType) Wrapf(err error, msg string, args ...interface{}) error {
newErr := errors.Wrapf(err, msg, args..)

return customError{errorType: errorType, originalError: newErr}
}

如您所见,我仅公开ErrorType和错误类型。 我们可以创建新错误并包装现有错误。

但是我们缺少两件事。

如何在不导出customError的情况下检查错误类型?

我们如何才能为错误添加/获取上下文,甚至可以从外部依赖项添加到已存在的错误中?

让我们采用github.com/pkg/errors的策略 首先包装这些库方法。

// New creates a no type error
func New(msg string) error {
return customError{errorType: NoType , originalError: errors.New(msg)}
}

// Newf creates a no type error with formatted message
func Newf(msg string, args ...interface{}) error {
return customError{errorType: NoType , originalError: errors.New(fmt.Sprintf(msg, args...))}
}

// Wrap wrans an error with a string
func Wrap(err error, msg string) error {
return Wrapf(err, msg)
}

// Cause gives the original error
func Cause(err error) error {
return errors.Cause(err)
}

// Wrapf wraps an error with format string
func Wrapf(err error, msg string, args ...interface{}) error {
wrappedError := errors.Wrapf(err, msg, args...)
if customErr, ok := err.(customError); ok {
return customError{
errorType: customErr.errorType,
originalError: wrappedError,
contextInfo: customErr.contextInfo,
}
}

return customError{errorType: NoType , originalError: wrappedError}
}

现在,让我们构建用于处理上下文和类型的通用错误的方法:


// AddErrorContext adds a context to an error
func AddErrorContext(err error, field, message string) error {
context := errorContext{Field: field, Message: message}
if customErr, ok := err.(customError); ok {
return customError{errorType: customErr.errorType, originalError: customErr.originalError, contextInfo: context}
}

return customError{errorType: NoType , originalError: err, contextInfo: context}
}

// GetErrorContext returns the error context
func GetErrorContext(err error) map[string]string {
emptyContext := errorContext{}
if customErr, ok := err.(customError); ok || customErr.contextInfo != emptyContext {

return map[string]string{"field": customErr.context.Field, "message": customErr.context.Message}
}

return nil
}

// GetType returns the error type
func GetType(err error) ErrorType {
if customErr, ok := err.(customError); ok {
return customErr.errorType
}

return NoType
}

现在回到我们的示例,我们将应用这个新的错误包:

import "github.com/our_user/our_project/errors"
// The repository uses an external depedency orm
func getFromRepository(id int) (Result, error) {
result := Result{ID: id}
err := orm.entity(&result)
if err != nil {
msg := fmt.Sprintf("error getting the result with id %d", id)
    switch err {
case orm.NoResult:
err = errors.Wrapf(err, msg);
default:
err = errors.NotFound(err, msg);

}
    return Result{}, err
}
  return result, nil 
}
// after the error wraping the result will be 
// err.Error() -> error getting the result with id 10: whatever it comes from the orm

现在,交互器:

func getInteractor(idString string) (Result, error) {
id, err := strconv.Atoi(idString)
if err != nil {
err = errors.BadRequest.Wrapf(err, "interactor converting id to int")
err = errors.AddContext(err, "id", "wrong id format, should be an integer)

return Result{}, err
}
  return repository.getFromRepository(id) 
}

最后是Web服务器:

r := mux.NewRouter()
r.HandleFunc("/result/{id}", ResultHandler)
func ResultHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
result, err := interactor.getInteractor(vars["id"])
if err != nil {
handleError(w, err)
}
  fmt.Fprintf(w, result)
}
func handleError(w http.ResponseWriter, err error) { 
var status int
errorType := errors.GetType(err)
switch errorType {
case BadRequest:
status = http.StatusBadRequest
case NotFound:
status = http.StatusNotFound
default:
status = http.StatusInternalServerError
}
   w.WriteHeader(status) 

if errorType == errors.NoType {
log.Errorf(err)
}
   fmt.Fprintf(w,"error %s", err.Error()) 

errorContext := errors.GetContext(err)
if errorContext != nil {
fmt.Printf(w, "context %v", errorContext)
}
}

如您所见,使用导出的类型和某些导出的值,我们可以使我们的生活更轻松地处理错误。 我从此解决方案中喜欢的一件事是,根据设计,在创建错误时,我们会明确显示其类型。

你有什么建议吗? 在下面发表评论。

github存储库https : //github.com/henrmota/errors-handling-example

From: https://hackernoon.com/golang-handling-errors-gracefully-8e27f1db729f

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/dfsgwe1231/article/details/105997004
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2021-06-14 19:47:53
  • 阅读 ( 1162 )
  • 分类:Go

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢