解决二元函数assertEquals(预期的,实际的)带来的问题


10

经过多年的牛仔编码,我决定拿起一本有关如何编写高质量代码的书。我正在阅读Robert Cecil Martin撰写的Clean Code。在第3章(函数)中,有关于二进位函数的部分。这是这本书的摘录。

甚至明显的二进位函数assertEquals(expected, actual)也有问题。您已将实际次数放置在预期的位置几次?这两个参数没有自然顺序。预期的实际排序是需要实践学习的惯例。

作者提出了一个令人信服的观点。我从事机器学习工作,并一直遇到这种情况。例如,sklearn库(可能是该领域中最常用的python库)中的所有度量标准函数都要求您注意输入的顺序。作为一个例子sklearn.metrics.homogeneity_score需要作为输入labels_truelabels_pred。该函数的作用不太相关,相关的是,如果您切换输入的顺序,则不会引发任何错误。实际上,切换输入等效于使用库中的另一个函数

然而,这本书并没有继续提到诸如assertEquals。我想不出一种assertEquals针对上述功能的固定解决方案。有什么好的做法可以解决此问题?

Answers:


11

即使没有修复措施,也要意识到一个可能的问题,这是很好的-这样,您可以在读取或编写此类代码时保持警惕。在此特定示例中,您只是习惯了一段时间后的参数顺序。

有多种语言级别的方法可以防止对参数顺序的任何混淆:命名参数。不幸的是,许多具有C样式语法的语言(例如Java或C ++)不支持此功能。但是在Python中,每个参数都可以是命名参数。而是调用一个函数的def foo(a, b)作为foo(1, 2),我们能做到foo(a=1, b=2)。许多现代语言(例如C#)具有相似的语法。Smalltalk语言族采用了最远的命名参数:没有任何位置args,并且所有内容都已命名。这可能导致代码读取的语言与自然语言非常接近。

一个更实用的替代方法是创建模拟命名参数的API。这些可以是流畅的API,也可以是创建自然流程的辅助函数。这个assertEquals(actual, expected)名字很混乱。我见过的一些替代方案:

  • assertThat(actual, is(equalTo(expected))):通过将一些参数包装在助手类型中,包装功能有效地用作参数名称。在单元测试断言的特定情况下,Hamcrest匹配器使用此技术来提供可扩展且可组合的断言系统。这里的缺点是您嵌套很多,并且需要导入很多辅助函数。这是我在C ++中使用的技术。

  • expect(actual).to.be(expected):一个流利的API,您可以在其中将函数串在一起。尽管这避免了额外的嵌套,但这不是很可扩展。尽管我发现流利的API读起来很不错,但是根据我的经验,设计良好的流利的API往往会花费很多精力,因为您需要为调用链中的非终端状态实现其他类。只有在可以建议下一个允许的方法调用的自动完成IDE的上下文中,这种努力才能真正获得回报。


4

有几种方法可以避免此问题。一种不会强迫您更改调用方法的方法:

而不是

assertEquals( 42, meaningOfLife() ); 

采用

expected = 42;
actual = meaningOfLife();
assertEquals(expected, actual);

这迫使惯例公开,很容易发现它们被切换。当然,它不那么容易编写,但很容易阅读。

如果可以更改所调用的方法,则可以使用键入系统来强制使用易于阅读的用法。

assertThat( meaningOfLife(), is(42) );

一些语言让您避免这种情况,因为它们具有命名参数:

assertEquals( expected=42, actual=meaningOfLife() );

其他人则没有,因此您可以模拟它们:

assertEquals().expected(42).actual( meaningOfLife() );

无论您做什么,都可以找到一种使其变得明显的方法,即在阅读时是正确的。不要让我猜测约定是什么。给我看看。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.