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

118 天前
 yujianwjj
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)
	}
}
1947 次点击
所在节点    Go 编程语言
22 条回复
mainjzb
118 天前
。。如果每次都要校验 value ,为什么要设计 error

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

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

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

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

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

这时候查询结果和错误都是 nil
Felldeadbird
118 天前
有意思,感觉是代码设计问题,a 的返回值应该是明确的。直接返回 nil ,算是写一个 BUG 了。

刚才翻看了自己写的 go 代码,确实有类似的现象。也是 erro != nil 。 不过其他类型返回都是正常值的,不会直接返回不明确的。 应该不会出现楼主遇到的现象。
w568w
118 天前
从 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
118 天前
不用担心,一般开源项目不会犯低级错误,err 为空则 a 一定是非空
但确实防不住一些大聪明同事,当然也有少数需要 a=nil 的情况,一般更建议加一个 bool
afxcn
118 天前
return nil, nil 这种写法是自己给自己找麻烦。

自找麻烦的话,谁也帮不了你。
Ayanokouji
118 天前
我的常规写法
rerord,err:=get()
switch {
case err == nil:
do something
case errors.is(err, NotFoundErr):
do something
default:
return nil, err
}
rrfeng
118 天前
所以 go 官方 http 的 resp,err := http.Do(xx)
还要特别说明 err 情况下不会有 resp (避免资源泄露)
rimutuyuan
118 天前
99%情况下 a 一定不为 nil

我在 db 查询没有记录时,习惯将 error = record not found 自己处理一下,返回 nil,nil
CoderXI
118 天前
是一个好问题
1. 非必需不建议使用指针, a.a 永远不会 panic, 但是你仍然需要类似指针检查的判断 0 值。
2. 让 0 值有意义
3. err == nil 大多不应该让 a 没有意义
pkoukk
118 天前
你的这个 f() 应该是个初始化函数,一般会取名叫 NewA() (*A, error)
这个 NewA ,正常人都不会觉得,err==nil 的时候,还能初始化一个空值来
meowrain
117 天前
有人会写 return nil,nil 吗。。。。
ragnaroks
117 天前
这应该属于编辑器代码检查的功能,就像 typescript 的 type = {hasError:true, data:null} | {hasError:false, data:TheDataType} 可以让编辑器提示用户不要写出 return {true,null}
eddievim
117 天前
防御性编程,对于指针类型的返回值,额外加判空逻辑

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1055644

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX