【翻译】理解得墨忒耳法则

得墨忒耳法则经常被陈述为”只同你最亲密的朋友交谈”。在面向对象语言中,使用一个点作为领域标识符,得墨忒耳法则并简述为”只使用一个点”。虽然这是很有启发性的,但是这是一个不那么好的规则,因为事情几乎没有那么简单。这经常很难向寻求一些列帮助他们提升技能的新手程序员解释。现实是,并没有什么简单的规则可以遵循,更多的时候是一种直觉。我能够想到的对得墨忒耳法则的最好的解释是”不要操作你可能不理解的对象”。

为了解释这些,让我们从调用对象的角度来考虑事情,Ruby中的self,假设我们有一个self.x.y的表达式,那么我们不太可能告诉接收者x来调用y,因为这会变得更难理解y或者x更容易改变。

最简单的例子是接收者就是调用者,这个例子等价于原始的更加严格的得墨忒耳法则。所以在self.y这个表达式中,self是调用者也是接收者。由于我们对自己的方法有很高程度的自信,我们可以调用x。

更一般的情况下,给定self.x.y.z,如果我们对于x的信心程度低,我们不应高告诉x调用y。更进一步,从self的角度看,如果我们对y没有自信,那么我们不应该告诉y去调用z。

Ruby实例

[1,2,3,4].map{ |k| k*2 }.join(',')

根据原始的定义,上面的代码违反了得墨忒耳法则,因为调用Array#join是同非亲密的朋友交流的例子。在这个例子中,[1,2,3,4]是self的亲密朋友。调用Array#map是可以的,但是这个操作的结果并不是亲密的朋友,所以我们不能让它做任何事情。

修复这行代码将导致我们掉到一个坑里面,是高度的非生产性的。很可能会导致将一行简单优美的代码变得很复杂。实际上,这个简单的例子说明了在严格的得墨忒耳法则下,我们完全不能使用方法的链式调用。

为了解决链式调用的问题,大多数人倾向于这样的警告”你的朋友的朋友也是你的朋友,如果他的类型和你的朋友一样”。在前一个例子中,Array#map操作返回了一个数组,我们也是从一个数组开始的,所以我们可以对这个结果进行操作。但是我们不能对Array#join操作返回的字符串进行操作,因为我们并不是从字符串开始的。

然而,这个警告是不够的,在一些情况中这有太多的限制,其他的情况中限制又不够。

一个复杂的例子

Ruby Code:
User
  .where(active: true)
  .map(&:id)
  .map{ |id| Group.where(owner_id: id) }
  .map(&:name)
  .grep('Rails')

这很轻微的违法了得墨忒耳法则,但是它确实违反了我早前介绍的模糊版本,这里问题是这确实是很复杂的代码。同代码相关联的User或者Group有很高的几率会改变,我们并不能确切的知道它们何时会发生改变。此外,这行代码假设我们对调用链的每一个操作都很有信心,我对这些感觉很不舒服。并不是调用链的每一个调用都很复杂,但是当我们使用Rails的grep操作时,我们已经丢失了对我们正在进行的工作的追踪。让我们来看一个能够表现这个问题的一个简单的例子。

Ruby Code:
users = User.active
Group.owned_by_any_of(users).with_name_matching('Rails')

这就是得墨忒耳法则的优美之处,它提倡按照这种方式编写代码,而不是按照上一个例子。这和你用了多少个点无关,只和你代码中的对象的职责有关。调用者需要理解并且追踪多少个对象?调用者包含了多少逻辑?调用链的逻辑路径是否容易追踪?

一个不明输出的例子

user.group.try(:name)

如果你发现你自己按照这种方式编写代码,那么你就违反了得墨忒耳法则的所有版本,这行代码应该被简化。

user.group_name

让我们来看一下轻微违反得墨忒耳法则的例子。

uer.group.name

在这行代码中,你很可能明白你所处理的对象,事实上这行代码很容易理解,如果我们确信group存在,我们将会知道输出的结果是什么,使用这个表达式是非常好的。

总结

得墨忒耳法则并不是一个法则,它并不是简单的统计你代码中的点操作符的数量。它是关于培养一个对于的你创建的代码,你都能够理解每一个对象告诉其他的对象做什么的,应该基于该理解做出什么样的响应的感觉。

原文: