news 2026/10/20 7:23:06

从能跑到改得动:软件可维护性设计的四个关键维度

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从能跑到改得动:软件可维护性设计的四个关键维度

1. 可维护性决定系统寿命:先算清楚一笔成本账

做软件这行十几年,我见过太多"上线即巅峰"的系统。上线那天风风光光,功能一应俱全,老板笑得合不拢嘴,可等到第二年开始迭代需求的时候,整个团队就开始陷入泥潭——改一个字段要翻三天代码,加一个促销活动要动四个服务,排查一个线上问题像大海捞针。最典型的场景是:某一年某公司需要对一个老系统的订单状态流转做一次小调整,预估工作量是"半天",结果实际花了三天,因为涉及到订单状态机、消息队列、定时任务补偿、前端状态展示四个层面的逻辑,而每一层对"订单状态"都有自己的一套理解和判断。

这就是可维护性设计失败的典型症状。很多人以为"能跑就行"和"设计得好"之间的差别在于代码写得漂不漂亮,但真正决定系统寿命的是改动成本。软件行业的统计数据反复指向同一个结论:在一个软件系统的整个生命周期中,前期开发成本只占20%~30%,剩下70%~80%都花在维护上——这还不算因为迟迟改不动而错失的市场机会。换句话说,可维护性设计本质上是在降低软件生命周期里的总拥有成本,它不是在给开发添麻烦,而是在给一年后的自己和接手的同事留活路。

我写这篇文章,不是为了给你讲一堆教科书上的设计原则名词,而是想从一个在一线摸爬滚打多年的从业者角度,把"可维护性"这件事拆开揉碎,讲清楚它到底意味着什么、具体要怎么落地。适合正在做架构设计的开发者、在维护老系统时痛不欲生的同学,以及那些即将启动新项目、想在第一天就把地基打好的团队。

先看一张我平时做技术评估时经常画的对比表:

对比维度可维护性差的系统可维护性好的系统
新增一个普通功能3~5人日,且容易引入回归问题0.5~1人日,改动局部化
新人上手成本需要一两个月,且不敢轻易碰代码一周内能独立完成小任务
线上问题排查靠猜、靠日志全文搜索、靠重启靠链路追踪和结构化日志快速定位
修改接口契约牵连多个模块,全局联动爆炸通过防腐层隔离,影响可控
老化速度两年后没人敢重构,只能推翻重写持续小步重构,反而越用越顺手

这张表不是我拍脑袋编的,而是来自我亲眼见过的无数项目。可维护性这东西,短期看不出收益,因为它不像性能优化那样有一个直观的响应耗时数字可以炫耀,但它呈现的是另一种曲线:系统运行时间越长,可维护性好的系统效率优势越明显,二者的差距是指数级的。

另一个经常被忽视的点:可维护性其实是团队心理安全和士气的底层支撑。在一个烂代码库里,每个人做事都战战兢兢、如履薄冰,改个几行代码都要做最坏的预案;而在一个好维护的系统里,工程师才有余力去做技术升级、做性能优化、尝试新工具。这两种团队的产出质量和人员留存率,长期下来天差地别。这也是为什么很多公司在做技术面试时越来越重视设计能力——因为他们吃过太多"快速扩张后技术债爆炸"的苦。

所以这篇文章要讲的核心是:可维护性不是一种模糊的"代码品味",而是可以通过复杂度控制、可观测性建设、抽象边界划分、变更流程保障这四个维度去具体设计、度量和持续改进的东西。后面我逐层展开。

2. 复杂度与耦合:可维护性设计最大的两个敌人

2.1 先搞清楚复杂度从哪来

如果让我用一个词概括可维护性差的根源,那就是"复杂度失控"。但要注意,复杂度分两种:本质复杂度和偶然复杂度。

本质复杂度是业务本身带来的,比如一个资金交易系统天然就需要处理并发、幂等、对账这些复杂问题,这些复杂度躲不掉,设计者的任务是把它们约束在合理的范围内,不至于到处乱窜。偶然复杂度则是我们在实现过程中"自己给自己加的戏",比如为了少写几行代码搞了一个全局静态类,结果所有模块都依赖它;比如过度设计了一个可以配置一切规则引擎,结果根本没人能看懂配置规则。

