0%

软件工程 roadmap:概念、方法、边界和反模式

1. 软件工程是什么

1.1 本质追问

软件工程不是「写代码」,也不是「写文档」。它的核心命题是:

如何在不确定性(需求会变、人员流动、技术演进)中,让一群人持续产出可维护、可交付的软件。

这个定义里有三个关键词:

  • 不确定性:不是已知路径的复现,而是应对变化的能力
  • 一群人:协作规模是关键变量。一个天才能搞定的事,十个天才不一定能搞定
  • 持续:做出来不算完,还要持续交付、持续维护

1.2 软件 vs 传统工程的根本差异

  • 物理边界:传统工程里重力固定不变,软件的需求、技术和业务全在变。
  • 材料成本:钢铁价格可预测,软件里时间是最贵的资源,而且无法计量。
  • 成品复用:零件可以互换,代码复用要带上上下文。
  • 试错成本:桥塌了才知道,bug 可能在生产环境才暴露。
  • 复杂性来源:传统工程受材料、载荷和环境约束。软件的复杂性来自模块之间的交互,n 个模块两两之间就有 n(n-1)/2 条接口,模块越多,需要维护的关系增长越快。

结论:软件工程不能照搬传统工程的方法论,必须承认软件的根本属性:可修改性、复杂性、无惯性。

1.3 为什么「工程化」不是银弹

场景适合工程化?原因
1-2 人 MVP❌ 不适合沟通成本 > 工程收益
3-10 人团队✅ Scrum/Kanban需要节奏和可见性
10-100 人团队✅+架构治理需要接口契约、代码规范、CI/CD
100+ 人团队✅+组织工程Conway 定律:组织结构决定系统结构
探索性项目(AI 实验)❌ 不适合方向不确定,流程会拖慢创新

关键原则:工程化的「度」取决于团队规模 × 项目风险。


2. 金字塔结构:从战略到战术

1
2
3
4
5
6
7
8
9
10
11
12
13
┌──────────────────────────────┐
│ 战略层:治理 │ 组织、流程选择、技术决策框架
│ (Governance & Strategy) │
├──────────────────────────────┤
│ 战术层:工程方法 │ 架构、设计模式、测试策略、发布策略
│ (Tactics & Methodologies) │
├──────────────────────────────┤
│ 基础层:工程纪律 │ 代码规范、审查、CI/CD、Git 工作流
│ (Discipline & Practices) │
├──────────────────────────────┤
│ 底层:个人能力 │ 抽象力、调试、读代码能力
│ (Individual Capability) │
└──────────────────────────────┘

底层地基不稳时,往上建任何架构都是空中楼阁。但只守底层不往上走,永远停留在「好码农」。


3. 需求工程

3.1 需求的层级

1
2
3
4
愿景(Vision)
→ 用户故事 / 用例(User Story / Use Case)
→ 需求规格(SRS / 验收标准)
→ 技术方案(设计文档)

每一层的转换都有信息损失,需求工程的核心就是「控制损失」。

3.2 常见陷阱

陷阱 1:把解决方案当需求

  • 用户说:「我要一个导出按钮」
  • 真正需求:「我需要把数据拿出去」
  • 更深层需求:「我需要向财务证明销售额」
  • 正确做:追问三层,找到根本问题
  • 反模式:用户要什么就给什么

陷阱 2:验收标准模糊

  • 错误:「性能要好」
  • 正确:「首页加载 ≤ 2s(P95)」
  • 高阶标准:用数据说话。没有可测量的要求,等于没有要求。

陷阱 3:假设共享上下文

  • 「你们之前说的都理解了吧?」
  • 结果:产品经理理解的「登录」和工程师理解的「登录」不是同一个东西
  • 正确做法:接口文档写清楚输入输出前提条件,口头对齐只是补充

3.3 实用方法

方法适用场景产出物
User Story + AC敏捷迭代故事卡片、验收条件
用例图复杂业务流程角色-用例关系图
数据字典系统交互字段级定义
状态机图有状态系统状态流转图
契约测试 (Test-Driven)团队并行开发接口 Mock + 测试

3.4 高阶工程师的判断

什么时候可以少写文档?: 当沟通成本 < 文档维护成本时。

判断标准:

  • 需求变更频率高 → 文档跟不上 → 不要写长篇文档
  • 团队规模大 → 新人多 → 文档是必需品
  • 系统复杂度低 → 脑内建模即可 → 用注释+简单图表
  • 系统复杂度极高(微服务、分布式)→ 架构决策文档(ADR)是刚需

4. 架构设计

4.1 架构的核心问题

架构不是「选哪个框架」,而是回答:

  1. 组件如何划分?(边界在哪里)
  2. 组件如何通信?(接口契约是什么)
  3. 哪些决策不可逆?(技术选型、数据模型选型)

4.2 架构模式速查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
单体(Monolith)
→ 简单部署、开发快
→ 瓶颈:协作规模、部署频率
→ 适合:团队 < 20 人,一次发布能装下所有人的改动

模块化单体(Modular Monolith)
→ 模块内聚合,模块间清晰
→ 适合:从单体到微服务的中间态

微服务(Microservices)
→ 独立部署、独立扩展
→ 代价:分布式复杂性、跨服务事务、运维负担
→ 适合:团队 > 30 人,多个团队要按各自的节奏高频发布

事件驱动(Event-Driven)
→ 解耦、可扩展
→ 代价:数据一致性难度陡增
→ 适合:异步场景(订单、通知、审计)

4.3 用 ADR 记录决策

为什么需要:项目中期离职的人,六个月后回来会问「为什么当初不选 X?」

ADR 模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# ADR-XX: [标题]

## 背景
上下文、约束

## 决策
我们选择了 X,原因:
- ...
- ...

## 权衡
选了 X,放弃了 Y:
- 好处:...
- 代价:...

## 后续
- 如果未来需要换回 Y,成本评估:...

高阶实践:

  • 只记「不可逆」的决策,可回退的不记
  • 决策的时效性要写:「当前条件下最优,条件变了可重新评估」
  • ADR 数量 > 技术实现是正常信号

4.4 非功能需求:架构质量属性

每个系统都需要回答这些「怎么衡量」的问题:

  • 可用性:能承受多久故障。指标是 99.9%,折算下来每月约 43 分钟。
  • 性能:延迟上限和吞吐量。指标是 P99 延迟和 QPS。
  • 一致性:允许多长的不一致窗口。最终一致还是强一致。
  • 可扩展性:10 倍流量怎么扛。水平扩展还是垂直扩展。
  • 可维护性:改一个字段要动几个服务。指标是改动影响面。
  • 安全性:攻击面在哪。对照 OWASP Top 10。

高阶判断:不要追求所有属性都最优。性能 vs 一致性是经典 trade-off。选一个主要指标,其余设底线。


5. 编码实践

5.1 可维护性的核心指标

代码被读的时间远多于被写的时间。给别人看的代码才是好代码。

评估「好代码」的六个维度:

  • 可读性:60 天后的自己看得懂吗。读一段代码,说不清逻辑就是不合格。
  • 可测试性:不跑代码能测吗。看单元测试覆盖率和测试是否脆弱。
  • 可重构性:改一行要动几处。用 git blame 加搜索量。
  • 可组合性:能当依赖包给别人用吗。看接口是否稳定。
  • 可调试性:出问题时能看到什么。看日志、追踪、指标。
  • 可演进性:加需求要改核心还是新增。看依赖方向是否单向。

5.2 Code Review:从走流程到真有用

Reviewer 该看的(按优先级):

  1. 设计意图(30% 时间):这是对的方案吗?有更好方案吗?
  2. 边界条件(25% 时间):空值?超时?并发?幂等性?
  3. 安全性(20% 时间):注入?越权?敏感数据?
  4. 可测试性(15% 时间):这段能写单测吗?
  5. 命名/格式(10% 时间):只标注不修改,工具能解决的事别花人脑

作者该做的:

  • 提交前自己过一遍(self-review checklist)
  • PR 描述写清楚为什么而不是做了什么
  • 大 PR = 坏 PR。> 400 行应该拆。
  • 把评审成本当设计成本考虑

高阶实践:

  • 小型团队(< 10 人)做 Pair Programming 比 Code Review 更高效
  • 新人多的团队,Code Review 是最高效的学习机制
  • 成熟团队,Automated Lint + 轻量 Review 就够了

5.3 技术债的经济学

技术债不是「坏事」,欠债是理性决策。关键是要「有意识地欠」:

债务类型特征怎么处理
故意债明知不完美但先上线写 TODO,排进迭代
无意识债没想清楚就做了重构,补设计
高利贷债无法定位的坏代码隔离、包装、逐步替换

判断标准:

  • 技术债的利息 = 维持成本 × 时间
  • 还债的代价 = 重构人力
  • 借债比还债便宜 → 借
  • 利息超过还债代价 → 还

5.4 注释哲学

应该写不该写
解释了「为什么」解释了「是什么」
解释了边界条件写了显而易见的逻辑
决策依据(来自 PR 评审)复述代码

高阶原则:代码自解释了 80% 的事。好的变量名、函数名、模块结构能消除 80% 的注释需求。注释留给那 20% 的隐含信息。


6. 测试策略

6.1 测试金字塔的现实修正

1
2
3
4
5
6
7
      /  \
/ E2E \ 实际:E2E 测试应该少
/ \
/ 集成 \ 实际:集成测试最多
/___________\
/ 单元测试 \ 实际:按需写,不强制
/_______________\

现实版测试金字塔(高阶工程师的认知):

1
2
3
4
5
E2E 测试(少而精,覆盖关键流程)
↓
集成测试 / 契约测试(最多,确保组件交互正确)
↓
单元测试(按需,适合纯函数/复杂逻辑)

6.2 各层测试的评判标准

层级测什么谁写怎么评判
单元测试纯逻辑、边界条件开发覆盖率是参考值,不是目标
集成测试接口交互、事务边界开发测的是「协作模式」
契约测试API 向后兼容跨团队防止第三方依赖断裂
E2E 测试核心用户流程QA/SDET覆盖 top-5 流程即可
性能测试瓶颈点SRE/开发场景要贴近真实负载

6.3 测试的反模式

  • 过度单测:为覆盖率写测试 → 测试变成代码的附属品
  • 脆弱的测试:测试比代码还经常失败 → 测试变成了噪音
  • 只有 Happy Path:只测了正常流程 → bug 在异常路径
  • 测试即实现:测试改不动、不好写 → 代码耦合太紧

高阶信号:如果写测试比写代码还痛苦,说明代码有问题,不是测试的问题。


7. 发布与运维

7.1 发布策略演进

策略适用场景回滚时间风险
定时发布传统开发模式小时级高(问题集中暴露)
灰度发布互联网产品分钟级中
蓝绿部署状态可清除一键较低
Canary数据敏感型渐进低
Feature Flag + 暗部署前端/配置变更开关切换最低

7.2 DevOps 的真相

DevOps 不是「运维写 Dockerfile」,而是消除开发运维之间的墙。

关键度量:

  • 变更前置时间:从 commit 到生产,多久?
  • 部署频率:每天几次?(高频 = 每个变更更小 = 风险更低)
  • 变更失败率:发布后出问题的比例
  • 恢复时间:P0 故障多久恢复?

高阶实践:

  • 如果一个团队部署一次要 2 小时准备,说明流程有问题
  • 理想状态:任何代码变更,都能在 30 分钟内自动发布到生产
  • 但自动部署 ≠ 自动信任。需要门禁:测试覆盖率、安全扫描、合规检查

7.3 SRE 核心概念

1
2
3
SLI: 用户在意的东西(延迟、错误率、可用性),是量出来的值
SLO: SLI 的目标值,给内部团队的承诺目标
SLA: 给用户的合同目标(达不到要赔钱)

错用代价:SLA 定了但做不到 → 违约;SLA 定了做不到还不管 → 信任破产。

高阶判断:

  • 错误预算策略:有预算 → 敢发布;预算耗尽 → 冻结变更
  • 不要把 SLO 设成 100%。100% 意味着零容错,最后只能不发版
  • 故障复盘(Post-mortem)的核心是「下次怎么避免」,不是「谁的责任」

8. 项目管理

8.1 估算的真相

为什么估算总是偏?

  1. 只估了「写代码」的时间,没估「沟通/评审/部署」的时间
  2. 不确定因素被当成确定因素处理了
  3. 锚定效应:先给一个数字,后面全往这个方向调

实用估算方法:

方法精度适用阶段
经验类比±30%概念阶段
分解估算±15%方案阶段
蒙特卡洛模拟±10%需要概率分布时

高阶实践:用置信区间代替「确切日期」。

  • 「9/1 能上线」 → 错
  • 「95% 把握 8/25 到 9/10 上线」 → 对

8.2 进度管理:从「按时交付」到「及时止损」

高级工程师的价值不仅在于「能做到」,还在于知道什么时候该停下。

判断指标:

  • 连续 2 个迭代没交付承诺 → 计划不切实际
  • 预估工作量 > 实际 2 倍以上 → 技术债爆发
  • 需求变更 > 30% → 需求不稳定,需要重新对齐

8.3 技术决策的框架

做技术决策不是「谁对」,而是「在什么条件下对」。

框架:

1
2
3
4
约束条件(成本/时间/团队能力)
→ 备选方案(优缺点对比)
→ 选择标准(不是"最好",是"最适合")
→ 决策理由(ADR 记录)

