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

看到好多人吐槽 golang 的错误处理,但我用的很爽啊

  •  
  •   dafsic · 2020-09-11 09:22:06 +08:00 · 10002 次点击
    这是一个创建于 1536 天前的主题,其中的信息可能已经有所发展或是发生改变。

    golang 的错误处理,我之前也吐槽,但从 1.13 开始吧就挺好用了。 之前吐槽点:

    1. 如果底层函数出错,只在上层打印错误信息,会丢失调用栈,不知道最开始的错误发生在哪里。
    2. 如果通过字符串追加的方式,加入调用栈信息,那么错误类型会丢失,无法像 if err == io.EOF 这样判断是什么错误。

    现在已经不是问题了。

    // LineInfo 返回调用此函数的代码所在函数、文件、行号
    // 此函数应该在一个单独的文件中,比如,utils/getlineinfo.go
    func LineInfo() string {
    	function := "xxx"
    	pc, file, line, ok := runtime.Caller(1)
    	if !ok {
    		file = "???"
    		line = 0
    	}
    	function = runtime.FuncForPC(pc).Name()
    	return fmt.Sprintf(" -> %s():%s:%d", function, file, line)
    }
    
    var ErrAuth = errors.New("auth error")
    var ErrAccount = fmt.Errorf("%w: account not exist", ErrAuth)
    var ErrPassword = fmt.Errorf("%w: incorrect password", ErrAuth)
    
    func login(acc, pwd string) (string, error) {
    	if acc != "libai" {
    		return "", ErrAccount
    	}
    	if pwd != "123456" {
    		return "", ErrPassword
    	}
    
    	return fmt.Sprintf("key:AC34cvG-%d", time.Now().Unix()), nil
    }
    
    func getInfo(acc, pwd string) (string, error) {
    	key, err := login(acc, pwd)
    	if err != nil { // login 的错误
    		return "", fmt.Errorf("%w%s", err, LineInfo())
    	}
    
    	// 打开下面的注释就会是 key 过期
    	//time.Sleep(time.Second)
    
    	msg, err := getIntro(key)
    	if err != nil { // key 错误
    		return "", fmt.Errorf("%w%s", err, LineInfo())
    	}
    
    	return msg, nil
    }
    
    var ErrKey = errors.New("invalid key")
    
    func getIntro(key string) (string, error) {
    	if key != fmt.Sprintf("key:AC34cvG-%d", time.Now().Unix()) {
    		return "", ErrKey
    	}
    
    	return "李白,号青莲居士", nil
    }
    
    func main() {
    	info, err := getInfo("libai", "123456")
    	if err != nil && errors.Is(err, ErrAuth) { // 无论账号错误还是密码错误,都是认证错误
    		fmt.Printf("[info]%s\n", err.Error())
    	} else if err != nil {
    		fmt.Printf("[error]:%s\n", err.Error())
    	}
    
    	fmt.Println(info)
    }
    
    
    74 条回复    2020-09-28 14:27:44 +08:00
    ruanimal
        1
    ruanimal  
       2020-09-11 09:36:12 +08:00   ❤️ 6
    不可否认,if err != nil 导致代码的可读性变差,异常处理的逻辑和业务逻辑代码混杂在一起
    z0wjqnxi
        2
    z0wjqnxi  
       2020-09-11 09:47:03 +08:00 via iPhone   ❤️ 1
    你说的两个点,第三方的 errors 包都已经足够好用,现在的版本,go 自带的 error.Is(), 判断错误类型也够用了。

    我觉得现在最大的痛点应该是要写一堆‘ if err!= nil {}’ ,代码块里可能有很多重复的错误处理逻辑还不好抽离封装, 为了少写这些重复逻辑,社区许多人推荐用 defer,现在我偶尔用 goto 。
    sryanyuan
        3
    sryanyuan  
       2020-09-11 09:48:08 +08:00   ❤️ 5
    写惯 c/c++的表示错误码很正常,普通错误,比如文件找不着这种,不算异常,就返回一个 errorcode 上层继续处理,程序可以继续正常运行
    碰到各种异常,直接崩溃了就得了
    damngood
        4
    damngood  
       2020-09-11 10:00:06 +08:00
    @ruanimal 感觉是各有各的好处, 就地处理还是在 catch 块处理.
    dafsic
        5
    dafsic  
    OP
       2020-09-11 10:04:09 +08:00
    @z0wjqnxi 我觉得可读性和可维护性更重要,有时候一个功能会用到另一个功能的部分函数,我可能会通过复制代码的方式,使两个功能减少联系。功能划分的合理,可能一个函数里只有一两个 if err != nil{}的地方,一点不影响阅读和美感,每个错误处理逻辑非常清晰,反而更优雅。而且不是所有的错误都要处理,信任边界的问题。总之,这是因人而异的,不是普适的,受人生观价值观的哲学范畴。
    MadbookPro
        6
    MadbookPro  
       2020-09-11 10:15:02 +08:00
    没觉得 if err != nil 有哪里不爽。。 一直写 lua 也是这么处理的
    L2AKnG8GXx60bc6P
        7
    L2AKnG8GXx60bc6P  
       2020-09-11 10:16:29 +08:00 via iPhone   ❤️ 1
    rust 的?才是真香
    zsdroid
        8
    zsdroid  
       2020-09-11 10:26:36 +08:00
    这不是反人类吗?返回错误时需要同时返回其他非错误的返回值
    zoffy
        9
    zoffy  
       2020-09-11 10:32:50 +08:00
    通常来说,代码表达要保持正向语义:isXXX 、==

    少数表达可以使用反向语义:disabled 、prevent 、ignore
    heiheidewo
        10
    heiheidewo  
       2020-09-11 10:35:08 +08:00
    挺好用的,有必要强制检查接口返回的正确性
    zhuangzhuang1988
        11
    zhuangzhuang1988  
       2020-09-11 10:37:03 +08:00
    开心就好
    dafsic
        12
    dafsic  
    OP
       2020-09-11 10:42:40 +08:00
    @zsdroid 兄弟,你代表不了全人类,对 windows 用户来说,linux 反人类吧。因人而异的东西不值一提。
    no1xsyzy
        13
    no1xsyzy  
       2020-09-11 10:45:06 +08:00   ❤️ 2
    俺寻思这和 .then(good, bad) 一样吧
    JavaScript 已经快进到下一步了。
    zjsxwc
        14
    zjsxwc  
       2020-09-11 11:03:10 +08:00   ❤️ 2
    还不如直接抄 rust 的 Result<Ok, Err> 配合 ?宏 模式
    zsdroid
        15
    zsdroid  
       2020-09-11 11:03:15 +08:00   ❤️ 3
    @dafsic 因人而异的东西不值一提?那你发帖的东西就不是因人而异了?这双标真牛逼!!!!!
    CosimoZi
        16
    CosimoZi  
       2020-09-11 11:10:58 +08:00
    说了多少次, 应该实现为 coproduct 的东西, 愣是实现成了 product. 真是把类型系统喂了狗了.
    wysnylc
        17
    wysnylc  
       2020-09-11 11:20:49 +08:00
    @zsdroid #15 他即世界中心
    cmdOptionKana
        18
    cmdOptionKana  
       2020-09-11 11:22:49 +08:00
    很爽倒不至于,多数人会有点小烦,但问题不是很严重,主要是 golang 槽点太少,才经常有人提起这个小缺点。
    dafsic
        19
    dafsic  
    OP
       2020-09-11 11:38:51 +08:00 via Android   ❤️ 1
    @zsdroid 1.我说的不是 if err != Nil 的问题。2, 你可以说你不喜欢,反人类?你不配。不再回复你了。
    no1xsyzy
        20
    no1xsyzy  
       2020-09-11 11:41:11 +08:00
    no1xsyzy
        21
    no1xsyzy  
       2020-09-11 11:50:18 +08:00
    看上去是类似反射的机制实现?
    甚至写法上不如 C 宏优雅。
    ZSeptember
        22
    ZSeptember  
       2020-09-11 11:52:44 +08:00   ❤️ 1
    写分布式或者一致性要求高的场景,Golang 的这种错误处理是很爽的。
    每一步,都要考虑怎么处理,如果用异常,才是真正的反人类。
    当然,写一般的业务代码,自然是异常爽,直接抛出去,在顶层捕获处理就可以了。
    lance6716
        23
    lance6716  
       2020-09-11 12:33:41 +08:00 via Android
    抛异常不就是 panic 吗
    yamasa
        24
    yamasa  
       2020-09-11 14:51:57 +08:00
    还是觉得 rust 那样的最优雅
    pmispig
        25
    pmispig  
       2020-09-11 14:54:03 +08:00
    if err 没什么不爽 ,不爽的是需要错误信息自己手动拼接,层层上传,才能传到顶层
    chengxiao
        26
    chengxiao  
       2020-09-11 15:03:36 +08:00
    刚开始写的时候觉得很不爽,但是写久了突然不写了会感觉很慌.....怕漏错
    kuro1
        27
    kuro1  
       2020-09-11 15:06:03 +08:00
    每一个 err 都处理,心智负担很小~
    HiShan
        28
    HiShan  
       2020-09-11 15:08:41 +08:00   ❤️ 2
    看到好多人吐槽 Java 啰嗦,但我用的爽啊.
    virusdefender
        29
    virusdefender  
       2020-09-11 15:09:47 +08:00
    同样写的很爽,觉得虽然 if err != nil 确实有些繁琐,但是也还好。

    最近写 lua,因为太容易出 bug 了,现在用 https://github.com/teal-language/tl/ 加一些自定义的类型,基本上和写 go 没啥区别了。
    buffzty
        30
    buffzty  
       2020-09-11 18:08:55 +08:00
    C 语言不也是这样处理的吗? 那些吐槽的是没用过 c 吗?
    monkeyWie
        31
    monkeyWie  
       2020-09-11 18:35:32 +08:00   ❤️ 1
    其实大多数情况下错误处理都是向外抛
    ```
    err := case1()
    if err!=nil{
    return nil,err
    }

    err := case2()
    if err!=nil{
    return nil,err
    }

    doSome()
    ```
    用 try catch 的话只用:
    ```
    try{
    case1()
    case2()
    doSome()
    }catch(e){
    throw e
    }
    ```

    这不是高下立判吗?
    pisc
        32
    pisc  
       2020-09-11 19:13:25 +08:00 via Android   ❤️ 1
    这东西就属于 taste 的问题,而 taste 的问题往往是因为见的少,大多数情况下,我不相信一个写习惯 Haskell (还有其他抽象程度足够高的语言)的人,会对 Go 的错误处理感到满意。
    sagaxu
        33
    sagaxu  
       2020-09-11 19:20:55 +08:00 via Android
    @monkeyWie 但是异常引入了其它问题

    1. 构造析构函数里能不能抛,怎么处理
    2. checked exception vs unchecked exception
    3. finally 中修改了返回值,会不会生效
    4. 性能问题,异常比返回值慢的多
    5. 忘记处理异常,或者处理不当
    blless
        34
    blless  
       2020-09-11 20:00:27 +08:00
    @monkeyWie 在 Java 里面其实也很容易无脑 catch 尤其是对新手,给程序埋下更大的隐患
    dafsic
        35
    dafsic  
    OP
       2020-09-11 20:38:43 +08:00
    @monkeyWie 那我问一个问题,一个函数里需要字符串转整型的功能,如果字符串不是整数字符串,就当作 0 来处理怎么实现。现实中我有许多类似的需求,golang 实现如下:
    ```
    func foo(index string) {
    i, _ := strconv.Atoi(index)
    // 忽略错误,继续处理,比如数据库某个字段默认是 0
    }

    ```
    用抛出异常的方式怎么实现?
    Nugine0
        36
    Nugine0  
       2020-09-11 21:00:37 +08:00 via Android
    rust 用问号运算符和异常差不多爽,要严格处理错误可以当场判断,能确保错误被处理,上下都涵盖了。
    C 还能用宏,go 怎么偷懒?没法偷懒不就是反人类吗?
    dafsic
        37
    dafsic  
    OP
       2020-09-11 22:22:15 +08:00
    @Nugine0 中宫飞出乾,次与兑艮连, 离坎接坤位,震循巽入中。
    Nugine0
        38
    Nugine0  
       2020-09-11 22:33:23 +08:00 via Android
    @dafsic ¿
    sagaxu
        39
    sagaxu  
       2020-09-11 23:02:15 +08:00 via Android
    @dafsic

    "123".toIntOrNull() ?: 0
    damngood
        40
    damngood  
       2020-09-11 23:06:47 +08:00   ❤️ 1
    @pisc go 的 taste 比较另类点而已, 或者作者觉得直白够 simple 也是一种 good taste.

    另外, 撇开 taste, 整体来说 go 用来做事情我个人还是比较满意的.
    crclz
        41
    crclz  
       2020-09-11 23:12:26 +08:00
    golang 错误处理适合一般的团队,团队成员层次不齐的情况下依然保证不会在错误处理上面出重大问题。
    java 异常会被参差不齐的开发人员滥用。所以,只有那些真正理解异常并正确使用异常的人,才有权利说 java 异常用得爽、go 的繁琐。
    Jirajine
        42
    Jirajine  
       2020-09-11 23:13:56 +08:00 via Android   ❤️ 1
    @cmdOptionKana golang 槽点太少?你怎么好意思说。
    错误处理、泛型、duck typing 、编译时不严格检查、零值初始化、关键词过度复用、unused variable/import 该是 warning 非要当 error,等等。网络上成千上万字批判 go 的文章一大堆。
    wamson
        43
    wamson  
       2020-09-11 23:38:00 +08:00 via iPhone
    写过一个月 go,还是没 python 写得爽,弃了,要性能的话我就写 cpp,再用 py 调吧
    pisc
        44
    pisc  
       2020-09-11 23:39:57 +08:00 via Android   ❤️ 1
    @damngood 你说你觉得满意没问题,毕竟人嘛自己自己看着舒服最重要,但它哪里称得上“另类”?这玩意儿除了突出一个简单无脑,我看不出有另类的地方,拿 product type 来表现天然 union 结构的东西,如果这也算是 good taste 的话。。。
    damngood
        45
    damngood  
       2020-09-12 00:12:30 +08:00
    @pisc 我意思是作者从另外一个角度去看待你说的 taste, 或许他们认为这种 c 类似的方式比较简单直白所以算 good taste? 毕竟 go 的口号之一就是 simple and straight.

    另外 taste 这个东西和别的东西权衡下来或许也没有那么重要? 当然如果有种完美语言又有 taste 又工程性很高有简单上手, 那当然是最好不过了. 不过目前看下来 swift 个各方面权衡不错的, 只不过框在 apple 的生态了, 其他领域还远着...
    chenqh
        46
    chenqh  
       2020-09-12 01:29:14 +08:00
    @dafsic 如果是 python
    try:
    v = int(param_str)
    except ValueError:
    v = 0
    return 0
    nuk
        47
    nuk  
       2020-09-12 02:43:32 +08:00
    除了没有调用栈,唯一的缺点就是有点麻烦
    一般 if err !=nil 在我写的 go 里面要占一半左右
    曾经我也觉得没有调用栈调试会比较麻烦
    不过事实证明只要 error 写的够详细
    基本上错误打印出来就能大概知道怎么发生的了
    jiangzm
        48
    jiangzm  
       2020-09-12 03:35:37 +08:00
    同样是 google 开发的编程语言,dart 比 golang 看着顺眼多了
    因为要维护一个 golang 项目,学了点皮毛,给我的感觉是 golang 在语言特性方面为了区别而区别,看着不伦不类的感觉。
    不过对于 go 主力开发人员来说看多了应该也习惯了
    YzSama
        49
    YzSama  
       2020-09-12 10:20:42 +08:00
    @HiShan #28 有一说一,我也觉得 Java 异常处理也很爽。

    不过,我觉得 Go 的异常处理机制,既然现在可以判断异常类型。那为啥不能统一处理。
    kneep
        50
    kneep  
       2020-09-12 11:03:51 +08:00
    官方邮件列表里面也经常有人提议各种简化 if err != nil 等样板代码的方案
    darknoll
        51
    darknoll  
       2020-09-12 11:21:59 +08:00
    我也不喜欢用异常啥的
    linvaux
        52
    linvaux  
       2020-09-12 12:07:29 +08:00
    之前我不能理解为啥一定要返回 err,后来就习惯了,无所谓咯,go 人家就这么设计的,爱用不用
    monkeyWie
        53
    monkeyWie  
       2020-09-12 14:15:01 +08:00
    @dafsic #35 这个很简单啊
    ```
    int i;
    try{
    i = Integer.valueof(index)
    }catch(NumberFormatException e){
    i = 0;
    }
    ```
    monkeyWie
        54
    monkeyWie  
       2020-09-12 14:19:56 +08:00
    @dafsic #35 而且你给的这个例子就奇怪,都没做错误处理,正确的写法不应该是这样吗:
    ```
    func foo(index string) {
    i, err := strconv.Atoi(index)
    if err!=nil{
    i = 0
    }
    }

    ```
    因为不能保证这个方法在有 err 返回的时候,第一个返回值的就一定是你想要的 0 。
    dafsic
        55
    dafsic  
    OP
       2020-09-12 15:34:05 +08:00 via Android
    @monkeyWie 只能说你根本就不会用 golang,哪怕写过一个月的 golang 也知道我写的就是普遍写法。
    dafsic
        56
    dafsic  
    OP
       2020-09-12 15:38:49 +08:00 via Android
    @chenqh golang 只需忽略错误即可,返回值默认是零值。
    dafsic
        57
    dafsic  
    OP
       2020-09-12 15:45:41 +08:00 via Android
    @Nugine0 奇门遁甲九宫飞泊口诀
    dafsic
        58
    dafsic  
    OP
       2020-09-12 15:50:01 +08:00 via Android
    @wamson 写过半年 Python,写的确实爽,读起来,维护起来能要命,全靠注释。
    Nugine0
        59
    Nugine0  
       2020-09-12 17:03:30 +08:00 via Android
    @dafsic 这和编程语言有什么关系?
    reus
        60
    reus  
       2020-09-12 17:06:34 +08:00
    @dafsic 错,你看看 strconv.ParseInt 的文档就知道了。或者跑下这个程序,看看返回是不是 0 ? https://play.golang.org/p/0dLcyPJFt32
    reus
        61
    reus  
       2020-09-12 17:12:06 +08:00
    @dafsic 我看你这个“普遍写法”是要爆大锅,你以为是 0,实际输入如果超出 64 位整数能表示的范围,那它返回的是最大可以表示的整数,如果这个返回值用在循环里,你的程序就要跑很久很久了。所以错误最好不要忽略,也不要认为返回错误时,其他返回值就是零值。很简单的例子,io.Reader.Read,返回 io.EOF 时,另一个返回值也可能非零。
    treblex
        62
    treblex  
       2020-09-12 18:18:51 +08:00
    工具类直接 panic,在外层逻辑使用 defer recover
    最近学到这个用法,看起来会比较干净
    monkeyWie
        63
    monkeyWie  
       2020-09-12 18:35:56 +08:00
    @dafsic #55 👨‍🦳笑了,谁跟你说的 err 返回了就一定返回 0,编译器的约定吗?
    herozzm
        64
    herozzm  
       2020-09-12 18:54:07 +08:00 via Android
    比 try 好用
    dafsic
        65
    dafsic  
    OP
       2020-09-12 19:11:58 +08:00 via Android
    @monkeyWie 行啦,不回复你了,有时间多学学习吧
    monkeyWie
        66
    monkeyWie  
       2020-09-12 19:28:22 +08:00 via Android
    @dafsic 不是,60 61 楼打脸了就不回了?还有希望你提高点姿势水平,跟你不在一层讨论的没意义,后面我也不会回复了
    xcp3555
        67
    xcp3555  
       2020-09-12 21:06:47 +08:00
    看起来不太爽
    dafsic
        68
    dafsic  
    OP
       2020-09-12 22:22:28 +08:00 via Android
    @reus 确实是,我这样写了三年多,运气好没遇到问题。至于错误忽略不忽略看信任边界了。
    gamexg
        69
    gamexg  
       2020-09-12 22:40:32 +08:00
    没用前觉得到处是 if err!=ni 不想用。
    真用了才发现这个比异常更可靠,使用异常时经常对一大块代码一个 try 完事;但是用了 go 的 err 后,会对每个 可能出错的地方进行检查,更加仔细了。

    打起来麻烦及显示占空间的问题,goland ide 本身会进行处理。
    davichi2009
        70
    davichi2009  
       2020-09-14 09:07:03 +08:00
    @dafsic 转之前不先判断参数类型? 类型转换的操作任何时候都要先判断类型的吧? 类型不匹配返回一个指定的默认值不就可以了?
    flywheel
        71
    flywheel  
       2020-09-18 11:06:35 +08:00
    不做特殊处理,方便理解及代码阅读
    sutra
        72
    sutra  
       2020-09-21 09:50:47 +08:00
    满屏 if err !=nil 很是酸爽。
    simenet
        73
    simenet  
       2020-09-22 11:31:49 +08:00
    很希望多一个 try 就不用写一大堆 err!=nil 了
    zunceng
        74
    zunceng  
       2020-09-28 14:27:44 +08:00
    上面一口一个 try catch 的 不用看你们的代码 我就知道充满各种错误

    简单来说
    如果说 if err != nil 的问题是丑
    try catch 的问题就是没法写出对的代码
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1184 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 32ms · UTC 18:36 · PVG 02:36 · LAX 10:36 · JFK 13:36
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.