一个残酷的事实是:大量的系统坍塌不是毁于本质复杂度过高,而是毁于偶然复杂度堆积。我在一个物流项目里见过一个让我印象深刻的例子——当时的团队为了让"获取用户信息"这个接口在各种业务场景下都能复用,给它加了一堆可选的查询参数,最后这个接口有十几个入参,内部根据入参组合走出了几十条分支路径。表面上看是"高复用",实际上每个人调用它之前都要先搞清楚"我该传哪几个参数,会不会触发什么诡异逻辑"。这种通用性幻觉,是偶然复杂度最重要的来源。

2.2 圈复杂度和依赖结构:给系统做"体检"

度量复杂度最常用的两个指标:圈复杂度和依赖结构。

圈复杂度衡量一个函数或模块中独立路径的数量。经验上,单个函数的圈复杂度超过10就应该警惕,超过20基本就是重构信号了。这里有个简单的计算方法:对一段代码画出控制流图,圈复杂度等于边数减去节点数加二,也可以理解为"判断节点的数量+1"。

依赖结构则看模块之间的调用关系。理想的架构图应该像一棵层次分明的树,或者像一组相对独立的岛群。你对着一张模块依赖图,如果发现大量循环依赖、跨层依赖、双向依赖,那基本可以断定这个系统在朝着"能跑但难改"的方向狂奔。我在实际项目中会定期做一次依赖扫描,用工具导出模块间的依赖关系图,重点揪出三类问题:

  • 循环依赖:A依赖B,B又依赖C,C又回到A。这种结构在启动时不一定报错,但在后续修改和扩展时会让开发者的脑子反复绕圈。
  • 超高层依赖:比如本该只做数据展示的前端组件直接依赖了后端的存储模型,这种依赖一旦建立,修改后端字段就会顺着依赖链炸到前端。
  • 依赖方向倒置:高层业务模块依赖低层实现模块,导致想替换底层实现时,高层代码被迫改动。

2.3 用架构约束和代码级约束压制复杂度

解决复杂度问题的核心思路不是"开发者自律",而是用硬性约束让错误代码无法被提交。人都会偷懒、都会在压力下做出短视选择,所以好的设计要把防线前置到工具层面。

架构约束的手段有:模块化拆分(从物理上隔离)、私有化(从语法上禁止外部访问)、明确依赖方向的检查规则(用自动化脚本拦截非法依赖)。我经历过一个进步很大的团队,他们的做法很简单:在CI流程里加了一个依赖检查任务,凡是出现跨层依赖或者循环依赖的提交,直接构建失败并给出报告。这个规则执行了一个月后,架构图肉眼可见地变得清爽了。

代码级约束的手段更细,但同样有效:

  • 禁止全局可变状态:全局可变状态是调试地狱的头号入口,改起来牵一发动全身,排查时又看不见摸不着。
  • 限制函数参数数量:超过4个参数就考虑封装成对象或配置类。
  • 领域逻辑必须存在领域层:禁止在Controller和Service里堆砌大量业务判断。

拿我自己常用的圈复杂度检查来说,在CI里挂一个静态检测,超过阈值就报警,直接阻断合入。这样做的价值在于:不是每次等代码写得稀烂之后再去人工评审口水战,而是机器先给你划出底线。

2.4 耦合的本质是"改动连锁反应"

如果你仔细思考"什么让一个系统难维护",答案大多能归结到一点:改A要连带改B、C、D,而你根本记不住B、C、D在哪。耦合度衡量的正是这种"改动的连锁反应范围"。

我常给团队举一个隐喻:耦合度高的系统就像一堆电子元器件直接用焊锡堆在面包板上——每个元件都能工作,但你想换掉其中一个电容,会发现它和旁边五个元件都焊死了,只能带电烙铁小心翼翼逐个拆。耦合度低的系统则像标准的模块化电路板,每个功能模块有标准的接口和插槽,拔下来、插上去,旁边的东西不受影响。

在代码层面,降低耦合有几个非常实用的切入点:

  • 接口最小化:别的模块能访问到的只应该是它真正用到的,而不是把所有内部方法都公开。
  • 依赖倒置:面向抽象编程,而不是面向具体实现编程。这样替换实现时,调用方不用改。
  • 事件驱动:把强同步调用改为事件通知,让上下游解耦。这招在处理多系统集成时尤其有效。

