经济学里面有一个名为“外部性“的概念。外部性是指一个人或企业的行为对其他人或企业产生的影响。
外部性可以是正的,也可以是负的。比如,一个企业的生产活动可能会产生污染,这就是一种负的外部性,对周围的环境和居民造成了伤害。相反,一个企业的生产活动也可能带来正面的外部性,比如提高周围地区的就业机会或改善周围地区的基础设施。
外部性对于组织职责划分的启示
利用这个概念,我们可以用来分析组织的职责划分。
什么样的职责应该由下级组织负责?我们说,当某一件事没有产生外部性或产生了正的外部性,那么下级组织应该负责。此时如果上级组织非得介入,则反而会增加下级组织的沟通成本,降低效率。因为上级组织往往由于掌握不了足够的细节,而要求下级组织频繁的汇报信息。
但是,当某一件事产生了负的外部性,那么上级组织应该负责。此时,下级组织往往没有驱动力去解决这个问题,因为这会增加下级组织的成本,但是却不会增加下级组织的收益。
举个例子。比如,企业在生产过程中赚到了钱,顺便改善了周边的经济环境,创造了正的外部性,很高兴。但是对于产生污染这样的负外部性,企业就不太会在意,且没有驱动力去解决,因为这会增加企业的成本。这时,作为上级组织的政府就应该介入,协调企业和周围居民的矛盾,并负责督促企业处理污染,避免对周围环境和居民造成伤害。
外部性对于程序设计的启示
在了解了外部性及其应用之后,我发现它对程序设计也有很大的启发。
无外部性或负外部性与高内聚
其一是在类或模块的职责划分上。如果一个类或模块的行为不会对其他类或模块产生影响,那这个行为就应该让这个类或模块自己处理。我们常常说的内聚性就是这个道理。如果一个类可以基于自己管理的数据独立完成某个功能,那么这个功能就应该由这个类自己实现,而不是由调用它的类来插手。
当我们看到在某一个类的方法中直接修改另一个对象的内部状态时,就应该警惕,因为维护这个状态可能是另一个对象自己的职责。越俎代庖很可能破坏了另一个对象的内聚性,并增加了系统的耦合度。
而当一个类或者一个模块的行为对其他类或者模块产生了影响,就产生了外部性。
如果这种外部性是正的,那么我们可以说这个函数或者模块是“无害的”,无需处理,并应极力鼓励。比如,某一个类优化了内部的算法,使得整个系统的性能提高了,这就是一种正的外部性。我们应该经常鼓励这样的优化。
但是,如果这种外部性是负的,那么我们就需要特别警惕,并考虑如何处理这种负外部性。这样的负外部性常常隐藏较深难以发现。
负外部性与上层协调
举一个大家经常碰到的例子。在基于数据库的后端程序开发中,我们常常需要从数据库中读取数据构建领域对象。JPA 可以帮我们自动完成这个过程。但是,如果我们不注意,JPA 可能会产生一种负的外部性,即 N+1 问题。
N+1 问题是指,当我们从数据库中读取一个领域对象时,JPA 会自动为这个领域对象的每一个关联对象发送一个额外的查询。如果这个领域对象有 N 个关联对象,那么就会发送 N+1 个查询。这将导致性能问题。
JPA 默认在非所有者端默认使用一种叫做“惰性加载”的模式来处理关联对象。惰性加载是指,当我们从数据库中读取一个领域对象时,不会立即查询关联对象,而是等到我们真正需要使用关联对象时,再发送查询。大多时候,这种模式是有效的,因为我们可能并不需要使用所有的关联对象。但是,如果我们需要使用所有的关联对象,那么就会产生 N+1 问题。
这种负外部性很难在领域对象内部自己解决,因为它不知道调用者何时会访问到哪些关联对象。
为了解决这个问题,我们可以在对应的 repository 中显示地定义一个方法,用于一次性加载所有关联对象(或通过参数指定需要加载哪些关联对象)。这样,我们就可以在需要的时候,在调用方(上级组织)显式地加载所有关联对象。在实现时,我们可以使用 JPA 的 fetch join 特性,在查询领域对象时,同时查询关联对象。这样,就可以避免 N+1 问题。
下面是一个示例。
1 |
|
上述代码 findOrderByIDWithItems
被设计为供上层调用,其实现过程中:
SELECT o FROM Order o
: 指定了我们要查询的主实体是Order
,并将其别名命名为o
。LEFT JOIN FETCH o.items
: 这是fetch join
的关键部分。这里我们执行了一次 左连接 (LEFT JOIN) 来包括所有订单。这意味着当你遍历结果列表中的每个订单并访问其items
集合时,不会触发额外的数据库查询,因为所有必要的数据都已经在初始查询中被加载了。
在发现 N+1 问题时,如果我们想在领域对象内部解决这个问题就很困难。此时应该改变思路,通过提供接口让上层组织中显式地调用,这个问题就迎刃而解了。
总结
外部性这一经济学概念在软件设计中也有鲜活的体现。通过识别和分析系统中的外部性,我们有以下启示:
- 无外部性或正外部性:类或者模块的行为对其他部分没有影响(无外部性)或产生积极影响(正外部性),则其职责应尽量内聚,由自己处理,并鼓励正外部性优化。
- 负外部性:行为对其他部分产生了负面影响,需要通过上层组织进行协调、解决。通过显式地提供清晰的接口,上层组织就可以灵活处理问题。