V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
pkupyx
V2EX  ›  程序员

go 有没有比较合适的异常处理流程方案

  •  
  •   pkupyx · 151 天前 · 2222 次点击
    这是一个创建于 151 天前的主题,其中的信息可能已经有所发展或是发生改变。

    新转 go ,发现接的项目的错误都是这种模式,并且出现次数极多: data, err:= xxx.xxx if err != nil { log.xxx return xxx,err }

    有没有 java 那种,只要在业务逻辑里面: throw XxxException

    然后在最外层有一个统一的方法拦截全部 RuntimeException 的方式来处理异常流的方法么? exceptionHandler(XxxException xxx){ ... }

    24 条回复    2022-07-13 18:02:50 +08:00
    BugCry
        1
    BugCry  
       151 天前 via Android
    panic/recover 。但是用 Java 的思维写 go ,为什么不直接写 Java ?
    golangLover
        2
    golangLover  
       151 天前 via Android   ❤️ 4
    不用想了,没有,问就是大道至简
    golangLover
        3
    golangLover  
       151 天前 via Android
    对了,除了大道至简,还有招人上手容易,一两天就能写代码。
    Trim21
        4
    Trim21  
       151 天前
    panic/recover (
    wangx0102
        5
    wangx0102  
       151 天前
    感觉就 panic/recover
    LukeEuler
        6
    LukeEuler  
       151 天前   ❤️ 1
    https://go.googlesource.com/proposal/+/master/design/go2draft-error-handling-overview.md

    官方很早就在思考并设计这块内容了,至于什么时候来,就不知道了。我个人更喜欢 rust 的设计。
    iyaozhen
        7
    iyaozhen  
       151 天前
    没有,写 Go 千万不要和 Java 对比,完全不是一个阶段。
    frodez
        8
    frodez  
       151 天前   ❤️ 4
    虽说我日常看不惯 go ,但我仍然要问:你为什么需要一个统一的错误处理逻辑?你真的想清楚了原因,而非一时偷懒选择了它?

    你或许应该了解到,和错误处理相关的问题永远都包括两个议题:如何正确处理错误,和如何方便地处理错误。但很可惜绝大多数人都混淆了它们,并把它们视为同一个问题。
    frodez
        9
    frodez  
       151 天前
    另外,方便地处理错误往往会让程序员有精力正确地处理错误,但不是说方便地处理错误就会让程序员一定能正确地处理错误。另一方面,正确地处理错误在某种程度上可以让错误处理更方便,但跨过了某个分界点后,正确处理错误又不能与方便地处理错误相妥协。所以这必须是一个与实际业务紧密相关的问题,如果不相关,那么你的做法就会既不方便也不正确。
    pkupyx
        10
    pkupyx  
    OP
       151 天前   ❤️ 2
    @frodez 因为服务端业务代码 95%以上的 err 只需要封装到统一结构然后层层传给调用方。
    frodez
        11
    frodez  
       151 天前
    @pkupyx 我个人的建议仍然是优先选择正确性。如果真的可以把错误归类为 95%的不需要自己处理的错误,和 5%的需要自己处理的错误,你可以把前者和后者分开,前者一路返回,后者自己专门处理。

    不是非常建议使用 panic 和 recover ,因为它们都是函数级别的跳转逻辑——如果你要在循环中处理错误,使用 panic 和 recover 很可能会导致遗漏。
    iseki
        12
    iseki  
       151 天前 via Android
    panic 吧,一般可以用这种方法处理的错误,都不是真的能处理的错误,panic 最方便了
    kidlj
        13
    kidlj  
       151 天前 via iPhone
    写 web 的话,echo 框架有集中处理错误的方式,比如加上 message 字段或者根据 accept header 返回不同的 content-type 数据。在业务 handler 里只需要 return error 就好了。

    另外,同意楼上说的,方便的不一定是正确的。Go 的错误处理虽然还有优化空间,但在很多人看来这种方式起码是正确的。
    hallDrawnel
        14
    hallDrawnel  
       151 天前   ❤️ 2
    正确的做法就是就地判断 error ,当场决定是否要处理。相比抛出异常,有了更合理的处理时机——在错误发生的地方,持有错误发生的上下文信息,可以自由地编写针对性的处理逻辑;而统一的异常处理节点已经丢失了错误发生的上下文信息。赞同 frodez 的说法,统一的逻辑往往就会变得既不方便也不正确。

    panic 仅仅应该用在整个程序已经无法继续运行的时候,对于业务逻辑来说很少有这种情况。
    joesonw
        15
    joesonw  
       151 天前 via iPhone
    @pkupyx err 往上传就是为了在每一层调用的地方加上现场信息,方便查询错误在哪。
    无脑往上抛要还原现场就只能抓 stacktrace 了,这样的运行时成本就高了。你要不在乎这个性能损失的话无脑 panic 也是可以的,最外层 recover 抓个 stacktrace 。例如一众 web 框架里面都会有的 Recover 的 middleware 。
    LeslieWongH
        16
    LeslieWongH  
       151 天前
    插一句,JS 的 try catch 异步函数的替代方案就是模仿 go 的思路,返回[err, data]数组——
    参见 NPM 仓库 await-to-js
    pkupyx
        17
    pkupyx  
    OP
       151 天前   ❤️ 1
    @joesonw 然而绝大多数场景就是只有在错误发生的位置抓 stacktrace ,其他层级无脑往上抛。
    比如常见的一个服务的调用,展开之后是这个结构:

    handler.getCompanyAdminList(companyId) (list, err) {
    // 封装了校验和调用 service
    // ...
    list, err := service.getCompanyAdmin( companyId ) (list, err) {
    // 封装了获取 admin 和拼接 admin 权限
    // ...
    list, err := adminRepo.getCompanyAdmin() (list, err) {
    // 封装了取 cache 和 orm
    // ...
    list, err := orm.getCompanyAdmin() {
    // 封装 db
    list, err := db.select()
    if err!=nil { 1
    // log
    return nil, err
    }
    }
    if err!=nil { 2
    return nil, err
    }
    // ...
    // 其他业务代码
    }
    if err!=nil { 3
    return nil, err
    }
    // ...
    // 其他业务代码
    }
    if err!=nil { 4
    return nil, err
    }
    // ...
    // 其他业务代码
    }

    只有 1 需要 log ,然后抛出错误到最外层,根据错误直接返回 response 。2 、3 、4 步骤就是无脑略过。
    mekingname
        18
    mekingname  
       150 天前 via iPhone
    无脑略过的 err ,可以直接用_代替啊。

    data, _ = 函数()

    也不需要用 if 去判断。
    partystart
        19
    partystart  
       150 天前
    @mekingname

    如果 data 是指针类型,你用下划线忽略。 你怎么处理?

    如果 data == null 就返回? 万一下游 API 调用正常但是无数据也返回空指针呢?
    mekingname
        20
    mekingname  
       150 天前
    @partystart 我忽略的是 err ,又没有忽略 data
    mekingname
        21
    mekingname  
       150 天前
    @partystart

    而且我第一句也写了,对于『无脑略过"的"err 』。不是无脑略过所有 err 。
    nothingistrue
        22
    nothingistrue  
       150 天前
    @frodez 实际上你并不知道,甚至只是了解一下 Java 的异常机制。Java 的异常机制是:该是自己的事就 catch 住自己处理,不是自己的事就 throw 出去。该自己处理的自己处理,不该处理的抛出去而不要隐藏,这是即正确又方便的事。按照你的思路,来了错误不管是不是自己的就硬去处理,那才是不正确的事。

    此外,统一的错误处理逻辑也不是偷懒,它要在全局层面管理异常,需要花费更多而不是更少的精力。那种捕获到异常就只提示一个“有错误”的统一错误处理才是偷懒,但这不是全局异常处理专属的,if (err ) then print "有错误",也能这样偷懒。
    frodez
        23
    frodez  
       149 天前
    @nothingistrue 你恐怕理解错我的意思了,我没有一句话说过非要自己处理错误,甚至哪怕是不应该自己处理的错误也要自己来处理。而且这和我的意思正相反,因为这不是正确的错误处理方式。

    至于统一的错误处理逻辑,统一的错误处理逻辑不会导致必然的错误(但也不等于一定正确),但基本上是更方便的。很多程序员往往混淆了正确处理错误和方便处理错误两件事,因此实际的统一错误处理实践,往往就会通向方便但错误的处理模式。

    当然,没有统一的错误处理逻辑,每个错误都必须显式地处理,也不等于必然的正确。就像你说的,if err != nil { print } 也是显式的处理错误方式一样。由于显式处理错误的不方便,就会让很多程序员抵触错误处理,这样也会导致错误。

    但在我看来,前者与后者相比,后者起码提供了一个正确且良好的基础,只不过容易因为程序员偷懒而败坏基础。而对于前者,方便比正确更重要,这使得它的基础不够牢固。

    所以个人认为,错误处理最好的实践应该是在尽可能显式处理错误的基础上,给程序员提供语法糖、胶水方法、linter 之类的工具,让错误处理变得方便。
    SmiteChow
        24
    SmiteChow  
       147 天前
    没有
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   4768 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 51ms · UTC 09:29 · PVG 17:29 · LAX 01:29 · JFK 04:29
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.