高阶实践:决策框架比结论重要。十年后这个决策会被推翻,但框架会一直在。


9. 组织与 Conway 定律

9.1 Conway 定律的现代版

设计系统的组织,产生的系统架构等于组织的沟通结构。

翻译:如果你想解耦系统,先解耦团队。

反直觉事实:

  • 微服务不是「解决架构问题的方案」,通常是「组织问题的技术映射」
  • 团队拆分不好 → 服务拆分一定不好 → 分布式复杂度飙升
  • 团队重组的成本 < 系统重构的成本(先动组织)

9.2 团队规模与架构的匹配

团队规模架构模式管理重点
1-3 人直接沟通不需要正式流程
4-8 人轻量流程(Kanban)可见性 > 纪律
9-15 人Scrum节奏感 > 流程精确
16-50 人敏捷+架构治理接口契约 > 代码质量
50+ 人领域驱动限界上下文 > 技术栈统一

9.3 技术债与团队规模的关系

1
团队规模 × 技术债 = 系统熵增速度
  • 小团队(< 5 人):技术债增长慢,可以容忍
  • 中型团队(5-20 人):技术债需要定期偿还,否则交付速度断崖下降
  • 大团队(> 20 人):技术债是系统性问题,需要制度性应对(重构日、技术债迭代)

10. 个人知识体系图谱

10.1 进阶路径

1
2
3
4
5
6
高阶工程师(当前)
→ 领域驱动设计(DDD)
→ 分布式系统集成经验
→ SRE/SLO实践
→ 技术团队管理
→ 架构评审与治理

10.2 知识地图

必须掌握的(不能跳过):

  • 设计模式(GoF 23 种中的前 10 个)
  • 领域驱动设计(战略设计 > 战术设计)
  • 分布式系统概念(CAP、一致性、事务、服务发现)
  • 测试策略(分层测试的设计哲学)
  • CI/CD pipeline 设计
  • ADR 实践
  • SLO/SLI 工程实践

值得了解的(按项目需要):

  • C4 模型(架构可视化)
  • DDD 事件溯源
  • SLO 错误预算计算
  • Git commit 消息规范(conventional commits)
  • 架构评估方法(ATAM、SAAM)

选学(特定场景):

  • 形式化验证
  • 模型驱动架构(MDA)
  • 领域特定语言(DSL)
  • 量子计算对软件工程的影响

10.3 必读书单

按优先级排,P0 是现在就该读的,P3 是补视野的。

  • 《设计数据密集型应用》:分布式系统设计底层逻辑,P0
  • 《架构整洁之道》:架构设计的实用框架,P0
  • 《重构》(第 2 版):技术债的偿还策略,P1
  • 《SRE:Google 运维解密》:运维工程化方法论,P1
  • 《持续交付》:CI/CD 落地指南,P1
  • 《领域驱动设计》:DDD 战略和战术,P2
  • 《人月神话》:项目管理的经典教训,P2
  • 《架构师修炼之道》:从工程师到架构师的思维转换,P3

11. 核心取舍总结

11.1 工程师的永恒矛盾

矛盾对本质应对策略
速度 vs 质量短期交付 vs 长期维护用错误预算控制质量
规范 vs 灵活一致性 vs 适应性渐进式引入规范
文档 vs 代码可理解性 vs 实时性文档写「为什么」,代码写「做什么」
微服务 vs 单体独立 vs 简单先做好模块化单体,再拆分
技术债 vs 新功能还债 vs 变现技术债 = 贷款,必须有还款计划

11.2 一句话原则

  1. 工程化的目的是降低协作成本,不是增加流程。
  2. 架构的选择取决于团队,不是技术。
  3. 好的代码是可以被人理解的,而不是写得聪明。
  4. 测试金字塔的底座是「信任」,不是「覆盖率」。
  5. SLO 不是运维的事,是产品的事。
  6. 技术债要明码标价,不能隐形积累。
  7. Conway 定律是铁律:不改组织,改不了架构。

12. 参考

  • Mel Conway · How Do Committees Invent? · Conway 定律的原始论文(1968)
  • Google SRE · Embracing Risk · 错误预算的出处,以及把可用性定到 100% 的代价
  • Martin Fowler · TechnicalDebt · 技术债与利息的划分,故意债与无意识债的区分
  • Martin Fowler · TestPyramid · 测试金字塔的原始表述,文末也写了这套模型的局限
  • Michael Nygard · Documenting Architecture Decisions · ADR 的最初提案