单一职责原则(原创翻译)

第一次翻译,肯定有很多问题,大家凑合看吧,欢迎提出修改意见=。=

文章不错,作者是资深老极客,有能力的最好看原文,文中提到的几个参考文献也不错,值得一读。

原文地址:https://8thlight.com/blog/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html


单一职责原则

Uncle Bob

1972年David L. Parnas发表了一经典篇论文《将系统分解成模块的标准(On the Criteria To Be Used in Decomposing Systems into Modules)》。它发表在12月份的《ACM通讯》卷5第12号(Communications of the ACM, Volume 15, Number 12)上。

在这篇论文中,Parnas比较了一个简单算法的逻辑的分解的两种策略。这篇论文把我迷住了,我也强烈推荐你学习一下。他的部分结论如下:

“我们试图通过这些例子证明,通过流程图来把系统分解成模块几乎总是错误的。我们建议不要基于一系列的困难的设计目标,而是基于一些可能会改变的设计目标。然后每个模块设计成彼此隐藏其目标。”

我要强调一下最后一句,Parnas的结论是说模块应该基于它们可能会改变(至少是部分的)来进行分离。

两年后,Edsger Dijkstra写了另一篇经典的论文《科学思想的作用(On the role of scientific thought)》,在这篇文章中他引入了一个术语:关注点分离(The Separation of Concerns)

上世纪70和80年代是软件体系结构的重要时代,结构化编程和设计曾风靡一时。在那个时候Larry Constantine引入了低耦合高内聚(Coupling and Cohesion)的概念,然后Tom DeMarco、Meilir Page-Jones以及其它许多人把它发扬光大。

上世纪90年代晚期,我试图把这些概念整合成一个原则,我把它称之为:单一职责原则(The Single Responsibility Principle)。(我有一个模糊的感觉,我从Bertrand Meyer那里偷了这个原则的名字,但我还没能确定。)

单一职责原则(缩写为SRP)是指,每个软件模块都应该有且仅有一个改变的原因。这听起来不错,而且似乎符合Parnas的构想。然而它带来了一个问题:什么是改变的原因?

有人问一个bug的修复是否有资格作为改变的原因,还有人说重构是不是可以作为改变的原因。这些问题可以通过指出“改变的原因”和“职责”两个术语的关系来回答。

毫无疑问,代码本身是对bug修复或重构不负有责任的。这些是程序员的责任,而不程序。但是如果这样的话,程序本身要对什么负责呢?或者,可能这样问更好:程序需要对谁负责?也许这样更好:程序设计必须对谁负责。

想像一个典型的商业组织。CEO在最上面。向CEO报告的是C字头的高管:CFO、COO、CTO等等。CFO的职责是控制公司的财务。COO的职责是管理公司的运营。CTO的职责是公司内的技术基础设施以及开发。

现在,考虑这样一段Java代码

public class Employee {  
  public Money calculatePay();
  public void save();
  public String reportHours();
}
  • calculatePay方法基于员工的合同、地位、工作时间,实现了决定一个员工应该付多少钱的算法
  • save方法把属于员工管理的数据存储到企业的数据库中
  • reportHours方法返回一个用于报告的字符串,审计员通过这个报告来确定员工工作时间以及应该支付的薪水

现在,哪个向CEO报告的C字头高管对calculatePay方法的具体定义负责呢?如果这个方法没有被定义,谁应该被炒掉呢?显然答案是CFO。确定员工的薪水是一个财务职责。如果由于CFO的原因没有定义好计算薪水的规则,向所有员工支付了双倍薪水,那CFO肯定会被炒掉。

另一个C字头高管的职责是定义reportHours方法返回的字符串的格式和内容。这个高管管理审计员和审核员,这是一个运营职责。所以,如果那个报告没有被好的定义,COO会被炒。

最后,如果save方法没有被定义,哪个C字头高管会被炒掉是很显然的了:CTO。

当calculatePay方法的算法需要被改变时,这些改变肯定是被CFO为首的部门发起。类似的,发起reportHours方法改变的是COO的部门,CTO的部门将发起save方法的变更。

这就是单一职责原则的关键所在,这个原则是关于人的

当你写一个软件模块时,你需要弄清楚变更发起时,这些变更只能通过一个人来发起,或者说,一个单一的紧密耦合的人群代表一个单一的狭义的业务功能。你希望将你的模块从整个组织的复杂性中分离出来,并设计你的系统让每个模块只负责一个业务功能的需求。

为什么?因为我们不想因为CTO做了一个变更而让COO炒掉了。没有什么比发现程序由于他们提出的变更而导致一个完全无关联的故障更让我们客户或经理们害怕了。如果你更改了calculatePay方法,然后不经意的把reportHours方法弄坏了,那么COO就会开始要求永远不要变更calculatePay方法了。

想象你把你的车送到修车师傅那里要求修一下损坏的电子车窗。他第二天打电话给你说都修好了。当你提车时,你发现车窗好了,但是车发动不起来了。你不太可能再去找那个修车师傅了,因为它显然是一个白痴。

这就是客户和经理们在我们弄坏了他们没有让我们更改的东西的时候的感觉。

这就是我们不把SQL放到JSP中的原因。这就是我们不在计算结果的模块中生成HTML的原因。这就是业务规则不应该知道数据库模式的原因。这就是我们分离关注点的原因。

另一种单一职责原则的说法是:

把由于同样原因进行变更的东西放到一起;把由于不同原因进行变更的东西分离开。

如果你思考一下这个说法,你将意识到这只是低耦合高内聚的另一种定义。我们想增加由于相同原因进行变更的东西的内聚,同时我们想降低由于不同原因进行变更的东西的耦合。

然后,当你思考这个原则时,记住变化的原因是人。是人来要求进行更改。而且你也不想通过把基于不同的原因由不同的人关心的代码混在一起,来使那些人或你自己困惑吧。