《现代软件工程:做有效之事以更快更好地构建软件》核心摘要
本书深入探讨了现代软件工程的核心原则和实践,旨在帮助读者以更高效、更高质量的方式构建软件。本摘要将以超轻认知负荷的方式,提炼并高亮其中的
核心事实
和
关键观点
,并特别关注
AI/ML相关内容
。
第一部分:什么是软件工程?
►
第一章:引言
软件开发是
发现和探索
的过程,软件工程师需成为
学习的专家
。
工程是
科学的实践应用
,其核心方法是:
描述、假设、预测、实验
。
软件工程的定义:
将经验主义和科学方法应用于寻找软件实际问题的有效、经济解决方案。
核心支柱:成为
学习的专家
(迭代、反馈、增量、实验、经验)和
管理复杂性的专家
(模块化、内聚、关注点分离、抽象、松耦合)。
软件工程不应是官僚主义或僵化的,而应是
“行之有效的方法”
,它能
更快更好地构建软件
。
历史上的
“软件危机”
促使了“软件工程”概念的诞生(Margaret Hamilton,NATO会议)。
软件进步慢于硬件是
一个持久的现象
(Fred Brooks的“没有银弹”)。
需要思维上的
范式转变
:从“事先完美计划”到“接受错误并快速学习适应”。
第二章:什么是工程?
软件工程不是
生产工程
(数字产品的生产成本几乎为零),而是
设计工程
。
设计工程是
探索性、创造性
的,与物理世界不同,软件模型本身就是产品,可
精确验证
。
工程不等于代码:工程是
创造软件的整个过程
,包括流程、工具、文化。
工程之所以重要:
克服
“工匠”的局限性
:工匠模式难以实现
高精度和可伸缩性
,每次产品都是唯一的。
工程通过
工具和自动化实现超人精度和可伸缩性
(例如自动化测试比人工测试快百万倍)。
有效
管理复杂性
:通过
模块化、组件化
等方式将大问题分解成可管理的小部分。
强调
可重复性和测量精度
:精确测量能更快发现问题(例如水管漏水例子)。
现代软件工程的决策应基于
理性标准和证据
,而非直觉或信仰。
权衡
:所有工程都是优化和权衡的博弈,例如安全性与易用性,分布式与集成成本。
进步的幻觉
:
很多技术变革(如特定语言、框架)并未带来根本性进步
。真正重要的是
底层设计原则
(如Serverless因鼓励模块化和关注点分离而重要)。
从工匠到工程的转变:
工程是工匠精神与科学理性相结合
,它放大创造力、降低成本、提供更健壮灵活的解决方案。
第三章:工程方法的基石
软件行业的变化常是
短暂的
,真正的进步速度
远低于预期
(例如多数语言改变未带来10倍提升)。
测量的核心重要性:
多数软件开发指标无效或有害(如速度、代码行数)。
DORA研究
(Nicole Forsgren, Jez Humble, Gene Kim)提供了
有效衡量
:
稳定性
(变更失败率、恢复时间)和
吞吐量
(交付周期、部署频率)。
高绩效团队在稳定性和吞吐量上表现优异,且与组织绩效和商业成功相关。
速度和质量不是权衡关系,而是
正相关
。
证据驱动决策:
例如,
变更审批委员会(CAB)实际上会降低稳定性,拖慢速度
。
软件工程学科的基石:
成为
学习的专家
:
迭代、反馈、增量、实验、经验。
成为
管理复杂性的专家
:
模块化、内聚、关注点分离、抽象、松耦合。
这些原则相互关联,而非独立。它们提供了超越工具和技术、指导决策的持久框架。
第二部分:优化学习
►
第四章:迭代工作
迭代的定义:
通过重复操作序列逐步逼近期望结果的过程。
它驱动学习、允许纠错和适应。
敏捷革命:
敏捷宣言核心思想是
“检查与适应”
,接受必然犯错,并降低犯错成本。这是一种
科学思维
。
迭代的实践优势:
自然缩小批次大小,鼓励
模块化和关注点分离
。
提供
确定性反馈
,加快学习周期。
迭代作为防御性设计策略:
传统瀑布模型假设变更成本随时间增加(错误假设)。
敏捷目标是
“扁平化变更成本曲线”
:无论何时发生变更,成本都大致相同。
这允许在项目早期知识最少时做出关键决策,并在后续持续学习和适应。
计划的诱惑:
瀑布模型的“预先周密计划”在数字世界不成立。
行业数据表明,三分之二的产品构想零价值或负价值。
无限的开端:
迭代方法是
无边界、进化式
的。瀑布是有限的。
迭代的实用性:
从小批次开始工作。
持续集成(CI)
和
测试驱动开发(TDD)
是精细粒度的迭代过程。
第五章:反馈
反馈的定义:
关于行动、事件或过程的评价性或纠正性信息传回源头。
没有反馈就没有学习。
反馈的重要性:
平衡扫帚的例子
:规划式(不稳) vs. 反馈式(稳定)。软件开发环境变化,需反馈。
NATO会议洞见
:早期就认识到反馈回路、测试环境、模块化对软件工程的重要性。
在代码中获取反馈:
TDD
:先写测试(红),然后写代码使其通过(绿),再重构。提供
秒级甚至毫秒级
的快速反馈。
在集成中获取反馈:
持续集成(CI)
:频繁提交代码到共享主线,每天多次。每次提交都触发系统评估。
CI vs. 特性分支(FB)
:FB隔离变更,延迟集成,导致“合并地狱”。CI旨在尽早暴露变更,提供更早、更频繁的反馈。
在设计中获取反馈:
TDD
:
测试难写通常意味着设计糟糕。
TDD迫使代码具备
模块化、关注点分离、高内聚、信息隐藏、恰当耦合
等高质量特性。
测试的重要性
:软件对缺陷极其敏感。TDD将测试置于开发核心,缩短反馈周期至数秒。
在架构中获取反馈:
持续交付(CD)
要求软件始终可发布,这意味着它必须
易于部署
。
高可测试性/可部署性促进了
更模块化、更好抽象、更松耦合的设计
(如微服务)。
优先选择早期反馈:
“快速失败”(Fail fast)
或“左移”(Shift-left)。在编译、单元测试、更高层级测试中尽早发现问题。
在产品设计中获取反馈:
真正的产品价值只有通过用户/客户的
反馈才能确定
。
持续交付
的真正价值是闭合从构思到生产的反馈循环,从而更快地学习并适应市场。
遥测(Telemetry)
:从生产系统收集数据,诊断问题并指导产品设计,将“IT部门”转变为“数字化业务”。
在组织和文化中获取反馈:
传统软件衡量指标(LOC、开发日、测试覆盖率)无效。
DORA度量(稳定性、吞吐量)
提供了有意义的反馈。
高稳定性+高吞吐量的团队更成功、更赚钱。
速度与质量是正相关而非权衡
。
可以像科学家一样,设定当前状态和目标状态,进行小步实验来衡量变更效果。
第三部分:优化复杂性管理
►
第九章:模块化
模块化的定义:
系统组件可被分离和重组的程度,常带来灵活性和多样性。
模块化是
管理复杂性
的关键。现代系统太大,需分解为可理解的小块。
模块化的特征:
可重用、独立理解、有内部和外部概念、有清晰接口。
低估设计的重要性:
行业过于痴迷语言和框架,忽视了设计原则(如模块化、关注点分离)的核心价值。
可测试性的核心作用:
可测试性直接驱动模块化。
如果测试代码很困难,则设计很糟糕。
为可测试性而设计:
“风洞实验”类比
:通过隔离被测组件,控制变量,获得精确可重复的测试结果。
精确测量
:测试结果应是
确定性
的。
测量点
:设计系统时就考虑插入探针来注入数据和收集输出。
通过
依赖注入
和
良好模块设计
,可使代码更可测试,进而更灵活、更具可替换性。
服务与模块化:
服务
是
信息隐藏
的组织概念,代表着系统中的边界。
这些边界应被视为
翻译和验证点
,而不是简单透传。
可部署性与模块化:
持续交付(CD)
要求软件始终可发布,这意味着它必须
易于部署
。
部署流水线
的有效范围是
“独立可部署的软件单元”
。
这促进了
微服务
架构,因为微服务天生就是独立可部署的。
微服务是
组织可伸缩性
的模式,旨在解耦团队,但需以更复杂分布式架构为代价。
不同尺度的模块化:
从企业系统到单个方法,模块化理念都是
分形(fractal)
的。
人类系统中的模块化:
Conway定律
:组织结构影响系统设计。
模块化组织(如Amazon的“双披萨团队”)通过解耦团队,实现了前所未有的创新速度和可伸缩性。
第十章:内聚
内聚的定义(计算机科学):
模块内元素相互关联的程度。
模块化与内聚:设计基础。
Kent Beck:“将不相关的拉远,将相关的放近。”
模块化是分隔,内聚是聚合。
代码中的内聚:
通过将紧密相关的代码(如读取、处理、存储)放到独立的、命名的函数中,可提高内聚和可读性。
代码的主要目标是
向人类交流思想
,而非追求字符最少。
上下文的重要性:
内聚是情境化的概念,与模块化和关注点分离紧密交织。
高内聚软件的实现:
通过
DDD(领域驱动设计)
,让设计遵循问题领域,识别“有界上下文”来划分模块。
TDD
:
编写测试时,如果代码没有高内聚,测试会变得困难,从而反向推动设计改进。
分离意外复杂性与核心复杂性
:有助于提升内聚。
低内聚的代价:
代码和系统
灵活性差、难以测试、难以维护
。
导致“大泥球”代码,成为“遗留代码”。
识别低内聚的简单方法:阅读代码时感到“不知道这段代码在做什么”。
人类系统中的内聚:
高绩效团队通常在内部信息和技能上高度内聚,具备
自主决策能力
,无需外部许可。
第十一章:关注点分离
关注点分离的定义:
将计算机程序分解为独立的部分,每个部分处理一个独立的关注点。
这是
个人工作中
最强大的设计原则
,是提升模块化、内聚、抽象和降低耦合的关键技术。
数据库替换案例:
通过严格的关注点分离,将数据库从商业逻辑中解耦,使得更换数据库从数月工作变为数小时。
示例代码分析:
混合“核心逻辑”与“数据库存储细节”是糟糕的分离。
通过
依赖注入
和
抽象
,可以将存储逻辑解耦,使核心业务逻辑更纯粹。
选择哪种分离方式取决于上下文和权衡(如同步与异步)。
依赖注入:
通过参数而非内部创建来提供依赖,是实现关注点分离和降低耦合的
极其有用工具
。
分离核心复杂性与意外复杂性:
核心复杂性
:问题本身固有的复杂性(如计算账户价值)。
意外复杂性
:因使用计算机而引入的复杂性(如数据持久化、屏幕显示、并发、安全)。
通过清晰地划分这两类关注点,可显著改善设计,提高可测试性。
DDD的重要性:
通过
领域驱动设计
,可以从问题领域本身识别出自然的关注点分离边界。
可测试性:
可测试性是实现有效关注点分离的
强大工具
。
如果代码难以测试,往往是关注点混淆所致。
端口与适配器模式:
一种将核心业务逻辑与外部技术细节(如数据库、网络)隔离的设计模式。
核心逻辑通过“端口”与抽象接口交互,具体技术通过“适配器”实现。
这种模式在服务边界处尤其重要,因为它强制进行信息翻译和验证,有效
隔离变更影响
。
API是什么?
API是所有暴露给消费者的信息,包括数据结构和行为。设计API时,需考虑其暴露的粒度,并进行适当的封装和验证。
TDD驱动关注点分离:
TDD通过迫使先写测试,促使开发者设计出
高可测试性、关注点分离清晰
的代码。
测试的难度可作为设计质量的早期反馈。
总结:
关注点分离是可操作性最强的设计启发式方法,能明确指导代码设计,使其更易理解、测试、修改和扩展。
第十二章:信息隐藏与抽象
信息隐藏与抽象的定义:
在研究对象或系统时,去除物理、空间或时间细节或属性,以关注更重要的细节。
核心目的是
管理复杂性
:通过在代码中划定“界限”,隐藏实现细节,使开发者可专注于当前工作。
“大泥球”的原因:
组织和文化问题:
开发者未能履行“专业职责”,未能有效管理代码质量。
低质量软件长期来看
更慢、成本更高
。
技术和设计问题:
对现有代码缺乏修改意愿或能力(“死代码”)。
拥抱变化是关键。
抽象的力量:
操作系统抽象了硬件,云服务抽象了分布式系统。
“纯文本”生态系统、Unix的文件模型都是强大的抽象。
软件开发本质上就是在
创造新的抽象
。
泄漏的抽象:
“所有非琐碎的抽象都会泄漏。”
这并非反对抽象,而是提醒需谨慎管理。
分为
无法避免的泄漏
(如并发、内存访问时间)和
因设计不足造成的泄漏
(如业务逻辑返回技术错误码)。
目标是
最小化泄漏影响
,保持抽象的一致性。
选择恰当的抽象:
地图类比:不同的抽象服务于不同的目的(如航海图 vs. 地铁图)。
测试性可提供早期反馈,帮助识别抽象的脆弱性。
从问题领域抽象:
利用
事件风暴
等技术识别问题领域中的自然关注点分离。
抽象意外复杂性:
将处理计算机细节的代码与核心业务逻辑分离。
通过
抽象接口和依赖注入
,使业务逻辑不依赖具体技术实现(如数据库)。
这是在代码中隔离第三方代码的关键策略。
始终优先隐藏信息:
选择更通用的表示而非过分具体(如使用List而非ArrayList)。
这有助于保持代码的灵活性,为未来的变更保留开放选项。
TDD
是促进信息隐藏和抽象的最强大工具,它迫使你从外部视角设计接口。
第四部分:支持工程的工具
►
第十四章:工程学科的工具
真正的软件工程应关注
结果
:
更快更好地构建软件。
软件开发不仅仅是代码:需要
测试
来验证其工作原理,因为人类容易犯错且倾向于看到预期结果。
可测试性是工具:
依赖注入
是实现可测试性的关键,它使代码更
模块化、内聚、关注点分离、抽象和恰当耦合
。
测量点:
设计系统时就考虑如何注入测试数据和收集结果。
挑战:
系统边缘(与真实硬件交互)的测试性较差;文化问题(“我们尝试了TDD,但它不起作用”)。
TDD
是鼓励良好设计的基石,
它能放大开发者的技能和经验。
可部署性:
持续交付(CD)
的核心是使软件
始终处于可发布状态
。
部署流水线
是验证可发布性的机制。
部署流水线的有效范围是
“独立可部署的软件单元”
。
这迫使团队在系统级别重视模块化、内聚、关注点分离、抽象和松耦合。
速度:
反馈的速度和质量
是优化学习的关键。
吞吐量
衡量开发效率。
目标:
从提交到可生产部署,通常建议
小于1小时
。
对速度的关注会自然驱动团队采纳敏捷、精益、CD和DevOps的实践。
控制变量:
要实现快速、可靠、可重复的测试和部署,必须
控制变量
。
通过
自动化部署、基础设施即代码、版本控制
等来管理配置。
长期运行或手动测试通常是未能控制变量的症状。
软件系统是确定性的(除了并发)。设计可测试的代码会使代码更确定性、更易理解、更高效。
持续交付:
一种
组织哲学
,将上述所有思想整合为有效的工作方法。
它
迫使组织结构扁平化、解耦团队,提升自动化(尤其测试),鼓励可测试设计
。
持续交付是构建
强大软件工程学科
的高效策略。
通用工程工具:
评估第三方技术时,可使用这些原则作为标准:它
可部署、可测试、可控制变量、足够快(用于CD)、支持模块化
吗?
这些工具提供了
证据驱动
的决策模型,而非依赖时尚或猜测。
第十五章:现代软件工程师
►
本书所有概念(模块化、内聚、关注点分离等)
相互关联且无处不在
。它们是超越特定工具、语言和框架的
通用技能
。
核心目标:
优化学习
和
管理复杂性
,这是软件工程学科的基础。
软件工程是
人类过程
:
定义:
将经验主义和科学方法应用于寻找软件实际问题的有效、经济解决方案。
基于理性决策、经验反馈、科学推理,并追求效率和经济性。
组织和团队本身也是信息系统,复杂性管理同样适用于它们。
数字化颠覆性组织:
这些组织通常
由工程主导
,软件是核心业务而非成本中心。
采用
BAPO模型
(业务-架构-流程-组织),而非传统的OBAP模型,即组织结构服务于业务目标。
通过
解耦组织内部团队
,可实现高可伸缩性和效率。
结果与机制:
结果比机制更重要。
例如,持续交付定义了“始终处于可发布状态”的期望结果,而DevOps则是一系列实现该结果的实践。
科学方法(描述、假设、预测、实验)是
最持久和通用的原则
。
持久且普遍适用:
这些工程原则也适用于
机器学习(ML)
。
ML工作流
(数据准备、训练、生产)本质上是
迭代、反馈驱动、实验性和经验性
的。
需优化ML流程的
学习效率
,加快迭代,清晰反馈。
管理复杂性:
对ML系统而言,这意味着对脚本、训练数据进行
版本控制、模块化、内聚和关注点分离
。
ML中的偏见问题,可理解为训练数据中“关注点分离不佳”的体现。
工程思维能帮助ML领域提出新的优化和质量问题,指导更好的ML系统生产。
总结:
本书的理念是指导你
更有效地运用工具
。
通过
优化学习能力
和
管理复杂性
,能显著提高构建
更快更好软件
的成功几率。
这些是
真正软件工程学科的标志
。
原文
源链接
附件
中文PDF
(19.2M)
下载
中文epub
(3.6M)
下载
◁