go 没有异常 怎么判断逻辑以外的错误 全局的处理
比如数据库服务吧。
程序启动,先连 db 。连成功。启动 web 服务。然后 setup 路由啥的一堆。
好,服务启动成功了。
现在接受 http 请求,此时数据库崩了。
gorm 返回了 err 。比如代码如下
// userRepository.go
func GetUser(uid int64) (*User, error) {
user:=new(User)
if err :=db.model(user).Find(user).err; err != nil {
return nil,err
} else { return user, nil }
}
按照 java/php 这种的逻辑。我可以抛出个异常。然后有个地方是处理这个特殊的异常。返回 500,db no connection 。
go 里边咋做呢?现在数据库崩了以后,被业务中间件拦截到了 返回 401 unauthorized 。
repository 由 http 服务调用。我要直接 panic 吗 0.0 http 的中间件 recover 住判断 err 是哪种错误? 这么粗暴的吗?
综合起来看。成熟的 go 项目。应该是各种一路 return nil, err..
跟 java 一样向上抛啊
我一般是返回 500 ,然后记录一下错误
u,err:=GetUser(1)
if err!=nil {
w.WriteHeader(500)
log.Println(err)
return
}
其实返回个 401 也没毛病。就是觉得怪怪的
Panic + Recover + 自定义错误类型 就可以实现类似 Java 的操作 不过也仅仅是类似. 但是,这不是 go 推荐的.
别把 Java 的习惯带入 go 里面. 这两个语言的思想差别很大的.
你应该定义一个 ErrDBConnection. 然后在 http 服务 errors.Is(err, repository.ErrDBConnection).
可以直接 panic ,可以用中间件捕获。不一定比全局优雅,但是也是不错的,业务上我都这样。
func ExceptionMiddleware(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
c.JSON(500, gin.H{"msg": "ERROR"})
c.Abort()
}
}()
c.Next()
}
java 有个地方处理这个特殊异常,go 中间件不就是这个特殊的地方吗
我一般是自己定义一些 error 比如 db error, logic errr, client err, 在最先抛出 error 的地方包一层, 然后网上传 最终中间根据不同的 error 返回错误码 ,比如你这个例子, 发现 db 包 error 了 我就包一层 自己的 db error ,往上抛, 中间件捕获
抛就是 panic 呗?
还是不建议这么跨舒适区干活
玩惯了 exception 那一套 改 go 推荐这一套 可能一年都改不过来
不如再找个 java 工作
#7 error
改不过来习惯 ,不建议写 go ,难受自个。编程范式都不一样了。
#8 人家是 php 干嘛找 java 的
php 都是转 go 的
repository 返回正确的错误类型就好了。
直接 panic
因为数据库崩了属于 unrecoverable 的错误。这时候当成 err 向上抛没有意义,上一层也无法处理,只能层层往上抛。
而且,你的接口语义是 GetUser ,本身就不应该返回一个接口语义之外的错误。
按照接口语义,应该是返回一个 user ,或者是 NotFoundErr ,除此之外的错误都不应该返回。
我上周才遇见个 bug ,go 程序直接崩溃,log 没打出来,找不到 bug 在哪里。习惯了 java ,来写 go 就难受
被业务中间件拦截到了 返回 401 unauthorized
那是这个中间件垃圾,都不看错误类型就一股脑返回 401 。
从功能上来说
Go 的 error 相当于 Java 的异常
Go 的 panic 相当于 Java 的 Error
用 protobuf 定义错误码,一层一层往上抛
所有语言的最佳实践,都可以在流行框架内找到,推荐到 goframe 看看
go 一般是自己设计一个符合 error 接口(包含 Error 方法)的带 code 和 msg 的自定义 error
通过 recover 全局拦截异常,如果是底层报错(数据库连接失败等业务层无须接收),直接 panic
如果是业务异常(用户名重复),return 自定义 error
返回 error ,上层处理和家 warp 信息,最后到你接口层面你可以返回 500
我一般只有断言失败才用 panic ,其它情况都是返回 error ,视情况包装一层。
ctx 要一路带进去(适当的地方加上 WithTimeout / WithCancel 等)
err 要一路带出来(适当的地方包裹一下加上错误提示和相关参数上下文)
学 go 有个段子:和你们这些搞 java 的讲不清楚
java 以外我都可以这样讲:和你们这些搞 java 的讲不清楚😄
每次看到一些回复都看得好笑 用个开发语言还用出优越感来了。php 流行的时候: 有新人问问题 你不适合 php 还是回去吧 ; java 流行的时候:你不适合 java 还是回去吧 ; go 近几年因为云原生 加上国内字节用的多 不少项目开始用了 又开始了:你不适合 go 还是去搞 java 吧 ; rust 现在被大家喜欢很多软件开始锈化 是不是过一段时间 要出现 你不适合搞 rust 快回去搞 go 吧🙄
1.有处理错误的中间件,有处理 panic 的中间件,这两个是不一样的
2.这里是错误类型,我们走处理错误的中间件
3.你想返回的 http 状态码是 500 ,同时错误信息是 db no ....
却被 401 处理了
对于这种情况是创建自定义错误类型,参考
github.com/ardanlabs/service6-video/tree/main/app/api/errs
// Error represents an error in the system.
type Error struct {
Code ErrCode `json:"code"`
Message string `json:"message"`
}
这里的 message 就是我们的错误信息db no ...
这里的 code 是我们内部错误的编码,比如说授权错误,code = 1 ,数据库错误,code = 2
参考 code 设计
github.com/ardanlabs/service6-video/blob/main/app/api/errs/codes.go
4.如何将 不同的错误转换为对应的 http 状态码,需要建立 code 和 http 状态码的 map 关系
参考
github.com/ardanlabs/service6-video/blob/main/api/http/api/mid/errors.go
5.错误中间件处理错误
这里处理错误时,如果是我们自定义的错误,就将 code 转换为对应的 http 状态码,错误信息不变。
如果不是自定义错误,表明是未知错误,返回 {500,unknow err}
参考 github.com/ardanlabs/service6-video/blob/main/app/api/mid/errors.go
func Errors(ctx context.Context, log *logger.Logger, handler Handler) error {
err := handler(ctx)
if err == nil {
return nil
}
log.Error(ctx, "message", "ERROR", err.Error())
// 这里判断是不是自定义错误
if errs.IsError(err) {
return errs.GetError(err)
}
return errs.Newf(errs.Unknown, errs.Unknown.String())
}
每一层调用处理能处理的 error, 不能处理的加一些额外信息返回上一层
异常其实也差不多, 只不过你自己不需要手动处理, 默认是往上抛的
代码里尽量不要 panic, 程序启动的时候, 做一些必要的依赖检测, 如果一些关键依赖缺失或者启动异常可以 panic
和你们这些搞 java 的说不清楚.jpg
(狗头保命
java 是 try catch ,只需要处理 exception 。go 里面有两种一种是 panic ,一种是 error ,panic 通过 recover 捕获,error 就是带在函数返回值里面返回。可能是最佳实战的做法是,你调用了标准里,或者第三方库,这些库返回 error 之后,你应该先用一些第三方 error 库 wrap 一下,主要目的是记录一下 error 发生的调用栈,这样上层什么位置拿到 error ,都能打印出来这个 error 是哪个位置发生的。还有一种,目的类似的做法,不记录调用栈,而改为附加一个错误码进去,这样上层的任何调用者也可以知道 error 哪里发生的,不过维护错误码这种方式维护时间越长,越容易搞混乱,导致排查问题困难。
楼上有说,将 error 转换 panic 的做法,这样做看起来比较爽,不用多余定义 error 返回值,不用 if err!=nil 判断,只需调用入口 recover 住,但是这种做法,不是主流做法,至于为什么不主流,知道的可以解释下🤭。
直接 Recover 返回 API 啊
换个思路看下这个问题:调用栈比较深的时候,是否强制程序员显示处理流程异常终止情况。
- PHP 是不强制的,在调用处看不出来处理程序可能出错。如果不 Catch 一个 Exception 的基类,可能导致异常漏处理;;但是写的很多,代码就很啰嗦、过度防御 。两个方面不管从哪个角度来说,都是工程质量堪忧。
- Go err 最佳实践,其实就是强制显示处理错误,调用处就能看出来程序可能处理错误,整体看上去就是做 err 体操,有些呆板。
- Java Exception 的 Checked Exception 和 Runtime Exception 设计思路,分别是 Go err 体操路子和 PHP 的埋雷路子。Checked Exception 相比 err 体操只不过是标记在了方法签名处,看上去代码少写了两行,但 Checked Excption 是从哪里丢出来的就不太明确了。从 Checked Exception 的实际使用情况来看,偏 Runtime Exception 方向倾斜,就是逐层标记不处理的多,垃圾~
从设计和实际使用综合来看,还是 Go Err 相对来说更严谨一点
我也遇到过一个忽略 err 导致的问题,搞了整整一天
我的话这种情况不会使用 panic / recover ,那个是给意料之外的严重异常用的
这种确实需要一路 return nil, err
如果需要中间处理,那在最开始创建 err 的时候选择一个特定类型,中间件用 errors.Is 判断
以上是如果你用网上的常用框架的话就这么做
我个人认为这些框架的错误处理设计得不好,我开发自己的 web 项目的时候不用任何框架,只用 go 标准库
我设计的 controller 会返回:(结果, err1, err2)
其中 err1 代表用户输入错误,比如参数检查错误,要给用户返回 4xx
err2 代表服务器内部错误,要给用户返回 5xx
用这种方法,不需要 errors.Is 判断类型,只需要判断 err2 != nil 即可
之前一直是使用的树莓派 4B ,在上面使用了 openwrt ,开了 SMB ,WEBDAV ,clash, Jellyfin, VaultWarden, FreshRss 等…
不想自己搭博客主要是考虑到迁移成本,现在各大云服务器商都是老用户与狗不得入内,到期了就得考虑换别的服务器。 对于推荐的博客平台有 3 个要求: 1 、csdn 就不考虑了,原因…
下面是我在 Chatgpt 上得到的代码程,我是个初学者,这个程序这样写有问题吗?import requestsfrom bs4 import BeautifulSoupimp…