在2019年08月17日举办的 Gopher Meetup(深圳站)活动上,来自 Bindo Labs 公司的李雄飞进行了 《Go 编程陷阱》的演讲。李雄飞,Bindo Labs 后端技术负责人,全栈工程师。从事POS/支付业务架构以及通用Web系统建设工作,主要关注New SQL/ETL/Kubernetes等领域技术发展。以下为演讲实录。
前言
我大概是从 2015 年开始写下第一行 Go 代码,今天主要给大家分享我这几年以来所积累的一些让我非常难受地方以及感到崩溃的一些 BUG,我希望我的这些痛苦可以让大家快乐,当然我也希望我积累的这些经验以及案例可以拯救大家一些睡眠的时间,让我们有更多的时间去浪,去玩耍。为此,我们今天有一个副标题 ——— 为了早点下班。
1. Nil != Nil
我们先来讲我们第一个问题 Nil 不等于 Nil,这话听上去好像 1 不等于 1 一样, 骗小孩子的病句。其实在 Go 当中有的时候确实会发生这种问题,当我们认为我们的变量是 nil 的时候,甚至于我们很信誓旦旦说它是 nil 的时候,它其实并不是。
我为什么会第一个讲这个问题呢?因为这是让我最最刻骨铭心的问题,到现在我还仍能深深地记起那个下着雨的夜晚,那是一个情人节,月亮忽隐忽现,天空飘着茫茫细雨,我只能坐在办公室改 Bug,非常地绝望与愤怒。
为了表达我当时的愤怒,我做了一个特效来表达一下。
自定义错误类型
我们接下来看一下到底是什么样的问题会让我当时那么愤怒,即便到现在还是那么愤怒。
我们定义了自定义的错误类型,handle函数中判断了参数x的值是否等于1,如果不等于1就返回一个自定义的Error指针类型。否则就返回一个nil。在main函数中,我们分别用参数 0 和 1 调用了两次,然后判断了error是不是nil,不是nil 就打印一句话。好像就是一个很寻常的go函数。
那么我们的问题来了,它的输出是什么?
我们大家一起来稍微分析一下这个代码,先看第 24 行调用,这个很明显结果不是 nil, 所以我们第26行的打印应该是会成功的打印。我们再来看一下第28行的调用,我们用参数 1 来调用,1 等于 1 ,所以说它会return nil。应该不会去打印的。我觉得应该是这样,我觉得大家可能也觉得是这样。
给大家 10 秒钟时间大家可以判断一下到底是不是这样。
我们现在来看一下真正的表现是什么?
我们来看一下最终的输出。呃呃呃呃呃呃呃,好像和想的不太一样,什么鬼,一定是golang的bug。。我们看到第二个也已经打印了,并且我们注意一下这一部分,它打印的类型确实是 nil,但是 nil 为什么又不等于 nil 呢?
这是不是一个问题?明明是 nil,那这肯定是个 Bug,其实我也是这么想的,我还记得是凌晨两点钟左右,当时非常高兴,好像挖到一个大宝藏一样,我在github还提了一个issue
<https://github.com/golang/go/issues/16160>;
我说你们这个有 Bug,赶紧修,然后我就被分分钟打脸。
接口准则
我们来看一下别人怎么来打我的脸?
第一点,其实Go在实现接口的时候,保存了两个东西,一个是这一个接口背后的类型 T,一个是这个类型背后的值 V,因此当我们在讨论接口这个东西的时候,实际上我们永远是在讨论类型以及类型的值。
第二点,只有类型与值同时是 nil 的时候这个接口才会是 nil,这点就是我们问题的来源。
第三点,我们都知道接口是隐式实现的,当我们用一个接口类型去接收一个 nil 结构体的时候,这个结果将不是 nil,因为此时的接口值是有类型(T)的, 只是它的值(V)是 nil,所以说我们刚才的 if 判断应该是成立的。
方案一
我们基于这个认识来看一下我们应该怎么来修这个问题,就很简单的。第一个修复的办法是,我们不再用 interface,我们直接用 struct,因为这个时候从头到尾都没有出现interface 的事情,自然不会出现与interface有关的问题。
方案二
我们还会有第二种方案,我们看一下,我不返回这一个 struct,我返回一个 error,这样再打印第 30 行的 err 就真的是 bug 了
有疑问加站长微信联系(非本文作者)