- 后端早读课翻译计划 第二篇-

本文提供了一个优雅的处理 Golang 中错误的方法,解决了 Golang error 只有字符串信息的局限性,提供了上下文信息、错误类型判断的功能。

尽管 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}

// 内置错误接口类型用于表示错误状况的普通接口,其中 nil 值表示没有错误。

我们看到,错误是一个简单的 interface,实现了的 Error 方法,返回一个字符串。

这个定义告诉我们,创建一个错误只需要一个简单的字符串就可以了,所以如果我创建下面的结构体:

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

我就得到了一个最简单的错误定义。

注意:这里只是举一个例子。我们可以使用 Go 标准库里面的 fmt 和 errors 来创建错误:

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

一段简单的信息能否优雅の处理错误吗?让我们在最后回答这个问题,看看我是怎么做的。

Error flow 错误处理

我们已经知道了什么是错误,下一步我们来看看一个错误的生命周期是怎样的。

简单起见,不要重复自己的错误处理逻辑,最好一个地方只处理一个逻辑。

通过下面这个例子,我们看看为什么这么说:

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

// 错误的示范:在一个地方处理(打印)并返回了错误

这段代码有什么问题吗?

我们处理两次错误,第一次打印了他,第二次把它返回给调用者。

也许你的团队同事使用了这个方法,当错误返回时,他会将错误日志再一次的打印出来。在系统里就出现了日志噩梦(多次打印同一个日志)

想想看我们的应用里有三层,数据层、交互层、Web 服务层:

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

按照我之前提到的原则,这是一个正确的错误处理方式:把错误返回到最上层。然后他会被打印到日志里。将错误收集反馈在 Web 服务层,只在一个地方处理错误。

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

github.com/pkg/errors   拯救了这个问题。

我将上面的方法重写,添加堆栈跟踪,以及从数据层获取失败的信息,而且是在不改动原始的错误下:

import "github.com/pkg/errors"// The repository uses an external depedency ormfunc 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 返回的错误外面包装一层,在不影响原始错误的情况下,创建一个堆栈跟踪(译者注:wrap 的嵌套)。

让我们看下其他层是如何处理这个错误的。交互层:

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())}

正如你所见,我们只在顶层处理了错误。这样就完美了吗?并不是。如果你注意到,我们在错误的情况下都返回了 HTTP status 500.  除此之外,我们总是记录相同的错误,比如 “result not found” ,这样只会增加我们的日志噪音。

My Solution 解决方案

我们在上一个主题中看到,只在顶层处理错误时,简单的字符串错误信息不足以让我们做错误处理的决策。

我们知道,我们在错误中创建一些信息,我们通常会加入一些信息比如错误是在哪里发生的,错误需要在哪里被处理。

让我们为解决这个问题定义三个目标:

  • 提供错误栈

  • 打印错误(比如, Web 服务层)

  • 在必要时提供错误的上下文信息(比如,提示电子邮件格式不正确)

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

