技术不难,为什么项目却越改越不敢动?聊聊复杂系统开发该怎么想

「设计模式与范式」系列 Day05

写在前面

自动驾驶、图像识别、高性能消息队列------这些系统难,难在技术本身有门槛,靠算法和领域积累才能啃下来,不是多拉几个人就能提速的。但更多人日常在做的,是另一种难:技术上没什么新鲜的,业务逻辑却盘根错节,代码量一大、参与的人一多,改一个小需求都得小心翼翼,生怕牵一发动全身。这篇不聊前者,聊后者------面对这种"不难但复杂"的系统,到底该从哪几个角度想问题。


一、是什么:软件开发的两种难,以及应对复杂度的三个角度

软件开发的难度大体分两类:技术难度 指问题本身有硬核门槛,需要深的技术方案或算法支撑;复杂度指技术不难,但项目体量大、业务规则多、参与开发的人多。前者靠专业积累,属于细分领域知识,不是这篇的主题;后者才是绝大多数业务开发日常真正要面对的问题,也是这篇要拆解的对象。

面对复杂度,可以从三个角度思考:

flowchart LR subgraph 应对复杂度的三个角度 A[设计原则与思想 代码怎么组织] B[研发管理与开发技巧 团队怎么协作] C[Code Review 前两者怎么落地并传递下去] end A --> C B --> C

设计原则解决的是代码结构 层面的问题:怎么组织代码才容易理解、容易改;研发管理解决的是协作过程层面的问题:一群人怎么持续产出而不是靠个别人硬撑;Code Review 则是把这两层东西真正在团队里落地、传承下去的抓手------三者不是三份孤立的清单,而是环环相扣的。


二、为什么:单靠技术能力或者堆人,为什么搞不定复杂度

复杂度的麻烦之处在于,它不是某一个技术难点,而是无数个不难的小决策叠加出来的------这个模块该不该拆、这个类该依赖接口还是具体实现、这段逻辑该不该提前留扩展点。单点上随便怎么写都能跑起来,但缺了统一的设计原则,几百个这样的小决策堆在一起,系统就会自然演化成一团只有写代码的人自己看得懂、改一处影响一片的泥潭。

堆人也解决不了这个问题,甚至会让它更严重:人越多,如果没有编码规范、Code Review 这类机制去对齐大家的设计判断,代码风格和设计思路只会越来越分裂。这也是为什么应对复杂度必须同时抓"个人怎么写代码"(设计原则)和"团队怎么协作产出代码"(研发管理),少了任何一环,另一环都撑不住。


三、怎么用:三个角度具体落到哪些实践上

设计原则:7 条思路分别解决什么问题

设计原则 解决的问题
抽象与封装 隐藏实现细节,只暴露必要的接口,调用方不需要关心内部怎么实现
分层与模块化 把系统拆成职责清晰的层/模块,改一处不必牵连全局
基于接口通信 调用方依赖稳定契约而不是具体实现,实现可以自由替换
高内聚、松耦合 相关的逻辑聚在一起,不相关的模块之间尽量减少依赖
为扩展而设计 大概率会变化的地方提前留好扩展点,新增功能靠扩展而不是改动已有代码
KISS 原则 用最简单直接的方案解决问题,不为了显得灵活而人为制造复杂度
最小惊奇原则 代码的行为要符合调用方的直觉,不能藏着反常规的隐藏逻辑

挑两条最容易在真实代码里看出价值的,展开说:

基于接口通信 在 JDBC 里体现得很彻底:业务代码从头到尾只认 java.sql.Connectionjava.sql.Statement 这套接口,具体实现类是 MySQL、PostgreSQL 各自驱动包里的私有类,换一个数据库厂商,业务代码一行不用改,改的只是驱动依赖和连接串。这正是"调用方依赖稳定契约"带来的收益。

为扩展而设计 在 JDK 的 IO 流体系里也有真实样板:OutputStream 定义了基础写入行为,BufferedOutputStreamGZIPOutputStream 这些装饰器不修改原有类,而是包一层扩展出缓冲、压缩这些新能力:

java 复制代码
OutputStream out = new BufferedOutputStream(new FileOutputStream("data.txt"));

要新增一种能力(比如加密),写一个新的装饰器类包上去就行,不用改 FileOutputStream 或者已有的任何一个装饰器------这就是"靠扩展而不是修改"。

KISS 和"为扩展而设计"容易被误解成矛盾:一个要简单,一个要留扩展点。区别在于扩展点是不是有实打实的变化预期------支付渠道大概率会新增,值得提前抽象;一个从上线到现在从没变过、也看不出会变的逻辑,硬要包一层接口和策略类,就是 KISS 要反对的过度设计(这个度怎么把握,Day06 会专门展开)。最小惊奇原则则更朴素:一个叫 getUser() 的方法不该偷偷修改数据库,一个 equals() 不该抛异常------命名和行为对不上,是复杂度里最容易被忽视但最消耗排查成本的一种。

