---
title: "单一职责"
date: "2026-07"
category: "设计模式与架构"
tags: ["设计模式与架构"]
description: "我对单一职责（SRP）的启发式理解是「能单测，没有副作用，一个函数只做一件事」，当然我知道这不完全严谨。但作为快速判断我觉得是好的。 能单测..."
source: "https://enriquemark.com/zh-hans/posts/%E5%8D%95%E4%B8%80%E8%81%8C%E8%B4%A3"
---

我对单一职责（SRP）的启发式理解是「能单测，没有副作用，一个函数只做一件事」，当然我知道这不完全严谨。但作为快速判断我觉得是好的。

能单测。意味着这个函数不依赖外部状态，没有额外的规则，在纯粹的情况下就可以测试，往往也暗示了此函数已经分离到了相当的程度。

没有副作用。这点不总是绝对的，比如有些函数的合法职责就是带副作用的，像打log或者查数据库。核心点在于——除非这真的是这个函数的合法职责，否则它不应该影响到本该由其他函数做的事情，此时采用委派是最好的。即，委派给一个专职做此时的函数，而不是自己亲自去进行。

只做一件事。这里的一件事，本身不能从字面上理解，而是要考虑抽象的层次。比如在更高的抽象层次，这里的「一件事」能涵盖很多，不如说是一种「职责」。这里的「职责」本来就是蛮抽象的东西，我对其的理解是「职能」。此函数在某一地方发挥的最小的单一功能。

更明确的SRP信号其实是「改变」。比如当未来我进行修改的时候，是不是只会因为一个「原因」去改变他？或者更确切的说，我该这个函数的时候，不会改到不相干的东西，比如改一个查数据库的函数还得估计别把http请求改坏了——拿着就是职责混同。正是因为混了不同的东西，才会因为不同原因来这里改这个函数。而反过来，这种函数也是最难单测试的，因为为了测试它，你得构建不同的环境。所以「按变化原因切分代码」是更强烈的SRP信号，关键在于维护的边界。

还有一种函数的职责是「编排」。对这种函数来说，它就像最终的执行者一样，把好几个不同职责的函数打包在一起执行。要注意，业务逻辑和编排是不一样的。编排只是按照特定的顺序执行，但业务逻辑则会承担诸多状态控制和判断分支，对于后者来说，这完全就是职责不清晰。虽然同样是委派，但如果掺杂了单纯执行之外的其他因素，就可能变成一种包装——本质上只是把业务逻辑扔了出去而已，实际上还是耦合的。

关键在于「状态」。如果你的状态本身是别人的，那么这一部分的职责最好就是回到它发生的地方。如果一段代码主要都是在处理别人的状态，就说明它该拆分和重构到其状态所属之处了——这意味着当前的代码是耦合的，耦合与他处的状态。「依恋情结」正是这样一种坏味道，全在处理别人家的状态。如果对方只有一个，那这一整部分应该移到那边；如果有多个，这里就分两种情况：1.多个一起变，而且是为了一个目的变，每次都固定搭配，而且缺一不可，那就说明这一部分是一个单独的职责，应该给这个单独给一个方法或者对象，2.多个分开变，那就得拆出来，作为单独的方法存在；以及纯粹的排序，比如固定的生命周期，而不是彼此搭配的状态操作，那就是我们上面说的编排。

区分在于职责，如果某个职责本身已经有人——或者现在还没人——那就应该由它去做。而不是由职责不同的另一个方法去做。重点是分辨三个：委托、依恋和编排。这里有个地方很难搞清楚，那就是「自己的状态」——到底是啥？这里的核心就是，那些固定搭配，反复操作的状态，就是「自己」，这个「自己」在我们给他封装之前可能是不存在的，但一旦包装好，就可以统一把这谢谢状态给他自己管理了，此时它再修改的就是自己的状态，自己负责。当我不是单纯在这里进行一次的操作（这是委派）而是每次都在做一个流程（一组操作）的时候，这说明我可能越界了。
