BjarneStroustrup
关于软件工程可持续性和时间原则(Principle
Time)
的总结
“秘密是什么?”(What’s***
Secret?)
- 提示:没有万能方法(no
silver
bullet)
- 软件开发中不存在一劳永逸的方法。
- 真正的秘密在于理解时间与可持续性。
原则时间(Principle
Time)
核心思想
- 软件的预期寿命(expected
lifespan)决定设计和工程策略。
- 开发者需要问自己:这段代码会被使用多久?会被维护多久?
预期寿命范围示例
/>
| 预期寿命 | 典型项目 |
|---|---|
| 分钟 周 | 新手(Novices)的小型任务 |
| 月 年 | 初创企业(Startups)项目 |
年十年 | Google 等长期项目 |
/>
注:不同项目的时间尺度差别很大,工程策略也不同。
时间与经验
- 开发者经验决定能否应对长寿命项目。
- 如果项目寿命从分钟到十年:
- 经验不足的开发者可能只能处理短期项目(分钟、小时、天、周)。
- 对长期项目(年、十年)来说,可维护性、模块化和可扩展性要求更高。
可持续性目标(Sustainability)
- 软件工程的目标不是仅仅写完程序,而是让程序能够随时间安全演化。
- 可持续性意味着:
- 你可以安全地修改代码(change
safely)。
- 可以在预期寿命内调整需要调整的部分。
- 不同时间尺度有不同的要求:
- 短期(天/周):快速迭代、灵活修改。
- 中期(月/年):模块化、可测试、可扩展。
- 长期(多年/十年):良好的架构、文档、规范、团队经验传承。
- 你可以安全地修改代码(change
经验教训
- 很多开发者从未在长期可持续项目上工作过。
- 可持续性软件通常难以获得,需要计划和工程策略。
- 软件工程不仅仅是编程:
- 它是让程序在时间中保持可变性和可靠性的艺术。
总结要点
- 牢记代码预期寿命。
- 理解长期寿命项目难以规划且稀有。
- 可持续代码必须能够安全变化。
- 可持续性在不同时间尺度下意味着不同的策略。
小贴士
- 对短期项目,不必追求极致架构,但对长期项目必须考虑:
- 模块化设计
- 清晰接口
- 自动化测试
- 变更可控性
- 可持续性是经验和规划的结合,而非单靠技术手段。
原则
规模(Scale)
核心思想
- 随着时间推移,软件会不断变化并增长。
- 增长会带来问题:硬件、软件、人员资源都可能成为瓶颈。
- 问题来源:重复工作和资源消耗的非线性增长。
资源的尺度(Resources)
- 硬件资源(Hardware):
- CPU、RAM、磁盘、网络
- 软件资源(Software):
- 地址空间、端口、库、依赖
- 人力资源(Human):
- 开发者、测试人员、维护人员
随着项目增长,这些资源可能成为限制因素,需要提前设计可扩展的策略。
传统弃用(Traditional
Deprecation)
- 标记旧版本为过时(deprecated),引入新版本
- 这种方法简单,但如果每个人都还在用旧版本,会造成混乱。
- 强制更新,删除旧版本
- 设置最后期限,要求所有代码迁移。
- 一次性大规模重构
- 找一个勇敢的工程师,修改
API
及所有调用者,一次性提交。
- 风险很大,容易失败。
- 找一个勇敢的工程师,修改
更好的弃用策略(Better
Deprecation)
- 团队承担主要工作:
- 不依赖所有用户去做更新
- 让负责模块的团队主导迁移和重构
- 避免单点风险
可扩展性原则(Scaling
Principle)
- 在成功的组织中,所有重复性工作都应消耗子线性资源(sub-linear
resource)
- 尤其是人力资源
- 意思是:重复工作不应随着团队或项目线性增加,否则会快速失控。
案例:每周合并会议(WeeklyMerge
Meeting)
- 很多组织曾经需要定期开会讨论分支合并
- 问题:
- Git
分支较多时,长期分支合并风险大
- 频繁手动合并浪费人力
- Git
- 解决方案:
- Trunk-based
development(主干开发)
- 没有长期开发分支
- 不必选择在哪个分支提交
- 减少人为决策,提高可扩展性
- Trunk-based
时间与规模:提前左移(Time&
Left)
- “左移”(Shift-left)意味着:
- 尽早在开发流程中进行设计、测试、代码审查
- 提前发现问题,降低后期成本
- 流程示例:
- 设计(Design)
- 开发(Dev)
- 提交前测试(Presubmit
Test)
- 代码审查(Code
Review)
- 提交(Submit)
- 提交后测试(Post-submit
发布(小范围发布)
- 正式发布(Release)
关键要点
- 注意超线性成本(Superlinear
scaling
costs)
- 任何必须由人重复完成的工作,如果增长是线性或超线性,就会迅速成为瓶颈。
- 自动化和专家经验带来超线性收益
- 自动化测试、CI/CD、工具链可以让重复任务耗费大幅下降。
- 习惯做法可能无法扩展
- “正常”的手动操作在项目增长时会出现规模问题。
总结
- 原则
时间
关注代码寿命和可持续性 - 原则
规模
关注随时间增长的资源消耗和重复性工作 - 成功的长期项目需要:
- 可持续的代码设计
- 合理的团队工作方式
- 自动化与流程优化
原则
权衡(Tradeoffs)
核心思想
- 软件开发中总会存在冲突的目标和约束条件。
- 不存在“银弹”(Silver
Bullet)解决所有问题。
- 必须基于证据(Evidence-based)做决策,而非凭直觉或经验主义。
主要内容
- 基于证据做决策(Make
evidence-based
decisions)
- 不盲目遵循某种方法或工具
- 通过实际数据、性能测试、用户反馈、团队能力等因素做权衡
- 追求可持续性(Aim
for
sustainability)
- 决策应考虑长期影响
- 代码、架构、团队流程都应可持续维护
- 可持续性意味着:
- 代码能够安全地修改和扩展
- 团队能够长期高效运作
- 避免超线性增长(No
super-linear
scaling)
- 尤其针对人力资源(Human
resources)
- 如果重复工作随着团队或项目规模增长而呈超线性增加,就会迅速失控
- 所以选择方案时,需要考虑:
- 自动化工具
- 流程优化
- 减少人为重复工作
- 尤其针对人力资源(Human
- 根据情况重新评估(Re-evaluate
needed)
- 决策不是一劳永逸
- 随着需求、团队、技术或硬件环境的变化,需要重新权衡
- 例如:
- 某个架构在小团队时很好,但团队扩大后可能效率低下
- 某个算法适合小数据量,但大数据量时性能不足
总结思路
- 权衡原则告诉我们:
- 不存在完美方案
- 所有决策都有利弊
- 依赖数据和实际情况,而不是经验或习惯
- 考虑长期可持续性和可扩展性
- 持续观察和调整策略
/>小提示:
/>可以把三个原则结合起来理解:
- 时间(Time)→
代码生命周期长短
- 规模(Scale)→
项目增长和资源消耗
- 权衡(Tradeoffs)→
面对限制条件做出合理决策
GoogleSWE
的“秘密”
核心原则
- 软件工程不仅仅是编程,而是关于时间(Time)
- 代码的生命周期、维护成本、未来扩展都要考虑
- 时间因素影响设计决策:
- 短期优化
vs.
可持续发展
- 短期优化
- 关注规模(Scale)
- 当系统或团队增长时,要注意超线性成本(Super-linear
scaling)
- 超线性:工作量随着规模增长
>
线性增加(非常耗人力/资源)
- 超线性:工作量随着规模增长
- 专家/专才的贡献在其领域可能呈超线性增益
- 例如:某个架构专家优化算法
整个系统收益巨大
- 例如:某个架构专家优化算法
- 当系统或团队增长时,要注意超线性成本(Super-linear
- 基于证据做决策(Evidence-based
decisions)
- 不要凭个人权威或习惯下决定
- 决策要基于实际数据、测试结果或分析
- 随时间和情况变化,证据可能改变
必须不断重新评估
GoogleSWE
的支柱
- Pillars(支柱):上面提到的时间、规模、证据原则
- Culture(文化):快乐的开发者,协作高效的团队
- Policies
&
Processes(流程与政策)
:如何使开发顺畅、高效 - Tools(工具):选择适当技术和工具,降低重复劳动和复杂性
有趣的观点
- “Clever”
的区别:
- 如果别人说代码“聪明/巧妙(clever)”是称赞
纯编程
- 如果别人说代码“聪明/巧妙(clever)”是指责
软件工程
- 含义:编程要简单清晰,软件工程更注重可维护性和长期可持续性
- 如果别人说代码“聪明/巧妙(clever)”是称赞
ShiftingLeft(左移策略)
- 成本
vs.
Fidelity)
- Emerging
Truths(不断显现的事实)
- 早期发现问题(左移)
降低修复成本,提高开发效率
- Titus
Winters
强调:软件开发要把测试、设计、审查尽早嵌入开发流程
总结理解
- 时间:考虑代码长期影响
- 规模:注意系统增长带来的复杂性和成本
- 证据:基于数据而非权威做决策
- 文化和工具:支持开发者高效工作
- 左移思维:越早发现问题,修复成本越低
软件工程(SoftwareEngineering)定义和理解
软件工程(SoftwareEngineering)
定义与本质
- Dave
Parnas
定义
:“多个人开发多版本程序”(The
multi-person
programs)
- 强调团队协作
- 强调版本管理(程序不是一次写完就结束的)
- 说明软件工程区别于单人编程:复杂性、长期维护、协作是核心
- Russ
Cox
定义
:“当你在编程中加入时间和其他开发者时,软件工程就出现了”(Software
engineering
programmers)
- 时间(Time):
- 软件存在生命周期,随着时间推移需要维护、扩展
- 多人协作(O***r
programmers):
- 团队开发带来的沟通、接口、责任分工问题
- 与
Parnas
定义相呼应,但更强调实践角度
- 时间(Time):
编程vs.
软件工程
- 编程(Programming):
- 如果别人说代码“聪明/巧妙(clever)”是称赞
- 特点:解决问题、追求算法/逻辑巧妙性
- 注重单次功能实现
- 软件工程(Software
Engineering)
:- 如果别人说代码“聪明/巧妙(clever)”是指责
- 特点:可维护、可扩展、团队协作、长期可持续
- 注重系统性、规范、文档、协作
/>理解要点:
- 软件工程是“编程
+
团队协作”
的产物 - 目标是可持续、高质量、低维护成本的系统
- 巧妙代码不一定是好代码,可维护性和团队理解度更重要
图示化理解
你可以把这个理解画成一个层次图:
编程(Programming)加入时间
Engineering)
- 左下角:单人编程
- 右上角:团队协作
+
定律
Hyrum’s
API
有足够多的用户,无论你在文档中承诺什么,总有人会依赖你系统的所有可观察行为。
- 关键点:
- 不能只依赖文档承诺,代码行为必须谨慎
- 对外暴露的所有行为都可能被依赖
- API
的稳定性和向后兼容性非常重要
3⃣
Truth
Happen(变化是必然的)
可持续代码必须能够安全地修改所有应该修改的东西,以适应生命周期中的变化。
- 关键点:
- 软件不是一次写完就完事
- 可持续性要求代码能安全演进
- 设计时要考虑扩展性、维护性、模块化
4⃣
Truth
Left(提前介入,降低成本)
早期发现和处理问题比后期修复更便宜。
生命周期示意:
Design
Dev
Release}Design
Dev
Release
- Shift
Left
原则
:- 将安全性、缺陷检测、测试等活动尽可能提前到开发前期
- 提前介入(Shift
Left)
降低缺陷成本
- 例如:“Shift
Left
Security”:早期发现安全漏洞,而不是发布后修复
- 连续集成(Continuous
Integration,
CI)
:- CI
系统让每次提交都触发测试和告警
- 自动化保证Shift
Left
的实施 - 可视为持续预警系统(Alerting)
/>理解:
- CI
- Defect
Cost
Productivity
- 软件工程的目标是降低长期缺陷成本,而不仅仅是快速编程或短期乐趣
总结图示化
编程(Programming)
自动化保证持续预警
CI
Alerting(持续集成即告警)
1⃣
checks)
- Tests(测试):
- 用来验证与逻辑正确性相关的不变式
- 例如:对象状态、算法输出、业务规则
- 保证代码行为符合预期
- Monitoring(监控):
- 基于应用健康状态的不变式
- 例如:响应时间、CPU
使用率、请求错误率
- 预测系统是否会保持正常运行
- 告警类型:
- Paging/Alerting
因不合适条件触发
brittle(脆弱)
- 例如监控阈值设得不合理,容易产生误报
- Paging/Alerting
间歇触发
flaky(不稳定)
- 例如告警触发不稳定或没有明确原因
- 本质上可能监控的是同一个不变式
- 测试和监控的目标都是保证系统健康,只是形式不同
/>理解:CI
Monitoring
的核心是保持系统稳定,只是关注角度不同。
测试关注逻辑正确性,监控关注健康状态。
- 测试和监控的目标都是保证系统健康,只是形式不同
- Paging/Alerting
2⃣
的告警价值
- 最高价值的告警:
- 直接探测系统是否可用
- 例如:“网站是否在线?”
→ground-truth
- 其他告警:
- 间接指标,预测系统是否健康
- 例如
CPU/内存占用、请求延迟
- 预测网站是否会持续可用
/>理解:CI
不只是跑测试,更是一个持续健康检查
+
告警机制。
3⃣
的组成与告警层次
- Unittests(单元测试):
- 测试小粒度逻辑
- 可快速反馈代码逻辑是否正确
- 风险:flaky
brittle
(间歇失败或脆弱)
- End-to-end
tests(端到端测试)
:- 测试系统整体流程
- 保证
trunk
分支的稳定性
- 确保提交不会破坏整体功能
- Monitored
stats/metrics(监控指标)
:- 系统运行指标,如请求数、延迟、错误率
- 可以触发基于原因的告警
- End-to-end
probers(端到端探测器)
:- 自动化探测应用的真实可用性
- 模拟用户行为,保证网站可用
- Error
budgets(错误预算)
:- 系统允许的失败范围
- 超过预算则触发告警
/>理解:CI
+
测试反馈的组合,保证产品健康和稳定。
4⃣
优化目标
- “Product
fitness”
:- CI
和告警机制的最终目标是保证产品适用性和质量
- 不只是测试是否通过,而是确保系统整体健康
- CI
总结图示化
CI(Continuous
------------------------------------------------------
Tests
Fitness
Product
Fitness
Proxies(产品健康与代理指标)
1⃣
Product
的核心概念
- Product
Fitness
:衡量产品是否真正可用、可发布和可持续的指标- 不只是功能是否正确,还包括性能、稳定性、可用性
- 通过**代理指标(Proxies)**来近似衡量,例如:
- 单元测试覆盖率
- CI
构建是否通过
- 集成测试、端到端测试结果
- Canary
发布的健康指标
- 实时监控数据(延迟、错误率等)
/>理解:直接测量“产品是否适合用户使用”很难,使用代理指标代替,可以及时反馈问题。
2⃣
Product
流程与验证层次
从低成本、低保真到高成本、高保真的验证链条:
层次 内容 特点 Code Review(代码评审)
人工检查逻辑、风格、潜在问题 低成本、早期发现问题 Unit Tests
持续集成)
验证小粒度功能逻辑 自动化、快速、低成本 Integration Tests(集成测试)
测试模块间交互 保证模块协作正确 End-to-End Tests(端到端测试)
模拟用户行为 高保真、覆盖系统整体流程 Canary(金丝雀发布) 部分用户灰度发布 实时发现问题、限制风险 Monitoring(监控) 系统运行指标(延迟、错误率等) 高保真、持续反馈 End-to-end probes(端到端探测器)
自动化用户行为模拟 最接近真实用户体验 />
理解:每一层都是
Product
的代理指标,组合使用提高质量和可靠性。
3⃣
成本与保真度权衡
- 早期阶段(Design
Dev
Review)
:- 成本低
- 保真度低(仅验证逻辑、局部功能)
- 后期阶段(Submit
Post-submit
Release)
:- 成本高(运行复杂测试、灰度发布)
- 保真度高(接近真实用户体验)
/>Product
Fitness≈低成本测试+高保真测试+监控数据\text{Product
Fitness}
\text{监控数据}Product
Fitness≈低成本测试+高保真测试+监控数据
/>理解:CI/测试流程要平衡成本
vs.
保真度,早期用低成本、快速反馈,后期用高保真、实测数据保证质量。
4⃣
SRE
Budgets(错误预算)
- Error
Budget(错误预算)
:- 定义系统允许的失败范围(如可接受的
99.9%
可用率)
- 超过预算则必须触发警报或暂停发布
- 定义系统允许的失败范围(如可接受的
- 结合
Product
流程
:- CI/单元测试、集成测试
持续追踪健康指标
/>理解:通过
Error
和代理指标,可以让工程团队持续优化产品健康,而非仅依赖单次测试或经验。
- CI/单元测试、集成测试
5⃣
流程可视化示意
低成本低保真
---------------------------------------------------
Design
代理指标
总结:
- Product
Fitness
是对产品健康的整体衡量
- 通过多层次的代理指标(测试、CI、Canary、监控)逐步验证
- 早期低成本检测,后期高保真验证
- Error
Budget
用于量化可接受风险
Truth
#3:
Left(左移原则)
1⃣
核心概念
- Shifting
Left(左移)
:/>在软件工程流程中,把验证、测试、质量保证等步骤尽可能提前(靠近设计和开发阶段)
- 目标:以更低成本发现问题
- 原理:早期发现问题比后期修复便宜得多
- 本质是风险/成本与验证保真度的权衡
/>成本∼问题发现的时间\text{成本}
\sim
\text{问题发现的时间}成本∼问题发现的时间
/>风险∼问题越晚发现越严重\text{风险}
\sim
\text{问题越晚发现越严重}风险∼问题越晚发现越严重
2⃣
工程流程与通信
- SWE(软件工程师)Workflow:
- 流程的本质是团队内部沟通(intra-team
communication)
和产品健康(ProductFitness)
- 如果流程需要
1:N
人际沟通(一个人向多人汇报)且随组织规模增加,会导致超线性增长的成本和风险
- 因此,应尽量将验证和检查靠左(早期),减少后期的多方协调成本
/>理解:团队越大,后期发现问题的沟通成本和修改成本增长越快,左移原则可以避免这种超线性成本。
- 流程的本质是团队内部沟通(intra-team
3⃣
左移原则的决策规则
- 不要因为右侧已有机制而忽略左侧措施:
- 例:已有
检查成本低且风险可控
- 例:已有
- 不要因为右侧覆盖更多而放弃左侧:
- 例:右侧的测试覆盖更多内容,但左侧测试仍然便宜、可控
- 左侧测试的目的是快速、低成本地捕获问题,不追求完美
/>理解:左移原则强调“先做低成本、早期验证”,右侧机制只是补充,而不是替代。
4⃣
软件工程的本质
- 软件工程
编程
- 编程关注单次任务完成
- 软件工程关注长期可持续的多版本、多开发者协作
- Shifting
Left
是软件工程的典型实践之一
- 用于提高产品适用性(Product
Fitness)
- 控制风险和成本
- 强调循证决策(Evidence-based
decisions)
- 用于提高产品适用性(Product
5⃣
可视化理解
成本流程越靠右,成本/风险越高
高保真
- 左侧(设计/开发阶段):
- 单元测试、代码评审、静态分析
- 低成本、快速反馈
- 右侧(提交/集成/生产阶段):
- 集成测试、端到端测试、Canary
发布
- 高成本、高保真
- 集成测试、端到端测试、Canary
总结
- Shifting
Left
早期做事,低成本捕获问题
- 核心目标:在保证产品健康(Product
Fitness)同时降低超线性成本
- 工程实践:
- 提前测试、代码审查、静态分析
- 避免依赖右侧机制来替代左侧检查
- 用证据驱动决策,而非经验主义
- 关键点:


