V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
dzdh
V2EX  ›  Go 编程语言

go 没有异常 怎么判断逻辑以外的错误 全局的处理

  •  
  •   dzdh · 4 天前 · 3391 次点击

    比如数据库服务吧。

    程序启动,先连 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 是哪种错误? 这么粗暴的吗?

    第 1 条附言  ·  4 天前
    综合起来看。成熟的 go 项目。应该是各种一路 return nil, err..
    40 条回复    2024-12-31 15:17:00 +08:00
    voidmnwzp
        1
    voidmnwzp  
       4 天前 via iPhone
    跟 java 一样向上抛啊
    sduoduo233
        2
    sduoduo233  
       4 天前 via Android
    我一般是返回 500 ,然后记录一下错误
    u,err:=GetUser(1)
    if err!=nil {
    w.WriteHeader(500)
    log.Println(err)
    return
    }
    dzdh
        3
    dzdh  
    OP
       4 天前
    @voidmnwzp @sduoduo233

    其实返回个 401 也没毛病。就是觉得怪怪的
    laikick
        4
    laikick  
       4 天前
    Panic + Recover + 自定义错误类型 就可以实现类似 Java 的操作 不过也仅仅是类似. 但是,这不是 go 推荐的.

    别把 Java 的习惯带入 go 里面. 这两个语言的思想差别很大的.

    你应该定义一个 ErrDBConnection. 然后在 http 服务 errors.Is(err, repository.ErrDBConnection).
    kk2syc
        5
    kk2syc  
       4 天前
    可以直接 panic ,可以用中间件捕获。不一定比全局优雅,但是也是不错的,业务上我都这样。
    ----

    func ExceptionMiddleware(c *gin.Context) {
    defer func() {
    if err := recover(); err != nil {
    c.JSON(500, gin.H{"msg": "ERROR"})
    c.Abort()
    }
    }()
    c.Next()
    }
    kingcanfish
        6
    kingcanfish  
       4 天前
    java 有个地方处理这个特殊异常,go 中间件不就是这个特殊的地方吗

    我一般是自己定义一些 error 比如 db error, logic errr, client err, 在最先抛出 error 的地方包一层, 然后网上传 最终中间根据不同的 error 返回错误码 ,比如你这个例子, 发现 db 包 error 了 我就包一层 自己的 db error ,往上抛, 中间件捕获
    dzdh
        7
    dzdh  
    OP
       4 天前
    @kingcanfish 抛就是 panic 呗?
    realpg
        8
    realpg  
       4 天前
    还是不建议这么跨舒适区干活
    玩惯了 exception 那一套 改 go 推荐这一套 可能一年都改不过来
    不如再找个 java 工作
    kingcanfish
        9
    kingcanfish  
       4 天前
    @dzdh #7 error
    lasuar
        10
    lasuar  
       4 天前
    改不过来习惯 ,不建议写 go ,难受自个。编程范式都不一样了。
    zoharSoul
        11
    zoharSoul  
       4 天前
    @realpg #8 人家是 php 干嘛找 java 的
    php 都是转 go 的
    afxcn
        12
    afxcn  
       4 天前
    repository 返回正确的错误类型就好了。
    Blackbelly
        13
    Blackbelly  
       4 天前   ❤️ 3
    直接 panic

    因为数据库崩了属于 unrecoverable 的错误。这时候当成 err 向上抛没有意义,上一层也无法处理,只能层层往上抛。
    而且,你的接口语义是 GetUser ,本身就不应该返回一个接口语义之外的错误。
    按照接口语义,应该是返回一个 user ,或者是 NotFoundErr ,除此之外的错误都不应该返回。
    chen11
        14
    chen11  
       4 天前
    我上周才遇见个 bug ,go 程序直接崩溃,log 没打出来,找不到 bug 在哪里。习惯了 java ,来写 go 就难受
    zsj950618
        15
    zsj950618  
       4 天前
    > 被业务中间件拦截到了 返回 401 unauthorized

    那是这个中间件垃圾,都不看错误类型就一股脑返回 401 。
    chevalier
        16
    chevalier  
       4 天前
    从功能上来说
    Go 的 error 相当于 Java 的异常
    Go 的 panic 相当于 Java 的 Error
    changz
        17
    changz  
       4 天前 via Android
    用 protobuf 定义错误码,一层一层往上抛
    wangritian
        18
    wangritian  
       4 天前
    所有语言的最佳实践,都可以在流行框架内找到,推荐到 goframe 看看
    go 一般是自己设计一个符合 error 接口(包含 Error 方法)的带 code 和 msg 的自定义 error
    通过 recover 全局拦截异常,如果是底层报错(数据库连接失败等业务层无须接收),直接 panic
    如果是业务异常(用户名重复),return 自定义 error
    guanzhangzhang
        19
    guanzhangzhang  
       4 天前
    返回 error ,上层处理和家 warp 信息,最后到你接口层面你可以返回 500
    leonshaw
        20
    leonshaw  
       4 天前 via Android
    我一般只有断言失败才用 panic ,其它情况都是返回 error ,视情况包装一层。
    lqs
        21
    lqs  
       3 天前
    ctx 要一路带进去(适当的地方加上 WithTimeout / WithCancel 等)
    err 要一路带出来(适当的地方包裹一下加上错误提示和相关参数上下文)
    iyaozhen
        22
    iyaozhen  
       3 天前
    学 go 有个段子:和你们这些搞 java 的讲不清楚
    jnyanmeng
        23
    jnyanmeng  
       3 天前
    @iyaozhen java 以外我都可以这样讲:和你们这些搞 java 的讲不清楚😄
    Ghostisbored
        24
    Ghostisbored  
       3 天前
    每次看到一些回复都看得好笑 用个开发语言还用出优越感来了。php 流行的时候: 有新人问问题 你不适合 php 还是回去吧 ; java 流行的时候:你不适合 java 还是回去吧 ; go 近几年因为云原生 加上国内字节用的多 不少项目开始用了 又开始了:你不适合 go 还是去搞 java 吧 ; rust 现在被大家喜欢很多软件开始锈化 是不是过一段时间 要出现 你不适合搞 rust 快回去搞 go 吧🙄
    rower
        25
    rower  
       3 天前
    1.有处理错误的中间件,有处理 panic 的中间件,这两个是不一样的

    2.这里是错误类型,我们走处理错误的中间件

    3.你想返回的 http 状态码是 500 ,同时错误信息是 db no ....

    却被 401 处理了

    对于这种情况是创建自定义错误类型,参考

    https://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 设计

    https://github.com/ardanlabs/service6-video/blob/main/app/api/errs/codes.go

    4.如何将 不同的错误转换为对应的 http 状态码,需要建立 code 和 http 状态码的 map 关系

    参考

    https://github.com/ardanlabs/service6-video/blob/main/api/http/api/mid/errors.go

    5.错误中间件处理错误

    这里处理错误时,如果是我们自定义的错误,就将 code 转换为对应的 http 状态码,错误信息不变。

    如果不是自定义错误,表明是未知错误,返回 {500,unknow err}

    参考 https://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())
    }
    ```
    z1829909
        26
    z1829909  
       3 天前
    每一层调用处理能处理的 error, 不能处理的加一些额外信息返回上一层
    异常其实也差不多, 只不过你自己不需要手动处理, 默认是往上抛的
    代码里尽量不要 panic, 程序启动的时候, 做一些必要的依赖检测, 如果一些关键依赖缺失或者启动异常可以 panic
    povsister
        27
    povsister  
       3 天前   ❤️ 1
    和你们这些搞 java 的说不清楚.jpg
    (狗头保命
    bli22ard
        28
    bli22ard  
       3 天前 via iPhone
    java 是 try catch ,只需要处理 exception 。go 里面有两种一种是 panic ,一种是 error ,panic 通过 recover 捕获,error 就是带在函数返回值里面返回。可能是最佳实战的做法是,你调用了标准里,或者第三方库,这些库返回 error 之后,你应该先用一些第三方 error 库 wrap 一下,主要目的是记录一下 error 发生的调用栈,这样上层什么位置拿到 error ,都能打印出来这个 error 是哪个位置发生的。还有一种,目的类似的做法,不记录调用栈,而改为附加一个错误码进去,这样上层的任何调用者也可以知道 error 哪里发生的,不过维护错误码这种方式维护时间越长,越容易搞混乱,导致排查问题困难。
    楼上有说,将 error 转换 panic 的做法,这样做看起来比较爽,不用多余定义 error 返回值,不用 if err!=nil 判断,只需调用入口 recover 住,但是这种做法,不是主流做法,至于为什么不主流,知道的可以解释下🤭。
    haierspi
        29
    haierspi  
       3 天前
    直接 Recover 返回 API 啊
    soul11201
        30
    soul11201  
       3 天前
    换个思路看下这个问题:调用栈比较深的时候,是否强制程序员显示处理流程异常终止情况。

    1. PHP 是不强制的,在调用处看不出来处理程序可能出错。如果不 Catch 一个 Exception 的基类,可能导致异常漏处理;;但是写的很多,代码就很啰嗦、过度防御 。两个方面不管从哪个角度来说,都是工程质量堪忧。

    2. Go err 最佳实践,其实就是强制显示处理错误,调用处就能看出来程序可能处理错误,整体看上去就是做 err 体操,有些呆板。

    3. Java Exception 的 Checked Exception 和 Runtime Exception 设计思路,分别是 Go err 体操路子和 PHP 的埋雷路子。Checked Exception 相比 err 体操只不过是标记在了方法签名处,看上去代码少写了两行,但 Checked Excption 是从哪里丢出来的就不太明确了。从 Checked Exception 的实际使用情况来看,偏 Runtime Exception 方向倾斜,就是逐层标记不处理的多,垃圾~

    从设计和实际使用综合来看,还是 Go Err 相对来说更严谨一点
    lysShub
        31
    lysShub  
       3 天前
    @chen11 我也遇到过一个忽略 err 导致的问题,搞了整整一天
    henix
        32
    henix  
       3 天前
    我的话这种情况不会使用 panic / recover ,那个是给意料之外的严重异常用的
    这种确实需要一路 return nil, err
    如果需要中间处理,那在最开始创建 err 的时候选择一个特定类型,中间件用 errors.Is 判断
    以上是如果你用网上的常用框架的话就这么做

    我个人认为这些框架的错误处理设计得不好,我开发自己的 web 项目的时候不用任何框架,只用 go 标准库
    我设计的 controller 会返回:(结果, err1, err2)
    其中 err1 代表用户输入错误,比如参数检查错误,要给用户返回 4xx
    err2 代表服务器内部错误,要给用户返回 5xx
    用这种方法,不需要 errors.Is 判断类型,只需要判断 err2 != nil 即可
    fovecifer
        33
    fovecifer  
       3 天前
    我的建议是要慎重使用 panic ,切记
    bv
        34
    bv  
       2 天前
    Don’t use panic for normal error handling. Use error and multiple return values.

    https://go.dev/wiki/CodeReviewComments#dont-panic
    ninjashixuan
        35
    ninjashixuan  
       2 天前
    慎用 panic ,因为你不知道什么时候你主动 panic 的函数跑着一个新的没有 recovery 的 goroutine 里,这样就 gg 了。
    wnanbei
        36
    wnanbei  
       2 天前
    不是,楼上你们这些人真的敢在生产环境用 panic 啊?真不怕万一吗?
    我们线上用 panic 的仅仅在启动服务的阶段,前置条件不满足服务无法启动才 panic 。
    DefoliationM
        37
    DefoliationM  
       1 天前 via Android
    有 Java 经验的不建议转其他语言。“Java 那种迂腐笨重的思维,你是忘不掉的。”
    northluo
        38
    northluo  
       1 天前
    @chen11 遇到程序崩溃,大概率是 map 的同时读写导致的,因为大部分的异常都能 recover 住,只有 map 的多协程同时读写会导致这个问题,建议你梳理下你们项目中的 map 是不是有些被其他协程读写了
    chen11
        39
    chen11  
       1 天前
    @northluo 自己写的代码一路 recover 都没找到错误,结果发现是之前的老代码那里报了个空指针,没有返回 error ,所以就像楼主说的,“成熟的 go 项目。应该是各种一路 return nil, err..”
    yyj08070631
        40
    yyj08070631  
       1 天前
    一路 return 出去也可以的,但有个小问题,这样拿不到初始位置的堆栈,所以还得加个类型把堆栈包进去抛出

    (不过我们业务项目也是直接 panic 出去给 recover 打错误日志的,这种做法由于日志信息不是显式的,所以查起来稍微麻烦点,但耐不住方便,错误处理的代码可以减少很多,本来一行 log 、一行 resp 、两行 if err!= nil ,现在一个 panic 就完事了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2626 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 10:33 · PVG 18:33 · LAX 02:33 · JFK 05:33
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.