cad致命错误如何处理_Golang 如何优雅地处理错误

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

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

904e15e841902bea094ad5bc4a4c71a0.png

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

79505a0aabb5abcac8ea210c367ebe70.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/511882.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

快速云原生化,从数据中心到云原生的迁移实践

简介:本文将介绍在帮助用户快速完成迁云中的解决方案、最佳实践以及迁云工具。 云原生的时代已经到来,云原生技术正在重塑整个软件生命周期,阿里巴巴是国内最早布局云原生技术的公司之一。 容器服务团队在过去的几年时间内帮助很多用户成功…

实力总结四类 Bean 注入 Spring 的方式

作者 | 阿Q来源 | 阿Q说代码一提到Spring,大家最先想到的是啥?是AOP和IOC的两大特性?是Spring中Bean的初始化流程?还是基于Spring的Spring Cloud全家桶呢?今天我们就从Spring的IOC特性入手,聊一聊Spring中把…

广州大学计算机网络期末考试2013,广州大学计算机网络技术试卷(A卷)

广州大学2006-2007 学年第 1 学期考试卷课程计算机网络技术考试形式(开/闭卷,考试/查)一、填空题(15分)1、现有一计算机要和另一设备进行通信,要实现此目标首先要考虑通过何种接口把计算机和相应设备连接起来;解决此问题属于层的任务。(2分)2…

阿里云全站加速DCDN升级

简介:相比传统CDN加速,全站加速DCDN具有更广阔的应用场景。在当下企业全面数字化的进程中,为了更全面地满足广大企业客户的个性化加速需求,全站加速DCDN从简单开通到个性化定制、从内容分发到安全防护,对客户侧的使用体…

Redis 内存满了怎么办?这样置才正确!

作者 | 码哥呀来源 | 码哥字节上回在《Redis 数据过期了会被立马删除么?》说到如果过期的数据太多,定时删除无法删除完全(每次删除完过期的 key 还是超过 25%),同时这些 key 再也不会被客户端请求,就无法走…

2000坐标系高程与85高程转换_【科普】测量人必须知道的几大坐标系

导读 不了解坐标系的测绘测量人员,不是好员工!无论科研的还是外业的,亦是如此。小编今天就为大家盘点出这几大传统坐标系,各位大大赶快收藏起来吧! 1 北京54坐标系 新中国成立以后,我国大地测量进入了全面发展时期,在全国范围内开展了正规的,全面的大地测量和测图工作,…

云原生数据仓库AnalyticDB支撑双11,大幅提升分析实时性和用户体验

简介:2021年双十一刚刚落幕,已连续多年稳定支持双十一大促的云原生数据仓库AnalyticDB,今年双十一期间仍然一如既往的稳定。除了稳定顺滑的基本盘之外,AnalyticDB还有什么亮点呢?下面我们来一一揭秘。 作者 | Analytic…

漫画:什么是IaaS、PaaS、SaaS?

‍‍作者 | 小灰来源 | 程序员小灰假如你想要一栋属于自己的房子,你有什么样的选择呢?第一种方式,我们可以自己上山砍树准备木材,然后自己去烧制砖瓦,还需要自己研磨油漆等其他材料.....材料准备齐全以后,我…

app获取个人信息是否合法_重拳出击!42款APP过度收集用户信息被点名

驱动中国2019年9月10日消息 近年来我国不断加强对互联网应用中个人信息的保护,但因相关监管机制尚不健全,实际上多数APP并未遵循最少够用原则,仍存在违规收集使用个人信息现象。日前,广东省公安厅持续加强对超范围收集用户信息行为…

无接触式智能服务 用“减法”重塑企业前台场景

简介:为了更好解决企业对前台工作效率、服务体验等诉求,阿里巴巴企业智能事业部联合阿里行政,推出的“非接触式服务”——云前台,集物品暂存、自助领取、物品临时借用、查询周边配套信息、一键呼叫视频客服、报销单收取等功能于一…

阿里园区的这个“格子间” 成为企业高效协同新利器

简介:第一期《数智进化论》带你走进阿里园区的“网红”格子间——视频会议吧,了解它是如何提升办公空间利用率,解决会议室资源供给不足的问题。 会议室是企业员工进行创意、讨论、决策的重要协作场所。传统会议室数量有限、大小相对固定&…

仅用一个 HTML 标签,实现带动画的抖音 Logo

作者 | 零一来源 | 前端印象今天给大家表演 仅用一个HTML标签实现带动画的抖音LOGO,涉及了很多知识点,欢迎交流讨论先上结果,最终实现效果如下:成品图还原度应该还可以吧?抖音Logo结构想要用CSS来画抖音的Logo&#xf…

vaex 处理海量数据_核心业务“瘦身”进行时!手把手带你搭建海量数据实时处理架构...

01 背景在线交易服务平台目的是减轻核心系统计算压力和核心性能负荷压力,通过该平台可以将核心系统的交易数据实时捕获、实时计算加工、计算结果保存于SequoiaDB中。并能实时的为用户提供在线交易查询服务。在线交易服务平台基于实时处理架构设计,通过将…

自己动手写符合自己业务需求的eslint规则

简介:eslint是构建在AST Parser基础上的规则扫描器,缺省情况下使用espree作为AST解析器。rules写好对于AST事件的回调,linter处理源代码之后会根据相应的事件来回调rules中的处理函数。另外,在进入细节之前,请思考一下…

双11特刊|一站式在线数据管理平台DMS技术再升级,高效护航双11

简介: 10万企业共同选择的数据库服务平台 阿里云数据库已连续多年稳定支撑天猫双11,历经极端流量场景淬炼。除了保障稳定顺滑的基本盘,今年大促期间数据库通过全面云原生化,大幅提升用户体验,让技术帮助业务产生更有价…

账户配置阻止使用计算机.怎样开机,开机自启动设置怎么操作 开机自启动设置如何禁止【图文介绍】...

我们都知道,如今的电脑被我们广泛地运用着, 因为它具有很多的功能,比如我们可以通过电脑实现我们平时工作的需要,还可以不出 门 便能够网上购物,非常方便快捷。电脑一般来说都是由硬件系统和软件系统组成的。有时候可能…

Gartner:2021年全球半导体收入增长26%

供稿 | Gartner 出品 | CSDN 云计算 根据Gartner公司的最终统计结果,2021年全球半导体收入同比增长26.3%,总计5950亿美元。 Gartner研究副总裁Andrew Norwood表示:“引起当前芯片短缺的各种事件继续影响全球原设备制造商(OEM&…

pandas 判断是否等于nan_Python之pandas笔记

一、创建1.创建 Series (一维,带标签的数组)t pd.Series(np.arange(10), index list(string.ascii_uppercase[:10]))2.创建 DataFrame (二维,Series的容器 )t pd.DataFrame(np.arange(6).reshape((2,3)), indexlist(ab),columnslist(csr) )3.DataFrame…

解密 Dubbo 三大中心的部署架构

简介:Dubbo作为一个微服务框架,Dubbo SDK与应用服务绑定在同一个进程内,它跟随着应用服务被部署在分布式集群各个位置,为了在分布式环境下实现各个应用服务间的协作, Dubbo 定义了一些中心化组件。 作者 | 华钟明 01…

历经7年双11实战,阿里巴巴是如何定义云原生混部调度优先级及服务质量的?

简介: 本文将聚焦在 K8s 层的容器优先级和服务质量模型上,希望给业界提供一些可借鉴的思路。 作者:南异 引言 阿里巴巴在离线混部技术从 2014 年开始,经历了七年的双十一检验,内部已经大规模落地推广,每…