我想添加其他答案提示的其他内容,但我认为并未明确提及:
@puck说:“仍然不能保证函数名称中提到的第一个参数确实是第一个参数。”
@cbojar说“使用类型而不是模棱两可的参数”
问题在于编程语言无法理解名称:它们只是被视为不透明的原子符号。因此,就像代码注释一样,函数的名称与其实际操作方式之间不一定存在任何关联。
比较assertExpectedEqualsActual(foo, bar)
一些替代品(从本页面和其他地方),如:
# Putting the arguments in a labelled structure
assertEquals({expected: foo, actual: bar})
# Using a keyword arguments language feature
assertEquals(expected=foo, actual=bar)
# Giving the arguments different types, forcing us to wrap them
assertEquals(Expected(foo), Actual(bar))
# Breaking the symmetry and attaching the code to one of the arguments
bar.Should().Be(foo)
这些都比冗长的名称具有更多的结构,这使该语言看起来不透明。函数的定义和用法也取决于此结构,因此它不会与实现的工作不同步(例如名称或注释可以)。
当我遇到或预见到这样的问题时,在沮丧地大喊大叫计算机之前,我先花一点时间问一下,责怪这台机器是否“公平”。换句话说,机器是否获得了足够的信息以区分我想要的东西和我想要的东西?
像这样的呼叫与assertEqual(expected, actual)
一样有意义assertEqual(actual, expected)
,因此我们很容易将它们混在一起,并且机器可以向前犁并做错事。如果我们assertExpectedEqualsActual
改用它,则可能使我们减少犯错的可能性,但不会为机器提供更多信息(它无法理解英语,并且名称的选择不应影响语义)。
使“结构化”方法更可取的原因,例如关键字参数,带标签的字段,不同的类型等,是额外的信息也是机器可读的,因此我们可以让机器发现不正确的用法并帮助我们正确地做事。的assertEqual
情况下,是不是太糟糕,因为唯一的问题是不准确的消息。一个更险恶的例子可能是String replace(String old, String new, String content)
,它很容易混淆,String replace(String content, String old, String new)
其含义却大不相同。一个简单的补救方法是采用一对[old, new]
,这会使错误立即触发错误(即使没有类型)。
请注意,即使使用类型,我们也可能发现自己没有“告诉机器我们想要的东西”。例如,称为“字符串型编程”的反模式将所有数据视为字符串,这使得很容易混淆参数(像这种情况),忘记执行某些步骤(例如转义),意外破坏不变式(例如制作无法解析的JSON),等等。
这也与“布尔盲”有关,在布尔盲中,我们在代码的一部分中计算一堆布尔(或数字等),但是当尝试在另一部分中使用它们时,尚不清楚它们实际代表什么。我们将它们混合在一起,以此类推。将其与例如具有描述性名称(例如LOGGING_DISABLED
而不是false
)的不同枚举进行比较,如果将它们混合在一起会导致错误消息。