学会定制化 Go 项目的 error,回溯错误的原因和发生位置

ops/2024/11/1 6:26:31/

‍Go语言的Error处理一直被人吐槽,吐槽的点除了一个接一个的 if err != nil 的判断外,还有人说Go的错误太原始不能像其他语言那样在抛出异常的时候的时候传一个Casue Exception 把导致异常的整个原因链串起来。

第一点确实是事实,但是写习惯了也能接受,而且对新手友好。第二点属实就有点尬黑了。

用Go开发项目时想让程序抛出的 error 信息不要那么单薄,需要自己搭建项目时先做一番基础工作,自己定义项目的Error类型在包装错误的时候记录上错误的原因和发生的位置,比如像下面这样。

{"code": 10000000,"msg": "服务器内部错误","cause": "db error: undefined column user_id","occurred": "go-study-lab/go-mall.TestAppError, file: building.go, line: 69"
}

同时它还要实现Go的error interface,能融入Go 的错误处理机制才行。

今天我就带大家通过自定义项目Error并实现 Go error interface ,让你的Go项目Error拥有更丰富的错误原因和发生位置的信息。看到一个错误能看出来时什么原因导致的、以及是哪的代码导致的这样能大大降低Go项目的维护难度。

f7b13856269ab51c0fd2857d7aa37a6e.png

Go Error 定制化 ‍ ‍ ‍ ‍

本节对应的代码版本为c4,订阅后加入课程的GitHub项目访问 https://github.com/go-study-lab/go-mall/compare/c3.3...c4 可以直接查看本章节对应的代码更新

fafd34fc6e4793beddea9df4aea3a1dc.png





订阅入口
扫码或者复制链接在浏览器中打开:https://xiaobot.net/p/golang  订阅后即可加入项目,还能阅读完整版实战教程

6140d49d4b503e526f231f31ecbd792c.png

定义项目的Error结构

首先我们在项目的common目录中增加errcode目录,该目录下会创建两个文件error.go 和 code.go。error.go文件用来存放自定义Error的结构和相关方法,code.go 用来放置项目各种预定义的Error。

.
|-- common
|   |-- errcode
|       |---code.go
|       |---error.go
|-- main.go
|-- go.mod
|-- go.sum

我们现在error.go 中定义AppError

type AppError struct {code     int    `json:"code"`msg      string `json:"msg"`cause    error  `json:"cause"`
}

cause 字段保存的是导致产生 AppErr 的原因,比如一个数据库查询语法错误,拿它再来生成项目的 AppError 或者是给预定义好的 AppError 追加上这个原因的error, 这样就能达到保存错误链条的目的。

现在AppError 还不是 error 类型,需要让他实现Go的 error interface,这个接口如下。

type error interface {Error() string
}

其中只定义了一个方法,我们让AppError实现Error方法把它变成 error 类型。

func (e *AppError) Error() string {if e == nil {return ""}formattedErr := struct {Code     int    `json:"code"`Msg      string `json:"msg"`Cause    string `json:"cause"`}{Code:     e.Code(),Msg:      e.Msg(),}if e.cause != nil {formattedErr.Cause = e.cause.Error()}errByte, _ := json.Marshal(formattedErr)return string(errByte)
}

Error方法返回的是AppError对象的JSON序列化字符串,其中如果cause字段不为空即错误原因不为空,再去错误原因的Error方法拿到底层的错误信息。

我们把Stringer 接口也实现一下,在没有类型字段转换的地方,它还是*AppErr类型,保证这个时候序列化它的时候仍然能得到期望的信息。

func (e *AppError) String() string {return e.Error()
}

接下来我们在code.go 中先预定义一些基础的错误

var (Success            = newError(0, "success")ErrServer          = newError(10000000, "服务器内部错误")ErrParams          = newError(10000001, "参数错误, 请检查")ErrNotFound        = newError(10000002, "资源未找到")ErrPanic           = newError(10000003, "(*^__^*)系统开小差了,请稍后重试") // 无预期的panic错误ErrToken           = newError(10000004, "Token无效")ErrForbidden       = newError(10000005, "未授权") // 访问一些未授权的资源时的错误ErrTooManyRequests = newError(10000006, "请求过多")
)

上面大家看到了 AppError 的类型定义中,字段的访问性都是包内可访问的,所以我们要定义一些 getter 方法,这样接口返回错误响应时,才能读到错误码和错误信息。

func (e *AppError) Code() int {return e.code
}func (e *AppError) Msg() string {return e.msg
}func (e *AppError) HttpStatusCode() int {switch e.Code() {case Success.Code():return http.StatusOKcase ErrServer.Code(), ErrPanic.Code():return http.StatusInternalServerErrorcase ErrParams.Code():return http.StatusBadRequestcase ErrNotFound.Code():return http.StatusNotFoundcase ErrTooManyRequests.Code():return http.StatusTooManyRequestscase ErrToken.Code():return http.StatusUnauthorizedcase ErrForbidden.Code():return http.StatusForbiddendefault:return http.StatusInternalServerError}
}

