~~~《名家名着》03 V.S. 《无瑕的程式码》03~~~
小记者︰能说说你对《无瑕的程式码──敏捷完整版》的读后心得吗?
工程师︰自从读了这本《敏捷完整版》之后,我再也不怕面对那些惯老板、惯客户了。而且客户满意度、专案完成度都一百分呢!
这本书是《无瑕的程式码》系列书的第三册,也是《名家名着》系列书的第三册。主题是「敏捷开发」,而重点仍旧是回归到「如何撰写出好的程式码」。
什么是「敏捷开发(Agile Development)」呢?简单来说,它是软体开发的一套方法,特点是只要透过这套方法,就能使你的专案更敏捷。
我们为何非得要让专案变得敏捷呢?原因无他,就是因为我们有惯老板、还有惯客户。也就是说,对于现今的市场环境而言,专案不够敏捷是不行的。这一点,相信所有的软体工程师都无法否认吧!
可是你可能会反驳说,各行各业都有惯老板和惯客户啊(至少在台湾是这样),为什么软体业就要一套特别的方式来应付他们呢?这就是要回归到一个最根本的问题,「什么是软体?」,或者更精确地说「什么是软体设计?」,而这个问题和所有的软体工程师(或程式设计师)习习相关,因为这是工作的本质。
各式各样的工程有着所谓的程序,例如桥樑工程师会先进行结构分析,他们会建立电脑模型并进行模拟,接着他们会建立比例模型,并在风洞中或用其他一些方法进行测试。当这些程序都完成了,才会将设计图交给桥樑的建造工人去建造出真实的桥樑。
以上是桥樑工程的开发程序,那么软体开发的程序呢?在很久很久以前(真的是很久很久以前了),软体开发也发展出了所谓的程序,也就是瀑布型开发程序。在瀑布型开发中,系统分析师会依照需求与规画,画出所谓软体的设计图(例如UML图),然后由「程序员」根据这些图去写出程式码,最后建置(build)成可使用的软体。
依照瀑布型开发程序开发出来的软体,客户只能选择要用,还是不要用。不要用的话,是否有其他选择?如果没有,那么客户即便不满意,也就只能将就着用(只是边用边骂而已)。当然,这是指套装软体的开发而言。
用一个例子来做比方,数十年前,台湾只有国道一号的日子,一位民众想要开车从彰化到新竹,就只能有一个选择,即便他不满意苗栗那段高爬坡会折损车辆寿命,他也别无选择。但当国道三号建造完毕后,他就有了第二个选择,因此他会选择他喜欢的国道来行使。建造国道的总经费是昂贵的(无论是时间还是金钱),但最贵的部分是在于建造部分,而非设计部份。所以国道并不多。竞争者很少。但这种商业模式在软体业是行不通的。
若用早期的瀑布型开发程序来对比于国道建设,真正的建造部分,其实就是软体建置(build)的部分,这部分只要一台电脑,一个编译器,一个连结器,还有一点点的时间就完成了。所以代价是极低的。或许有人会说,不对,建造的部分应该也要包含按照UML图去Coding的人工与时间成本。所以这部分的代价应该也是昂贵的。
这种说法表面上看似合理,但有多少程式码是完全依照UML图编写的呢?在撰写程式码的过程中是否会修改原有的UML设计呢?早期这类情况并不严重,但晚期因为客户的挑剔,这种情况早就屡见不鲜,甚至任何软体工程师在开发专案时,心中早有预期会出现需求发生变化的情况。
国道的建造工人是无权修改设计图的,他只能「按图施工」。而程序员却去修改了设计图,这将使得设计图无法作为最终产品的设计文件。因此,在这种情况下,最终产品的设计文件其实只有一份是准确的,这份文件就是「程式码」。同时,在这种情况下,程序员应该已经不再只是「程序员」或「码农」了,因为他参与了设计,换句话说,他应该称之为程式「设计师」或软体「工程师」。(在敏捷开发中,并不只有那些绘制UML图的才叫做设计人员,正确地说,绘制UML图的人常常也是负责写程式的人)。
好的,如果你已经承认「写程式」也算是「设计」的一环,那么软体建置(build)的成本(也就是软体的建造成本,而非设计成本),应该是无庸置疑的低廉了。这也就是为什么,客户说,那边改成XXX颜色,可以吗?你会很干脆地回答,当然没问题,然后五分钟内就给客户看改完之后的结果。想一想,如果要改的是一整段国道护栏的颜色,相信没有客户敢做这样要求,因为他们能预期到,这会花很多很多的钱。
所以说,建造软体的花费是很少的,大多数的钱都是花费在「设计」上的。但对于其他工程就不一样了,设计花费的钱相对于建造花费的钱来说,低廉了许多。
也就是软体的这种特殊性,导致了客户(更有可能的是上司)常常想要东改改、西改改,需求常常在变化。在现今这个快速变化的世界里,惯客户与惯老板们为了竞争优势(他们心中的竞争优势),提出需求的变化根本是家常便饭。
在确定了「需求会变化」、甚至是「会频繁地变化」这个软体工程师一定得面对的事实后,软体工程师该怎么办呢?有一群大师级的软体工程师,开始发明了一系列因应的对策,包含设计模式、极限程式设计、测试驱动开发等等的技艺,还总结了一些物件导向的设计原则。这些都有助于应付变化。最终,这些人集合起来成立了一个「敏捷联盟」,取名为敏捷(Agile),意思是软体开发者及软体本身应该如何敏捷地应付需求的变化,当中牵涉到的范围极广,从成员的组织到程式码的组织都必须敏捷起来,这是门现代软体设计的显学,国外大厂早已採用多年。
Robert C. Martin(Bob大叔)是敏捷联盟的创始成员之一,也是当中付诸行动并且有所成效的成员之一。他拥有极具说服力的文笔与口才。在这些年中,不断出书、演讲、作为顾问实际前往开发现场指导,并自创「Clean」一词,其着作还曾获得Jolt大奖,《Clean Code》一书也成为Amazon该类别最畅销的着作,这些都对于敏捷开发的推广有着极重要的贡献。
根据《Clean Code》内文的说法,《Clean Code》可说是本书的前传,而本书是完整说明如何实践敏捷的书籍。如果您也喜欢Bob大叔的着作,如果你也是Clean派的弟子,或者你想实际体验敏捷开发,那么你一定不能错过这本书。
本书的写作风格是循序渐进,由浅入深的,作者会先提出一个问题,然后分析问题,接着实作它,然后是检讨它,展现出初次实作时的错误与失策,接着就展示如何透过作者所主张的技术来解决这些问题。这是一本非常讲究实务的实践书籍。此外,本书主要使用的是C#程式码,这是由Bob大叔的儿子Micah Martin根据C#与.NET平台的特性重新改写Jolt得奖着作而来的,改写幅度包含所有的程式码与内文,并採用了更容易理解的案例来详述敏捷开发。如果你平常使用的是其他语言,也不必在意,因为传播的介质不重要,传授的内容才是本书的价值所在。
对于一些技术细节,本书果真是大师级的作品,原创性极高,在UML章节中,Bob大叔示范了他如何使用UML(果真和一般人不太一样),还示范了如何使用UML才能帮助你而非是制造混乱的来源。对于设计模式而言,除了GoF的知名设计模式之外,Bob大叔还在本书中提到几个他自己常用的设计模式,有些可以视为GoF 23个设计模式的变形,有些则不是,但重点是这些模式都非常好用,可以应用在不同的应用场合,同时Bob大叔也釐清了,某些模式为何不该在哪些场合中使用,他是以效益来看待这件事的,而这也是本书的最大特色:务实。
本书赞誉 这也许是第一本把敏捷方法、模式和最新的软体开发基本原则完美结合在一起的图书。当Bob Martin发言时,我们最好洗耳恭听。
John Vlissides ──《设计模式》作者
这本书中充满了对于软体开发的真知灼见。不管你是想成为一个敏捷开发人员,还是想提升自己的技能,本书都同样有用。我一直在期盼着这本书,它没有令我失望。
Erich Gamma ── JUnit之父,《设计模式》作者
我期待这本书已经很久了,关于如何去掌握我们的行业技能,本书作者拥有非常丰富的实际经验可以传授。
Martin Fowler ── 软体开发大师,《重构》作者
前几天,我找到了记有我对Bob大叔第一印象的备忘录。上面写着'优秀的物件思维'。你手中的这本书就是能让你受益终生的'优秀的物件思维'。
Kent Beck ── 软体开发大师,JUnit之父,设计模式先驱
读过无瑕的程式码,一定要再读「敏捷完整篇」,否则就是您的损失,它会解答您所有的疑惑。
《博硕文化》、《名家名着》 总编辑 ── 陈锦辉
软件构建的艺术与科学:系统性思维与高效能实践 本书导览 本书旨在为致力于软件工程深度实践的专业人士提供一套系统化的知识框架与实战指南。我们不再局限于单一语言或框架的表面操作,而是深入探讨软件系统的核心设计哲学、演进规律以及确保长期可维护性和高性能的构建策略。 本书聚焦于构建“健壮、可扩展、易于理解”的软件。它不侧重于特定框架的快速入门技巧,而是着眼于那些跨越技术栈、经受时间考验的设计原则与模式。通过对软件复杂性本质的剖析,读者将学会如何管理大型系统的熵增,确保代码库在持续迭代中保持清晰的结构和高效的性能。 --- 第一部分:复杂性管理的基石——结构化思维 软件的本质是管理信息和流程的复杂性。本部分将从根本上重塑读者对代码结构的认知,超越简单的功能实现,转向对系统整体形态的掌控。 1.1 抽象的层次与边界的定义 软件设计中的首要挑战是如何有效地划分职责。我们将探讨不同粒度的抽象级别,从函数到模块,再到整个服务架构。重点在于如何识别清晰的“边界”——这些边界定义了系统中不同组件之间的契约和依赖关系。 关注点分离 (Separation of Concerns, SoC) 的精细化应用: 不仅在模块层面,更深入到方法和数据结构层面,确保每个单元只负责一项明确的任务。我们将分析“职责分散”的常见陷阱,例如一个类同时处理业务逻辑、数据持久化和用户界面交互的情况,并提供重构策略。 隐式依赖的显性化: 探讨如何在不使用特定设计模式名称的情况下,通过良好的结构设计来消除隐式的、难以追踪的耦合。这包括对“时间耦合”和“数据流耦合”的识别与解耦技术。 1.2 状态管理的哲学 在任何复杂的系统中,状态是导致不确定性和并发问题的核心根源。本书将系统地审视不同类型状态的特性和管理策略。 可变性与不可变性 (Mutability vs. Immutability): 深入探讨在不同场景下(如高性能计算、并发处理、配置管理)选择不可变数据结构带来的优势和性能权衡。我们将展示如何通过函数式思维来最小化副作用。 状态传播与历史记录: 讨论如何设计能够清晰追溯状态变更路径的系统,这对于调试、审计和实现时间旅行(Time Travel Debugging)至关重要的机制。我们将分析事件溯源(Event Sourcing)的潜在应用场景,即使在传统面向对象环境中,也能借鉴其核心思想。 1.3 对称性与非对称性在设计中的角色 良好的设计往往具有某种内在的“对称性”,即对相似问题的处理方式应保持一致。 一致性原则的应用: 如何确保不同模块对同一概念的理解和操作方式保持同步。我们将研究如何利用编程语言特性(如接口、泛型)来强制执行这种一致性,避免出现“同名异义”的混乱。 何时引入非对称性: 识别那些确实需要特殊处理的“边缘情况”或“性能瓶颈点”,并探讨如何将这些非对称设计隔离在受控的边界内,以防污染核心逻辑。 --- 第二部分:演进式设计与重构的艺术 软件的生命周期在于持续的演化。本书的第二部分专注于如何在不中断现有服务的情况下,安全、有效地改进和重构代码库。 2.1 适应性架构:应对未知的变化 优秀的架构不是预先固定的蓝图,而是能够适应未来需求变化的骨架。 松耦合的度量与实践: 介绍衡量耦合度和内聚度的高级指标,超越简单的类间调用数量。重点在于理解“信息传递的密度”对耦合度的影响。 分层架构的动态演进: 探讨传统三层或MVC架构在微服务时代可能遇到的挑战。我们不推崇盲目地采用最新架构风格,而是强调如何识别当前架构的瓶颈,并进行局部、渐进式的重构,而非推倒重来。例如,如何安全地将一个紧密耦合的内部组件转化为一个独立的、通过消息或API通信的服务。 2.2 安全重构的艺术:最小化风险 重构的阻力往往来自于对引入新错误的恐惧。本部分提供了确保重构安全性的方法论。 契约驱动的重构 (Contract-Driven Refactoring): 强调在修改内部实现前,必须先用测试来固化外部契约。我们将详细讨论如何构建“隔离层”或“适配器”来保护外部消费者,使其不受内部重构的影响。 “僵尸代码”的识别与清除: 探讨如何系统性地识别那些虽然存在但从未使用或已过时的代码路径。这需要依赖精确的覆盖率分析和静态分析工具,以及一种组织文化,鼓励清除“安全地删除”的旧逻辑。 2.3 优化性能的深层考量 性能优化不应是事后的补救,而应融入设计之初的考量。 算法复杂度与实际运行时间的平衡: 分析在现代多核、高缓存系统中,理论上的 $O(N^2)$ 算法在数据量较小时可能优于复杂的 $O(N log N)$ 算法的原因。这要求设计者具备对底层硬件模型的基本理解。 延迟与吞吐量的权衡: 探讨在设计并发机制时,如何根据业务需求(例如,需要快速响应的实时交易系统 vs. 后台批处理系统)来调整锁的粒度、数据结构的布局和异步处理的深度。 --- 第三部分:构建高质量交付物的工程实践 本部分聚焦于如何将设计理论转化为可交付、可信赖的最终产品,强调自动化、质量保证和持续集成的重要性。 3.1 测试的真正价值:设计而非验证 本书将测试视为“设计行为的副产品”,而非事后的验证步骤。 依赖注入 (Dependency Injection) 的纯粹作用: 不仅仅是“为了测试方便”,而是通过强制分离构造时配置和运行时代替物,来构建高内聚、低耦合的组件。我们将探讨更精细的“构造函数注入”与“属性注入”的适用场景。 集成测试的边界设定: 如何在单元测试的快速反馈和端到端测试的真实性之间找到最佳平衡点。讨论如何使用“模拟(Mocking)”和“存根(Stubbing)”来精确隔离和测试特定行为,避免过度依赖外部系统。 3.2 可读性超越简洁性 代码的长期维护成本远高于编写成本。本书强调“为后来的自己”和“为团队新成员”编写代码的策略。 命名与意图的对齐: 深入探讨如何通过精确的命名(变量、函数、类)来编码系统的意图,减少阅读代码时进行心智转换的开销。 注释的“真伪”: 分析何时注释是必要的“为什么”(Why),何时注释只是重复了代码的“是什么”(What)。推崇“自解释性代码”的极限,以及在复杂领域特定语言(DSL)中注释的有效用法。 3.3 工具链与自动化部署的集成 将优秀的工程实践固化到自动化流程中,确保每一次提交都遵循既定的质量标准。 静态分析的进阶使用: 不仅使用 Linter 检查语法错误,更利用高级静态分析工具来识别潜在的运行时错误、未使用的资源和不一致的模式应用。 构建流程中的质量门禁: 如何在持续集成(CI)管道中嵌入性能测试、安全扫描和代码质量度量,确保只有满足预设阈值的代码才能进入部署阶段。 本书为读者提供了一套成熟的、经过反复验证的软件构建思维模型,旨在培养能够设计和维护十年以上生命周期的复杂系统的顶尖工程师。它要求读者愿意深入理解底层原理,并对构建“优雅而强大”的软件怀有不懈的追求。