记住一个判断标准:好的模块之间的连接应该是细线,而不是粗管道。如果两个模块之间要传递一个巨大的数据结构才能协作,那它们就是重度耦合,后续这个数据结构字段的任何调整都会变成一场灾难。

3. 可观测性不是加分项,而是排错和维护的地基

3.1 维护系统的前提是能"看懂"系统

可维护性设计有一个被低估的基础条件:当系统出问题时,你能不能快速看明白它内部发生了什么。很多人对可维护性的理解停留在"代码结构清晰",但实际排错时的第一需求是"懂系统当前的状态"——数据在哪、走到什么环节了、为什么卡住了、哪一步出错了。

我接手过一个已经运行了三年多的电商后台系统,它的订单退款链路经常莫名其妙超时。由于当年没有做链路追踪,生产环境的日志是各个服务各自打各自的,时间戳还不完全一致。排查这类问题只能先根据订单号去数据库里翻订单状态,再去消息队列看消费延迟,然后逐条对照多个服务的日志时间线,靠人肉拼出一次退款请求的全貌。

这样的排查过程有多痛苦呢?一次中等复杂度的线上问题,少则两三个小时,多则一个下午就耗进去了。后来我们把日志规范化和链路追踪补齐之后,同样的场景,打开链路查询界面输入订单号,一眼就能看到整条调用链每个环节的耗时和异常点,定位时间压缩到十几分钟。这个效率差距,就是可观测性建设最直观的回报。

3.2 日志、指标、链路:三位一体的黄金信号

可观测性在技术层面由三根支柱构成:日志、指标、链路追踪。这三者不是替代关系,而是互补关系:

  • 日志解决的是"某个具体请求发生了什么",它是排错时最细粒度的依据。
  • 指标解决的是"系统整体健康状况如何",比如QPS、错误率、响应时延、JVM内存,它负责发现趋势性问题和异常波动。
  • 链路追踪解决的是"一次跨服务请求的完整路径是什么样的",它把分散在各服务里的日志片段串成一条完整的故事线。

一个可维护性好的系统,这三个信号都必须覆盖到位。如果只能选一个优先投入,我建议先做结构化日志——因为它的性价比最高,不需要引入复杂的基础设施就能立竿见影。

所谓结构化日志,指的是每一条日志都包含标准化的字段,比如时间戳、请求ID、业务ID、操作类型、结果状态。有了这些字段,在日志系统里就可以直接检索"某个订单号相关的全部日志",而不需要在茫茫日志文件里用文本搜索硬抠。规范的日志还需要有统一的级别语义,比如ERROR必须包含异常堆栈和上下文关键数据,WARN必须说明"虽然没挂但发生了什么值得注意的情况"。

3.3 日志规范是"写给别人看的"设计

日志这件事看起来很简单,但很容易做坏。最常见的坏法有两种:一是滥用日志,把业务逻辑的每一步print都打出来,导致日志量爆炸,真正需要的信息被淹没;二是日志内容残缺,像"I got an error"这种连上下文都没有的日志,排错时等于没有。

我在项目里定下的日志规范遵循几个原则:

  • 每一条关键业务动作都带唯一语境ID:比如订单号、用户ID、支付流水号。这样无论日志散落在哪个服务,都能通过语境ID聚合同一次操作。
  • 日志结构分三个层次:入口请求日志(谁调用了什么、入参、耗时)、业务过程日志(关键状态流转、决策点)、异常日志(错误码、堆栈、上下文快照)。
  • 禁止在日志中记录敏感信息,比如密码、手机号、身份证号全量明文,这既是对用户的负责,也避免合规风险。
  • 定期做日志人大扫除:删除不再有价值的DEBUG日志,避免它们变成噪音。

3.4 从"能重启就行"到"带着数据做运维"

这里还要强调一个观念转变:可观测性的核心目标是让大家不用依赖重启来解决线上问题。

很多团队历史遗留的习惯是"服务出了问题先重启试试",因为在系统不透明的情况下,重启确实能解决一部分内存泄漏、连接池耗尽等问题,但它消除的是症状,不是病因。如果你有完善的指标和日志体系,就能在进程挂掉之前看到内存曲线持续攀升,就能在用户报告之前收到错误率突增的告警,然后带着完整的数据去定位和修复问题。