研发管理:让复杂度不靠个人硬撑

  • 编码规范要吹毛求疵地执行:统一的风格能省掉大量"这段代码为什么这么写"的沟通成本,规范只写在文档里没用,得真的靠 lint 工具和 Review 卡住。
  • 单元测试不是为了覆盖率好看:是给后续重构兜底------没有可信的测试,谁都不敢碰一段代码,复杂度只会原地固化。
  • 文档先行:复杂需求先把设计想清楚写下来,比代码写到一半才发现方向错了要便宜得多。
  • 持续重构:复杂度会随时间自然增长,没有持续的小步重构,"等有空了再重构"基本等于永远不会重构。
  • 对项目和团队做拆分:单个系统和团队大到一定规模后,靠流程和自律压不住协作成本,直接按业务边界拆分往往比死磕流程更有效------这也是康威定律的体现,系统架构最终会趋同于团队的沟通结构。

Code Review:把前两者真正落地、传递下去

Code Review 的价值远不止"找 bug"这一层:

  • 避免单点依赖:一段代码只有一个人看得懂,这个人一休假、一离职,系统就没人敢碰,Review 保证代码至少不止一个人熟悉。
  • 传帮带:对新人来说,Review 是最快理解团队代码风格和业务背景的渠道,比看文档更直接。
  • 反向提升代码质量:知道自己的代码要被别人看到,写的时候自然会多想一步"这样写别人看得懂吗",可读性和自律性是被 Review 倒逼出来的,不是自觉养成的。
  • 打造技术氛围:把"代码质量好不好"从个人对自己代码的责任,变成团队共同维护的东西,也摒弃了"这段代码只有我能改"的个人英雄主义。

四、面试追问

Q1:软件开发的技术难度和复杂度有什么区别?为什么这篇只聊复杂度?

技术难度指问题本身有硬核门槛,需要较深的算法或专业方案支撑,不是多拉人就能加速的,比如自动驾驶、高性能消息队列;复杂度指技术本身不难,但业务规则多、代码量大、参与开发的人多,典型如企业信息系统。业务开发日常遇到的绝大多数挑战属于后者,所以这篇讨论的是应对复杂度的方法论,不涉及细分领域的技术深水区。

Q2:应对复杂系统可以从哪三个角度思考?它们分别解决什么层面的问题?

设计原则与思想解决代码结构层面的问题,即怎么组织代码使其易懂易扩展;研发管理与开发技巧解决协作过程层面的问题,即一群人怎么持续产出高质量代码而不是靠个人硬撑;Code Review 是把前两者在团队里落地并传递下去的抓手。三者环环相扣,不是三份孤立清单。

Q3:KISS 原则和为扩展而设计,听起来一个要简单一个要留扩展点,是不是矛盾的?

不矛盾,区别在于扩展点是不是有实打实的变化预期。为扩展而设计针对大概率会变化的地方,比如支付渠道会新增,值得提前抽象;KISS 反对的是没有实际需求支撑、纯粹为了显得灵活而堆的抽象层,这种过度设计往往比它要解决的问题更复杂。

Q4:为什么说 Code Review 不只是找 bug 的手段?

找 bug 只是最表层的价值。更重要的是它能避免代码只有一个人熟悉的单点依赖问题、是新人快速上手团队风格和业务背景的传帮带渠道,也会倒逼作者写代码时多想一层可读性------这些累积起来对团队的意义比挑出几个 bug 大得多。

Q5:如果团队规模和代码量已经大到光靠流程压不住协作成本了,还能做什么?

从组织结构上拆分项目和团队,而不是继续在流程上打补丁。这也是康威定律的体现------系统架构最终会趋同于团队的沟通结构,与其让一个大团队靠更严格的流程勉强协调一个大而全的系统,不如按业务边界把系统和团队一起拆开,拆分本身就能降低协作成本。


下一篇预告

Day06 如何避免过度设计和设计不足:怎么找到那个平衡点。

相关推荐
geovindu1 小时前
rust: Factory Method Pattern
开发语言·设计模式·rust·工厂方法模式·创建型模式
一木 之林1 天前
第 8 节 C++ 设计模式在 AI 推理 SDK 和 Agent 工具运行时中如何落地
c++·人工智能·设计模式
Zane19941 天前
MVC 三层架构被打上反模式标签?一次账户扣款需求,讲清楚贫血模型和充血模型该怎么选
设计模式
geovindu2 天前
rust:Builder Pattern
开发语言·设计模式·rust·建造者模式
小王师傅662 天前
【设计模式】装饰模式(三):从装饰模式看 Java 与面向对象设计(原理篇)
设计模式
Zane19942 天前
从一次支付渠道扩展需求,看懂"多用组合少用继承"到底在说什么
设计模式
geovindu2 天前
CSharp:Condition Variable Pattern
后端·设计模式·c#·.net·.netcore·条件变量模式·同步型模式
geovindu2 天前
sql: Data Modeling Patterns using mysql
sql·mysql·设计模式·数据库开发
sarasuki3 天前
失败重试:Agent 中指数退避的正确姿势
人工智能·设计模式·agent