package errorsconst(  NoType = ErrorType(iota)  BadRequest  NotFound  //add any type you want)type ErrorType uinttype customError struct {  errorType ErrorType  originalError error  contextInfo map[string]string}// Error returns the mssage of a customErrorfunc (error customError) Error() string {   return error.originalError.Error()}// New creates a new customErrorfunc (type ErrorType) New(msg string) error {   return customError{errorType: type, originalError: errors.New(msg)}}// New creates a new customError with formatted messagefunc (type ErrorType) Newf(msg string, args ...interface{}) error {   err := fmt.Errof(msg, args...)   return customError{errorType: type, originalError: err}}// Wrap creates a new wrapped errorfunc (type ErrorType) Wrap(err error, msg string) error {   return type.Wrapf(err, msg)}// Wrap creates a new wrapped error with formatted messagefunc (type ErrorType) Wrapf(err error, msg string, args ...interface{}) error {   newErr := errors.Wrapf(err, msg, args..)   return customError{errorType: errorType, originalError: newErr}}

我只定义了public ErrorType 以及错误类型,我们可以创建新的错误,并且可以将已有的错误进行包装。

但是我们缺少两件事。

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

  • 我们如何添加/获取错误的上下文,甚至是一个已存在的来自外部依赖的错误?

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

// New creates a no type errorfunc New(msg string) error {   return customError{errorType: NoType, originalError: errors.New(msg)}}// Newf creates a no type error with formatted messagefunc Newf(msg string, args ...interface{}) error {   return customError{errorType: NoType, originalError: errors.New(fmt.Sprintf(msg, args...))}}// Wrap wrans an error with a stringfunc Wrap(err error, msg string) error {   return Wrapf(err, msg)}// Cause gives the original errorfunc Cause(err error) error {   return errors.Cause(err)}// Wrapf wraps an error with format stringfunc 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}}

添加一些方法来处理上下文和类型来解决已知或者未知错误(NoType)。

// AddErrorContext adds a context to an errorfunc 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 contextfunc 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 typefunc GetType(err error) ErrorType {   if customErr, ok := err.(customError); ok {      return customErr.errorType   }   return NoType}

回到我们的例子,我们将使用这个新的 error 方法

import "github.com/our_user/our_project/errors"// The repository uses an external depedency ormfunc 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 repository: https://github.com/henrmota/errors-handling-example

cad致命错误如何处理_Golang 如何优雅地处理错误相关推荐

  1. 如何优雅的处理错误逻辑

    程序的健壮性 程序在运行的时候总是不可避免地遇到各种错误.这些错误有一些是包含在原有的逻辑判断中的.而有一些是被程序描述了,但是我们并不认为它是正常逻辑的一部分.不论是什么形式的问题,我们在进行我们预 ...

  2. c语言编译器内部错误,C++致命错误C1001:编译器中发生内部错误

    在发布模式下编译时出现以下错误. 1>d:\users\eyal\projects\code\yalla\core\src\runbox\win32\window.cpp : fatal err ...

  3. 一套优雅的 Go 错误问题解决方案

    作者:andruzhang,腾讯 IEG 后台开发工程师 在使用 Go 开发的后台服务中,对于错误处理,一直以来都有多种不同的方案,本文探讨并提出一种从服务内到服务外的错误传递.返回和回溯的完整方案, ...

  4. 深入理解PHP异常和错误处理(6)PHP如何优雅的处理错误

    前言:有错就改,错误光屏蔽是不行的,还需要对错误进行处理和记录. 内容概要: 1.顶层错误处理器的介绍 2.示例代码 一.顶层错误处理器的介绍 php在处理错误的时候,可以简单的使用exit()和di ...

  5. cad无法安装_安装失败、弹窗错误!Autodesk都是娇气的主...(CAD/MAX完美安装工具)...

    CAD/3D/REVIT/MAYA安装失败? 你是不是遇到:CAD/3dmax/maya/Revit/Inventor  安装失败或者安装不了的问题了呢? --文末附:AUTODESK完美卸载安装工具 ...

  6. CAD二次开发-MFC对话框domal显示错误

    问题:CAD二次开发时添加MFC对话框后显示错误,显示为多重引线样式管理器. 解决:在对话框之前需要使用AfxGetResourceHandle和AfxSetResourceHandle进行模块资源切 ...

  7. 如何处理使用ngrx时遇到的错误消息: NullInjectorError R3InjectorError(AppModule)[StoreFeatureModule]

    错误消息: main.ts:12 NullInjectorError: R3InjectorError(AppModule)[StoreFeatureModule -> ReducerManag ...

  8. 如何处理Marketing Cloud OData服务的错误消息

    错误消息:The Data Services Request coulnot be understood due to malformed syntax 少了两个换行符: 加上之后就工作了: 要获取更 ...

  9. 如何处理postman Self-signed SSL certificate blocked错误

    今天我在使用postman测试我开发的restful API时,遇到这个错误信息: Self-signed SSL certificates are being blocked: 解决方案 选择这个s ...

最新文章

  1. 《LeetCode力扣练习》第70题 爬楼梯 Java
  2. 信息系统运维安全管理规定(可作为范文参考)
  3. php进销存 带apk,php进销存配送管理系统,支持h5/ios/android/微信小程序
  4. Min_25筛学习Tip+链接
  5. 大数据开发:剖析Hadoop和Spark的Shuffle过程差异
  6. linux操作系统下建用户,如何用Linux操作系统批量建立用户的shell
  7. 李航统计方法——感知机
  8. J2ee项目环境搭建常用工具
  9. Python的开源人脸识别库:离线识别率高达99.38%
  10. 数数苹果手机中的不科学
  11. matlab 画x a的直线方程式,matlab画如x=a和y=b这种水平线和垂线的命令是什么,谢谢...
  12. git add 之后没有push 怎么找回代码?
  13. CPU、内存、主板、显卡等是什么?计算机基本的硬件介绍,计算机组成元件
  14. 扰动观察法怎么写matlab,扰动观察法
  15. unity设置中文版
  16. NOI Online #2 普及组 第二题:荆轲刺秦王
  17. Mono 3.2 测试NPinyin 中文转换拼音代码
  18. linux下的EC20的监控python脚本
  19. varchar varchar2异同
  20. PostgreSql vacuum

热门文章

  1. 我今年89岁,刚刚拿了个物理学博士学位
  2. 中国500多名理工科研究生被美国拒签!美国「制裁清单」影响开始深入校园!...
  3. 孩子从全班倒数第一到第二名,这位妈妈只做了这1件事!
  4. 深度学习-词嵌入(word2vec)
  5. 红米android4.4.2,新版红米Note配置升级详解:系统其实是基于Android 4.4.2
  6. 23种设计模式之备忘录模式
  7. 通过jquery回显操作(笔记)
  8. LinkedHashSet类
  9. java中随机数Random和ThreadLocalRandom()用法与区别
  10. 数据结构实验之查找五:平方之哈希表