我见过一个很好的实践案例:某支付系统在引入全链路监控后,团队把线上问题应急手册从"重启大法"更新为"先看SLA大盘,再查链路追踪,最后定位具体代码"。几个月后,他们的平均故障恢复时间下降了60%以上。这个提升不是来自某一次奇迹,而是来自"看得见系统内部"这个地基能力。

4. 抽象边界与分层的本质:寻找稳定边界

4.1 什么才是好的抽象

谈可维护性,必然绕不开"抽象"这个词,但我发现很多开发者对抽象的理解停留在"抽一个函数/类出来"的层面,以为把重复代码提取出来就是抽象。这是误区。好的抽象不是消除重复,而是定义稳定边界。

举个例子,一个支付模块对外暴露的接口应该是支付(订单、金额、支付方式),而不是调用微信接口(签名参数1, 参数2, 参数3)。前者把底层支付渠道的差异藏在了模块内部,对外是一个稳定的语义;后者则把实现细节暴露给了调用方,一旦换了渠道供应商,所有调用方都得跟着改。

好的抽象有三个特征:

  • 稳定:接口不容易变,变了也只影响模块内部。
  • 窄小:暴露给外部的面尽可能小,调用方不用了解内部复杂性就能使用。
  • 语义明确:接口的名字和参数反映的是业务概念,而不是技术实现。

我曾经在一个项目里因为抽象边界没划好栽过大跟头。当时我们做一个文件存储模块,为了省事,直接把对象存储的底层API封装了一下就暴露给业务方使用。刚开始一切顺利,但后来存储方案从一家云厂商切换到另一家时,才发现业务方在底层API之上做了各种奇奇怪怪的姿势——有人直接用了供应商SDK的特有方法,有人往文件名里塞业务编码,由此产生了大量深层耦合。最终这个切换花了整整一个月解决兼容问题,而如果我们当初就提供存文件、取文件、删除文件、获取访问链接这四个稳定接口,可能两天就切完了。

4.2 边界划分的真问题:什么会变,什么不变

边界设计不只有一个"分层"维度,还涉及另一个核心问题:识别系统中的稳定部分和易变部分,然后在二者之间画出隔离带。

分层架构(Controller-Service-DAO)之所以经典,就是因为它做了这个划分:Controller处理的是"如何与外部交互"这种易变的部分,Service处理的是"业务规则"这种相对稳定的部分,DAO处理的是"数据存取"这种易变的部分。每一层之间通过清晰的接口通信,避免了"外部协议的变化泄漏到业务规则里"这种灾难。

但这个划分在现代工程实践里已经不够用了。更细的边界划分是从单一职责的维度去切,比如把一个复杂的电商系统切分为商品域、订单域、库存域、支付域、用户域。每个域内部的模型和逻辑是自治的,域之间通过明确的对外API协作,不允许直接访问对方领域的数据库表。这种约束一旦建立起来,跨域修改的成本就会陡然升高,倒逼团队通过API协商而非直接改别人领域来解决问题。

在实际设计边界时,有一个很实用的判断方法:按变化频率找聚合点。问一问自己:"在一个季度内,哪些需求变更可能发生?它们会影响哪些模块?"把变化频率高的部分聚在一起,用接口把它包起来,变化频谱不同的部分放在边界两侧,这样出问题时的"爆炸半径"就被控制在一个局部。这个思路和"高内聚低耦合"是一个硬币的两面。

4.3 分层被打破时的典型症状与修复方案

理论上大家都认可分层,但实践里分层总是会悄悄被打破。常见症状有以下几种:

  • Controller里写了很重的业务逻辑:比如在Controller里做了校验、拼装、甚至数据计算。结果业务规则散落在表示层,改了需求可能还要动接口签名。
  • Service之间互相调用对方的内部方法:比如订单Service直接调商品Service的某个私有方法,导致两个服务的领域逻辑开始互相渗透。
  • DAO层泄漏数据库细节:比如业务层拿到了一个查询对象之后,还要自己拼接查询条件。这说明数据访问层面的抽象没有做好。

