『软件工程』计算机界 TOP 3 难题:“相等”是软件工程中许多重大问题的根源( 七 )
考虑如下用C#编写的MSTest单元测试:
[TestMethod] public void Calculation_Is_Correct { var expected = new Result(SOME_EXPECTED_VALUE); var actual = _service.DoCalculation(SOME_INPUT); Assert.AreEqual(expected, actual);} 这段代码能正常工作吗?我们不知道!Assert.AreEqual最终会调用Object.Equals , 默认会进行引用比较 。 除非你重载了Result.Equals进行结构比较 , 否则这个单元测试无法正常工作 。 Object.Equals认为 , 如果类型是可改变的 , 那么不应该重载 。 通常来说这是合理的 , 但在单元测试中却未必 。 (这是因为.Equals()本应比较.GetHashCode() , 而一个对象的hash code在对象的生命周期中应该不发生改变 。 ).NET framework中对于引用类型的最接近“有保证的结构比较”的是IEquatable , 但Assert.AreEqual并没有使用 , 即使实现了也不会使用 。
而NUnit的情况更糟(https://github.com/nunit/nunit/issues/1249) 。
(相反 , Java的Object.hashCode在对象的字段发生变化时是允许变化的 。 https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#hashCode())
本文插图
应该怎样看待相等
没想到关于=运算符我写了这么多还没写完!好吧 , 这已经远远超过了运算符本身 。 为什么如此复杂?基本上有两个原因:
- 非本质的复杂性:我们的编程语言在相等比较方面做得并不好 。 经常完全不能正常工作 , 甚至在不能正常工作时也不会明确表示这一点 , 例如会在本应进行引用比较的地方使用结构比较 。
- 本质的复杂性:相等性本身就是复杂的 , 如比较浮点数 。 而在诸如比较函数等边缘情况下就更为复杂 。
编程语言应该怎么做?
关于非本质的复杂性 , 现状是几乎每一种主流编程语言对于相等性的实现都有问题 。 这个“必须遵循几个定律的简单运算”正是编程语言为了保证正确性而依赖的东西!但在我看来 , 只有SML真正思考了怎样在语义和运行时/标准库方面同时保证符合定律的相等性 , 而SML完全不是主流语言 。
首先 , 在禁止相等比较的地方 , 编程语言应该很容易创建不允许相等比较的类型 , 因为这易操作完全没有必要复杂(如F#中的[]) , 然后应该在标准库中尽可能多地使用该特性 , 如浮点类型 。
编程语言必须非常明确地指出结构相等和引用相等之间的差异 。 永远都不应该存在行为不确定的情况 。 绝大多数编程语言会重载== , 根据引用的类型(多数情况是根据值或引用的区别) , 用它来表示结构相等或引用相等 , 这样做一定会让开发者感到困惑 。
Kotlin已经非常接近正确了 , 它的===表示引用相等 , ==表示结构相等 , 尽管出于某些原因 , 对于值类型它会将===看做== , 而不是引发编译错误 。 目标应该是减少开发者的困惑 。 它希望让开发者明白 , ===表示“引用相等” , 而不是等号越多越好 。
我不知道还有哪些允许改变变量值的语言能够用不困惑的方式处理结构相等的 。 但很容易想象理想状态应该怎样!准备两个运算符 , 一个表示结构相等 , 一个表示引用相当 , 只在编程语言可以合理地支持的语境下才允许相应的运算符 。 例如 , 如果.NET的Object.ReferenceEquals和值类型不进行包裹 , 并且使用类似于IEquatable的东西允许成功够许愿使用结构相等运算符 , 那么开发者就很容易弄清楚哪个是哪个 。
推荐阅读
- Q1全球电视出货量公布:海信跻身TOP3,竞争转向价值战
- 【数据中心专家Top】光模块基本知识
- 「微型计算机」打造“小钢炮”别只顾机箱主板!SFX电源应该这样选
- 微型计算机@打造“小钢炮”别只顾机箱主板!SFX电源应该这样选
- @2020中国TOP100独角兽企业发展现状、机遇与挑战分析
- 极速聊科技:华为河图横空出世,【计算机-胡又文】打造AR数字底座
- TOPWOMEN时髦笔记@撞脸Jennie堪称芭比娃娃,未来YG的门面担当,仅11岁坐拥百万粉丝
- 「数据中心专家Top」光模块基本知识
- 「娱乐圈小松鼠」第一热度高达85.9,电视剧全网热度榜Top5:《安家》挤进前五
- 『倚心于生活』计算机破译需100万年,中国女子只用了2年,美国的超难密码
