1. 三个原则被摆在一起时,我才发现它们都在回答同一个问题
1.1 一次排查两小时的线上问题,根子在一个"什么都能干"的类
几年前处理过一次线上问题,现象很普通:某笔业务单据的状态字段偶尔会变成中间态,卡在那里不再推进。日志里没有异常堆栈,数据库没有死锁告警,监控曲线也看不出毛刺。我和同事从消息队列开始查,一路查过定时任务、缓存、连接池,两个小时过去,最后定位到的地方让人有点无语——一个名叫CommonService的类,八百多行,同时承担了参数校验、库存扣减、状态流转、消息发送、日志落库这几件事。问题出在其中一段"顺手补的"状态更新逻辑,它恰好在某个分支里绕过了事务边界。
那次之后我把三样东西写在一张便签上贴在显示器边缘:单一职责原则、第一性原则、ACID原则。不是为了装样子,是因为我发现这三个东西虽然分属设计模式、思维方式、数据库事务三个完全不同的语境,但它们处理的是同一个敌人——失控的复杂度。单一职责负责在空间上划线,第一性原则负责在思考上刨根,ACID负责在时间上兜底。三个方向,一个目标。
这篇内容我想聊的就是这三个原则怎么在实际工作里配合使用,以及"安全、学习、节约"这三条线怎么变成可执行的判断标准。适合已经写过一些代码、做过一些设计、但总觉得"原则都懂,落地就变形"的读者。如果你正在维护一个越改越怕动的系统,或者正在纠结某个模块该不该拆,下面的内容应该能对上你的场景。
1.2 边界、拆解、状态:三个原则各自的守土范围
先把三者的分工说清楚,不然后面容易串味。
单一职责原则(SRP)管的是"边界"。它回答的问题是:这段代码、这个模块、这个服务,应该负责到什么程度就该停手。它的价值不在代码好不好的层面,而在"变化会不会互相牵连"的层面。一个模块只有一个变化原因,意味着需求变动时你的改动范围是可预测的。
第一性原则管的是"拆解"。它回答的问题是:我面对的这个东西,往下拆到什么程度就不能再拆了,然后从这些不可再分的单元重新往上搭。它的价值在于把你从"沿用现成方案"的惯性里拽出来。很多人做架构设计的第一反应是"业界都这么干",第一性原则要求你先问一句:业界的约束条件和我一样吗?
ACID管的是"状态"。它回答的问题是:一组操作要么全成要么全败,中间态不能被外人看见。它的价值在于给数据变更划一条不可逾越的线。这条线划错了,前面两个原则做得再好也没用——数据脏了,重构就是灾难。
三者串起来是一条完整的链:用第一性原则想清楚本质 → 用单一职责把本质切成模块 → 用ACID保证模块间的状态变更不出岔子。反过来,如果跳过第一步直接拆模块,很容易拆出一堆"看起来合理但根本不解决问题"的东西。
1.3 为什么"安全/学习/节约"是检验原则是否落地的三把尺子
原则这种东西最怕变成口号。判断一个原则有没有真的落地,我习惯用三个很朴素的维度去量:
- 安全:出问题时,故障的影响范围是不是可控的?能不能被快速隔离?
- 学习:新来的人接手,需要花多久才能看懂并安全地改?
- 节约:同样的需求变更,需要改几个地方?返工率是多少?
这三个维度不玄乎,都能观察、能度量。单一职责做得好,安全性和学习成本都会下降;第一性原则用得好,节约效果最明显——因为你没在做无用功;ACID执行到位,安全性有底线保障。后面每一节我都会回到这三把尺子上,看原则到底有没有产生实际收益。
2. 单一职责原则:判断标准从来不是"代码多少行"
2.1 被误读最深的四个字:一个类只做一件事
"一个类只做一件事"这句话害了不少人。问题在于"一件事"的粒度完全没有标准。有人理解成一个方法,于是一个类里只剩一个方法;有人理解成一个业务动作,于是把校验、计算、持久化全塞进一个方法里,理由是"这整件事就是下单"。两种理解都能自圆其说,但实际效果天差地别。
我见过最典型的误用是按代码行数拆。某个类超过三百行就拆,拆出来的两个类互相调用对方的私有逻辑,最后不得不把方法改成public,封装彻底破功。也见过按"技术分层"拆,把业务逻辑硬切成XxxValidator、XxxCalculator、XxxSaver,结果一个业务规则变更要动三个类,改动成本反而上升。
正确的判据只有一条:**这个模块因为哪些原因会发生变化?如果变化原因超过一个,就该拆。**注意,是"变化的原因",不是"做的事情数量"。一个类可以做十件事,只要这十件事永远因为同一个原因一起变,它就是合格的。反过来,一个类只做两件事,但这两件事分别受市场和受财务驱动,那就必须拆。
2.2 真正可操作的判据:变化的原因只有一个
怎么识别"变化的原因"?我的方法是找变化轴——把需求里所有可能导致这段代码改动的外部因素列出来,看它们是不是同一个来源。
举个具体例子。一个订单服务里同时有这几段逻辑:
public class OrderService { public void createOrder(Order order) { // 1. 校验地址格式、手机号格式 // 2. 计算优惠价、税费、运费 // 3. 落库并更新库存 // 4. 发送下单成功通知 // 5. 写入操作审计日志 } }这五件事的变化轴分别是:
| 逻辑段 | 变化驱动方 | 变化频率 |
|---|---|---|
| 地址/手机号校验 | 运营规则、合规要求 | 中 |
| 优惠/税费/运费计算 | 营销策略、财务政策 | 高 |
| 落库与库存扣减 | 数据结构、并发策略 | 低但影响大 |
| 下单通知 | 触达渠道、文案 | 高 |
| 审计日志 | 合规审计要求 | 低 |
五条变化轴,五类驱动方,频率还都不一样。这说明这五段逻辑的"变化原因"完全不重叠,硬塞在一个类里的直接后果是:营销改一次优惠规则,你要重新跑一遍下单的完整回归测试;合规改一次日志格式,你要担心会不会碰坏库存扣减。
**拆分的正确姿势是按变化轴拆,不是按行数拆,也不是按技术分层拆。**上面的例子拆完之后大概是:AddressValidator(跟合规走)、PricingCalculator(跟营销走)、OrderRepository(跟数据结构走)、NotificationSender(跟渠道走)、AuditLogger(跟合规走)。每拆出一个,就多一个独立的变化轴,改动的影响半径就缩小一圈。
2.3 用"变化轴"给一个臃肿服务做体检
实际工作中,我给老系统做体检有个固定流程,五步,不需要任何工具,白纸加需求文档就行。
**第一步,找改动记录。**翻最近半年的提交历史,看同一个文件被改了多少次、每次改动的理由是什么。如果同一个文件被营销需求改过、又被合规需求改过、还被性能优化改过,那基本可以确定它承担了多个职责。
**第二步,列驱动方。**把每次改动的"发起方"列出来——是业务方提的、是财务提的、是运维提的、还是安全审计提的。同一驱动方反复出现的改动,往往属于同一个职责。
**第三步,画依赖图。**看这个模块被谁调用、调用了谁。如果它被三个完全不同领域的模块依赖,说明它大概率是个"万能类",是SRP的重灾区。
**第四步,做变更模拟。**假设现在要改一条营销规则,问自己:我需要动几个文件?需要跑多少测试?需要通知几个人?如果答案是"三个文件、全量回归、通知后端和前端",那变化的耦合度已经很高了。
**第五步,算影响半径。**把每个职责对应的代码行数、被引用次数、测试覆盖率列成表,优先处理"改动频率高 + 影响半径大"的那个。
这套流程走下来,一般能在一两天内定位出系统里最需要拆的几个点。这比一上来就大动干戈重构要安全得多,也符合"节约"的诉求——先治最疼的地方,而不是均匀用力。
2.4 拆过头的代价:过度细分的三种典型症状
SRP不是越拆越好。拆过头会有三种很明显的症状,遇到了就该往回合并。
**症状一:调用链像迷宫。**一个下单请求要穿过七个类、十二次方法调用才能完成,中间还夹着三层接口抽象。这种设计名义上"职责清晰",实际上没人能在脑子里完整还原一次请求的全貌。新人接手的学习成本直接翻倍,这违背了"学习"这把尺子。
**症状二:为了拆而拆的转发类。**大量形如XxxManager、XxxHelper、XxxProcessor的类,方法体只有一行return xxxService.doSomething()。它们不承载任何逻辑,只增加跳转次数。判断标准很简单:如果一个类的所有方法都是转发,它就该被删掉。
**症状三:跨类的事务无法收敛。**这个坑最隐蔽。你把库存扣减和订单落库拆成了两个服务,各自职责清晰,但事务边界被拆散了,ACID就保不住了。这时候正确的做法不是继续拆,而是在两者之间建立明确的补偿机制或者分布式事务方案——先想清楚一致性怎么保证,再决定拆不拆。
提示:拆分的下限是"能独立变化",上限是"一次业务操作能在一个可理解的范围内完成"。摸到上限就该停手,不要被"职责一定要纯粹"这种洁癖带着走。
3. 第一性原则:把"别人都这么干"从决策依据里删掉
3.1 第一性拆解的三步走:定目标、拆到底、重新组装
第一性原则这个词被用得很泛,落到工程实践里,我把它简化成三个动作。
**第一动作,把目标写死。**这里的"目标"必须是一句可以被验证的话,不能是"提升系统性能"这种模糊表述,得是"下单接口P99从800ms降到200ms"这种可量化的。目标不清晰,后面的拆解全是散的。
**第二动作,拆到不可再分的单元。**持续追问"这件事由哪些基本面构成",直到问不下去为止。比如"下单接口慢",往下拆是:网络传输耗时、序列化耗时、数据库查询耗时、业务计算耗时、外部调用耗时。再往下拆,"数据库查询耗时"又分成连接获取、SQL执行、结果传输、结果映射。拆到这个粒度,你就知道瓶颈的真实位置了,而不是拍脑袋加缓存。
**第三动作,从基本面重新往上组合。**这一步最容易被忽略。拆解本身不产生价值,价值在于拆完之后你发现"原来这个环节可以整个去掉"或者"原来这两个环节可以并行"。比如拆完发现序列化占了40%的时间,而这两个服务本来可以直接传二进制,那答案就不是"优化序列化",而是"取消这次序列化"。
这套方法的价值在于它强制你区分"事实"和"惯例"。加缓存是惯例,序列化耗时占40%是事实。第一性原则要求你先拿到事实,再谈方案。
3.2 一个反例:照着模板抄出来的架构
我参与过一次技术方案评审,某团队要做一个内部数据查询系统,架构图画得很漂亮:网关、认证中心、服务注册、消息队列、读写分离、缓存集群、分库分表,一应俱全。问他们为什么这么设计,回答是"主流互联网架构都这样"。
实际情况是:这个系统的数据量不到两百万行,日查询量三千次左右,用户是公司内几十号人。按第一性原则拆一遍:数据量小,单机内存全装得下;查询量低,单库完全撑得住;用户少,并发压力可以忽略。真正的基本面只有两条——查询要快和数据要准。
最后的方案是:单机数据库加一份内存索引,总共不到两千行代码,两周上线。如果按原方案走,保守估计三个月,还要搭进去一个专职运维。这不是说原方案错,而是它的约束条件和实际场景不匹配。把别人的方案当起点,等于把别人的约束条件当自己的约束条件,这是最常见的思维偷懒。
这件事也直接印证了"节约"这条线:节约不是省钱,是不做无用功。第一性原则最大的节约价值,就是让你在动手之前发现哪些事根本不用做。
3.3 第一性原则和单一职责的关系:一个负责想,一个负责落
这两个原则经常被混着讲,实际上它们处在不同的层面。
第一性原则处理的是认知层:我面对的问题,本质是什么?它的构成要素有哪些?哪些是必须的,哪些是历史包袱?
单一职责处理的是结构层:想清楚之后,代码怎么摆?模块怎么切?谁依赖谁?
它们的关系是:第一性原则产出的"不可再分单元",往往就是单一职责要找的"变化轴"。你拆到基本面之后,每个基本面天然就是一个独立的变化原因,按它划模块,职责自然就单一了。
反过来,如果你跳过第一性拆解直接谈SRP,很容易拆出"技术上合理但业务上无意义"的边界。比如把"参数校验"和"业务计算"分成两个类,技术上确实职责不同,但如果这两件事永远因为同一条业务规则一起变,拆开就是给自己找麻烦。
我在实际项目里的做法是:**先用第一性原则列出所有基本面,标出每个基本面的变化驱动方,变化驱动方相同的合并,不同的拆开。**这一步做完,模块划分基本就出来了,SRP只是给这个结果贴个标签。
4. ACID不是教条,是四道有成本的保险
4.1 四个字母背后的真实语义
ACID这四个字母,很多人能背出来,但落到"什么时候该用、用到什么程度"就开始含糊。我按实际语义重新捋一遍。
原子性(Atomicity):一组操作要么全做,要么全不做。注意它的重点不是"全做",是"不做"——失败时能不能干净地回到起点。真正难的是回滚路径的设计,尤其是涉及外部系统调用时(比如已经发了消息、已经调了第三方接口),数据库事务是回滚不了这些的。
一致性(Consistency):这个字母最容易被误解成"数据一致"。它在ACID语境里的原意是"约束不被破坏"——唯一索引、外键、检查约束、业务上的余额不能为负,这些在事务前后都必须成立。它不是"主从数据一致",那是复制范畴的事。
隔离性(Isolation):并发事务之间互不干扰。这一条是四者里唯一有"档位"的,因为完全隔离的代价太高了。后面的小节专门讲。
持久性(Durability):提交了就不能丢。它依赖的是日志落盘策略,跟"事务"本身关系不大,但经常被绑在一起谈。
我特别想强调一点:ACID是一组保险,保险是有保费的。原子性要写undo日志,隔离性要加锁或做多版本,持久性要fsync。这些全是性能开销。所以正确的问法不是"怎么保证ACID",而是"这个场景需要哪一个级别的保证"。
4.2 隔离级别的取舍:一致性从来是要花钱买的
四个隔离级别,从低到高,代价递增,能防的问题递减。用一个表说清楚:
| 隔离级别 | 能防住 | 防不住 | 典型代价 |
|---|---|---|---|
| 读未提交 | 基本无 | 脏读、不可重复读、幻读 | 最低 |
| 读已提交 | 脏读 | 不可重复读、幻读 | 低 |
| 可重复读 | 脏读、不可重复读 | 幻读(视实现而定) | 中 |
| 串行化 | 全部 | 无 | 高,并发度骤降 |
实际选型的思路是从业务后果倒推,而不是从级别高低顺推。问三个问题:
- 读到"别人还没提交的数据",后果是什么?如果只是展示层的一个数字,选读已提交就够;如果涉及扣款金额,必须防住脏读。
- 同一个事务里读两次同一行,结果不一样,会不会导致错误决策?比如先查余额再扣减,中间被别人改了,这就是不可重复读,必须防。
- 同一个事务里查两次,第二次多出几行,会不会算错总数?比如统计订单量时被新订单插进来,这就是幻读。
按这三个问题走一遍,绝大多数业务系统落在"读已提交"或"可重复读"上。盲目上串行化,往往换来的是死锁率飙升和吞吐量断崖,最后不得不加各种重试,反而更不稳定。
注意:隔离级别不是全局设置,很多数据库支持会话级、语句级覆盖。真正有效的做法是默认级别保守,热点路径单独降级或升级,而不是一刀切。
4.3 事务边界怎么划,其实还是单一职责问题
这一节是三个原则真正交汇的地方。
事务边界划错,通常有两种表现。一种是边界太大:把参数校验、外部调用、消息发送全裹进一个事务,导致事务持有时间过长,锁竞争加剧,还可能出现"事务里调了第三方接口超时,数据库连接被占着"这种连锁反应。另一种是边界太小:把一个完整的业务动作拆成多个独立事务,中间失败就留下一堆半成品数据。
正确的划法,我认为应该遵循两条规则:
**规则一,事务只包住"必须同时生效"的写操作。**读操作、校验、外部调用、消息发送,原则上都应该在事务之外。消息发送尤其要注意,放在事务里会导致"事务还没提交消息已经发出去了",消费方读不到数据。
**规则二,一个事务对应一个业务不变量。**什么叫业务不变量?就是"在任何时刻都必须成立的条件"。比如"账户余额不能为负"、"库存数量不能小于已占用数量"。一个事务应该恰好维护一组这样的不变量,不多不少。
第二条规则其实就是单一职责在时间维度上的投影:一个事务只负责一个业务不变量,就像一模块只负责一个变化原因。你按这个标准去审视现有的代码,会发现大量"一个事务里塞了五条不变量"的情况,这类代码一旦出问题,排查难度极高,因为你不知道是哪条不变量被破坏了。
4.4 分布式场景下ACID的变形记
单库事务好办,一旦跨服务,ACID就得打折扣。这里不谈具体框架,只谈判断思路。
跨服务场景下,强一致的代价是可用性和响应时间,这是绕不过去的物理限制。所以第一性原则在这里的应用是:先问"这个业务真的需要强一致吗"。
- 转账、扣库存、扣额度,这类涉及资金和资源的,必须强一致,或者至少做到"最终一致 + 可对账"。
- 发通知、写日志、更新统计,这类允许延迟,用最终一致加补偿就够了。
- 更新缓存、刷新推荐列表,这类甚至可以接受短暂不一致。
判断标准很朴素:如果中间态被用户看见,用户会不会做出错误决策、造成实际损失。会,就必须强一致;不会,就别为了"架构干净"去上重方案。
分布式事务的实现路径大致有三类:两阶段提交(强一致,性能差)、TCC(业务侵入大,但可控)、基于消息的最终一致(实现简单,需要幂等和补偿)。选哪条路,取决于你的业务能不能接受中间态、能不能接受补偿逻辑的复杂度。这里没有银弹,把约束条件列清楚,答案自己会浮出来。
5. 安全、学习、节约:三条线串起三个原则
5.1 安全线:ACID兜底,边界防御
"安全"这个词在工程语境下我理解成两层:数据不出错和故障不扩散。
数据不出错靠ACID。前面讲过,事务边界对应业务不变量,这条线守住了,数据的基本盘就在。我额外补充一个实操习惯:给每条业务不变量写断言。比如在扣减库存的代码后面加一行校验,确认剩余数量不为负;在事务提交前做一次不变量检查。这些断言在正常流程里永远不触发,但一旦触发就是救命信号。成本极低,收益极高。
故障不扩散靠单一职责。模块边界清晰,一个模块挂了不会把整条链路拖死;职责混杂的模块,一处异常会沿着调用链蔓延。这也是为什么我坚持"变化轴不同的逻辑不放在一个类里"——不只是为了好维护,是为了出问题时能快速定位并隔离。
还有一个容易被忽略的点:第一性原则可以用来做故障演练的推演。把系统的关键依赖拆到基本面,逐个问"这个挂了会怎样、有没有降级、降级后业务能不能跑",比盲目做全链路压测更能发现真实的薄弱点。
5.2 学习线:第一性原则拆知识,单一职责拆课程
这一条听起来有点跳,但我觉得是最实用的迁移。
学一门新技术,大多数人的路径是"找教程 → 跟着做 → 跑通了就算学会"。这条路的问题在于,你学到的是特定场景下的操作步骤,换个场景就懵。用第一性原则重新组织:
- 先问"这门技术解决的最根本问题是什么"。比如某个状态管理方案,根本问题是"多组件共享状态的同步"。
- 再问"在它之前,这个问题是怎么解决的,为什么不够好"。这一步能让你理解设计的取舍,而不是背API。
- 最后问"它的约束条件是什么,什么场景下不该用它"。这一步决定了你能不能做正确的技术选型。
单一职责在这里的应用是给学习内容分块。一个知识领域,按"变化原因"切成若干块:不变的基础原理、随版本变化的API、跟具体场景绑定的最佳实践。基础原理值得深挖,API查文档就行,最佳实践按需查阅。很多人学习效率低,是把这三块混在一起,花大量时间记会过时的API,却没搞懂不会变的基本原理。
5.3 节约线:少返工就是最大的节约
工程上的浪费,大头不是服务器成本,是返工成本。
返工的根源通常有两个:一是一开始没想清楚,二是改动的影响半径太大。第一个靠第一性原则解决——在动手前把基本面和约束条件拆清楚,能挡掉大量"做到一半发现方向错了"的情况。第二个靠单一职责解决——影响半径小,改了A不会碰坏B,回归测试的范围也小。
我做过一个粗略的观察:同一个团队,模块边界清晰的系统,一次中等需求变更平均改动三到五个文件;边界混乱的系统,同样规模的需求要改十几个文件,还得全量回归。差距不在开发速度,在验证成本。改五个文件跑半小时测试,改十五个文件跑一整天,这是实打实的时间差。
ACID在节约这条线上的作用比较隐蔽:它减少的是数据修复的成本。数据一旦脏了,修复往往要停服、要写脚本、要人工核对,代价是正常开发的好几倍。所以事务边界的投入,本质上是买保险。
把三条线放在一起看,会发现它们不冲突:
| 维度 | 主要依赖的原则 | 关键动作 | 可观测指标 |
|---|---|---|---|
| 安全 | ACID + SRP | 事务边界对齐业务不变量、模块隔离 | 故障影响面、数据修复次数 |
| 学习 | 第一性原则 + SRP | 按变化原因分块、区分原理与API | 新人上手时间、改动前的理解成本 |
| 节约 | 第一性原则 + SRP | 动手前拆基本面、缩小影响半径 | 单次需求改动文件数、回归时长 |
三条线同时变好的方案,才是真正值得做的方案。如果一个重构只让代码"看起来更整洁",但三个指标都没动,那它大概率是自我感动。
6. 一次完整的重构路径复盘
6.1 起点:一个八百行的"万能服务"
用一个虚构的场景把前面的东西串起来。某个业务系统里有个BusinessService,八百多行,承担了参数组装、规则校验、费用计算、状态流转、消息投递、审计落库。团队不敢动它,每次需求都在末尾加if分支,分支嵌套已经到了第六层。
体检结果:半年内被修改四十七次,涉及六个不同的需求方;被十一个模块引用;测试覆盖率不到三成。按前面说的五步流程走完,结论很明确——这是典型的多变化轴混杂。
6.2 拆解:先画变化轴,再定事务边界
第一步不是拆代码,是画变化轴。把四十七次改动按驱动方归类,得到五组:规则策略(营销)、计费逻辑(财务)、状态机(业务)、消息模板(运营)、审计格式(合规)。五组之间几乎没有交集。
第二步,用第一性原则问一句:这个服务的本质是什么?答案是一次业务状态推进,附带费用结算。那么不可再分的单元就是:状态合法性判断、状态推进、费用计算、副作用通知。参数组装和审计落库不属于本质,它们是外围。
第三步,定事务边界。业务不变量只有两条:状态只能按规定路径推进、费用结算不能重复。这两条必须在一个事务里。消息投递和审计落库放在事务外,用事务提交后的事件触发。
最终的模块划分:
StateMachine:只管状态合法性判断和推进,对应的变化驱动是业务规则PricingEngine:只管费用计算,对应的驱动是财务政策BusinessRepository:只管持久化,对应数据结构EventPublisher:事务提交后触发,负责消息和审计,对应运营和合规
6.3 落地顺序和验收标准
重构最忌讳一次性推翻重来。我采用的顺序是从外围往核心切,理由是外围改动风险低、能快速验证拆分思路是否成立。
- 先把审计落库抽出去,改成事件驱动。改动小,验证"事务外副作用"这个模式能不能跑通。
- 再把消息投递抽出去,同上。
- 然后抽费用计算,这一步要写大量对照测试,用老逻辑和新逻辑跑同一批数据比对结果。
- 最后抽状态机,风险最高,放在最后,且保留开关,可以随时切回旧逻辑。
验收标准提前定好,三条:所有历史用例结果一致;单次营销规则变更的改动文件数从平均九个降到三个以内;核心路径的P99响应时间不劣于重构前。
6.4 踩过的三个坑
**坑一:事件驱动带来的顺序问题。**审计落库改成事件之后,出现了"审计记录比业务数据晚到"的情况,报表对不上。解决办法是给事件加上业务流水号,报表侧按流水号关联,接受秒级延迟。这个坑的教训是:把副作用移出事务,就要接受它可能延迟,业务侧必须提前知情。
**坑二:费用计算的浮点误差。**老代码用double算钱,抽出来之后对照测试大面积不一致。查下来是累加顺序变了导致精度差异。最终统一改成整数分单位加BigDecimal,顺带修掉了一个潜在的生产隐患。这个坑提醒我:重构时任何"顺便优化"都要单独评估,混在一起会让问题定位变难。
**坑三:状态机抽取后的并发问题。**老代码在事务里做了"先查后改",抽取之后两处逻辑分离,出现了并发下的状态覆盖。最终在状态推进处加了乐观锁版本号,冲突时重试。这正是ACID里隔离性要解决的问题——拆分不会消除并发问题,只会让它更明显,该加的锁一个都不能少。
回头看,整个过程花了大概六周,其中写对照测试占了一半时间。这个投入值不值?看指标:重构后半年内该模块的线上问题从七起降到一起,单次需求平均改动文件数从九个降到两个,回归测试时长从四小时降到四十分钟。三条线全部改善,我认为值。
我个人在实际操作中的体会是,这三个原则最大的价值不在于它们本身多正确,而在于它们给了你一套可以对外解释的判断依据。当有人说"就这么点改动,为什么要拆"的时候,你不用讲情怀,直接把变化轴和影响半径摆出来,用改动文件数和回归时长说话。原则落到可度量的指标上,才有说服力,也才守得住。