针对这一类问题,修复思路是逐步地把违反边界的代码"搬回正确的位置"。但这个过程中有个心理障碍:人总是觉得"现在能跑就别动它",害怕改了以后出问题。我建议用服务分层检查规则+小步重构双管齐下——先通过自动检查工具列出越界清单,然后按照影响面从低到高的顺序,每次只拆一个边界违规点,配合测试保护,渐进式地回归健康状态。

另外一个很重要的点:边界不应该只存在于代码层,也要存在于组织协作方式上。如果两个模块的代码是独立的,但是负责人是同一拨人、没有明确的接口评审机制,那边界也会在"快速改一下"的冲动中慢慢消失。所以团队里要有人对边界的存在负责,一般来说是架构师或核心开发者,他们的职责就是确保系统的边界在设计意图中存续。

5. 变更流程、测试体系与重构的日常化

5.1 可维护性的真义:让变更变得安全

前面讲的内容都围绕"系统的结构状态",但可维护性还有另一半来自"系统的演进过程"。一个不存在变化的系统是不需要谈可维护性的——恰恰是因为市场需求不断变化、业务规则持续演进,我们才需要维护。所以可维护性的本质能力是"在不破坏既有功能的前提下,安全、高效地实施变更"。

这里有一个常见的认知误区:以为可维护性是靠"代码写规范"就能得到的,忽略了变更流程和测试体系同样起着决定性作用。试想一个模块的抽象设计得很漂亮,但没有任何测试保护,每次改动都靠人肉验证——当模块越来越大时,还有多少人敢改它?改动风险一高,开发者就会倾向于"绕过重构、复制粘贴补丁",系统就开始加速腐化。

5.2 测试金字塔在维护阶段的价值

测试体系是变更安全最可靠的保障。不同层级的测试价值差异很大,合理分配测试资金的逻辑是:

测试层级覆盖目标执行速度维护成本建议占比
单元测试单个类/函数的逻辑正确性毫秒级低70%左右
接口/组件测试模块间通信与集成协议秒级中20%左右
端到端测试核心用户主流程分钟级高最多10%

很多团队的测试体系是倒金字塔:大量的端到端测试挂在UI上,跑一次要半小时还各种不稳定,单元测试却寥寥无几。这种测试结构带来的问题是:想快速确认一次改动没有炸到相关模块,根本没有可靠的轻量级手段,只能依赖重型端到端测试,而重型测试不稳定,又让大家不愿意跑,形成了恶性循环。

我的实践建议是:优先给核心业务模块补齐单元测试,尤其是那些包含复杂分支判断、状态流转、计算逻辑的纯函数部分。测试的核心价值不完全在于"证明代码是对的",更在于给后续重构者一张安全网。有了这张网,修改代码时才发现"原来这个隐藏的分支逻辑是有人依赖的"。

5.3 重构不是项目,而是日常习惯

重构这件事最大的误区是把它当成一个"专项项目"来做——比如设定一个技术债清零月,集中一段时间大改特改。这种做法在多数情况下都会中途夭折,因为它带来巨大的回归风险,还难以在业务迭代的压力下保住优先级。

真正可持续的重构方式是小步、持续、日常化:在每次给某个模块新增需求时,顺手把附近的烂代码理一下;在每次bug修复时,遇到混淆的命名就顺手改名;在每次代码评审时,发现重复逻辑就顺手抽取。这些操作单个看都很小,但经过一年积累,系统的可维护性会得到质的提升。

小步重构有两条铁律:

  • 重构和功能变更不能混在同一个提交里:每一次代码变更应该只有一个目的,是"改行为"还是"改结构"。混在一起,出问题时定位的范围就变大了。
  • 重构必须依赖测试保护:动结构之前,先确认这个模块有足够测试覆盖,或者先为它补上关键路径的测试,然后才开始动手。

5.4 代码评审与文档地图

可维护性设计还需要两件"软性"基础设施:有效的代码评审和不被忽视的文档。

代码评审的价值不只是找bug,更是在团队层面传递设计标准和知识。一份好的代码评审意见应该关注的是:这个改动的复杂度是否合理、有没有考虑边界场景、是否符合模块划分的约定,而不是逐行挑语法毛病。团队里应该有一个"评审清单"帮助新成员知道在评审时看什么。

