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 | ┌──────────────────────────────┐ |
底层地基不稳时,往上建任何架构都是空中楼阁。但只守底层不往上走,永远停留在「好码农」。
3. 需求工程
3.1 需求的层级
1 | 愿景(Vision) |
每一层的转换都有信息损失,需求工程的核心就是「控制损失」。
3.2 常见陷阱
陷阱 1:把解决方案当需求
- 用户说:「我要一个导出按钮」
- 真正需求:「我需要把数据拿出去」
- 更深层需求:「我需要向财务证明销售额」
- 正确做:追问三层,找到根本问题
- 反模式:用户要什么就给什么
陷阱 2:验收标准模糊
- 错误:「性能要好」
- 正确:「首页加载 ≤ 2s(P95)」
- 高阶标准:用数据说话。没有可测量的要求,等于没有要求。
陷阱 3:假设共享上下文
- 「你们之前说的都理解了吧?」
- 结果:产品经理理解的「登录」和工程师理解的「登录」不是同一个东西
- 正确做法:接口文档写清楚输入输出前提条件,口头对齐只是补充
3.3 实用方法
| 方法 | 适用场景 | 产出物 |
|---|---|---|
| User Story + AC | 敏捷迭代 | 故事卡片、验收条件 |
| 用例图 | 复杂业务流程 | 角色-用例关系图 |
| 数据字典 | 系统交互 | 字段级定义 |
| 状态机图 | 有状态系统 | 状态流转图 |
| 契约测试 (Test-Driven) | 团队并行开发 | 接口 Mock + 测试 |
3.4 高阶工程师的判断
什么时候可以少写文档?: 当沟通成本 < 文档维护成本时。
判断标准:
- 需求变更频率高 → 文档跟不上 → 不要写长篇文档
- 团队规模大 → 新人多 → 文档是必需品
- 系统复杂度低 → 脑内建模即可 → 用注释+简单图表
- 系统复杂度极高(微服务、分布式)→ 架构决策文档(ADR)是刚需
4. 架构设计
4.1 架构的核心问题
架构不是「选哪个框架」,而是回答:
- 组件如何划分?(边界在哪里)
- 组件如何通信?(接口契约是什么)
- 哪些决策不可逆?(技术选型、数据模型选型)
4.2 架构模式速查
1 | 单体(Monolith) |
4.3 用 ADR 记录决策
为什么需要:项目中期离职的人,六个月后回来会问「为什么当初不选 X?」
ADR 模板:
1 | # ADR-XX: [标题] |
高阶实践:
- 只记「不可逆」的决策,可回退的不记
- 决策的时效性要写:「当前条件下最优,条件变了可重新评估」
- 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 该看的(按优先级):
- 设计意图(30% 时间):这是对的方案吗?有更好方案吗?
- 边界条件(25% 时间):空值?超时?并发?幂等性?
- 安全性(20% 时间):注入?越权?敏感数据?
- 可测试性(15% 时间):这段能写单测吗?
- 命名/格式(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 | / \ |
现实版测试金字塔(高阶工程师的认知):
1 | 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 | SLI: 用户在意的东西(延迟、错误率、可用性),是量出来的值 |
错用代价:SLA 定了但做不到 → 违约;SLA 定了做不到还不管 → 信任破产。
高阶判断:
- 错误预算策略:有预算 → 敢发布;预算耗尽 → 冻结变更
- 不要把 SLO 设成 100%。100% 意味着零容错,最后只能不发版
- 故障复盘(Post-mortem)的核心是「下次怎么避免」,不是「谁的责任」
8. 项目管理
8.1 估算的真相
为什么估算总是偏?
- 只估了「写代码」的时间,没估「沟通/评审/部署」的时间
- 不确定因素被当成确定因素处理了
- 锚定效应:先给一个数字,后面全往这个方向调
实用估算方法:
| 方法 | 精度 | 适用阶段 |
|---|---|---|
| 经验类比 | ±30% | 概念阶段 |
| 分解估算 | ±15% | 方案阶段 |
| 蒙特卡洛模拟 | ±10% | 需要概率分布时 |
高阶实践:用置信区间代替「确切日期」。
- 「9/1 能上线」 → 错
- 「95% 把握 8/25 到 9/10 上线」 → 对
8.2 进度管理:从「按时交付」到「及时止损」
高级工程师的价值不仅在于「能做到」,还在于知道什么时候该停下。
判断指标:
- 连续 2 个迭代没交付承诺 → 计划不切实际
- 预估工作量 > 实际 2 倍以上 → 技术债爆发
- 需求变更 > 30% → 需求不稳定,需要重新对齐
8.3 技术决策的框架
做技术决策不是「谁对」,而是「在什么条件下对」。
框架:
1 | 约束条件(成本/时间/团队能力) |
高阶实践:决策框架比结论重要。十年后这个决策会被推翻,但框架会一直在。
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 | 高阶工程师(当前) |
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 一句话原则
- 工程化的目的是降低协作成本,不是增加流程。
- 架构的选择取决于团队,不是技术。
- 好的代码是可以被人理解的,而不是写得聪明。
- 测试金字塔的底座是「信任」,不是「覆盖率」。
- SLO 不是运维的事,是产品的事。
- 技术债要明码标价,不能隐形积累。
- 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 的最初提案