在Go语言的开发过程中,经常会遇到需要判断两个对象是否相等的场景。本次我们聚焦于实现一个Equal函数,用于断言两个对象是否相等,输入两个值,输出是否相等。接下来,我们将从我的思路、源码思路以及两者的对比等方面进行详细探讨。
问题描述
实现Equal函数,断言两个对象相等,输入两个值,输出是否相等。
- 示例1:
- 输入:123, 123
- 输出:相等
- 原因:相同的基本类型值。
- 示例2:
- 输入:&a, &b(其中a和b都是123)
- 输出:相等
- 原因:指针指向的值相同。
- 示例3:
- 输入:func() {}, func() {}
- 输出:不相等
- 原因:函数无法比较,总是失败。
- 源码地址: github.com/stretchr/te…
我的思路
首先这两个对象是接口类型的,我记得Go语言里面有判断接口类型方法,那么第一步是判断接口类型。
- 基础类型:比如123 | 123,我查找go.dev/tour/basics… 网站知道Go语言有基本bool、string、int、byte、float32、complex64,这些使用
==就可以判断,这样解决了基础类型。 - 指针类型:比如 &a (a=5) | &b (b=5),指针类型我记得用
*可以去到值,再使用==比较,然后我不清楚指针的地址值是怎么比较的,是使用==比较吗? - 结构体类型:比如
User{Name:"Alice"} | User{Name:"Alice"},判断结构体怎么相等,不相等,甚至多字段少字段的方式我很模糊,不清楚。 - 集合类型:比如
[]int{1,2} | []int{1,2},这种比较先比较len的长度,然后将每一个取出来,看是否是上面的3个类型进行比较。 - 其余特殊类型:我先默认全部无法比较,返回不相等。
源码思路
- Equal函数:它接受
TestingT接口、expected和actual参数,以及可选的msgAndArgs。首先检查t是否为tHelper类型,调用Helper方法,然后调用validateEqualArgs来验证参数是否有效,如果验证失败,则返回Fail。接着使用ObjectAreEqual来判断expected和actual是否相等,如果不相等,生成差异信息并返回Fail,否则返回true。 - validateEqualArgs函数:检查两个函数是否都为
nil,如果是则返回nil。然后检查是否有参数是函数类型,如果是则返回错误。这里参数在验证代码时就排除了函数类型,和我的思路一致。 - isFunction函数:通过反射检查函数类型是否是
reflect.Func,以此判断是否是函数。这里的reflect包是Go语言SDK自带的包。 - ObjectAreEqual:是函数的核心比较逻辑,首先处理了
nil的情况,如果其中一个为nil,则比较两者是否都是nil。然后处理[]byte类型的特殊情况,使用bytes.Equal进行比较。对于其他类型,直接使用reflect.DeepEqual进行深度比较。
对比两者思路
- 基础类型:我认为使用
==即可,但代码中使用reflect.DeepEqual,实际上reflect.DeepEqual对于基础类型也会使用==比较,所以结果一致。 - 指针类型:我认为比较指针指向的值,而代码
reflect.DeepEqual会递归解引用的值,比较指向的值,而不是地址。例如,&a和&b如果指向相同的值,即使地址不同,也会被视为相等。 - 结构体类型:我认为需要逐个字段比较,而代码中
reflect.DeepEqual会自动比较结构体的所有字段,包含导出和非导出字段。如果结构体所有字段相等,则结构体相等。这处理了我说的字段数量不相同的问题(如果结构体类型不相同,直接不相等)。 - 集合类型:我提到比较长度和每个元素,而
reflect.DeepEqual对于切片会检查顺序和元素值,对于映射会检查键值对的匹配,但不考虑顺序。例如,[]int{1,2}和[]int{2,1}视为不相等,而map[int]string{1:"a"}和map[int]string{1:"a"}视为相等。 - 特殊类型:函数类型在
validateEqualArgs被过滤,直接返回错误。通道类型在reflect.DeepEqual中比较通道的类型和值,但我认为通道无法比较。
感悟
如果按照我的思路,实现起来不仅繁琐且耗时,而且不容易维护。而这次探究源码的解决思路,我一下子发现解决方式可以如此高效。特别是我之前不知道Go语言中好用的reflect.DeepEqual可以帮我简化大量特殊类型的比较,也不知道原来将一个对比问题分而治之效果如此显著。
比如源码将Equal这个函数分为「检查参数」和「对比」,将「检查参数」分解为「判断是否为函数」。将「对比」分解为「如果一个是nil则比较两者是否都为nil」、「[]byte类型的特殊情况使用bytes.Equal比较」、「其他类型直接使用reflect.DeepEqual进行比较」。
这启发我要探究更多的源码解题思路,熟悉更多高质量的解题思路,提升自己的编程能力。