文档方面,我比较反感那种写完就没人看的几万字需求说明书,但有一点非常重要:系统的架构决策和模块地图必须被记录,且保持更新。每个模块是干什么的、边界在哪里、和其他模块有什么关系、当前有哪些关键设计和遗留问题,这些“地图”信息对于新人接手和后续演进是不可或缺的。实践中最好的载体不是Wiki里的大文章,而是仓库里的ADR(架构决策记录)和模块的README——它离代码近、修改路径短,更容易被维护保持陈旧。

6. 从"能跑就行"到"改得动":一个核心模块的演进复盘

6.1 案例背景:优惠计算模块的混沌起点

前面讲了那么多原则和方法,最终还要落到一次实际的演进过程复盘里,才更有说服力。我有一个做过多次技术咨询的朋友,他给我讲过一个客户某电商平台的优惠计算模块的例子,我觉得非常典型,特此转述。

最初的优惠计算模块是这样实现的:所有优惠逻辑全部堆在一个名叫CouponService的大类里,里面有一个近两千行的方法叫calculate。这个方法接收订单快照和一堆优惠码参数,内部通过连续的if-else按优先级逐条判断每个优惠是否命中,命中后直接修改订单总价和优惠明细列表。

对应的测试寥寥无几,唯一还算完整的是两个端到端的用例,跑一次要等十几分钟。产品需求一来,"再增加一种新的优惠组合规则"这样一个看似很小的变更,开发同学要花两三天去梳理这段千行代码里的执行顺序和状态修改,且每次改动都会引入新的bug,最常见的bug就是某个优惠在特定组合下被错误地叠加。

这个模块就是可维护性设计的反面教材:复杂度集中在一个大方法里,无法拆分测试;优惠规则彼此耦合,新增规则要理解所有已有规则;没有状态模型,优惠计算过程对调用方是一个黑盒。

6.2 演进方案:四步拆解

演进过程被设计成四个步骤,每一步都是小步可交付的:

第一步:搭建测试保护网。不急着改代码,而是先为现有的核心优惠场景写单元测试。凡是能直接从calculate方法中剥离出来的纯函数,先单独测;复杂的组合场景用构造好的订单和优惠码快照来做参数化测试。这一步的目的很明确:在改动结构之前,先建立“改坏了立刻能发现”的回归保障。

第二步:引入领域对象。把订单和优惠相关的数据结构从简单的Map和List重组为领域对象——比如定义Order、Promotion、DiscountLine,每个对象携带自己的行为方法。这一步先把散落在千行方法里的"数据操作"收拢到对象内部,让数据变化范围局部化,为后续拆分逻辑做准备。

第三步:拆分规则策略。把"每个优惠规则"抽象为独立的RuleHandler,每个类只负责一种优惠的计算逻辑。一个中央调度器负责按照优惠优先级依次调用这些RuleHandler,并将结果合并。每个规则类的单元测试从原来的"千合一测"变成了"每规则单独测",定位问题的范围大幅缩小。

第四步:显式状态流转。优惠计算的执行过程不再是一个隐形的顺序堆叠,而是通过一个可观测的上下文对象来传递订单状态和优惠明细,每一步规则都从上下文读取输入、写入结果。这个对象还会记录执行轨迹,方便排查"为什么这个订单没有享受某优惠"之类的问题。

6.3 演进之后的可维护性对比

演进完成后,同一个模块的指标发生了非常明显的变化:

  • 新增一种优惠规则:从原来的2~3天缩短到半天,而且不再需要理解所有既有规则,只需实现一个新的RuleHandler并在注册表里登记优先级即可。
  • 定位线上优惠计算异常:从靠人肉写SQL验证订单金额,升级为直接查看规则执行轨迹里哪一步产生了非预期结果。
  • 单元测试数量:从个位数增长到每个规则至少覆盖正常、边界、异常三类用例,总计上百个用例,且执行时间从端到端的十几分钟压缩到秒级。
  • bug引入率:重构完成后连续两个迭代没有收到相关优惠计算的回归缺陷。

案例最值得学习的不是那四个步骤本身,而是它的推进策略:每一步都不做大爆炸式重写,每一步结束系统都能照常上线,每一步都让下一个改动变得更安全。这种节奏,才是在真实业务压力下可持续改进的正确姿态。

6.4 演进过程中的三个实际教训

