关注点分离
§1摘要与结论
按不同关注点维度把系统分解为可独立处理的部分,使每部分只承担一组相关职责。它是分解问题的元原则,为模块化与分层提供依据,成败取决于维度是否真正正交。若干流派在细节上各异,却共享同一目标:把软件切成层,且至少一层承载业务规则、另一层承载用户与系统接口。分离到位后应表现出五项可观察性质:不依赖某个功能齐备的库;业务规则脱离界面与库即可测试;界面可整体替换而其余部分不动;持久化产品可更换而规则不被绑定;业务规则对外部世界的接口一无所知。因此验收标准不是画了几条线,而是替换任意一个外部元素时改动是否被限制在边界外侧。
别名:separation of concerns、SoC、正交性 | 英文:Separation of Concerns | 类型:方法原则 / concept
§2局部关系图
§3原理 · 为什么
从认知负荷看这条价值的底层理由:任何有复杂度的程序,其细节都超出人脑不经辅助可管理的范围,漏掉一个细节就会得到看似能跑、失败方式却出乎意料的程序。结构化编程的由来正与此相关——某些跳转用法让模块无法被递归分解为越来越小的单元,从而堵死分而治之;而可证明的单元恰好也是可被局部理解的单元。维护的主要成本是「翻找」与「风险」:在一堆既有代码里判断该在哪里加功能或修缺陷,以及改动时无意引入缺陷的概率。边界与稳定接口的作用是照亮路径、压低这份风险。判断标准落在变化轴上:被切开的两侧应以不同原因、不同频率变化,由此各自可独立理解、独立变更。正确性的量尺是变化独立性而非块数;块数增加而两侧仍同因同频地一起变,只是把耦合搬了个位置。深层价值可表述为:把无法整体把握的问题切成若干可分别完成的局部理解。这条理由还推出一个配套要求:切分之后,两侧之间必须有明确的、稳定的约定,否则局部理解无法各自独立成立,读一侧的人仍要反复去看另一侧才能确认行为。约定的内容应当尽量少而稳,只保留协作真正必需的部分;约定越宽,两侧被同时牵动的机会越多,切分带来的独立理解也随之缩水。反过来,如果两侧本就需要共享大量信息、且这些信息几乎总是一起变化,那么把它们分开只会制造两个互相依赖的局部,理解成本不降反升,此时更合理的做法是承认它们属于同一块,并在这一块内部寻找真正的变化轴。关注点是广义的:功能维度、正交的质量维度、不同的变化原因与生命周期节奏、不同团队的职责,都可作为切分轴。轴的选取本身有优劣之分:功能维度最容易观察,但沿功能切出来的部分往往仍共享同一批数据与同一批变化原因;按变化原因切更接近这条原则的本意,却要求先弄清系统各部分究竟为何而变。选错轴的表现是切完之后两侧依然频繁联动,改动一处仍要同时理解另一处,此时应当换轴而不是增加边界数量。另外,边界一旦划出就产生了契约,跨越它的每一次调用都要付出转换与协调的代价,因此切分的收益必须体现为变化被局部化,而不是仅仅体现为目录结构更整齐。还要注意这套标准并不要求边界永久不变:随着系统演化,新的变化原因会出现,原有切法可能不再正交,此时重新划界属于正常工作,而不是对先前设计的否定。
§4机制 · 如何运作
切法只需要一刀:把系统分成策略与细节两个元素。策略承载业务规则与流程,是价值所在;细节是让人、外部系统和程序员与策略沟通所需之物,涵盖 IO 设备、数据库、Web 系统、服务器、框架、通信协议,它们对策略行为毫无影响。据此可推迟的具体决定包括:不必早选数据库形态,不必早选 Web 服务器,策略甚至不必知道要走 Web,不必早定 REST、微服务或 SOA 框架,不必早引入依赖注入框架。每一刀都配一条职责声明:策略侧只承认「有一组可取数、可存数的函数」,其余归属细节侧。这条轴还可继续叠加:界面与领域的圈层分工、界面与业务规则、数据库与界面等同类边界,都是同一切法在不同位置的重演。递归判据随之成立——任何一部分先问它内部还有没有按不同原因变化的子轴,若有就再切,切到每一小块都能被单独读懂、单独验证为止。落到操作层面,这一刀可以通过一个反复使用的提问来完成:这段代码里哪些内容即使外部设施全部更换也仍然成立,哪些内容只是因为当前选用了某种外部设施才存在。前者属于策略侧,后者属于细节侧。提问的答案不必一次到位,可以先粗后细,先切开最明显的混合处,再逐步收窄。配套的检查是回头看边界是否真的隔开了变化:如果某次外部设施的更换仍然迫使策略侧改动,说明还有细节渗在策略侧,需要继续切;如果策略侧的规则调整不再波及细节侧,说明这一刀生效。还要留意一个常见偏差:把细节侧误当成不重要的部分而草率对待。细节侧同样需要清晰的结构与测试,只是它承载的是与外部世界打交道的复杂度,而不是业务规则。
§5工程 · 怎么落地
落地形态有三层粒度。最外层是同心圈层:外圈放机制、内圈放策略,越向内抽象度与策略层级越高;圈数只是示意,可以多于四层,方向约束不随层数变化。中间粒度是组件,可定义为可部署的最小单位,也可理解为执行环境内、藏在干净接口背后的一组相关功能,是否单独打包属于正交问题。最细粒度是类与接口。三种粒度共用一条方向约束:依赖只能由外向内。策略可按两把尺子重组:同因同时者同级并归入同一组件,异因异时者分属不同层级与不同组件。重组后得到一个有向无环图,节点是同级策略所在的组件,有向边是编译期依赖,边的方向由两端组件的层级决定,低层组件依赖高层组件。层级按到输入与输出的距离定义:离输入输出越远越高。由此推出一条反直觉结论:数据流方向与源码依赖方向不必一致,应让依赖与层级绑定、与数据流解耦。这套结构可以用一条检查来验证:沿任意组件的出向依赖走一遍,若能回到起点就说明出现了环,环的存在表明层级判定与依赖安排至少有一处不自洽,必须先消除环再谈其他。另一个实用约束是让上层需要的下层能力以上层自己定义的接口形式出现,由下层去实现该接口,这样即使控制流是从上向下发起,源码依赖仍保持向内,方向的约束不会被调用关系破坏。检验分离是否真的落地,还可以看测试的写法:如果核心逻辑的验证必须启动真实的外部设施才能进行,说明边界仍被穿透;如果核心逻辑可以用替代实现独立验证,且这些验证不随外部设施的更换而重写,说明切分起到了作用。测试是否稳定、是否能在不搭环境的情况下运行,往往是这类分离最容易观察到的回报。
§6权衡卡
代价过高时可退到部分边界。
评审时还应识别纠缠信号:同一处代码同时改写业务规则与协议细节,或同一需求变更同时迫使界面与存储改动,都提示边界被穿透;多一层边界就多一次转换、多一处契约、多一份理解负担,跨边界调用的代价可能超过分离带来的收益。
- 完整边界的账单包括:互反的多态边界接口、输入与输出数据结构,以及把两侧隔成可独立编译、可独立部署的组件所需的全部依赖管理,前期与长期维护都要付。
- 跳过最后一步能省掉多组件管理,却仍要付全部代码与设计准备;只保留单向接口时,缺少互反接口就没有机制阻止反向通道,只能靠开发者自觉;门面更省,连依赖倒置都放弃,客户端对全部服务类产生传递依赖,静态语言下服务端改动会迫使客户端重新编译。
- 退化样本同样存在:曾被设计成可分离的两部分,一旦确定永远不需独立部署,依赖就开始反向穿线,事后重新分开已相当费事。
- 合并判据可推得:若跨边界的协作比边界内部更频繁,该边界就没有承担隔离职责,应重新合并或换一条正交轴。
- 分离宜「够用即止」,以当前可预见的正交维度为界,为未来可能出现的新维度留出重划空间,而不必一次性预判所有切法。
- 因此判断应回到变化本身:只有当被隔开的两侧确实会各自独立变化,边界才有回报;两侧同步变化的场合,边界只是在每次改动时多收一次手续费。
权衡原文仅按显式标记词归栏引用(收益 / 代价 / 反例),其余句子按原序列为「原文要点」;内容逐字引用未改写。
§7学习路径条
§8案例带
去中心化聊天架构逆向
对一个仅有 58 份设计文档、尚未实现的开源去中心化聊天项目做架构逆向:从设计文本提炼 18 个模块及其依赖关系,重建模块依赖 DAG 与三条核心数据流 DAG,并输出全景理解与深度需求分析。
Claude Code 架构逆向
以社区逆向源码、系统提示词与官方文档三源交叉验证,还原一个商业 Agent 的查询引擎、子Agent、工具与权限体系的逆向分析案例。
网申填写扩展逆向架构
对 Chrome MV3 网申自动填写扩展的逆向架构分析:梳理 popup、content、background、options 四模块职责,解剖 12 个站点适配器、双通道消息协议与隐私脱敏机制,并评估其零依赖零构建选型。
网申 v3 多模块解耦
一套面向高可扩展性的网申信息速查扩展架构:以 schema.js 为唯一数据驱动源,把数据模型、存储、纯函数与渲染组织为严格单向依赖的共享层,供 popup 与页面浮层双容器复用,并先立跨模块接口契约。
网申 v2 深度重构
一次由「改一处漏多处」症状触发的深度重构:定位到接口定义散落 12 个文件、存在 7 种接口形态的根因后,以单一契约源、dispatch 消息表与能力注册中心重建三层契约系统,恢复可审计与可校验性。
preset 通道耦合分析
对 harness「委派工具层→续用管理器→preset 服务→MCP 服务器」这条通道做耦合分析:识别契约、服务定位、数据契约、组合语义、路径与装配一致性六类耦合点,定位「静默丢弃特化」与「装配漂移」两处实质缝隙。
插件 DAG 两次级联升级
在同一知识库上继续两轮上游级联升级:处理数百次提交与包的增删,暴露「依赖声明不等于运行时引用」、分层须按最长路径深度、纯外部依赖边不得入主图等差分陷阱,并以全量门控校验收口。
dsh-manager 插件深度分析
以「编译依赖、运行时注入、组合装配」三硬依据加源码行号,对第三方插件做依赖、功能、需求溯源与设计问题裁决,并复用上游 DAG 知识库节点标识,使分析结果可与主图互查。
harness 源码架构解析
对 deepseek-harness 官方源码做分层解析:镜像官方架构文档,按九个层与九十余模块建立文档集群,产出跨层联动矩阵与七条关键路径,并钉住「模型可见即已记录」、能力缝三角等设计不变量。
dsh 插件依赖增量定位
以官方插件 DAG 为蓝本,对一个官方清单之外的全新社区插件做功能定位与依赖位置增量分析,产出可下钻交互依赖图与多形态文档的源码级架构逆向案例。
Hermes 七层架构总览
对八千余文件的工业级 AI Agent 项目做七层架构总览逆向,揭示其「越用越聪明」的自进化闭环如何在层间传导的控制型架构案例。
MCP 四件套选型对比
对四个同类 MCP 服务器做源码级能力与成本对比,并以常驻 token 预算为轴给出分级接入策略的技术选型实战案例。
Open Code Review 图谱
对阿里开源 AI 代码审查 CLI 做源码级解析并构建「知识 + 实践资产 + AI 索引」三合一知识图谱的工程案例。
§9信源与核验
§10原始关系清单
展开:本节点的全部原始关系边(9 条,含边 ID 与方向)
| 关系 | 方向 | 对端节点 | 说明 |
|---|---|---|---|
| 案例印证 | ← CAS-X-01 | 去中心化聊天架构逆向 E-938 | 印证:按关注点将系统分解为可独立处理的 18 个模块 |
| 案例印证 | ← CAS-X-13 | Claude Code 架构逆向 E-978 | 印证:查询引擎、工具、权限、记忆的关注点分离 |
| 组合 | → ESS-G-07 | 架构描述与对应关系 E-730 | 架构描述按干系人关注点组织局部表达 |
| 组合 | ← ESS-G-04 | 模块化与分解 E-111 | 模块化是关注点分离在结构上的体现 |
| 组合 | ← ESS-H-01 | 信息隐藏 E-10 | 信息隐藏与关注点分离常组合使用 |
| 前置依赖 | → ESS-G-05 | 架构风格 E-103 | 关注点分离是理解与选择架构风格的认知基础 |
| 前置依赖 | → ESS-G-06 | 视图与视角 E-105 | 关注点分离是视图与视角划分的基础 |
| 前置依赖 | → ESS-H-01 | 信息隐藏 E-01 | 关注点分离是信息隐藏的前提 |
| 前置依赖 | → ESS-H-03 | DRY 单一知识来源 E-08 | 关注点分离促成知识的单一归属 |
附对照资源 · 12factor 子库
| 12factor 节点 | 关系 | 对照说明 |
|---|---|---|
| 掌控控制流 ag-control | 跨体系对照 | 控制流由代码决定、LLM 仅作决策点,属关注点分离与自动化方式选择 |