关于 Go 在 `Return Nil or Pointer to Empty Struct on Error` 上的讨论?

2022-08-12 11:47:48 +08:00
 ryalu
type User struct {}


// 1. return nil
func GetUser() (*User, err) {
	....
    
    return nil, err
}

// 2. return pointer to empty struct
func GetUser() (*User, err) {
	....
    
    return &User{}, err
}

个人会担心下游的不注意( nil 判断)导致进程 panic ,所以基本都是用第二种。想听听大家的意见或见解?

4569 次点击
所在节点    Go 编程语言
82 条回复
codehz
2022-08-12 11:53:27 +08:00
有 err 的情况还这样用?
那只能掩盖漏洞了
wellsc
2022-08-12 11:56:08 +08:00
2
javaisthebest
2022-08-12 12:08:57 +08:00
2 宁愿出现了 Panic 都别用初始值。

万一上游调用漏掉了 error 。你这个 error 会一直漏掉
ryalu
2022-08-12 12:12:38 +08:00
@codehz #1 因为有些接口逻辑中可以容忍部分功能模块出错
https://imgur.com/YKWjZ2r
ryalu
2022-08-12 12:14:37 +08:00
@javaisthebest #3 一般是在 err 开始的地方直接打的日志,下游只做判断
codehz
2022-08-12 12:31:49 +08:00
@ryalu 可以容忍不代表你就得惯着(忽略错误然后假装无事发生肯定不是正确的行为)要么你就加个接口不返回 err 的,来明确表达这个目的
万一出现一个真的需要区分的时候,漏判断 err 还没测试出来,生产环境把错误内容写入数据库(或者别的危险操作,如权限判断默认值啥的地方),不就留了漏洞了?
egen
2022-08-12 12:39:29 +08:00
1. fail-fast 原则

“有些情况” 应该根据特殊情况才使用,其他情况应该判断 error ,调用方自行处理默认值
neoblackcap
2022-08-12 13:01:04 +08:00
能不能容忍错误不是你来决定的,是 caller 决定。当然你这样搞,出问题也只能怪 caller 不检查错误。但是一般还是 fail-fast 。
因为你不知道这样的错误会被拖到什么时候才爆发。程序如果不能处理错误,那么就应该尽早死掉重启
fzdwx
2022-08-12 13:07:23 +08:00
都返回 error 了,应该根据 error 是否为 null 来判断。
liaohongxing
2022-08-12 13:14:19 +08:00
go 不就是要判断 err 吗,缺了 err 就不是 go 了
liaohongxing
2022-08-12 13:14:39 +08:00
我都是用 1
BeautifulSoap
2022-08-12 13:22:15 +08:00
肯定选 1 啊,选 1 如果调用方因为漏了判断导致空指针崩了看下异常好歹明确知道崩在哪,2 返回一个空结构到时候如果勉强能运行下去的话出了业务错误光是调查都查到半死。
cloudzhou
2022-08-12 13:25:45 +08:00
肯定 1 阿,你是在掩饰错误
fyibmsd
2022-08-12 13:37:45 +08:00
1
不注意是下游的问题,2 存在二义性
tairan2006
2022-08-12 13:40:17 +08:00
1
DefoliationM
2022-08-12 13:43:47 +08:00
1
都返回 err 了,不处理怪谁
tianyou666shen
2022-08-12 13:45:56 +08:00
1
没有 if err != nil{} 的 go 是没有灵魂的
fiypig
2022-08-12 13:46:12 +08:00
肯定是 1 啊 , 外面以判断 err
zaunist
2022-08-12 13:47:00 +08:00
不应该返回一个初始化的对象,如果中途有错误,需要返回,要么你把原来的对象抛出去,要么给个 nil ,你这里给一个新的初始化的对象,下游如果不知道你的逻辑,他会怎么想,为什么莫名其妙多出来一个空结构体?
liuxu
2022-08-12 13:53:34 +08:00
不止 golang ,外面这几天吵的不可开交的语言都有 nil/null 这个问题
要回答这个问题,可以从 rust 的思想入手

首先在 php/java 这类常规语言开发中,要么返回 null ,要么返回空对象 User{},返回 null 就有个有名的 java 常见的运行时异常 NullPointException ,php 就是 call xxx on null
作为强类型语言,java 定义方法是需要有确定返回类型,如果返回 User 却返回 null ,但是 null 明显不是 User 类型,这个很矛盾,所以调用者即使知道方法返回 User ,却还要处理 null 的情况,而 php 可以不定义返回类型,所以锅就强行推给了方法的调用者


现在就有了楼主说的 2 种解决办法:
1. 返回 null ,让调用者处理 null 问题(目前常规解决方案)
2. 返回空 User ,调用者放心使用返回值,而不用担心问题,但是在业务中却需要注意空 User 问题,例如在 php/laravel 中调用 guard 的方法,$user->isAdmin()就会产生歧义,到底是没有这个 user ,还是 user 的权限不是管理员,只是普通用户

为了解决这个问题,rust 给出了以下解决方案:
首先 rust 中有 Option 这个东西,枚举为:
enum Option<T> {
None,
Some(T),
}
对于你的问题,返回就应该是 Some(User)或者 None ,在方法定义中,由于返回是 enum 枚举 Option<User>,所以 None 和 Some(User)在类型检查中都是合格的
但是到了方法调用时,调用者必须处理 Option<T>,因为 Some(User) != User ,None != User ,即使是 Some(User),也得手动将 Some(User)解析为 User 才能使用
编译器会检查这个问题,所以就不会有运行时异常了,也不会有返回 User 却返回 null 还合规这种矛盾的事情了


golang 的解决方案稍微变换了一下,按照常规解决方案是返回(nil, err),这样在调用时:
if user, err := x.GetUser(); err != nil {return err}

golang 在调用后 if 中处理,如果 err 不为 nil 则 user 值不合规,而不必管是 nil 还是空 User{}
说起来这个和 rust 的 Option 有一点异曲同工之妙
区别就是 rust 是为了编译时合规而给出了 Option 方案,golang 是运行时使用判断 err 给出了 if 方案


而关于担心下游不注意 nil 判断就很多余了,随便给了 api 文档说明:
func (b *Builder) Write(p []byte) (int, error)
方法定义既然给出了(int, error),如果有 error!=nil ,不管 int 真的是 int 还是 nil ,此时都不合规,调用者不能使用,这就是 golang 的规范



当然在 php/java 常用另一种做法,就是在 GetUser()中没有找到方法就 throw 个 Exception 而不是返回 null ,让外面 try/catch 解决,没有触发异常则返回的 User 是合规的对象
对于复杂的代码来说,经常有 try 包 try 的代码,看上去反而有些奇怪,于是有了 php/java 的 try 加 if 判断的混合用法,反倒有点混乱的感觉,具体喜好还是看个人

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

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

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

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

© 2021 V2EX