这个项目也不是一帆风顺的,朋友给我分享了三个实际教训,我觉得很有普适性:

第一个教训是,测试保护网一定要在重构动手之前建好。他们一开始没忍住,想先抽一个方法出来,结果抽到一半发现原有逻辑里有个隐藏的来源不明的分支,导致行为对不上,最后不得不回滚。后来老老实实先花时间补测试,后面的大动作反而很顺利。

第二个教训是,不要企图一次把所有规则都策略化。最初设计时想全部推翻,但有些老规则只有线上环境才可能复现,很难在测试里构造出完全一致的历史数据。后来他们决定只把还在活跃使用、还有变更需求的规则纳入新框架,对几个遗留规则使用适配器模式过渡。这样既推进了重构,又没有因为处理长尾而拖垮全局。

第三个教训是,边界保护要坚持到演进完成之后。项目刚重构完,团队处于成就感爆棚的状态,结果很快有人在调度器里图省事,直接放了一个特殊规则类来绕过通用策略。这个"例外"很快又吸引了第二个例外,眼看又要回到混沌状态。后来经过一次代码评审的纠正,把这条规则"规范正式化"——把一个快速补丁转化成了合规的策略实现,这才保住了边界。

这个复盘有力地说明了一点:可维护性从来不是一次性完成的,它是一个需要持续维护和守卫的状态。代码本身再精美,如果团队没有意识去维持它,它依然会在半年后重新变成丛林。

说实话,可维护性设计这条路,真正难的不是学会那些原则和模式,而是对抗人性里"图快、图省事、先跑再说"的本能冲动。我自己踩过的坑、见过的烂摊子够多了,现在的体会是:写代码时多想五分钟边界怎么划、接口怎么定、日志怎么留、测试怎么补,看起来是在"浪费"时间,实则是在给未来的自己买入场券。你不需要一次做到满分,但每一次提交,都值得让系统比之前稍微好维护一点点。长期积累下来,你就会拥有一个"越改越轻松"的系统和一支敢动手、不怕改的团队——这大概就是工程师最有成就感的状态了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 7:22:04

YOLOv8重复检测排查:NMS与conf/iou阈值调参实战指南

重复检测这个问题,我真是踩过太多次了。训练好的YOLOv8模型,跑测试图一看,目标本身没漏检,但同一个目标硬生生被框出来三四个框,每个框还都带个差不多的置信度,看起来就像目标周围长了一层框。老手一看就知…

作者头像 李华
网站建设 2026/10/20 7:21:33

从零搭建书籍分享系统:轻量级去中心化图书流转的实操指南

1. 从零搭建一个书籍分享空间:为什么我选择自己动手很多人第一次听到“书籍分享”这四个字,脑子里浮现的可能是拉个群、丢几本电子书进去,或者在公司茶水间放个书架让大家随手取阅。我一开始也是这么想的,直到真正动手做起来才发现…

作者头像 李华
网站建设 2026/10/20 7:21:22

VMware命令行操作实战:从vmrun到esxcli的虚拟机运维指南

晚上十一点接到电话,说虚拟机开机失败,直接卡在一个黑色控制台上。等我赶回机房,图形客户端连不上,网页管理端也转圈,最后是一行一行敲指令把虚拟机捞回来的。玩VMware这两年,类似的场面不止一次&#xff0…

作者头像 李华
网站建设 2026/10/20 7:21:13

PCIe配置空间详解:从ECAM到扩展能力链表的4KB世界

1. 系列进展与本文定位这一篇是PCIe配置空间系列的第五篇。前面几篇我们把PCI兼容配置空间(0x00~0x3F)里的Vendor ID、Device ID、Command、Status、Class Code、BAR等基础字段梳理了一遍,也讲了Capability结构怎么组织、MSI/MSI-X能力寄存器…

作者头像 李华
网站建设 2026/10/20 7:20:05

PhotoCraft:用AI重写设计软件全家桶的开源新星

如果说最近开源圈有什么事值得专门写一篇长文,那一定是 PhotoCraft。这个号称“用 AI 重写商业设计软件全家桶”的项目,GitHub 星标在几周内冲到了 3.87 万,讨论度从技术社区一路烧到了设计师论坛。我作为常年折腾图像处理工具链的开发者&…

作者头像 李华