这里的 HttpStatusCode 返回的是HTTP 状态码,如果你们的研发习惯是请求接口的响应一律是 HTTP 200 再通过相应里的code码判断是否正确,这个方法可以放着不用, 规范化一点肯定是这种比较好,况且HTTP Status 不是 200 状态码,也是可以返回 code msg 那些信息给客户端的。

底层Error怎么变成项目Error

上面我们预定义好了几个应用错误,这里说明一下,预定义好的错误会最终返回给发起请求的客户端,所以控制器层各个URI的路由处理控制器中最后一定要返回预定义的错误,这个我们会在未来给Go项目封装统一的响应组件时处理。

那一个底层的错误怎么才能变成我们自定义的错误呢?大家可以订阅后查看完整版。

扫下方海报二维码订阅专栏,即可阅读完整版,也有专属的读者群,欢迎加入一起学习07aa0a69b879abc4780a994a318c9dd3.png专栏分为五大部分,主要内容架构如下:

4a2d51d5f5163890e8f2183850924817.png

  • 第一部分介绍让框架变得好用的诸多实战技巧,比如通过自定义日志门面让项目日志更简单易用、支持自动记录请求的追踪信息和程序位置信息、通过自定义Error在实现Go error接口的同时支持给给错误添加错误链,方便追溯错误源头。

  • 第二部分:讲解项目分层架构的设计和划分业务模块的方法和标准,让你以后无论遇到什么项目都能按这套标准自己划分出模块和逻辑分层。后面几个部分均是该部分所讲内容的实践。

  • 第三部分:设计实现一个套支持多平台登录,Token泄露检测、同平台多设备登录互踢功能的用户认证体系,这套用户认证体系既可以在你未来开发产品时直接应用

  • 第四部分:商城app C端接口功能的实现,强化分层架构实现的讲解,这里还会讲解用责任链、策略和模版等设计模式去解决订单结算促销、支付方式支付场景等多种多样的实际问题。

  • 第五部分:单元测试、项目Docker镜像、K8s部署和服务保障相关的一些基础内容和注意事项

扫描上方的海报二维码或者访问 https://xiaobot.net/p/golang

点击阅读原文可跳转。


http://www.ppmy.cn/ops/130060.html

相关文章

机器学习算法工程师笔试选择题(1)

1. 关于梯度下降的说法正确的是: A. 梯度下降法可以确保找到全局最优解。B. 随机梯度下降每次使用所有数据来更新参数。C. 批量梯度下降(Batch Gradient Descent)通常收敛更快。D. 学习率过大会导致梯度下降过程震荡。答案:D(学习率过大会导致不稳定,可能震荡或无法收敛)…

linux学习笔记 Ubuntu下的守护进程supervisor安装与多项目部署

我这里首先是在本地WSL上进行安装,WSL2的是ubuntu 24.04,之后又再正式环境的ubuntu 20.04上安装,再次记录一下。 1、首先安装supervisor apt install -y supervisor 2、创建配置文件 echo_supervisord_conf > /etc/supervisor/supervisor…

【http协议笔记】-- 浏览器简单分析get、post请求

环境:为了了解http协议的交互方式,使用edge浏览器简单分析协议内容,给刚入门的小伙伴分享一下,方便大家学习。 以菜鸟教程的网站为例子: 分析post: 请求url: 请求参数: 请求相应&a…

Oracle视频基础1.3.3练习

1.3.3 检查数据库启动情况 ps -ef | grep oracle启动数据库 sqlplus /nolog conn / as sysdba修改 fast_start_mttr_target 参数为初始值-50,缺省 scope 和 sid,查看修改结果 show parameter fast; alter system set parameter 250; show parameter fa…

讲一讲 kafka 的 ack 的三种机制?

大家好,我是锋哥。今天分享关于【K讲一讲 kafka 的 ack 的三种机制?】面试题?希望对大家有帮助; 讲一讲 kafka 的 ack 的三种机制? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Kafka的消息确认机制&…

【jvm】新生代和老年代

目录 1. 说明2. 新生代(Young Generation)2.1 定义与用途2.2 内存分配与回收2.3 特点 3. 老年代(Old Generation)3.1 定义与用途3.2 内存分配与回收3.3 特点 1. 说明 1.在Java虚拟机(JVM)中,新…

详解SQL单表查询

SQL单表查询 1. SELECT 语句的基本结构2. 查询所有列和指定列查询所有列查询特定列 3. WHERE 条件筛选比较运算符多条件查询:使用 AND 和 ORNOT 操作符 4. ORDER BY 进行排序多列排序 5. LIMIT 限制返回行数只获取前N行数据使用 LIMIT 和 OFFSET 进行分页查询 6. 常…

鸿蒙UI开发——基于全屏方案实现沉浸式界面

1、概 述 典型应用全屏窗口UI元素包括状态栏、应用界面和底部导航条。 其中状态栏和导航条,通常在沉浸式布局下称为避让区,避让区之外的区域称为安全区。 开发应用沉浸式效果主要指:通过调整状态栏、应用界面和导航条的显示效果来减少状态…