go 关于函数返回 error 的一个疑问 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
yujianwjj
V2EX    Go 编程语言

go 关于函数返回 error 的一个疑问

  •  
  •   yujianwjj 2024-07-08 13:53:45 +08:00 2593 次点击
    这是一个创建于 530 天前的主题,其中的信息可能已经有所发展或是发生改变。
    type A struct { a int } func f() (*A, error) { // do something // if err != nil { // return nil, err // } return nil, nil } func TestX(t *testing.T) { a, err := f() if err != nil { t.Log(err) } else { t.Log(a.a) // 这里 a 为 nil ,所以 a.a 非法访问,崩溃 } } 

    go 关于错误处理,似乎默认都是大家约定,如果 err != nil ,另外一个值是正常的值,但是理论上来说,即使 err = nil ,另外一个值也不一定是合理的值。感觉下面的写法似乎更好一点。

    func TestX(t *testing.T) { a, err := f() if a == nil { t.Log(err) } else { t.Log(a.a) } } 
    23 条回复    2024-11-25 23:34:05 +08:00
    mainjzb
        1
    mainjzb  
       2024-07-08 14:04:08 +08:00   2
    。。如果每次都要校验 value ,为什么要设计 error

    rust 和 zig 这样的语言只有两种形式,value nil 或者 nil error
    而 golang 多一种 value error 的形式 在 io.write 上很常用,代表写到一半的时候出错了。可以知道已经写了多少。
    而你,试图引入第四种,nil nil 形式。
    很难评这是一个工作了很多年的程序员。
    zzhaolei
        2
    zzhaolei  
       2024-07-08 14:04:28 +08:00
    这就是约定,err == nil ,默认另一个值就是正常的,err != nil ,则另一个值就返回对应的零值。

    err == nil ,为什么另一个值你不返回正常的呢?这设计本身也是不对的。没有异常不应该说明函数的流程是正常的?流程是正常的为什么结果是错误的?
    kdwnil
        3
    kdwnil  
       2024-07-08 14:05:44 +08:00 via Android
    err 不等于 nil ,那一定有问题,先处理(返回),函数后续如何就不需要再关心这个 err 了;不等于 nil 不代表其他返回值就没问题,所以后续也还要继续判断返回值合不合法。这样做虽然会有点罗嗦,但很干净

    如果处理 err 放在后面,像 op 示例这种单个的还好,但如果我有一个复杂的函数,嵌套了很多层这种判断,我是不是应该先开发一个斜 45°的滚动条?
    kdwnil
        4
    kdwnil  
       2024-07-08 14:07:37 +08:00 via Android
    处理错误是很灵活的,要根据实际情况来决定先处理等于还是先处理不等于
    deplives
        5
    deplives  
       2024-07-08 14:14:02 +08:00
    err != nil 表示 result 是有效的值,按理说至少也应该返回一个 A 的空对象而不是 nil
    GuuJiang
        6
    GuuJiang  
       2024-07-08 14:15:29 +08:00 via iPhone   4
    恭喜你发现了 go 的一个致命伤,例如 Haskell 中的 Either 、rust 和 swift 中的 Result 等等基于 Monad 的返回类型,都应该是一个 sum type ,然而 go 却定义了一个 product type
    leonshaw
        7
    leonshaw  
       2024-07-08 14:23:47 +08:00
    0 值未必不是正常值
    lolizeppelin
        8
    lolizeppelin  
       2024-07-08 14:40:42 +08:00
    脱离业务实际逻辑讨论是错误的

    一个简单的例子
    本地查询缓存与数据库查询 map[int]*value

    当本地查询不到结果的时候,去数据库查询,也没有结果
    这时候应该缓存一个 nil,避免不匹配时,击穿缓存

    这时候查询结果和错误都是 nil
    Felldeadbird
        9
    Felldeadbird  
       2024-07-08 14:45:05 +08:00
    有意思,感觉是代码设计问题,a 的返回值应该是明确的。直接返回 nil ,算是写一个 BUG 了。

    刚才翻看了自己写的 go 代码,确实有类似的现象。也是 erro != nil 。 不过其他类型返回都是正常值的,不会直接返回不明确的。 应该不会出现楼主遇到的现象。
    w568w
        10
    w568w  
       2024-07-08 14:51:47 +08:00   2
    从 Type Theory 来说结论早就有了:Go 的 Prod type 抽象就是错的。

    在错误的抽象前提下,不管讨论什么都是错的。我就不掺和楼主对具体情况的讨论了。

    正确的抽象如 Rust 、Zig 、Haskell 都是 Sum type ,即要么 Value ,要么 Error 。

    ====

    先打预防针:我猜肯定会有人 Argue 说「难道你不考虑既有错误又有返回值的情况吗?」,拜托,那应该是 Value | Error | (Value, Error) 这个复合类型,怎么也轮不到 (Value, Error) 吧。即使 (Value, Error) 确实从结果来看「巧合」地达成了复合类型的结果,后续处理中也依然要分成 value != nil && error ==nil, value == nil && error != nil, value != nil && error != nil 三种情况来处理。发现了吗?只不过是在重新发明 Sum type 用一个 pattern matching 就能搞定的简单任务罢了。
    wangritian
        11
    wangritian  
       2024-07-08 14:52:46 +08:00
    不用担心,一般开源项目不会犯低级错误,err 为空则 a 一定是非空
    但确实防不住一些大聪明同事,当然也有少数需要 a=nil 的情况,一般更建议加一个 bool
    afxcn
        12
    afxcn  
       2024-07-08 15:03:45 +08:00
    return nil, nil 这种写法是自己给自己找麻烦。

    自找麻烦的话,谁也帮不了你。
    Ayanokouji
        13
    Ayanokouji  
       2024-07-08 15:10:54 +08:00
    我的常规写法
    rerord,err:=get()
    switch {
    case err == nil:
    do something
    case errors.is(err, NotFoundErr):
    do something
    default:
    return nil, err
    }
    rrfeng
        14
    rrfeng  
       2024-07-08 15:37:13 +08:00
    所以 go 官方 http 的 resp,err := http.Do(xx)
    还要特别说明 err 情况下不会有 resp (避免资源泄露)
    14v45mJPBYJW8dT7
        15
    14v45mJPBYJW8dT7  
       2024-07-08 15:44:56 +08:00
    99%情况下 a 一定不为 nil

    我在 db 查询没有记录时,习惯将 error = record not found 自己处理一下,返回 nil,nil
    CoderXI
        16
    CoderXI  
       2024-07-08 15:54:54 +08:00
    是一个好问题
    1. 非必需不建议使用指针, a.a 永远不会 panic, 但是你仍然需要类似指针检查的判断 0 值。
    2. 让 0 值有意义
    3. err == nil 大多不应该让 a 没有意义
    pkoukk
        17
    pkoukk  
       2024-07-08 16:12:21 +08:00
    你的这个 f() 应该是个初始化函数,一般会取名叫 NewA() (*A, error)
    这个 NewA ,正常人都不会觉得,err==nil 的时候,还能初始化一个空值来
    meowrain
        18
    meowrain  
       2024-07-09 00:34:16 +08:00
    有人会写 return nil,nil 吗。。。。
    ragnaroks
        19
    ragnaroks  
       2024-07-09 00:38:38 +08:00
    这应该属于编辑器代码检查的功能,就像 typescript 的 type = {hasError:true, data:null} | {hasError:false, data:TheDataType} 可以让编辑器提示用户不要写出 return {true,null}
    k911PVQ1xKAbqv5W
        20
    k911PVQ1xKAbqv5W  
       2024-07-09 09:43:28 +08:00
    防御性编程,对于指针类型的返回值,额外加判空逻辑
    k911PVQ1xKAbqv5W
        21
    k911PVQ1xKAbqv5W  
       2024-07-09 09:45:05 +08:00
    你的场景应该是只在乎返回值是不是 nil, 那可以 a, _ := f(),对错误进行忽略即可
    body007
        22
    body007  
       2024-07-09 10:50:48 +08:00
    我之前也有过疑问,但我尽量规范自己的代码,当 err != nil 时其他返回值数据必须正确,如果有特殊情况,我会返回特殊 err ,在 err != nil 代码块内部使用 errors.Is(err, 特殊错误),我感觉这样代码更清晰易懂。

    直到我需要用到标准库的这个字段时,翻看源代码发现有 nil,nil 的场景。



    于是我的代码就返回了不规范的 nil,nil 了



    所以 Go 这个没有强制规范略微蛋疼,即使规范了,标准库也在违反规范。我们可以善用 golangci-lint 检查这些存疑的代码吧。
    BKH3h4F
        23
    BKH3h4F  
       2024-11-25 23:34:05 +08:00
    防御性检测(
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2728 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 08:35 PVG 16:35 LAX 00:35 JFK 03:35
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86