<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>软件工程 on 方糖猫Blog</title>
    <link>https://ftmao.com/blog/tags/%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B/</link>
    <description>AI 视频、AI Agent 与工程实践笔记</description>
    <generator>Hugo</generator>
    <language>zh-CN</language>
    <lastBuildDate>Mon, 28 Sep 2026 16:00:00 +0800</lastBuildDate><atom:link href="https://ftmao.com/blog/tags/%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B/index.xml" rel="self" type="application/rss+xml" /><item>
      <title>十年前泡论坛啃书，现在问 AI：软件知识的分层与获取地图</title>
      <link>https://ftmao.com/blog/posts/how-to-get-reliable-tech-knowledge-in-ai-era/</link>
      <pubDate>Mon, 28 Sep 2026 16:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/how-to-get-reliable-tech-knowledge-in-ai-era/</guid>
      <description>&lt;h1 id=&#34;十年前泡论坛啃书现在问-ai软件知识的分层与获取地图&#34;&gt;十年前泡论坛啃书，现在问 AI：软件知识的分层与获取地图&lt;/h1&gt;
&lt;p&gt;先想象一个具体的任务：&lt;strong&gt;你要从零做一个秒杀下单系统&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;如果是 2015 年，你的路径大概是这样的：买《深入理解 Nginx》《Redis 设计与实现》，泡博客园和 ITEye，搜&amp;quot;秒杀系统 架构&amp;quot;找到几篇大厂的分享 PPT 如获至宝，照着 CSDN 上的帖子抄一段 Nginx 配置，跑不通就去 QQ 群里问。一个消息队列的选型，可能要在论坛里翻几十页帖子。&lt;/p&gt;
&lt;p&gt;如果是 2026 年，你打开 AI 问一句：&amp;ldquo;帮我拆解一个秒杀系统的技术选型和坑。&amp;ldquo;十秒钟，一份看起来像模像样的方案就出来了。&lt;/p&gt;
&lt;p&gt;然后呢？然后你照着 AI 给的代码配 Spring Boot，启动报错——它引用的包名是 Spring Boot 2 的。你问它 Redis 最新版本怎么配，它给你的参数三年前就废弃了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;渠道换了三代，踩的坑是同一个&lt;/strong&gt;：渠道选错，往往不是信息不够，而是拿错了知识层次。这篇文章想讲清楚两件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;软件知识分层，每一层有各自唯一靠谱的获取渠道；&lt;/li&gt;
&lt;li&gt;从&amp;quot;论坛时代&amp;quot;到&amp;quot;AI 时代&amp;rdquo;，每一层的渠道具体是怎么变迁的。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;一spring-boot-和-netty其实是两个层面的东西&#34;&gt;一、Spring Boot 和 Netty，其实是两个层面的东西&lt;/h2&gt;
&lt;p&gt;学习清单上它们常常排在一起：&amp;ldquo;Spring Boot、Redis、MQ、Nginx、Netty。&amp;rdquo;&lt;/p&gt;
&lt;p&gt;但如果看它们各自真正难的部分，这五个名字其实落在四个不同的层次上：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;技术&lt;/th&gt;
					&lt;th&gt;它真正难的部分&lt;/th&gt;
					&lt;th&gt;属于哪层&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Nginx、Netty&lt;/td&gt;
					&lt;td&gt;Reactor 模型、epoll、零拷贝、内存管理&lt;/td&gt;
					&lt;td&gt;贴着操作系统网络的底层组件，核心知识半衰期 &lt;strong&gt;10 年+&lt;/strong&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Redis、MQ（Kafka/RocketMQ）&lt;/td&gt;
					&lt;td&gt;数据结构、持久化策略、副本协议、消息语义&lt;/td&gt;
					&lt;td&gt;中间件，核心知识半衰期 &lt;strong&gt;5~8 年&lt;/strong&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Spring Boot&lt;/td&gt;
					&lt;td&gt;自动装配、约定优于配置、生态整合&lt;/td&gt;
					&lt;td&gt;应用组装框架，半衰期 &lt;strong&gt;3~5 年&lt;/strong&gt;，小版本还在月月变&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;用上面这些搭一个秒杀系统&lt;/td&gt;
					&lt;td&gt;流量整形、幂等、分布式事务、降级预案&lt;/td&gt;
					&lt;td&gt;方案层，跟着业务实践走，&lt;strong&gt;没有教材，只有案例&lt;/strong&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;层次不同，学法就不同：试图&amp;quot;背&amp;quot;Spring Boot 的配置（该查不背），或者试图靠刷博客学懂 Netty 的 EventLoop（该啃源码），都是拿错了工具。&lt;strong&gt;层次错位，是学习效率低的第一大原因。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;二四层知识四个时代的入口&#34;&gt;二、四层知识，四个时代的入口&lt;/h2&gt;
&lt;p&gt;把上面的分层展开，逐层对比&amp;quot;过去去哪获取&amp;quot;和&amp;quot;现在去哪获取&amp;rdquo;：&lt;/p&gt;
&lt;h3 id=&#34;原理层reactorcapb-树tcp&#34;&gt;原理层：Reactor、CAP、B+ 树、TCP&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;过去&lt;/strong&gt;：经典书和论文是唯一入口。《UNIX 网络编程》《TCP/IP 详解》摆在桌上，啃不动就是啃不动，没人能救你。这层知识获取的瓶颈从来不是&amp;quot;找不到&amp;quot;，而是&amp;quot;读不懂&amp;quot;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;现在&lt;/strong&gt;：书还是那批书——原理层十几年不变，没有新书可写。但多了一件过去不存在的东西：&lt;strong&gt;AI 是随问随答的讲解员&lt;/strong&gt;。&amp;ldquo;用大白话解释 Reactor 和 Proactor 的区别&amp;rdquo;、&amp;ldquo;CAP 里的 P 为什么必须满足&amp;rdquo;、&amp;ldquo;这段 epoll 代码为什么这么写&amp;rdquo;——过去需要一个导师领着啃的东西，现在可以按你自己的水平反复讲。这大概是 AI 对学习方式最大的真实贡献：&lt;strong&gt;它消灭了原理层的&amp;quot;读不懂&amp;quot;&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;组件层netty-的-eventloopredis-的持久化kafka-的副本协议&#34;&gt;组件层：Netty 的 EventLoop、Redis 的持久化、Kafka 的副本协议&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;过去&lt;/strong&gt;：官方文档（英文，劝退）+ 源码硬啃 + 少数系统化的书（《Redis 设计与实现》《Netty 权威指南》《Kafka 权威指南》）。肯读源码的人和不读的人，差距就在这一层拉开。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;现在&lt;/strong&gt;：锚点没变——官方文档和源码仍然是这一层唯一的事实来源。变的是&lt;strong&gt;门槛&lt;/strong&gt;：把 &lt;code&gt;NioEventLoop&lt;/code&gt; 的一段源码贴给 AI 问&amp;quot;这段在干嘛&amp;quot;，比自己在调用栈里迷路快十倍。书的意义也变了：不再是唯一的系统材料，而是帮你建立地图——地图有了，细节让 AI 陪你在源码里走。&lt;/p&gt;
&lt;p&gt;各组件的锚点，顺手列一下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Spring Boot&lt;/strong&gt;：spring.io 的官方 Guides + Reference；版本变化看 GitHub release notes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Redis&lt;/strong&gt;：redis.io 官方文档，每个命令一页带复杂度说明，质量极高&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kafka&lt;/strong&gt;：官方文档的 Design 章节，分布式系统教学级材料，值得全文读&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RocketMQ&lt;/strong&gt;：GitHub 仓库里的架构文档，配合 &lt;code&gt;DefaultMQProducer&lt;/code&gt; 源码读一遍发送流程&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nginx&lt;/strong&gt;：nginx.org 指令文档是&amp;quot;查&amp;quot;的，不是&amp;quot;学&amp;quot;的；架构思想看《深入理解 Nginx》&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Netty&lt;/strong&gt;：官方 User Guide 很短，真正的教材是 &lt;code&gt;NioEventLoop&lt;/code&gt; 源码本身&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;版本层spring-boot-3-的配置项redis-7-的新命令&#34;&gt;版本层：Spring Boot 3 的配置项、Redis 7 的新命令&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;过去&lt;/strong&gt;：本来就该看官方 release notes，但很多人不看——踩了坑再去搜博客，搜到别人踩过的同一个坑。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;现在&lt;/strong&gt;：&lt;strong&gt;这一层是唯一没有被 AI 改善、反而被 AI 污染的层&lt;/strong&gt;。AI 有知识截止日期，且会一本正经地编造不存在的配置项和 API。它记忆里的版本知识只能当初稿，不能当答案。正确姿势有两种：一是回归原始动作，直接去官方文档和 release notes 抄；二是把官方文档页面喂给 AI 当上下文再提问（或用 Context7、DeepWiki 这类把最新文档索引给 AI 的工具），让 AI 基于真实文档回答，而不是基于记忆编造。&lt;/p&gt;
&lt;p&gt;一句话：&lt;strong&gt;版本层的知识不要&amp;quot;学&amp;quot;，要&amp;quot;查&amp;quot;，唯一权威是官方文档。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;方案层秒杀订单幂等分布式事务&#34;&gt;方案层：秒杀、订单幂等、分布式事务&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;过去&lt;/strong&gt;：这是最难获取的一层。官方文档不管组合，书上没有，只能靠大厂技术博客、QCon/ArchSummit 的大会 PPT、以及口口相传。当年能拿到一份大厂秒杀架构的分享 PPT，如获至宝不是修辞，是事实。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;现在&lt;/strong&gt;：大厂博客依然是主力（地址整理在第四节），但增量有两个：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;成熟的开源脚手架&lt;/strong&gt;——mall、RuoYi 这类 Star 数高、持续更新的项目，看它们怎么把组件接起来、怎么处理幂等和分布式事务。重点看 issue 区，坑都在那里。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI 能做方案 review&lt;/strong&gt;——把你的设计贴给它问&amp;quot;这个链路哪里会丢消息&amp;quot;，它能列出你该检查的清单。它给出的方案未必对，但它给出的&amp;quot;检查清单&amp;quot;通常很全。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;还有一条没变过：&lt;strong&gt;看十篇方案不如自己搭一遍&lt;/strong&gt;。用 Docker Compose 把 MySQL + Redis + RocketMQ + Nginx 拉起来，写一个下单扣库存接口，把消息丢失、缓存穿透、重复消费全踩一遍。踩过的坑才是你的方案。&lt;/p&gt;
&lt;h2 id=&#34;三把例子走完秒杀系统的四层拆解&#34;&gt;三、把例子走完：秒杀系统的四层拆解&lt;/h2&gt;
&lt;p&gt;回到开头的任务，把&amp;quot;做一个秒杀系统&amp;quot;拆到四层，每层的获取动作清清楚楚：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;层&lt;/th&gt;
					&lt;th&gt;秒杀系统里对应的问题&lt;/th&gt;
					&lt;th&gt;2015 年的动作&lt;/th&gt;
					&lt;th&gt;2026 年的动作&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;原理层&lt;/td&gt;
					&lt;td&gt;为什么用队列削峰（流量整形）；超卖的本质是并发控制&lt;/td&gt;
					&lt;td&gt;啃书，啃不动就放弃&lt;/td&gt;
					&lt;td&gt;书 + AI 讲到懂为止&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;组件层&lt;/td&gt;
					&lt;td&gt;Redis Lua 扣库存的原子性；RocketMQ 事务消息&lt;/td&gt;
					&lt;td&gt;官方文档 + 源码硬啃&lt;/td&gt;
					&lt;td&gt;文档源码仍是锚点，AI 当导读&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;版本层&lt;/td&gt;
					&lt;td&gt;Spring Boot 3 整合 Redis 的依赖坐标；RocketMQ 5.x 客户端配置&lt;/td&gt;
					&lt;td&gt;踩坑后搜博客&lt;/td&gt;
					&lt;td&gt;官方文档抄，或喂文档给 AI&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;方案层&lt;/td&gt;
					&lt;td&gt;整条链路怎么组装、降级预案怎么定&lt;/td&gt;
					&lt;td&gt;大厂 PPT + 口口相传&lt;/td&gt;
					&lt;td&gt;大厂博客 + 开源脚手架 + AI review + 自己搭&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;注意一个细节：四层里只有原理层和方案层的&amp;quot;获取&amp;quot;被 AI 真正改变了；组件层是门槛降低，版本层反而要多一道验证。&lt;strong&gt;AI 没有改变知识的层次结构，只改变了每一层的获取效率。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;四趋势在哪看&#34;&gt;四、趋势在哪看&lt;/h2&gt;
&lt;p&gt;趋势是方案层之上的东西：不告诉你 API 怎么写，但告诉你行业在往哪走。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;过去&lt;/strong&gt;：书和杂志的序言、线下大会、以及&amp;quot;听说隔壁公司在用什么&amp;quot;。信息慢，但噪声也小。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;现在&lt;/strong&gt;：信号源多了，按信号强度排序——&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub Trending + Awesome 系列&lt;/strong&gt;：trending 看热度，awesome-xxx 看生态成熟度。一个方向的 awesome 列表两年没更新，基本就是凉了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;顶级会议议题表&lt;/strong&gt;：KubeCon、QCon/ArchSummit、Spring I/O、AWS re:Invent。连续三年都有专场的话题，已经不是趋势，是现实。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ThoughtWorks 技术雷达&lt;/strong&gt;：半年一期，敢说&amp;quot;暂缓&amp;quot;，比只会吹捧的报告值钱。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;年度调查&lt;/strong&gt;：Stack Overflow / JetBrains 开发者报告。&amp;ldquo;实际在用什么&amp;quot;和&amp;quot;媒体在吹什么&amp;quot;的差值，就是泡沫。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;大厂技术博客&lt;/strong&gt;：他们踩过的坑，就是你两年后的日常。常用入口（RSS 为发文前实测可用）：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;国内：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;团队&lt;/th&gt;
					&lt;th&gt;地址&lt;/th&gt;
					&lt;th&gt;RSS / 订阅方式&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;美团技术团队&lt;/td&gt;
					&lt;td&gt;&lt;a href=&#34;https://tech.meituan.com/&#34;&gt;tech.meituan.com&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;a href=&#34;https://tech.meituan.com/feed/&#34;&gt;RSS&lt;/a&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;阿里（淘天/阿里云等）&lt;/td&gt;
					&lt;td&gt;&lt;a href=&#34;https://developer.aliyun.com/group&#34;&gt;阿里云开发者社区&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;无官方 RSS，可用 &lt;a href=&#34;https://docs.rsshub.app/&#34;&gt;RSSHub&lt;/a&gt; 生成&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;字节跳动技术团队&lt;/td&gt;
					&lt;td&gt;&lt;a href=&#34;https://juejin.cn/user/1838039172387262&#34;&gt;掘金官方号&lt;/a&gt;、&lt;a href=&#34;https://opensource.bytedance.com/blog&#34;&gt;开源官网博客&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;无官方 RSS，可用 RSSHub 生成&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;腾讯云开发者&lt;/td&gt;
					&lt;td&gt;&lt;a href=&#34;https://cloud.tencent.com/developer&#34;&gt;cloud.tencent.com/developer&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;无官方 RSS&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;国外：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;团队&lt;/th&gt;
					&lt;th&gt;地址&lt;/th&gt;
					&lt;th&gt;RSS&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Netflix&lt;/td&gt;
					&lt;td&gt;&lt;a href=&#34;https://netflixtechblog.com/&#34;&gt;netflixtechblog.com&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;a href=&#34;https://netflixtechblog.com/feed&#34;&gt;RSS&lt;/a&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Meta Engineering&lt;/td&gt;
					&lt;td&gt;&lt;a href=&#34;https://engineering.fb.com/&#34;&gt;engineering.fb.com&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;a href=&#34;https://engineering.fb.com/feed/&#34;&gt;RSS&lt;/a&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;AWS&lt;/td&gt;
					&lt;td&gt;&lt;a href=&#34;https://aws.amazon.com/blogs/&#34;&gt;aws.amazon.com/blogs&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;a href=&#34;https://aws.amazon.com/blogs/aws/feed/&#34;&gt;RSS&lt;/a&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Uber&lt;/td&gt;
					&lt;td&gt;&lt;a href=&#34;https://www.uber.com/blog/engineering/&#34;&gt;uber.com/blog/engineering&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;无公开 RSS&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Google Developers&lt;/td&gt;
					&lt;td&gt;&lt;a href=&#34;https://developers.googleblog.com/&#34;&gt;developers.googleblog.com&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;页面提供 Atom 订阅&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;国内团队不提供 RSS 的居多（内容越来越多迁到公众号和自家社区），两个补救办法：用 RSSHub 把社区页面转成 RSS；或者干脆不订阅，按季度扫一遍历史文章列表——好内容不怕晚三个月。&lt;/p&gt;
&lt;p&gt;无论渠道怎么变，趋势这东西&lt;strong&gt;季度性地扫一眼就够了&lt;/strong&gt;。天天刷只会制造焦虑。真正值得追的趋势，三个月后它还在。&lt;/p&gt;
&lt;h2 id=&#34;五ai-时代真正改变的三件事&#34;&gt;五、AI 时代真正改变的三件事&lt;/h2&gt;
&lt;p&gt;把全文收拢成三句话：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;原理层的获取成本坍缩了。&lt;/strong&gt; 过去最大的瓶颈&amp;quot;读不懂&amp;quot;被 AI 消灭了——这是学习方式上最深刻的改变，没有之一。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;验证变得比知道重要。&lt;/strong&gt; AI 会编，尤其在版本层。靠谱的工作流只剩三步：AI 讲原理 → 官方文档定版本 → 跑起来验证。跑不通，把报错喂回去让它修。AI 时代最值钱的技能不是&amp;quot;知道答案&amp;rdquo;，而是&amp;quot;会写验证答案的实验&amp;quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;提问质量 = 知识杠杆率。&lt;/strong&gt; &amp;ldquo;Redis 怎么用&amp;quot;和&amp;quot;Spring Boot 3.2 + Redis 7，Lettuce 客户端，缓存用户会话 TTL 30 分钟，缓存穿透怎么处理，给最小示例&amp;rdquo;——后者的答案质量是前者的十倍。你掌握的原理层知识越多，问题就描述得越精确，AI 的产出就越好。&lt;strong&gt;底层知识没有贬值，它变成了杠杆。&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;获取渠道换了三代：书 → 博客论坛 → 公众号 → AI。不变的是知识的分层，和你对&amp;quot;什么知识值得花时间&amp;quot;的判断力。AI 让&amp;quot;知道&amp;quot;变得廉价，让&amp;quot;验证&amp;quot;和&amp;quot;判断&amp;quot;变得昂贵——未来拉开差距的，不是谁知道得多，而是谁分得清：哪句话该信 AI，哪句话必须去翻官方文档。&lt;/p&gt;
</description>
    </item>
    <item>
      <title>高 bug 率的人该怎么对待：一场项目争论背后的对事、对人与系统</title>
      <link>https://ftmao.com/blog/posts/high-bug-rate-blame-or-system/</link>
      <pubDate>Mon, 19 Jan 2026 16:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/high-bug-rate-blame-or-system/</guid>
      <description>&lt;h1 id=&#34;高-bug-率的人该怎么对待一场项目争论背后的对事对人与系统&#34;&gt;高 bug 率的人该怎么对待：一场项目争论背后的对事、对人与系统&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;引子是一次真实的项目讨论。组里有位同事，bug 率明显比其他人高——高到 QA 看见他的名字心里先咯噔一下。评审会上大家争论怎么处理，冒出来的意见很能代表一个团队的所有本能：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;「对事不对人，先别急着怪他，看看是不是模块本身烂。」&lt;/li&gt;
&lt;li&gt;「烂模块别人也在写，怎么就他 bug 多？这就是人的问题，该敲打敲打。」&lt;/li&gt;
&lt;li&gt;「要我说这是架构问题，没测试没 CI，谁写都得出事。」&lt;/li&gt;
&lt;li&gt;「都 2026 年了，他连 AI 辅助 review、AI 生成测试都不用，bug 多不是活该吗？」&lt;/li&gt;
&lt;li&gt;「你们都想简单了，这模块需求天天变、接口文档是过期的，这是设计问题。」&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;五种说法，五种药方，互相打架。这场争论当时没有结论，但它逼我把一个问题想透了：&lt;strong&gt;「高 bug 率」根本不是一个问题，而是一组完全不同的问题共用的一个症状。&lt;/strong&gt; 这篇文章就是我后来理出来的完整处理路径：先把症状变成数据，再用方法论找全病根，然后对照行业里的成熟做法，最后才轮到那个最初的问题——对事，还是对人。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一第一步把高-bug-率变成数据&#34;&gt;一、第一步：把「高 bug 率」变成数据&lt;/h2&gt;
&lt;p&gt;「bug 率高」是一个印象，不是事实。争论对错之前，至少要回答三个数据问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;分母是什么？&lt;/strong&gt; 按提交算、按功能点算，还是按代码行算？写核心复杂模块的人和写边角页面的人，天然不在一个 bug 密度量级上。不比单位产出只比绝对数量，是争论里最常见的暗坑。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分布长什么样？&lt;/strong&gt; 他的 bug 集中在某个模块，还是均匀散布在他写的所有代码里？&lt;strong&gt;同一个模块换谁写都 bug 多，是系统的问题；同一个人换什么模块都 bug 多，才轮到看人。&lt;/strong&gt; 这一条能砍掉一半的争论。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bug 的类型是什么？&lt;/strong&gt; 是低级错误（空指针、边界、拼写），还是逻辑误解（需求理解错、边界场景没想到），还是集成事故（改了 A 弄坏 B）？三种 bug 指向三种完全不同的病根。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;没有这三组数据，会议上的所有「我觉得」都只是立场。这也引出一个朴素的道理：&lt;strong&gt;对事不对人的第一步，是先把「事」看清楚——多数争论不是因为价值观不同，而是因为大家在争论一个没被测量过的东西。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;二第二步逐条验证五种说法归并成四种病根&#34;&gt;二、第二步：逐条验证，五种说法归并成四种病根&lt;/h2&gt;
&lt;p&gt;拿着数据回到会上的五种说法，逐条对照，会发现它们归并完只剩四种病根：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;会上的说法&lt;/th&gt;
					&lt;th&gt;数据特征&lt;/th&gt;
					&lt;th&gt;归并后的病根&lt;/th&gt;
					&lt;th&gt;正确药方&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;「就是人的问题」&lt;/td&gt;
					&lt;td&gt;跨模块一致偏高，明知规范仍绕过，同类错误重复犯&lt;/td&gt;
					&lt;td&gt;能力、习惯或态度&lt;/td&gt;
					&lt;td&gt;先教练，无效再惩戒&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;「架构问题」＋「AI 用得太少」&lt;/td&gt;
					&lt;td&gt;全组在该模块都偏高；逃逸率高（漏到线上才发现）；重复性低级错误占比高、测试覆盖率远低于组均&lt;/td&gt;
					&lt;td&gt;工程系统缺位：无测试、无 CI、高耦合、反馈慢，AI 工具链未流程化&lt;/td&gt;
					&lt;td&gt;补工程系统（含 AI 工具链默认化），不怪任何人&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;「设计问题」&lt;/td&gt;
					&lt;td&gt;bug 集中在「需求理解偏差」类；返工率高&lt;/td&gt;
					&lt;td&gt;需求模糊、接口契约缺失&lt;/td&gt;
					&lt;td&gt;修设计流程，不归咎执行者&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;「对事不对人」所暗含的一次性事故&lt;/td&gt;
					&lt;td&gt;无模式，单次事故&lt;/td&gt;
					&lt;td&gt;运气 + 单点疏忽&lt;/td&gt;
					&lt;td&gt;复盘机制，不动人&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;为什么「AI 用得太少」和「架构问题」并成一行？因为用要素维度分解（后文的鱼骨图），AI 工具链属于「机」这一格：团队没把 AI review、AI 生成测试、CI 静态检查做成流程默认项，&lt;strong&gt;这是系统缺位，不是哪个成员的问题&lt;/strong&gt;。只有一种情况它退化回「人」：工具链已经是默认项，而个人拒绝使用——那属于习惯与技能问题，交给后文的 Just Culture 框架处理。&lt;/p&gt;
&lt;p&gt;这个 2026 年的新变量值得多说两句，因为它有正反两面。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;正面：「AI 用得不够」首先暴露的是系统问题，不是个人懒惰。&lt;/strong&gt; 如果团队没有默认开启 AI 辅助 review、没有用 AI 生成测试基线，那么「某个成员不用 AI」暴露的是工程系统的缺失——别人不用你也发现不了，只不过他 bug 多先暴露了。正确的动作是把 AI 工具链做成流程默认项，让所有产出都过一遍机器筛子，而不是点名批评谁「不拥抱新技术」。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;反面：AI 生成的代码不经 review 直接提交，是 2026 年高 bug 率的新来源。&lt;/strong&gt; 见过不止一个案例，当事人解释「这是 AI 写的，我大意了」——这不能作为免责理由。AI 是放大器，责任心仍是人的。用 AI 放大产出可以，用 AI 稀释 review 不行。规矩要立在团队层面：AI 生成的代码，提交人负全责。&lt;/p&gt;
&lt;p&gt;另外单独提醒「设计问题」：它是最容易被冤枉成「人的问题」的一种。需求一句话、接口靠口头、边界场景靠猜——这种环境下产出的 bug，记在程序员头上是冤案。判断方法很简单：把他的 bug 逐条归因，如果一半以上可以追溯到「需求没说清」，那该改的是需求评审流程，不是他。&lt;/p&gt;
&lt;h2 id=&#34;三第三步病根怎么找全三层归因方法论&#34;&gt;三、第三步：病根怎么找全——三层归因方法论&lt;/h2&gt;
&lt;p&gt;五种说法来自一场会议的即兴发挥，够用吗？不够。它漏掉的病根我在不同团队真实遇到过：排期压垮质量、度量口径本身失真、外部依赖的脏数据、频繁切换任务导致的认知断裂……经验枚举永远有盲区，需要的是一套能&lt;strong&gt;生成&lt;/strong&gt;假设、保证不重不漏的方法论。我用三把刀。&lt;/p&gt;
&lt;h3 id=&#34;第一把刀引入链分解按-bug-的生命周期切&#34;&gt;第一把刀：引入链分解——按 bug 的生命周期切&lt;/h3&gt;
&lt;p&gt;每个 bug 都有完整的生命周期：&lt;strong&gt;需求 → 设计 → 实现 → 验证 → 集成 → 发布 → 运行&lt;/strong&gt;。沿链条逐环问「这一环会不会是病根」，得到一阶的病根空间：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;环节&lt;/th&gt;
					&lt;th&gt;典型病根&lt;/th&gt;
					&lt;th&gt;会上的对应说法&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;需求&lt;/td&gt;
					&lt;td&gt;需求模糊、频繁变更、边界未定义&lt;/td&gt;
					&lt;td&gt;设计问题&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;设计&lt;/td&gt;
					&lt;td&gt;接口契约缺失、状态机没画清、边界场景没想&lt;/td&gt;
					&lt;td&gt;设计问题&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;实现&lt;/td&gt;
					&lt;td&gt;人的能力/习惯、AI 生成未审查&lt;/td&gt;
					&lt;td&gt;人的问题&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;验证&lt;/td&gt;
					&lt;td&gt;无测试、review 走过场、AI 工具链缺位&lt;/td&gt;
					&lt;td&gt;架构问题&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;集成&lt;/td&gt;
					&lt;td&gt;高耦合、改动半径不可控、无回归机制&lt;/td&gt;
					&lt;td&gt;架构问题&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;发布/运行&lt;/td&gt;
					&lt;td&gt;环境差异、脏数据、依赖服务不稳定&lt;/td&gt;
					&lt;td&gt;（会上没人提——这就是盲区）&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;会上的说法覆盖了链条的前五环，却漏了最后一环——线上 bug 里有相当比例是环境、数据、依赖造成的，把这类算在程序员头上是彻头彻尾的冤案。&lt;/p&gt;
&lt;h3 id=&#34;第二把刀鱼骨图-6m按要素维度切&#34;&gt;第二把刀：鱼骨图 6M——按要素维度切&lt;/h3&gt;
&lt;p&gt;链条切法还有交叉项漏网，再换制造业用了半个世纪的鱼骨图（人机料法环测）做一次正交检查：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    R[&amp;#34;高 bug 率&amp;#34;] --&amp;gt; M1[&amp;#34;人&amp;lt;br/&amp;gt;能力 / 习惯 / 态度 / 状态&amp;#34;]
    R --&amp;gt; M2[&amp;#34;机&amp;lt;br/&amp;gt;工具链 / AI / 编译检查 / IDE&amp;#34;]
    R --&amp;gt; M3[&amp;#34;料&amp;lt;br/&amp;gt;需求质量 / 文档 / 输入数据&amp;#34;]
    R --&amp;gt; M4[&amp;#34;法&amp;lt;br/&amp;gt;流程 / 评审 / 排期 / 协作方式&amp;#34;]
    R --&amp;gt; M5[&amp;#34;环&amp;lt;br/&amp;gt;环境 / 依赖服务 / 任务切换频率&amp;#34;]
    R --&amp;gt; M6[&amp;#34;测&amp;lt;br/&amp;gt;测试覆盖 / 度量口径 / 统计方法&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;6M 的价值在于它逼你检查&lt;strong&gt;平时想不到的维度&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;法&lt;/strong&gt;里面藏着排期——连续赶工期的团队，bug 率上升是生理现象，不是态度问题；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;环&lt;/strong&gt;里面藏着任务切换——一个人同时被拉在三个项目里，认知断裂本身就是 bug 发生器；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;测&lt;/strong&gt;里面藏着最容易被忽略的一条：&lt;strong&gt;度量口径本身可能就是病根&lt;/strong&gt;。bug 怎么算、谁录入、严不严格，直接扭曲「谁 bug 率高」的结论。度量一旦成为目标就不再是好的度量（古德哈特定律），拿失真的数据去惩人，是双重错误。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;两把刀交叉，病根空间基本不重不漏：链条保证流程维度不漏，6M 保证要素维度不漏。&lt;/p&gt;
&lt;h3 id=&#34;第三把刀控制变量把相关性变成归因&#34;&gt;第三把刀：控制变量——把相关性变成归因&lt;/h3&gt;
&lt;p&gt;枚举出假设只是开始，归因要靠实验设计。核心思想是方差分析：bug 率是人、模块、需求、流程的多元函数，&lt;strong&gt;每次只动一个变量，看结果怎么变&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;换人写同模块&lt;/strong&gt;：bug 率不降 → 模块/需求的问题；降了 → 继续往下查人；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;同一个人写别的模块&lt;/strong&gt;：还是高 → 人的嫌疑上升；正常了 → 原模块的问题；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;同一需求给两个人分别实现&lt;/strong&gt;：bug 模式相似 → 需求/设计的问题；差异大 → 实现者的问题；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;横向对照组&lt;/strong&gt;：全组在同类模块上的 bug 基线是多少？没有基线，「偏高」二字本身就不成立。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这一套换组实验做完，多数「人的问题」会现出系统问题的原形，剩下真正属于人的部分，才轮到第五节处理。&lt;/p&gt;
&lt;p&gt;最后补一个贯穿全程的视角：&lt;strong&gt;每个线上 bug 都是两个失败的乘积——引入失败 × 拦截失败。&lt;/strong&gt; 只问「谁引入的」是在追责，同时问「为什么六道防线一道都没拦住」才是在建设。前者最多改变一个人，后者改变的是所有人的未来 bug 率。&lt;/p&gt;
&lt;h2 id=&#34;四第四步行业里的成熟方法论与工具地图&#34;&gt;四、第四步：行业里的成熟方法论与工具地图&lt;/h2&gt;
&lt;p&gt;三把刀都不是我的发明：引入链分解借自 IBM 的 ODC，鱼骨图来自石川馨，控制变量是实验设计的基本功。行业里的原版比我的速记法成熟，这一节直接对照。&lt;/p&gt;
&lt;h3 id=&#34;归因层odc正交缺陷分类&#34;&gt;归因层：ODC（正交缺陷分类）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;ODC（Orthogonal Defect Classification，Chillarege 等人 1992 年提出）&lt;/strong&gt; 的核心思想比「按环节分类」更深一层：把每个缺陷沿&lt;strong&gt;正交维度&lt;/strong&gt;（缺陷类型、触发条件、影响、来源、发现阶段）打标签，然后看&lt;strong&gt;分布特征&lt;/strong&gt;而不是个案——某一类缺陷在某个阶段的分布异常，直接反推哪个流程环节出了系统性问题。缺陷不是待修的孤案，而是流程的测量数据。这套方法在 IBM、电信和航空软件里跑了三十多年。小团队不必全套上 ODC，但最低限度要做到：&lt;strong&gt;缺陷入库时强制填「引入阶段」和「缺陷类型」两个字段&lt;/strong&gt;——没有这两个字段，第一节要的三组数据永远凑不齐。&lt;/p&gt;
&lt;h3 id=&#34;度量层dora-指标与缺陷逃逸率&#34;&gt;度量层：DORA 指标与缺陷逃逸率&lt;/h3&gt;
&lt;p&gt;「bug 率高」的行业标准度量不是我拍脑袋的三个数据问题，而是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DORA 四键指标&lt;/strong&gt;（《Accelerate》）：其中的&lt;strong&gt;变更失败率&lt;/strong&gt;就是「高 bug 率」最权威的同行参照系——顶级团队与垫底团队的差距是数量级的，而且它和其他三项（部署频率、变更前置时间、恢复时间）联动，单独压 bug 率而不管交付节奏是缘木求鱼；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺陷逃逸率&lt;/strong&gt;：bug 在哪一环被发现（开发自测 / CI / 测试 / 线上），逃逸越靠后成本越高——这是「拦截失败」的标准度量；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺陷聚集热力图&lt;/strong&gt;：Microsoft Research 多年的实证结论——&lt;strong&gt;缺陷不是均匀分布的，少数模块集中了多数缺陷&lt;/strong&gt;（与改动频率和复杂度强相关），CodeScene 这类工具可以直接画出热度图。这直接支撑了「换人写同模块」实验的必要性：热点模块的 bug 率天然高，不控变量就是在冤枉人。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;防线层shift-left-与工具链&#34;&gt;防线层：shift-left 与工具链&lt;/h3&gt;
&lt;p&gt;「拦截失败 × 引入失败」的乘积视角，行业里叫 &lt;strong&gt;shift-left（防线左移）&lt;/strong&gt;，配套工具链已经高度成熟：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;防线&lt;/th&gt;
					&lt;th&gt;方法论&lt;/th&gt;
					&lt;th&gt;工具（2026 年现网主流）&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;需求/设计&lt;/td&gt;
					&lt;td&gt;设计评审、ADR（架构决策记录）、契约先行&lt;/td&gt;
					&lt;td&gt;设计文档模板 + 评审清单&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;实现&lt;/td&gt;
					&lt;td&gt;测试金字塔（Google 的 small/medium/large 分层）、TDD、小步提交&lt;/td&gt;
					&lt;td&gt;JUnit/pytest + 覆盖率（只做参考，&lt;strong&gt;不做 KPI&lt;/strong&gt;——古德哈特定律）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;静态检查&lt;/td&gt;
					&lt;td&gt;静态分析、安全扫描&lt;/td&gt;
					&lt;td&gt;SonarQube、CodeQL、Semgrep、Snyk&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;测试有效性&lt;/td&gt;
					&lt;td&gt;变异测试（测试的测试）、模糊测试&lt;/td&gt;
					&lt;td&gt;PIT（Java）、fuzzing&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;代码审查&lt;/td&gt;
					&lt;td&gt;AI review 设为 CI 默认门禁&lt;/td&gt;
					&lt;td&gt;CodeRabbit（部署最广）、Greptile（全库上下文检出率最高）、Qodo（审查+测试生成）、GitHub Copilot Code Review、Cursor BugBot&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;测试生成&lt;/td&gt;
					&lt;td&gt;AI 生成单测基线&lt;/td&gt;
					&lt;td&gt;Diffblue Cover（Java）、Qodo&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;集成/发布&lt;/td&gt;
					&lt;td&gt;Trunk-Based Development、小 PR、CI 门禁、灰度&lt;/td&gt;
					&lt;td&gt;GitHub Actions / GitLab CI + 特性开关&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;两点强调：其一，2026 年的 AI review 工具已经过了尝鲜期——CodeRabbit 接入仓库数以百万计，Greptile 靠全代码库索引拿到最高检出率——&lt;strong&gt;「不配 AI 审查门禁」在今天的含义，和五年前「没有 CI」一样，属于工程系统缺位&lt;/strong&gt;，这印证了第二节把 AI 折进工程系统的判断。其二，覆盖率、检出数这些指标一旦变成 KPI 就失效，它们只能用来找盲区，不能用来排名。&lt;/p&gt;
&lt;h3 id=&#34;机制层错误预算与安灯宽猛相济的工程制度&#34;&gt;机制层：错误预算与安灯——「宽猛相济」的工程制度&lt;/h3&gt;
&lt;p&gt;第六节要讲的「宽与猛怎么配比」，行业里其实早已制度化了，两个现成机制：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Google SRE 的错误预算（error budget）&lt;/strong&gt;：服务的可靠性目标不是 100%，预算内随你折腾（宽），预算耗尽立刻冻结发布、全员还债（猛）。&lt;strong&gt;什么时候该宽、什么时候该猛，不再依赖管理者的手感，而是事先约定的数字&lt;/strong&gt;——这是「小惩大诫」最优雅的工程实现：惩戒自动触发、剂量事先封顶、对事不对人。书架上有我的《&lt;a href=&#34;https://ftmao.com/blog/blog/bookstore/google-sre-workbook-summary/&#34;&gt;Google SRE 工作手册&lt;/a&gt;》笔记，错误预算一章值得细看；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;丰田的安灯（andon）与防错（poka-yoke）&lt;/strong&gt;：流水线上任何人发现异常都可以拉灯停线——「对事不对人」不是口号，是给一线员工的制度性权力；防错设计则直接把「人会犯的错」设计成「犯不了的错」，比任何惩戒都高明。bug 率治理的终极目标不是把人管得更紧，而是让低级错误在机制上不可能发生。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以这一节的结论是：&lt;strong&gt;归因用 ODC 的思想，度量用 DORA 与逃逸率，防线用 shift-left 工具链，宽猛用错误预算制度化&lt;/strong&gt;——我的「三把刀」只是这些成熟方法论的穷人版速记，团队有条件就应该直接上原版。&lt;/p&gt;
&lt;h2 id=&#34;五第五步轮到人之后just-culture-三分法&#34;&gt;五、第五步：轮到「人」之后——Just Culture 三分法&lt;/h2&gt;
&lt;p&gt;排除了系统性因素，剩下的才轮到「人」——而处理人，最怕一刀切。安全科学里的 Just Culture（公正文化）把行为分成三类，每一类的正确回应完全不同：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;行为类型&lt;/th&gt;
					&lt;th&gt;特征&lt;/th&gt;
					&lt;th&gt;正确回应&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;无心之失&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;认真做事，但人都会犯错&lt;/td&gt;
					&lt;td&gt;安慰 + 改系统，绝不罚——罚了以后没人再上报错误&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;侥幸冒险&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;明知规范但绕过了（侥幸没出事）&lt;/td&gt;
					&lt;td&gt;教练谈话，明确后果预期&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;明知故犯 / 屡教不改&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;重复再犯，警示无效&lt;/td&gt;
					&lt;td&gt;及时、小剂量、成比例的惩戒&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;用「对事不对人」处理第三类，是在纵容——恶意和惰性会学习你的宽容；用「敲打」处理第一类，是在制造恐惧文化——从此 bug 都被藏起来，团队失去学习的输入。&lt;strong&gt;冲突从来不在两种哲学之间，而在用错了对象。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;六更高处的统一系辞与-dalio&#34;&gt;六、更高处的统一：系辞与 Dalio&lt;/h2&gt;
&lt;p&gt;「对事不对人」和「该敲打敲打」这两种看似对立的姿态，恰好各有一部经典背书，而两部经典在更高处说的是同一件事。&lt;/p&gt;
&lt;p&gt;《系辞传》讲「小惩而大诫，此小人之福也」。注意上下文——它的对象是「不威不惩」的小人，即内在荣辱感已经失灵、只有外部后果能触动的人；注意落点——&lt;strong&gt;「福」&lt;/strong&gt;：惩罚是为受罚者本人的长远利益服务的。《系辞》下传还有一句「无有师保，如临父母」：管教的心是父母之心，不是权力之心。&lt;/p&gt;
&lt;p&gt;Dalio《原则》讲对事不对人、极端求真（radical truth）：当面指出问题比背后留情更仁慈，这是 tough love。&lt;/p&gt;
&lt;p&gt;两者在最高处是同一个标准：&lt;strong&gt;评价一次干预，不看「有没有怪罪人」，而看「它有没有让反馈回路闭合、有没有让这个人变得更好」。&lt;/strong&gt; 惩罚如果是为了发泄、为了立威，就同时背叛了系辞和 Dalio。孔子论政说「宽以济猛，猛以济宽，政是以和」——宽（对事不对人）与猛（小惩大诫）不是二选一，是因人因事因时动态配比的阴阳两端：只宽不猛，组织失去边界；只猛不宽，组织失去真话。&lt;/p&gt;
&lt;p&gt;而两者共同信仰的，是《系辞》最看重的那个字——&lt;strong&gt;几&lt;/strong&gt;：「几者，动之微，吉之先见者也。」反馈必须早、必须真、必须被感觉到。Dalio 用真话送达信号，「小惩」用后果送达信号；信道不同，信源相同。等到 bug 率高到要开会讨论才处理，已经是「不俟终日」的反面教材了。&lt;/p&gt;
&lt;h2 id=&#34;七落地一棵判断树&#34;&gt;七、落地：一棵判断树&lt;/h2&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    A[&amp;#34;有人 bug 率明显偏高&amp;#34;] --&amp;gt; B{&amp;#34;看分布&amp;lt;br/&amp;gt;同一模块换谁写都高?&amp;#34;}
    B -- &amp;#34;是&amp;#34; --&amp;gt; C[&amp;#34;系统问题&amp;lt;br/&amp;gt;补测试 / CI / 解耦 / AI 工具链&amp;lt;br/&amp;gt;不归咎任何人&amp;#34;]
    B -- &amp;#34;否, 集中在他&amp;#34; --&amp;gt; D{&amp;#34;看 bug 类型&amp;#34;}
    D -- &amp;#34;需求理解偏差类居多&amp;#34; --&amp;gt; E[&amp;#34;设计问题&amp;lt;br/&amp;gt;修需求评审与接口契约流程&amp;#34;]
    D -- &amp;#34;低级重复错误居多&amp;#34; --&amp;gt; F[&amp;#34;工程系统缺失&amp;lt;br/&amp;gt;AI review / 测试基线设为流程默认项&amp;#34;]
    D -- &amp;#34;集成事故类居多&amp;#34; --&amp;gt; G[&amp;#34;架构耦合问题&amp;lt;br/&amp;gt;修边界与回归机制&amp;#34;]
    D -- &amp;#34;跨模块一致的逻辑疏忽&amp;#34; --&amp;gt; H{&amp;#34;看人: 无心之失&amp;lt;br/&amp;gt;还是明知故犯 / 屡教不改?&amp;#34;}
    H -- &amp;#34;无心之失&amp;#34; --&amp;gt; I[&amp;#34;教练 + 结对 + 改系统&amp;lt;br/&amp;gt;绝不罚, 保护上报文化&amp;#34;]
    H -- &amp;#34;侥幸冒险 / 屡教不改&amp;#34; --&amp;gt; J[&amp;#34;小惩大诫&amp;lt;br/&amp;gt;及时 / 小剂量 / 成比例&amp;lt;br/&amp;gt;动手前自问: 如临父母还是发泄立威?&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;动手惩戒之前，还有最后一道自检，三个问题按顺序问：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;什么类型？&lt;/strong&gt; 无心之失和善意试错，绝不罚——罚了就没人上报了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多早？&lt;/strong&gt; 越早代价越小，「小惩而大诫」的精髓不在「惩」而在「小」。问题在几微阶段，一次明确而克制的谈话就够；拖到「恶积而不可掩」，就只剩「何校灭耳」的选项了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;为谁？&lt;/strong&gt; 这一下是为了回路闭合和他的成长，还是为了我的情绪？答不出前者，就不该动。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;八收尾&#34;&gt;八、收尾&lt;/h2&gt;
&lt;p&gt;回到那场争论。会后我做了归因分析，结论有点扫兴也有点解脱：那位同事 60% 的 bug 出在同一个无测试、需求三天一变的老模块——是系统问题；剩下的低级错误里，AI review 能拦掉一大半——是工程系统问题；真正属于他个人的，是重复犯同类边界错误、提醒后照旧——这部分，一次正式的、明确的、带着「这是为你好」的谈话处理了。三个月后，他的 bug 率回到了组内均值。&lt;/p&gt;
&lt;p&gt;所以「对事还是对人」是个伪问题。完整的答案是：&lt;strong&gt;先对事（数据归因），再对系统（方法论找全病根），最后才对人（Just Culture 分类处理）；对事不对人是道的常态，小惩大诫是道的边界——两者的根，都是让反馈回路不失灵。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;延伸阅读：书架上《&lt;a href=&#34;https://ftmao.com/blog/blog/bookstore/principles-dalio-book-notes/&#34;&gt;原则&lt;/a&gt;》笔记里有 Dalio 的完整决策框架，《&lt;a href=&#34;https://ftmao.com/blog/blog/bookstore/thinking-in-systems-book-notes/&#34;&gt;系统之美&lt;/a&gt;》讲透了为什么「人的行为来自系统结构」，《&lt;a href=&#34;https://ftmao.com/blog/blog/bookstore/yijing-64-hexagrams-index/&#34;&gt;系辞传全文与白话译文&lt;/a&gt;》里有「小惩大诫」的原文语境。&lt;/p&gt;
</description>
    </item>
    <item>
      <title>《架构整洁之道》详解：从编程范式到整洁架构的完整指南</title>
      <link>https://ftmao.com/blog/bookstore/clean-architecture-book-notes/</link>
      <pubDate>Sat, 26 Oct 2024 14:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/bookstore/clean-architecture-book-notes/</guid>
      <description>&lt;h1 id=&#34;架构整洁之道详解从编程范式到整洁架构的完整指南&#34;&gt;《架构整洁之道》详解：从编程范式到整洁架构的完整指南&lt;/h1&gt;
&lt;p&gt;《架构整洁之道》（Clean Architecture: A Craftsman&amp;rsquo;s Guide to Software Structure and Design）是 Robert C. Martin（业内昵称&amp;quot;Bob 大叔&amp;quot;）在 2017 年出版的一本书。它是《代码整洁之道》《敏捷软件开发》之后，Bob 大叔对自己半个世纪编程经验的又一次系统总结。&lt;/p&gt;
&lt;p&gt;这本书要回答的核心问题只有一个：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;如何设计一个系统，让它活得久、改得动、换得起？&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;本文面向&lt;strong&gt;从未接触过软件架构概念&lt;/strong&gt;的读者，按照全书六个部分的结构逐层展开，配合图示和实例，帮你建立完整的知识地图。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第一部分-概述架构到底是什么&#34;&gt;第一部分 概述：架构到底是什么&lt;/h2&gt;
&lt;h3 id=&#34;1-设计与架构没有本质区别&#34;&gt;1. 设计与架构没有本质区别&lt;/h3&gt;
&lt;p&gt;很多人以为&amp;quot;架构&amp;quot;是宏观的大图，&amp;ldquo;设计&amp;quot;是微观的细节。Bob 大叔认为两者其实是一回事：&lt;strong&gt;架构就是系统设计，系统设计就是架构&lt;/strong&gt;。它们只是在不同的观察尺度上，不存在一条清晰的分界线，两者共同决定了系统的整体形状。&lt;/p&gt;
&lt;p&gt;一个好的架构目标可以用一句话概括：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;用最小的人力成本来满足构建和维护该系统的需求。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;衡量标准很朴素：如果每次新增需求所需的成本都维持在较低水平，不会因为系统变老而持续上升，那这个架构就是好架构。反之，如果每改一个功能都要牵动全身、修复一个 bug 引入三个新 bug，那架构就已经坏掉了。&lt;/p&gt;
&lt;h3 id=&#34;2-两个价值维度行为与架构&#34;&gt;2. 两个价值维度：行为与架构&lt;/h3&gt;
&lt;p&gt;每个软件系统都为利益相关者提供两种不同的价值：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;行为价值（Behavior）&lt;/strong&gt;：系统按需求文档干活，帮用户赚钱或省钱。这是程序员最关注的部分。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;架构价值（Architecture）&lt;/strong&gt;：软件之所以叫&amp;quot;软&amp;quot;件，就在于它&lt;strong&gt;应该是容易被修改的&lt;/strong&gt;。如果需求变了而系统改不动，这个系统就失败了。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这两者的优先级，可以用艾森豪威尔矩阵（重要/紧急四象限）来思考：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;&lt;/th&gt;
					&lt;th&gt;紧急&lt;/th&gt;
					&lt;th&gt;不紧急&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;重要&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;① 行为（功能）&lt;/td&gt;
					&lt;td&gt;② &lt;strong&gt;架构&lt;/strong&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;不重要&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;③ 界面细节等&lt;/td&gt;
					&lt;td&gt;④ 杂项&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;常见的悲剧是：业务经理永远把&amp;quot;紧急&amp;quot;排在前面，于是团队不断牺牲&amp;quot;重要但不紧急&amp;quot;的架构，最终系统腐化到无法维护。&lt;strong&gt;为好的架构而斗争，是软件架构师的职责&lt;/strong&gt;——因为架构师本身就是利益相关者之一。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第二部分-从基础构件开始编程范式&#34;&gt;第二部分 从基础构件开始：编程范式&lt;/h2&gt;
&lt;p&gt;这一部分回顾了软件历史上仅有的三种编程范式。一个很有意思的观点是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这三种范式都是 1958–1968 这十年间被发明的，之后的几十年里，&lt;strong&gt;再也没有出现过新的编程范式&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;三种范式它们都在限制我们&#34;&gt;三种范式：它们都在&amp;quot;限制&amp;quot;我们&lt;/h3&gt;
&lt;p&gt;三种范式有一个共性：它们都&lt;strong&gt;剥夺&lt;/strong&gt;了程序员的某种能力，而不是赋予新能力。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[结构化编程&amp;lt;br/&amp;gt;1968 Dijkstra] --&amp;gt;|限制了| A1[goto 的无约束跳转]
    B[面向对象编程&amp;lt;br/&amp;gt;1966 Simula] --&amp;gt;|限制了| B1[函数指针的滥用&amp;lt;br/&amp;gt;通过多态接管间接调用]
    C[函数式编程&amp;lt;br/&amp;gt;1958 λ演算/Lisp] --&amp;gt;|限制了| C1[赋值语句&amp;lt;br/&amp;gt;变量不可变性]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;结构化编程&lt;/strong&gt;（Structured Programming）：用顺序、选择、循环三种结构取代了自由的 &lt;code&gt;goto&lt;/code&gt; 跳转。Dijkstra 的名言是&amp;quot;goto 是有害的&amp;rdquo;。这一范式还带来一个深远影响：既然无法数学式地&amp;quot;证明&amp;quot;程序正确，那就通过&lt;strong&gt;测试&lt;/strong&gt;来&amp;quot;证伪&amp;quot;——结构化编程催生了测试驱动开发（TDD）的思想土壤。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;面向对象编程&lt;/strong&gt;（OOP）：把数据和函数打包成对象。关于 OOP 的三大特性，书里有独到见解：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;封装&lt;/strong&gt;：其实 C 语言用头文件隐藏实现做得更好，OOP 反而弱化了它；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;继承&lt;/strong&gt;：C 程序员早就手动模拟过，OOP 只是让它更方便；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多态&lt;/strong&gt;：这才是 OOP 真正的核心——它让&lt;strong&gt;源码依赖方向可以被反转&lt;/strong&gt;。调用方通过接口调用实现方，源码依赖与控制流方向相反，这就是全书后面反复强调的&amp;quot;依赖反转&amp;quot;的武器。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;函数式编程&lt;/strong&gt;（Functional Programming）：核心思想是&lt;strong&gt;变量不可变&lt;/strong&gt;（immutability）。没有赋值，就没有竞态条件、死锁、并发更新问题——所有并发 bug 都源于可变状态。现实中的妥协方案是&amp;quot;事件溯源&amp;quot;（Event Sourcing）：只存事务日志，不存状态，需要状态时用日志重算，从而消灭对可变状态的依赖。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;给初学者的 takeaway&lt;/strong&gt;：软件架构的底层构件只有三种——顺序、分支、循环；而范式教会我们的是&amp;quot;&lt;strong&gt;不要做什么&lt;/strong&gt;&amp;quot;，架构师要在这些限制内搭建系统。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第三部分-设计原则solid&#34;&gt;第三部分 设计原则：SOLID&lt;/h2&gt;
&lt;p&gt;SOLID 是函数/类级别（中层）的五个设计原则，目标是让软件模块：&lt;strong&gt;可扩展、易修改、易理解、可复用&lt;/strong&gt;。这部分是全书中读者基础最扎实的部分，但书里对每条原则的理解都比市面上的通俗版本更精确。&lt;/p&gt;
&lt;h3 id=&#34;srp-单一职责原则&#34;&gt;SRP 单一职责原则&lt;/h3&gt;
&lt;p&gt;最容易被误解的一条。SRP &lt;strong&gt;不是&lt;/strong&gt;&amp;ldquo;每个函数只做一件事&amp;rdquo;（那是函数层面的小原则），而是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一个模块应该只对一类行为者（actor）负责。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;经典例子：一个 &lt;code&gt;Employee&lt;/code&gt; 类同时有 &lt;code&gt;calculatePay()&lt;/code&gt;（CFO 关心）、&lt;code&gt;reportHours()&lt;/code&gt;（COO 关心）、&lt;code&gt;save()&lt;/code&gt;（CTO 关心）三个方法。三个方法服务三类不同的行为者，一旦为 CFO 改的代码意外影响了 COO 的报表，就是 SRP 被破坏的恶果。解法是按行为者拆分。&lt;/p&gt;
&lt;h3 id=&#34;ocp-开闭原则&#34;&gt;OCP 开闭原则&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;对扩展开放，对修改关闭。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;新增行为应该通过增加代码完成，而不是修改旧代码。实现手段是：把系统按&amp;quot;变更原因&amp;quot;分层，高层策略组件不受低层细节变化的影响，层与层之间用接口隔离。&lt;/p&gt;
&lt;h3 id=&#34;lsp-里氏替换原则&#34;&gt;LSP 里氏替换原则&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;任何使用基类的地方，替换成子类后行为依然正确。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;违反 LSP 的著名案例：正方形（Square）继承长方形（Rectangle）——&lt;code&gt;setWidth&lt;/code&gt; 语义在两个类里不一致，导致依赖长方形的代码出问题。在架构层面，LSP 延伸为&amp;quot;接口/服务的可替换性&amp;quot;：REST 接口协议不一致，本质上也是违反 LSP。&lt;/p&gt;
&lt;h3 id=&#34;isp-接口隔离原则&#34;&gt;ISP 接口隔离原则&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;不要强迫使用者依赖它们不需要的方法。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;胖接口会引入不必要的依赖——改动接口中别人不用的部分，也会强制所有使用者重新编译部署。架构层面的教训：&lt;strong&gt;任何层次的&amp;quot;多余依赖&amp;quot;都有害&lt;/strong&gt;，包括对一个打包了过多功能的库或框架的依赖。&lt;/p&gt;
&lt;h3 id=&#34;dip-依赖反转原则&#34;&gt;DIP 依赖反转原则&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;源码依赖方向应与控制流方向相反，高层策略不应依赖低层细节，两者都应依赖抽象。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这是整本书的&amp;quot;元原则&amp;quot;。Java 这类语言中，接口声明属于调用方一侧（内层），实现类属于被调用方一侧（外层），依赖箭头跨过边界指向内层——这就是后面整洁架构能成立的技术基础。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    subgraph 内层[&amp;#34;内层（高层策略）&amp;#34;]
        UC[用例] --&amp;gt;|依赖接口| PORT[&amp;#34;接口（输出端口）&amp;#34;]
    end
    subgraph 外层[&amp;#34;外层（实现细节）&amp;#34;]
        IMPL[Presenter 实现] -.实现.-&amp;gt; PORT
        DB[数据库实现] -.实现.-&amp;gt; PORT2[数据访问接口]
    end
    UC --&amp;gt;|依赖接口| PORT2
    style 内层 fill:#e8f0fe
    style 外层 fill:#fef3e8&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;第四部分-组件构建原则&#34;&gt;第四部分 组件构建原则&lt;/h2&gt;
&lt;p&gt;SOLID 管&amp;quot;类和函数怎么写&amp;quot;，这一部分管&amp;quot;&lt;strong&gt;类和函数怎么打包成组件&lt;/strong&gt;&amp;quot;（组件 = 独立部署的最小单元，如 jar、dll、服务）。&lt;/p&gt;
&lt;h3 id=&#34;组件聚合三原则哪些类该放进一个组件&#34;&gt;组件聚合三原则（哪些类该放进一个组件）&lt;/h3&gt;
&lt;p&gt;三条原则互相拉扯，架构师要做权衡：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;REP 复用/发布等同原则&lt;/strong&gt;：组件的复用粒度等于发布粒度——一个组件里的东西应该作为一个整体被版本化和发布。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CCP 共同闭包原则&lt;/strong&gt;：会因同一原因、同一时间变化的类应该聚在同一个组件里（SRP 在组件层面的放大版）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CRP 共同复用原则&lt;/strong&gt;：不要强迫组件的使用者依赖他们不需要的东西（ISP 在组件层面的放大版）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;CCP 和 CRP 通常是矛盾的：按变更原因聚合容易，但组件可能变胖；按使用方切得细，组件数量又会爆炸。&lt;/p&gt;
&lt;h3 id=&#34;组件耦合三原则组件之间怎么依赖&#34;&gt;组件耦合三原则（组件之间怎么依赖）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ADP 无环依赖原则&lt;/strong&gt;：组件依赖图中不允许出现环。打破环的手段有两个：依赖反转（在中间加接口）或新建一个公共组件。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SDP 稳定依赖原则&lt;/strong&gt;：依赖方向必须指向更稳定的组件。越难修改的组件越稳定，不稳定（易变）的组件应该依赖稳定的组件。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SAP 稳定抽象原则&lt;/strong&gt;：越稳定的组件应该越抽象。所以架构应该是：&lt;strong&gt;核心策略 = 稳定 + 抽象（接口）&lt;/strong&gt;，外围实现 = 不稳定 + 具体。极端的两端是&amp;quot;最稳定且最抽象&amp;quot;（理想）和&amp;quot;最不稳定且最具体&amp;quot;（胶水代码），大部分组件落在两者之间的主序列附近。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第五部分-软件架构本书的核心&#34;&gt;第五部分 软件架构：本书的核心&lt;/h2&gt;
&lt;h3 id=&#34;1-什么是软件架构&#34;&gt;1. 什么是软件架构&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;架构的实质是：&lt;strong&gt;尽可能长时间地推迟决策，保留尽可能多的可选项。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;一个软件系统的架构质量，取决于它&lt;strong&gt;支撑系统全生命周期&lt;/strong&gt;的能力：开发、部署、运行、维护。好的架构让系统易于理解、易于开发、易于维护、易于部署，其终极目标是&lt;strong&gt;最小化整个生命周期的人力成本&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;架构的次要目标还包括让系统可以按用例独立开发、独立部署、独立运行。&lt;/p&gt;
&lt;h3 id=&#34;2-独立性&#34;&gt;2. 独立性&lt;/h3&gt;
&lt;p&gt;好架构必须支持：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;按用例解耦&lt;/strong&gt;：购物车、订单、用户管理……不同用例之间的代码互不干扰；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;解耦运行方式&lt;/strong&gt;：系统可以跑成单体、SOA、微服务，部署方式是可推迟的决策；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;独立开发与独立部署&lt;/strong&gt;：不同团队可以并行开发不同模块而互不阻塞。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;3-划分边界软件架构就是画线的艺术&#34;&gt;3. 划分边界：软件架构就是画线的艺术&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;架构师的工作是&lt;strong&gt;画出边界线&lt;/strong&gt;，把系统分割成组件。一部分组件实现核心业务逻辑，另一部分组件作为与核心逻辑无关的&amp;quot;插件&amp;quot;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;划线的依据是：&lt;strong&gt;哪些东西变化的原因不同&lt;/strong&gt;。UI、数据库、业务规则、第三方服务，它们变化的理由完全不同，所以它们之间应该画线。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    UI[用户界面] -.插件式依赖.-&amp;gt; CORE
    DB[(数据库)] -.插件式依赖.-&amp;gt; CORE
    WEB[Web 框架] -.插件式依赖.-&amp;gt; CORE
    EXT[外部服务] -.插件式依赖.-&amp;gt; CORE
    subgraph CORE[&amp;#34;核心业务逻辑（不知道外围的存在）&amp;#34;]
        ENT[业务实体]
        UC[用例]
    end
    style CORE fill:#e8f0fe&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;关键点：&lt;strong&gt;核心业务逻辑根本不依赖这些东西，是这些东西反过来依赖核心业务逻辑&lt;/strong&gt;。数据库不是系统的中心，业务规则才是。这种&amp;quot;以核心业务为中心，外设皆插件&amp;quot;的思想，Bob 大叔称之为&lt;strong&gt;插件式架构&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;4-边界剖析跨边界的调用怎么发生&#34;&gt;4. 边界剖析：跨边界的调用怎么发生&lt;/h3&gt;
&lt;p&gt;跨越边界的通信分两种方向：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;顺控制流调用&lt;/strong&gt;（外层主动调用内层）：直接调用即可，依赖方向天然向内，没有架构问题；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;逆控制流回调&lt;/strong&gt;（内层需要通知外层）：必须用&lt;strong&gt;依赖反转&lt;/strong&gt;——内层定义接口，外层实现接口，保证源码依赖依然指向内层。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;5-策略与层次&#34;&gt;5. 策略与层次&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;软件系统就是一种对&amp;quot;策略（policy）&amp;ldquo;的陈述：详细描述如何根据输入计算输出。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;架构要做的就是把策略按&lt;strong&gt;层次&lt;/strong&gt;分开：越靠近输入输出的策略层次越低（格式化、校验、IO），越远离的策略层次越高（业务规则）。高层策略与低层细节分离，低层策略应该成为高层策略的&amp;quot;插件&amp;rdquo;。&lt;/p&gt;
&lt;h3 id=&#34;6-业务逻辑实体与用例&#34;&gt;6. 业务逻辑：实体与用例&lt;/h3&gt;
&lt;p&gt;书里把业务逻辑分成两类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;业务实体（Entity / 关键业务逻辑）&lt;/strong&gt;：与具体应用无关的、企业级最核心的规则。比如&amp;quot;贷款利率 = 基准利率 + 风险系数&amp;quot;，不管系统做成 Web、APP 还是命令行，这条规则都不变。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用例（Use Case / 应用相关逻辑）&lt;/strong&gt;：描述某个具体应用如何使用实体。比如&amp;quot;贷款审批流程：先查征信 → 再评估额度 → 最后出审批结果&amp;quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;用例依赖实体，实体不知道用例的存在。&lt;strong&gt;实体是最通用、最高层、最不容易变的；用例次之。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;7-尖叫的软件架构&#34;&gt;7. 尖叫的软件架构&lt;/h3&gt;
&lt;p&gt;这一章名字很有趣：&lt;strong&gt;你的架构应该&amp;quot;尖叫&amp;quot;着说出自己是做什么的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;看一个系统的目录结构，如果看到的是 &lt;code&gt;models / views / controllers&lt;/code&gt;，这是框架在尖叫；如果看到的是 &lt;code&gt;order / payment / shipping&lt;/code&gt;（订单、支付、物流），这是业务在尖叫。好的架构应该一眼看出这是一个&amp;quot;图书管理系统&amp;quot;或&amp;quot;电商系统&amp;quot;，而不是&amp;quot;一个 Spring MVC 项目&amp;quot;。框架是工具，不应该喧宾夺主。&lt;/p&gt;
&lt;h3 id=&#34;8-整洁架构四层同心圆全书最著名的一张图&#34;&gt;8. 整洁架构：四层同心圆（全书最著名的一张图）&lt;/h3&gt;
&lt;p&gt;Bob 大叔综合了三种前辈架构思想——六边形架构（Hexagonal / Ports and Adapters）、DCI、BCE——提炼出统一的模型：&lt;strong&gt;整洁架构（The Clean Architecture）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这些架构的共同特点是系统：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;独立于框架&lt;/strong&gt;：框架是工具，不是牢笼；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可被测试&lt;/strong&gt;：业务逻辑可以脱离 UI、数据库、Web 服务测试；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;独立于 UI&lt;/strong&gt;：Web 界面换成命令行界面，业务逻辑一行不用改；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;独立于数据库&lt;/strong&gt;：Oracle 换成 MongoDB，业务逻辑不知道；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;独立于任何外部机构&lt;/strong&gt;：业务逻辑对外部接口一无所知。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;用 Mermaid 重绘经典的同心圆图（越靠内层，策略越高层、越抽象）：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    subgraph L4[&amp;#34;🟠 框架与驱动层 Frameworks &amp;amp; Drivers（最外层：实现细节）&amp;#34;]
        direction LR
        FW[Web 框架]
        DB[(数据库)]
        DEV[设备 / 外部接口]
    end
    subgraph L3[&amp;#34;🟡 接口适配层 Interface Adapters&amp;#34;]
        direction LR
        CTRL[Controllers]
        PRE[Presenters]
        GATE[Gateways]
    end
    subgraph L2[&amp;#34;🟢 用例层 Use Cases（应用业务逻辑）&amp;#34;]
        UC[用例交互器&amp;lt;br/&amp;gt;Use Case Interactor]
    end
    subgraph L1[&amp;#34;🔵 业务实体层 Entities（企业核心业务逻辑，最内层）&amp;#34;]
        ENT[业务实体]
    end
    L4 --&amp;gt;|依赖指向内层| L3
    L3 --&amp;gt;|依赖指向内层| L2
    L2 --&amp;gt;|依赖指向内层| L1&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;贯穿整个架构的只有一条&lt;strong&gt;依赖关系规则（The Dependency Rule）&lt;/strong&gt;：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;源码中的依赖关系必须只指向同心圆的内层，即由低层机制指向高层策略。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;内层代码不能引用外层代码中声明的任何名字——函数、类、变量，一概不行。外层使用的数据格式（尤其是框架生成的）也不能被内层使用。&lt;/p&gt;
&lt;p&gt;各层职责：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;业务实体（Entities）&lt;/strong&gt;：封装全系统最关键、最通用的业务逻辑，是整个系统中最不容易受外界变化影响的部分。页面导航改版、安全策略调整，都不应该触及实体层。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用例（Use Cases）&lt;/strong&gt;：封装特定应用的业务逻辑，指挥数据在实体间流动，调度实体完成用例目标。用例的变化不影响实体，数据库/UI 的变化也不影响用例。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;接口适配器（Interface Adapters）&lt;/strong&gt;：一组数据格式转换器。GUI 的整个 MVC 都在这一层：Controllers、Presenters、Views。所有 SQL 语句也应该被限制在这一层。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;框架与驱动（Frameworks &amp;amp; Drivers）&lt;/strong&gt;：最外层，只写与内层沟通的胶水代码。Web 是细节，数据库是细节，都放在外层，&amp;ldquo;让它们伤不到内层&amp;rdquo;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：四层不是硬性规定，真正的系统可以多于四层，但依赖关系规则永远成立。&lt;/p&gt;
&lt;h3 id=&#34;9-一个完整的请求流程web--数据库的典型场景&#34;&gt;9. 一个完整的请求流程（Web + 数据库的典型场景）&lt;/h3&gt;
&lt;p&gt;书中最实用的一张图描述了一个请求如何穿越各层。用&amp;quot;用户下单&amp;quot;为例：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;sequenceDiagram
    participant U as 用户
    participant C as Controller&amp;lt;br/&amp;gt;(接口适配层)
    participant IB as InputBoundary&amp;lt;br/&amp;gt;(输入端口接口, 用例层)
    participant UCI as UseCaseInteractor&amp;lt;br/&amp;gt;(用例层)
    participant E as Entities&amp;lt;br/&amp;gt;(实体层)
    participant DAI as DataAccessInterface&amp;lt;br/&amp;gt;(数据访问接口, 用例层)
    participant DB as 数据库实现&amp;lt;br/&amp;gt;(最外层)
    participant OB as OutputBoundary&amp;lt;br/&amp;gt;(输出端口接口, 用例层)
    participant P as Presenter&amp;lt;br/&amp;gt;(接口适配层)
    participant V as View&amp;lt;br/&amp;gt;(最外层)

    U-&amp;gt;&amp;gt;C: 提交订单(HTTP 请求)
    C-&amp;gt;&amp;gt;IB: 包装成简单数据对象, 跨边界传入
    IB-&amp;gt;&amp;gt;UCI: 调用
    UCI-&amp;gt;&amp;gt;E: 调度实体执行业务规则
    UCI-&amp;gt;&amp;gt;DAI: 通过接口请求数据
    DAI-&amp;gt;&amp;gt;DB: 外层实现执行 SQL
    DB--&amp;gt;&amp;gt;UCI: 返回简单数据结构
    UCI-&amp;gt;&amp;gt;E: 收集结果
    UCI-&amp;gt;&amp;gt;OB: 组装 OutputData, 通过输出端口传出
    OB-&amp;gt;&amp;gt;P: Presenter 实现该接口
    P-&amp;gt;&amp;gt;V: 格式化成 ViewModel(纯字符串/开关)
    V--&amp;gt;&amp;gt;U: 渲染 HTML&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;三个要点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;控制流&lt;/strong&gt;从 Controller 出发，穿过用例，最终到达 Presenter——方向是从外到内再到外；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;源码依赖&lt;/strong&gt;每一步都指向内层：Controller 依赖 InputBoundary 接口、Presenter 实现 OutputBoundary 接口，两者都属于&amp;quot;外层依赖内层&amp;quot;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;跨边界的数据&lt;/strong&gt;永远是&lt;strong&gt;简单的数据结构&lt;/strong&gt;（DTO / 函数参数 / POJO），绝不直接传递业务实体对象或数据库行结构——那会偷偷引入指向外层的依赖。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Presenter 的工作也很值得注意：它把 &lt;code&gt;OutputData&lt;/code&gt; 中的 &lt;code&gt;Date&lt;/code&gt;、&lt;code&gt;Currency&lt;/code&gt; 对象&lt;strong&gt;预先格式化&lt;/strong&gt;成字符串，把按钮名称、是否置灰的开关都放进 &lt;code&gt;ViewModel&lt;/code&gt;。于是 View 只剩下一件机械的事：把 ViewModel 搬进 HTML。&lt;/p&gt;
&lt;h3 id=&#34;10-展示器与谦卑对象&#34;&gt;10. 展示器与谦卑对象&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;谦卑对象（Humble Object）模式&lt;/strong&gt;是贯穿架构边界的通用模式：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;难以测试的代码（GUI、数据库网关、外部服务客户端）应该被压缩成尽量薄的&amp;quot;谦卑对象&amp;quot;，把可测试的逻辑抽到旁边的对象里。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;GUI：View 是谦卑对象（只搬数据，不测试）；Presenter 是可测试对象（负责格式化、决定按钮置灰）。&lt;/li&gt;
&lt;li&gt;数据库网关：SQL/ORM 部分是谦卑对象；用例与网关之间的接口实现是逻辑所在。&lt;/li&gt;
&lt;li&gt;服务网关：类似处理。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;11-不完全边界架构的成本控制&#34;&gt;11. 不完全边界：架构的成本控制&lt;/h3&gt;
&lt;p&gt;完整边界（双向接口 + 依赖反转）的构建和维护成本不低。敏捷社区会拿 YAGNI（&amp;ldquo;你不会需要它&amp;rdquo;）来反对预防性设计，但架构师的本职就是做预见。折中方案是&lt;strong&gt;不完全边界（Partial Boundary）&lt;/strong&gt;，书给了三种：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    subgraph 方案一[&amp;#34;方案一：省掉最后一步&amp;#34;]
        C1[Client] --&amp;gt; SB1[组件A 与组件B&amp;lt;br/&amp;gt;已按完整边界设计好接口与数据格式&amp;lt;br/&amp;gt;但合并为单个组件编译部署]
    end
    subgraph 方案二[&amp;#34;方案二：单向边界（策略模式）&amp;#34;]
        C2[Client] --&amp;gt; IF[ServiceBoundary 接口]
        IMPL2[ServiceImpl] -.实现.-&amp;gt; IF
    end
    subgraph 方案三[&amp;#34;方案三：门户模式 Facade（最简）&amp;#34;]
        C3[Client] --&amp;gt; F[Facade 类&amp;lt;br/&amp;gt;只做调用转发]
        F --&amp;gt; S1[服务函数1]
        F --&amp;gt; S2[服务函数2]
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;三种方式成本递减、保护强度也递减。实践启示：&lt;strong&gt;哪怕预算有限，也要至少用单向接口把边界的位置&amp;quot;占住&amp;quot;&lt;/strong&gt;，给将来升级成完整边界留好路。&lt;/p&gt;
&lt;h3 id=&#34;12-main-组件系统的脏秘密&#34;&gt;12. Main 组件：系统的&amp;quot;脏秘密&amp;quot;&lt;/h3&gt;
&lt;p&gt;每个系统都有一个&lt;strong&gt;最底层的细节：入口 Main 组件&lt;/strong&gt;。它是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;初始细节的所在地：负责创建所有工厂、策略、全局设施；&lt;/li&gt;
&lt;li&gt;最大的&amp;quot;具体&amp;quot;组件：它知道所有依赖关系，负责用依赖注入把它们组装起来；&lt;/li&gt;
&lt;li&gt;系统中&lt;strong&gt;唯一允许&amp;quot;知道一切&amp;quot;的组件&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可以把它理解为插件系统的总装车间：所有插件都插到 Main 上，而插件们彼此互不相识。Spring 的 ApplicationContext 初始化代码，本质上就是 Main 组件。&lt;/p&gt;
&lt;h3 id=&#34;13-服务与测试&#34;&gt;13. 服务与测试&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;服务化（SOA/微服务）的收益被高估了&lt;/strong&gt;：服务之间的解耦往往不彻底（共享数据、共享网络），服务边界并不自动等于架构边界。架构边界由&amp;quot;依赖方向&amp;quot;决定，而不是由&amp;quot;是否拆成服务&amp;quot;决定。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;测试也是系统的一部分&lt;/strong&gt;：测试代码应该是系统中最独立的组件——通过测试 API 调用业务逻辑，而不是通过 UI 或真实数据库。测试套件本身也要遵守整洁架构，否则会脆弱到&amp;quot;一改代码就挂一片&amp;quot;，反过来吓得团队不敢重构。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第六部分-实现细节震碎三观的四章&#34;&gt;第六部分 实现细节：震碎三观的四章&lt;/h2&gt;
&lt;p&gt;这一部分只反复强调一件事：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;数据库是细节。Web 是细节。框架是细节。它们都是可以被替换、被推迟决策的 IO 设备。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;数据库只是实现细节&#34;&gt;数据库只是实现细节&lt;/h3&gt;
&lt;p&gt;关系数据库本质上只是一种&amp;quot;把数据组织在磁盘上方便存取&amp;quot;的技术，它对业务逻辑而言就是一个细节。业务模型关注的是数据结构和操作，不是表结构和 SQL。今天用 MySQL，明天换 MongoDB，核心业务逻辑应该无感。&lt;/p&gt;
&lt;h3 id=&#34;web-是实现细节&#34;&gt;Web 是实现细节&lt;/h3&gt;
&lt;p&gt;Web 只是一种 IO 设备。过去几十年 GUI 技术换了一茬又一茬（桌面 → Web → 移动 → …），Web 也终将过时。架构要做的是把系统核心设计成与设备无关，让&amp;quot;通过 Web 交付&amp;quot;成为一种可插拔的交付方式。&lt;/p&gt;
&lt;h3 id=&#34;应用程序框架是实现细节&#34;&gt;应用程序框架是实现细节&lt;/h3&gt;
&lt;p&gt;框架作者对你的人生没有承诺——框架升级时的破坏式变更、框架的局限性绑架，都是真实风险。正确姿势：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;与框架保持&amp;quot;单向婚姻&amp;quot;：你可以依赖框架，但不让框架侵入核心业务代码。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;不要从框架基类派生业务对象，不要在核心业务层用框架的注解——把框架摁在最外层。&lt;/p&gt;
&lt;h3 id=&#34;案例分析视频销售网站&#34;&gt;案例分析：视频销售网站&lt;/h3&gt;
&lt;p&gt;书里用一个&amp;quot;在线卖视频的网站&amp;quot;贯穿分析了完整的架构过程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先识别&lt;strong&gt;用例和 actor&lt;/strong&gt;（购买者、管理员、作者等）；&lt;/li&gt;
&lt;li&gt;按 actor 和变更原因划分&lt;strong&gt;组件边界&lt;/strong&gt;（视图、用例、实体、数据库各自成组）；&lt;/li&gt;
&lt;li&gt;用&lt;strong&gt;依赖反转&lt;/strong&gt;保证所有依赖指向高层策略；&lt;/li&gt;
&lt;li&gt;最后得到一个可以按团队分工并行开发、按需单体或分组件部署的结构。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;结论：架构设计的输入不是技术选型，而是&lt;strong&gt;用例、行为者、变更原因&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;拾遗simon-brown-执笔打包方式的争论&#34;&gt;拾遗（Simon Brown 执笔）：打包方式的争论&lt;/h3&gt;
&lt;p&gt;最后一章讨论了四种代码组织方式，相当接地气：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    A[&amp;#34;① 按层打包&amp;lt;br/&amp;gt;controllers/ services/ repositories/&amp;lt;br/&amp;gt;（经典但业务意图不明显）&amp;#34;] 
    B[&amp;#34;② 按功能打包&amp;lt;br/&amp;gt;orders/ customers/ payments/&amp;lt;br/&amp;gt;（业务意图清晰）&amp;#34;]
    C[&amp;#34;③ 端口与适配器&amp;lt;br/&amp;gt;domain 与 infrastructure 分离&amp;lt;br/&amp;gt;（六边形架构风格）&amp;#34;]
    D[&amp;#34;④ 按组件打包&amp;lt;br/&amp;gt;粗粒度组件 + 严格的访问控制&amp;lt;br/&amp;gt;（Bob 大叔推荐，配合编译器/访问修饰符强制隔离）&amp;#34;]
    A -.演进.-&amp;gt; B -.演进.-&amp;gt; C -.演进.-&amp;gt; D&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;要点是：无论选哪种，&lt;strong&gt;关键是用编译器（访问修饰符、模块系统）来强制执行依赖规则&lt;/strong&gt;，而不是靠&amp;quot;大家自觉&amp;quot;。架构约定如果没有工具强制，迟早被腐蚀。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;实战举例一个订单服务的整洁化改造&#34;&gt;实战举例：一个订单服务的&amp;quot;整洁化&amp;quot;改造&lt;/h2&gt;
&lt;p&gt;光看理论容易晕，用一个简化的 Java/Spring 风格例子收尾。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改造前&lt;/strong&gt;（典型的三层贫血架构，业务逻辑泄漏到各处）：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Controller 直接调 Repository，业务规则散落在 SQL 和 Service 里&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@RestController&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;OrderController&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@Autowired&lt;/span&gt; OrderRepository repo;      &lt;span style=&#34;color:#75715e&#34;&gt;// 直接依赖 JPA/SQL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@PostMapping&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/orders&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;create&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;@RequestBody&lt;/span&gt; OrderEntity e) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (e.&lt;span style=&#34;color:#a6e22e&#34;&gt;getAmount&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; 10000 &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;e.&lt;span style=&#34;color:#a6e22e&#34;&gt;getVip&lt;/span&gt;()) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; RuntimeException(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;超限&amp;#34;&lt;/span&gt;);   &lt;span style=&#34;color:#75715e&#34;&gt;// 业务规则混在 Web 层&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        repo.&lt;span style=&#34;color:#a6e22e&#34;&gt;save&lt;/span&gt;(e);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;问题：想换成命令行下单？重写。想换掉数据库？重写。想单测这条&amp;quot;超 1 万必须是 VIP&amp;quot;的规则？得把 Spring 容器和数据库拉起来。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改造后&lt;/strong&gt;（按整洁架构分层）：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// ① 实体层：与企业无关环境的核心规则，零依赖&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;OrderPolicy&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;boolean&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;canPlace&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;long&lt;/span&gt; amount, &lt;span style=&#34;color:#66d9ef&#34;&gt;boolean&lt;/span&gt; vip) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; amount &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;=&lt;/span&gt; 10000 &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; vip;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// ② 用例层：定义输入/输出端口，依赖反转&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;interface&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;OrderGateway&lt;/span&gt; {                 &lt;span style=&#34;color:#75715e&#34;&gt;// 用例层定义的接口（输出端口）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;save&lt;/span&gt;(String orderId, &lt;span style=&#34;color:#66d9ef&#34;&gt;long&lt;/span&gt; amount, &lt;span style=&#34;color:#66d9ef&#34;&gt;boolean&lt;/span&gt; vip);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;PlaceOrderUseCase&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;final&lt;/span&gt; OrderGateway gateway;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    PlaceOrderUseCase(OrderGateway gateway) { &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;gateway&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; gateway; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    String &lt;span style=&#34;color:#a6e22e&#34;&gt;execute&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;long&lt;/span&gt; amount, &lt;span style=&#34;color:#66d9ef&#34;&gt;boolean&lt;/span&gt; vip) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;OrderPolicy.&lt;span style=&#34;color:#a6e22e&#34;&gt;canPlace&lt;/span&gt;(amount, vip))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;REJECTED&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        String id &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; UUID.&lt;span style=&#34;color:#a6e22e&#34;&gt;randomUUID&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;toString&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        gateway.&lt;span style=&#34;color:#a6e22e&#34;&gt;save&lt;/span&gt;(id, amount, vip);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; id;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// ③ 接口适配层 + 最外层：Controller / Repository 实现端口&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@RestController&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;OrderController&lt;/span&gt; {                    &lt;span style=&#34;color:#75715e&#34;&gt;// Controller 是适配器&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;final&lt;/span&gt; PlaceOrderUseCase useCase;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    OrderController(PlaceOrderUseCase u) { &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;useCase&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; u; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@PostMapping&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/orders&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ResponseEntity&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;String&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;create&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;@RequestBody&lt;/span&gt; OrderRequest req) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; ResponseEntity.&lt;span style=&#34;color:#a6e22e&#34;&gt;ok&lt;/span&gt;(useCase.&lt;span style=&#34;color:#a6e22e&#34;&gt;execute&lt;/span&gt;(req.&lt;span style=&#34;color:#a6e22e&#34;&gt;amount&lt;/span&gt;(), req.&lt;span style=&#34;color:#a6e22e&#34;&gt;vip&lt;/span&gt;()));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@Repository&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SqlOrderGateway&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;implements&lt;/span&gt; OrderGateway {  &lt;span style=&#34;color:#75715e&#34;&gt;// 数据库是插件&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 这里可以写 SQL/JPA，内层完全不知道&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;save&lt;/span&gt;(String id, &lt;span style=&#34;color:#66d9ef&#34;&gt;long&lt;/span&gt; amount, &lt;span style=&#34;color:#66d9ef&#34;&gt;boolean&lt;/span&gt; vip) { &lt;span style=&#34;color:#75715e&#34;&gt;/* INSERT ... */&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;改造带来的可验证收益：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;可测试&lt;/strong&gt;：&lt;code&gt;new PlaceOrderUseCase(inMemoryGateway)&lt;/code&gt; 直接跑单元测试，不需要 Spring、不需要数据库；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可替换 UI&lt;/strong&gt;：再加一个 &lt;code&gt;OrderCliRunner&lt;/code&gt; 调用同一个 &lt;code&gt;PlaceOrderUseCase&lt;/code&gt;，一行用例代码不改；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可替换数据库&lt;/strong&gt;：写一个 &lt;code&gt;MongoOrderGateway implements OrderGateway&lt;/code&gt;，在 Main（配置类）里换掉注入的实现即可。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个例子里每一条收益，都直接来自&amp;quot;依赖关系规则&amp;quot;那一条铁律。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;全书要点一页纸&#34;&gt;全书要点一页纸&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;部分&lt;/th&gt;
					&lt;th&gt;核心问题&lt;/th&gt;
					&lt;th&gt;一句话答案&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;概述&lt;/td&gt;
					&lt;td&gt;架构是为了什么&lt;/td&gt;
					&lt;td&gt;最小化系统全生命周期的人力成本&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;编程范式&lt;/td&gt;
					&lt;td&gt;代码的底层约束&lt;/td&gt;
					&lt;td&gt;三大范式都是&amp;quot;剥夺能力&amp;quot;：限制 goto、函数指针、赋值&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;SOLID&lt;/td&gt;
					&lt;td&gt;类与函数怎么设计&lt;/td&gt;
					&lt;td&gt;中层五原则，其中 DIP 是架构边界的武器&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;组件原则&lt;/td&gt;
					&lt;td&gt;类怎么打包、组件怎么依赖&lt;/td&gt;
					&lt;td&gt;聚合（REP/CCP/CRP）与耦合（ADP/SDP/SAP）的权衡&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;软件架构&lt;/td&gt;
					&lt;td&gt;系统怎么分层&lt;/td&gt;
					&lt;td&gt;划边界 + 依赖规则：依赖永远指向高层策略&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;实现细节&lt;/td&gt;
					&lt;td&gt;数据库/Web/框架是什么&lt;/td&gt;
					&lt;td&gt;都是细节、都是插件、都可以推迟决策&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;如果只能记住一句话：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;好的架构让最重要的东西（业务规则）不依赖任何东西，让最不重要的东西（数据库、Web、框架）依赖业务规则——依赖方向永远由外向内。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;参考资料：《Clean Architecture: A Craftsman&amp;rsquo;s Guide to Software Structure and Design》，Robert C. Martin 著，&lt;a href=&#34;https://github.com/gdut-yy/Clean-Architecture-zh&#34;&gt;中文译本&lt;/a&gt;。&lt;/em&gt;&lt;/p&gt;
</description>
    </item>
    <item>
      <title>《持续交付》笔记：每步可退，把集成风险日常化</title>
      <link>https://ftmao.com/blog/bookstore/continuous-delivery-book-notes/</link>
      <pubDate>Fri, 08 Sep 2023 10:40:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/bookstore/continuous-delivery-book-notes/</guid>
      <description>&lt;h1 id=&#34;持续交付笔记每步可退把集成风险日常化&#34;&gt;《持续交付》笔记：每步可退，把集成风险日常化&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;《持续交付》（Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation）是 Jez Humble 和 David Farley 2010 年的作品，拿过 2011 年的 Jolt 大奖。这本书可以说是 DevOps 运动的奠基文献之一——后来《DevOps Handbook》《Accelerate》这些书，根子上都能追溯到这里。&lt;/p&gt;
&lt;p&gt;全书 500 多页，核心其实就一个问题：&lt;strong&gt;怎么让&amp;quot;把软件放到生产环境&amp;quot;这件事，从一年几次的高风险仪式，变成随时可以按一下按钮的平常操作？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我读完最大的感受是：这本书表面讲工具和流程，骨子里讲的是一种风险管理哲学——&lt;strong&gt;不消除风险，而是把风险切碎、摊平到每一天&lt;/strong&gt;。这篇笔记是我消化后的版本，不是章节摘要。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一全书的核心主张一张图看懂&#34;&gt;一、全书的核心主张：一张图看懂&lt;/h2&gt;
&lt;p&gt;先把整本书的骨架画出来，后面每节都能对号入座：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TB
    subgraph P[&amp;#34;核心哲学：把风险日常化&amp;#34;]
        PHIL[&amp;#34;&amp;#39;If it hurts,&amp;lt;br/&amp;gt;do it more often&amp;#39;&amp;#34;]
    end
    subgraph PL[&amp;#34;部署流水线 Deployment Pipeline&amp;#34;]
        S1[&amp;#34;① 提交阶段&amp;lt;br/&amp;gt;编译 + 单元测试&amp;lt;br/&amp;gt;分钟级反馈&amp;#34;]
        S2[&amp;#34;② 自动化验收&amp;lt;br/&amp;gt;验收测试 + 容量/安全测试&amp;lt;br/&amp;gt;小时级反馈&amp;#34;]
        S3[&amp;#34;③ 手工验证&amp;lt;br/&amp;gt;UAT / 探索性测试 / 演示&amp;#34;]
        S4[&amp;#34;④ 发布&amp;lt;br/&amp;gt;点按钮，随时可发&amp;#34;]
        S1 --&amp;gt; S2 --&amp;gt; S3 --&amp;gt; S4
    end
    subgraph F[&amp;#34;支撑地基&amp;#34;]
        F1[&amp;#34;配置管理&amp;lt;br/&amp;gt;一切皆版本控制&amp;#34;]
        F2[&amp;#34;自动化测试金字塔&amp;#34;]
        F3[&amp;#34;主干开发 + 特性开关&amp;#34;]
    end
    PHIL --&amp;gt; PL
    F --&amp;gt; PL&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;这本书的立论链条是这样的：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;软件发布之所以痛苦，是因为它&lt;strong&gt;罕见&lt;/strong&gt;——几个月才发一次，每次集成了几百个变更，出问题没法定位；&lt;/li&gt;
&lt;li&gt;解决思路不是&amp;quot;更小心地发布&amp;quot;，而是&lt;strong&gt;更频繁地发布&lt;/strong&gt;——频率高了，每次变更就小，小了就好定位、好回滚；&lt;/li&gt;
&lt;li&gt;要支撑高频发布，必须有一条自动化的&lt;strong&gt;部署流水线&lt;/strong&gt;，让每个提交自动走完所有质量关卡；&lt;/li&gt;
&lt;li&gt;流水线要可靠，底下得有配置管理、自动化测试、增量式开发三块地基。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;感悟&lt;/strong&gt;：这个逻辑其实和健身一个道理——很久不运动，偶尔跑一次十公里肯定受伤；每天跑两公里反而没事。发布也一样，&amp;ldquo;少做&amp;quot;不是降低风险的办法，&amp;ldquo;把每次做小&amp;quot;才是。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;二if-it-hurts-do-it-more-often全书最反直觉的一句话&#34;&gt;二、&amp;ldquo;If it hurts, do it more often&amp;rdquo;：全书最反直觉的一句话&lt;/h2&gt;
&lt;p&gt;书里有一句被广泛引用的话：&lt;strong&gt;&amp;ldquo;如果某件事让你痛苦，那就更频繁地去做它&amp;rdquo;&lt;/strong&gt;（If it hurts, do it more often, and bring the pain forward）。&lt;/p&gt;
&lt;p&gt;直觉上，痛苦的事应该少做。但作者的论证是：痛苦的根源是&lt;strong&gt;批量太大、间隔太久&lt;/strong&gt;。以集成为例：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;两个程序员各写两周代码再合并，冲突一大堆，合并花两天——于是大家说&amp;quot;少合并&amp;rdquo;；&lt;/li&gt;
&lt;li&gt;正确的解法恰恰相反：&lt;strong&gt;每天合并多次&lt;/strong&gt;，每次冲突只有几行，顺手就解了。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;同样的道理适用于：数据库迁移、发布上线、甚至和客户的沟通。频率提高会逼着你把流程自动化、把每一步做小，而&amp;quot;小&amp;quot;本身就是最好的风险对冲。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;踩坑记录（书外）&lt;/strong&gt;：我待过的一个团队，发布周期是一个月一次，每次发布日都要通宵，发布完还要留人值班一周&amp;quot;灭火&amp;rdquo;。所有人的共识是&amp;quot;发布太危险，得再慎重些&amp;quot;——于是变更冻结期越来越长，每次积压的变更越来越多，发布越来越危险。这是典型的&lt;strong&gt;用制造问题的方式解决问题&lt;/strong&gt;。后来换了 CTO 强推两周一次、再到一周一次，逼着团队把部署脚本自动化、把回滚预案做出来，半年后发布变成了一个普通下午就能做的事。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;感悟&lt;/strong&gt;：&amp;ldquo;痛苦&amp;quot;是个信号，但它指示的方向经常和直觉相反。看到团队里某件事人人回避，正确的反应不是绕着走，而是问：能不能把它自动化、高频化、常态化？&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三部署流水线每个提交都是可发布候选&#34;&gt;三、部署流水线：每个提交都是可发布候选&lt;/h2&gt;
&lt;p&gt;这是全书最核心的工程概念。部署流水线（deployment pipeline）的正式定义是：&lt;strong&gt;从代码提交到生产发布之间，所有验证步骤的自动化呈现&lt;/strong&gt;。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    C[&amp;#34;提交 Commit&amp;lt;br/&amp;gt;触发流水线&amp;#34;] --&amp;gt; B[&amp;#34;① 构建 + 单元测试&amp;lt;br/&amp;gt;编译 / 静态检查&amp;lt;br/&amp;gt;几分钟内出结果&amp;#34;]
    B --&amp;gt; A[&amp;#34;② 自动化验收测试&amp;lt;br/&amp;gt;面向业务的端到端场景&amp;lt;br/&amp;gt;在类生产环境跑&amp;#34;]
    A --&amp;gt; U[&amp;#34;③ 手工验证&amp;lt;br/&amp;gt;UAT / 探索性测试&amp;lt;br/&amp;gt;非功能需求确认&amp;#34;]
    U --&amp;gt; R[&amp;#34;④ 发布&amp;lt;br/&amp;gt;同一构建产物&amp;lt;br/&amp;gt;一键部署到生产&amp;#34;]
    B -.-&amp;gt;|&amp;#34;失败即止&amp;#34;| X[&amp;#34;❌ 流水线终止&amp;lt;br/&amp;gt;修复优先于新功能&amp;#34;]
    A -.-&amp;gt;|&amp;#34;失败即止&amp;#34;| X&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;几个关键点，都是容易理解偏的：&lt;/p&gt;
&lt;h3 id=&#34;31-每个阶段都是质量关卡失败即止&#34;&gt;3.1 每个阶段都是质量关卡，失败即止&lt;/h3&gt;
&lt;p&gt;流水线的本质是&lt;strong&gt;递进式的信心建设&lt;/strong&gt;：便宜的、快的测试放前面（单元测试几秒出结果），贵的、慢的放后面（验收测试、性能测试）。任何一个阶段失败，这个构建版本就&amp;quot;死&amp;quot;了，不进入下一阶段。&lt;/p&gt;
&lt;p&gt;这带来一条重要的团队纪律：&lt;strong&gt;流水线红了，修复它就是最高优先级&lt;/strong&gt;——比写新功能优先。因为流水线红着的时候，后面所有人的提交都拿不到反馈，等于团队集体失明。书里管这个叫&amp;quot;不要带着broken的构建回家&amp;rdquo;。&lt;/p&gt;
&lt;h3 id=&#34;32-每次提交都产生一个可发布候选&#34;&gt;3.2 每次提交都产生一个可发布候选&lt;/h3&gt;
&lt;p&gt;这句话值得单独强调：&lt;strong&gt;流水线的每个成功产出都是 release candidate&lt;/strong&gt;。注意是&amp;quot;候选&amp;quot;不是&amp;quot;一定发&amp;quot;——它保证了技术上随时可以发，发不发是业务决策（这点在第六节展开）。&lt;/p&gt;
&lt;p&gt;做到这一点有个硬要求：&lt;strong&gt;部署到每个环境的必须是同一个构建产物&lt;/strong&gt;。测试环境用 A 包、生产环境重新打一个 B 包，等于你在生产环境跑了一个从没被验证过的东西——测试全白做了。正确的做法是：构建只发生一次，产出的二进制包（jar、镜像）在流水线里逐级晋升（promote），环境差异全部由外部配置注入。&lt;/p&gt;
&lt;h3 id=&#34;33-流水线的反馈速度是硬指标&#34;&gt;3.3 流水线的反馈速度是硬指标&lt;/h3&gt;
&lt;p&gt;提交阶段要在几分钟内给出结果——慢了就没人愿意等，大家开始绕过流程。验收测试可以慢一些，但也要控制在小时级。如果一个阶段慢到天天被跳过，那它不是&amp;quot;严格&amp;quot;，是&amp;quot;无效&amp;quot;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;感悟&lt;/strong&gt;：流水线画出来都不难，难的是维护它的&amp;quot;纪律成本&amp;quot;。红了几天的流水线比没有流水线更糟——它教会团队的是&amp;quot;告警可以无视&amp;quot;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四自动化测试金字塔而不是冰淇淋筒&#34;&gt;四、自动化测试：金字塔而不是冰淇淋筒&lt;/h2&gt;
&lt;p&gt;书里的测试分层思想，后来被大家画成了著名的&amp;quot;测试金字塔&amp;quot;（Mike Cohn 提出，本书把它发扬光大了）：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TB
    subgraph 金字塔[&amp;#34;自动化测试金字塔（数量：下多上少；成本：下低上高）&amp;#34;]
        E2E[&amp;#34;少量：端到端 UI 测试&amp;lt;br/&amp;gt;慢、脆、维护贵&amp;#34;]
        ACC[&amp;#34;适量：验收/服务层测试&amp;lt;br/&amp;gt;面向业务行为&amp;lt;br/&amp;gt;不依赖 UI&amp;#34;]
        UNIT[&amp;#34;大量：单元测试&amp;lt;br/&amp;gt;毫秒级、定位精准&amp;#34;]
    end
    UNIT --&amp;gt; ACC --&amp;gt; E2E&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;书中几个至今仍然尖锐的观点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;验收测试要用业务语言写，由开发者、测试、业务三方共同拥有&lt;/strong&gt;。它不只是测试，更是&amp;quot;可执行的规格说明书&amp;quot;——需求变了，先改验收测试；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;别把测试挂在 UI 上&lt;/strong&gt;。UI 是系统里最不稳定的层，通过 UI 驱动的测试又慢又脆。验收测试应该打在 UI 之下的服务/API 层；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;手工测试不会被消灭，但角色变了&lt;/strong&gt;：从&amp;quot;重复执行回归用例&amp;quot;变成&amp;quot;探索性测试、可用性评估&amp;quot;——机器做重复的，人做需要判断力的。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;反面形态叫&amp;quot;冰淇淋筒&amp;quot;：大量手工测试 + 一堆脆弱的 UI 自动化 + 几乎没有单元测试。症状是测试跑一遍要一整天、失败了大半天定位不到问题、最后团队对测试结果失去信心开始无视红灯。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;感悟&lt;/strong&gt;：测试金字塔真正的价值不在&amp;quot;比例&amp;quot;，在它背后的经济学：&lt;strong&gt;反馈越快、定位越准的测试越便宜，应该最多&lt;/strong&gt;。所有&amp;quot;冰淇淋筒&amp;quot;团队的问题，本质都是把测试成本结构搞反了。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;五增量式发布把风险一刀一刀切小&#34;&gt;五、增量式发布：把风险一刀一刀切小&lt;/h2&gt;
&lt;p&gt;这部分是全书我最服气的一章——它回答了一个实际问题：大功能开发要好几个月，总不能这几个月不发版吧？书里给的答案是&lt;strong&gt;把&amp;quot;部署&amp;quot;和&amp;quot;发布&amp;quot;解耦&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;51-主干开发--特性开关和长寿命分支说再见&#34;&gt;5.1 主干开发 + 特性开关：和长寿命分支说再见&lt;/h3&gt;
&lt;p&gt;业界常见的做法是&amp;quot;功能分支&amp;quot;：每个大功能拉一个分支，开发几个月再合并。书里的态度很明确：&lt;strong&gt;长寿命分支是持续交付的头号敌人&lt;/strong&gt;。分支活得越久，合并那天的集成爆炸越大——你只是把集成风险攒起来，并没有消除它。&lt;/p&gt;
&lt;p&gt;替代方案是&lt;strong&gt;主干开发（trunk-based development）&lt;/strong&gt;：所有人都频繁提交到主干（至少每天），没做完的大功能用**特性开关（feature toggle）**藏起来——代码已经在生产环境，但功能入口被开关挡住，用户看不见。&lt;/p&gt;
&lt;p&gt;这样做的好处是双重的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;集成每天都在发生，冲突永远是小的；&lt;/li&gt;
&lt;li&gt;&amp;ldquo;部署新代码&amp;quot;和&amp;quot;向用户开放功能&amp;quot;变成了两件独立的事。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;代价也要认账：开关本身是技术债，上线后要记得清理；半成品代码要保证&amp;quot;编译得过、不破坏现有行为&amp;rdquo;，对代码组织（抽象分支、按业务边界拆分）有要求。&lt;/p&gt;
&lt;h3 id=&#34;52-蓝绿部署与金丝雀发布&#34;&gt;5.2 蓝绿部署与金丝雀发布&lt;/h3&gt;
&lt;p&gt;发布阶段的两个经典模式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;蓝绿部署（blue-green deployment）&lt;/strong&gt;：维护两套生产环境。当前流量在&amp;quot;蓝&amp;quot;，新版本部署到&amp;quot;绿&amp;quot;，验证没问题就把流量切过去。出问题？切回来就是秒级回滚。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;金丝雀发布（canary release）&lt;/strong&gt;：先把新版本暴露给一小部分流量（比如 1% 的用户或一台服务器），盯着错误率和关键指标，没问题再逐步扩大范围。名字来自煤矿里的金丝雀——鸟先下井，出事人有时间撤。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;两者的共同点：&lt;strong&gt;回滚能力内建在发布机制里&lt;/strong&gt;，而不是出事后临时想办法。书里有句话大意是：发布计划里最重要的一条是回滚计划，而回滚计划必须&lt;strong&gt;真的演练过&lt;/strong&gt;才算数——没演练过的回滚方案约等于没有。&lt;/p&gt;
&lt;h3 id=&#34;53-数据库变更expand-contract-模式&#34;&gt;5.3 数据库变更：expand-contract 模式&lt;/h3&gt;
&lt;p&gt;蓝绿部署最怕的拦路虎是数据库：两套环境共用一个库，schema 一变，新旧代码没法同时跑。书里的解法是&lt;strong&gt;expand-contract（扩张-收缩）模式&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Expand&lt;/strong&gt;：先做一次只做&amp;quot;加&amp;quot;的变更——加新列、加新表，旧的什么都不动。这次变更对旧代码完全兼容，可以安全部署；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;迁移&lt;/strong&gt;：应用代码改为同时读写新旧结构（或双写），后台任务把存量数据搬过去；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contract&lt;/strong&gt;：等所有在用版本都不依赖旧结构了，再单独做一次变更删掉旧列、旧表。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;核心思想一句话：&lt;strong&gt;每一次变更都向前向后兼容，让 schema 演进和应用部署可以分开走&lt;/strong&gt;。这也解释了为什么&amp;quot;上线一个大变更顺便重构一下表结构&amp;quot;是事故高发操作——你把两个本可以解耦的风险捆在了一起。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;六配置与环境把环境也当代码管&#34;&gt;六、配置与环境：把环境也当代码管&lt;/h2&gt;
&lt;p&gt;这本书反复强调的一个纪律是：&lt;strong&gt;一切进版本控制&lt;/strong&gt;——不只是源代码，还包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;构建脚本、部署脚本；&lt;/li&gt;
&lt;li&gt;应用配置（环境相关的那部分参数化）；&lt;/li&gt;
&lt;li&gt;数据库迁移脚本；&lt;/li&gt;
&lt;li&gt;环境本身的定义（后来演化成了 Infrastructure as Code，IaC）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;理由很朴素：如果环境的搭建靠&amp;quot;运维同学的记忆和一台手工调出来的服务器&amp;quot;，那你的测试环境和生产环境一定存在没人知道的差异，而每个差异都是一颗雷。书里对这种手工维护的服务器有个后来的流行叫法——&lt;strong&gt;宠物（pets） vs 牲畜（cattle）&lt;/strong&gt;：宠物有名字，病了要治；牲畜只有编号，病了换掉重来。环境应该朝&amp;quot;牲畜&amp;quot;方向走：不可变、可重建、随时销毁再拉起。&lt;/p&gt;
&lt;p&gt;配置管理上的一条实用原则：&lt;strong&gt;把&amp;quot;什么变&amp;quot;和&amp;quot;什么不变&amp;quot;分开&lt;/strong&gt;。代码和各环境共用的配置进版本库，环境差异（连接串、密钥、开关值）由部署流程注入。同一份代码 + 不同的配置 = 不同的环境，而不是各环境维护不同的代码分支。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;七发布是业务决策不是技术决策&#34;&gt;七、发布是业务决策，不是技术决策&lt;/h2&gt;
&lt;p&gt;这是全书观点中我认为最容易被低估、也最改变组织行为的一条。&lt;/p&gt;
&lt;p&gt;传统模式下，&amp;ldquo;什么时候发布&amp;quot;是技术团队说了算的——因为只有他们觉得&amp;quot;差不多了&amp;quot;才敢发，发布本身是高危操作，自然由承担风险的人拍板。&lt;/p&gt;
&lt;p&gt;持续交付把这件事倒过来：当流水线保证了&lt;strong&gt;每个提交技术上都可发布&lt;/strong&gt;之后，&amp;ldquo;何时发布、对谁发布&amp;quot;就纯粹变成了业务问题——市场时机、合规窗口、用户沟通节奏，都应该由产品和业务来定。技术团队的职责是保证这个按钮&lt;strong&gt;随时可以按、按了不出事、出了事能秒回&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这也是特性开关、金丝雀发布的深层价值：它们把&amp;quot;发布&amp;quot;从技术动作变成了业务杠杆——按客户群分批开放、给内部员工先试用、关键客户定制节奏，都是业务手段而不是工程手段。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;感悟&lt;/strong&gt;：很多团队的技术债，根子其实是&amp;quot;发布权力错配&amp;rdquo;：业务想要的速度给不了，因为发布在技术上做不到随时可做。先把工程能力补到位，权责自然归位。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;八案例正面与反面&#34;&gt;八、案例：正面与反面&lt;/h2&gt;
&lt;h3 id=&#34;正面高频部署的先行者&#34;&gt;正面：高频部署的先行者&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Flickr&lt;/strong&gt;：2009 年 Velocity 大会上，Flickr 的 John Allspaw 和 Paul Hammond 做了那场著名演讲《10+ Deploys Per Day: Dev and Ops Cooperation at Flickr》——每天 10 次以上部署。这个演讲被公认为 DevOps 运动的引爆点之一。他们的经验核心正是本书的思想：小批量、特性开关、完善的监控和一键回滚，让高频发布不是冒险而是常态。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Amazon&lt;/strong&gt;：以部署频率著称。后来 Amazon 高管在公开分享中提到过，2011 年前后 Amazon 平均每隔十几秒就有一处变更部署到生产（这个数字随年份增长，到 2014 年的分享里已经是秒级）。支撑它的是高度自动化的流水线和&amp;quot;谁构建谁运行&amp;quot;的服务负责制。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些公司证明了一件事：&lt;strong&gt;部署频率和生产稳定性不是此消彼长，而是可以同向增长的&lt;/strong&gt;——前提是自动化、小批量、快速回滚三件套齐全。后来 DORA 的年度行业报告用数据反复验证了这一点：部署频率最高的那批组织，变更失败率反而最低、恢复最快。&lt;/p&gt;
&lt;h3 id=&#34;反面knight-capital45-分钟-44-亿美元&#34;&gt;反面：Knight Capital，45 分钟 4.4 亿美元&lt;/h3&gt;
&lt;p&gt;2012 年 8 月 1 日，美国做市商 Knight Capital 发生了一起教科书级的部署事故，大约 45 分钟内亏损约 4.4 亿美元，公司险些倒闭，最终被收购。&lt;/p&gt;
&lt;p&gt;事后的公开报道和美国 SEC 的调查文件还原了链条：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;他们为新的&amp;quot;零售流动性计划&amp;rdquo;（Retail Liquidity Program）功能做部署，生产环境共 &lt;strong&gt;8 台服务器，人工部署只更新了 7 台&lt;/strong&gt;，漏了一台；&lt;/li&gt;
&lt;li&gt;新代码&lt;strong&gt;复用了一个旧的标志位（flag）&lt;/strong&gt;，而这个标志位多年前曾用于一个早已弃用的功能&amp;quot;Power Peg&amp;quot;——沉睡的旧代码没有被删除，只是被开关挡着；&lt;/li&gt;
&lt;li&gt;那台漏更新的服务器上，旧标志位被请求触发，&lt;strong&gt;沉睡多年的 Power Peg 代码被唤醒&lt;/strong&gt;，开始以错误逻辑疯狂下单；&lt;/li&gt;
&lt;li&gt;现场&lt;strong&gt;没有一键回滚机制&lt;/strong&gt;，工程师手忙脚乱，花了 40 多分钟才止住。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;用本书的概念框架看，这个事故几乎踩中了所有反面：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;部署靠人工&lt;/strong&gt;——8 台漏 1 台是人工部署的必然概率问题，不是偶发失误；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;死代码 + 标志位复用&lt;/strong&gt;——特性开关没被治理，旧开关复用、死代码不清理，开关从保护伞变成了定时炸弹；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;没有快速回滚&lt;/strong&gt;——如果有一键回滚，损失可能是零头。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;感悟&lt;/strong&gt;：这个案例我逢人就讲，因为它完美诠释了本书的中心论点：&lt;strong&gt;事故的根源不是&amp;quot;发布太多&amp;quot;，而是发布过程不可重复、不可回滚&lt;/strong&gt;。Knight Capital 一年也发不了几次版，&amp;ldquo;少发布&amp;quot;没有保护他们。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;九思考与感悟&#34;&gt;九、思考与感悟&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&amp;ldquo;持续&amp;quot;是方法，&amp;ldquo;交付信心&amp;quot;才是目的。&lt;/strong&gt; 读完回头看，这本书所有技术手段——流水线、金字塔、特性开关、蓝绿——都服务于同一件事：让团队在任何时候都能有底气地说&amp;quot;现在的版本是可发布的&amp;rdquo;。这种底气本身会改变团队的开发方式：敢重构、敢提交、敢试验。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;纪律比工具难买。&lt;/strong&gt; 流水线的搭建一个 Sprint 就能做完，难的是&amp;quot;红了就停下手头一切去修&amp;quot;这条纪律。工具解决的是&amp;quot;能不能&amp;rdquo;，纪律解决的是&amp;quot;做不做&amp;rdquo;。后者只能靠团队共识和管理者背书。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;和客户开发方法论同构。&lt;/strong&gt; 这是我读完联想最多的一点：之前读的创业方法论里，客户开发（Customer Development）讲的是市场侧的小步试错——别闭门造车一年再赌一把大的，把假设拆小，快速放到真实客户面前验证。持续交付是&lt;strong&gt;工程侧的同一件事&lt;/strong&gt;：别把代码攒三个月再赌一次大发布，把变更拆小，快速推到真实环境里验证。一个在验证&amp;quot;需求假设&amp;quot;，一个在验证&amp;quot;代码正确性假设&amp;quot;，底层的认识论完全一致：&lt;strong&gt;小批量、快反馈、可回退，用高频试错替代单次豪赌&lt;/strong&gt;。精益创业（Lean Startup）把这两条线拧在了一起，不是巧合——Eric Ries 本人就是持续部署的早期实践者。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;每步可退，才能每步敢进。&lt;/strong&gt; &amp;ldquo;可回滚&amp;quot;听起来是防御性能力，实际上是进攻性能力——只有确信每一步都退得回去，团队才敢把步子迈快。这本书最深的洞察也许就这一句：&lt;strong&gt;速度和安全不是天平的两端，可回滚性把它们焊在了同一侧。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&#34;本篇小结&#34;&gt;本篇小结&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;部署流水线是核心骨架：提交 → 构建+单元测试 → 自动化验收 → 手工验证/UAT → 发布，每个阶段是质量关卡，失败即止，修复红灯优先于新功能；&lt;/li&gt;
&lt;li&gt;哲学上是 &amp;ldquo;If it hurts, do it more often&amp;rdquo;：痛苦的事要做小、做勤，风险切碎摊平，而不是攒起来赌一次大的；&lt;/li&gt;
&lt;li&gt;每次提交产出可发布候选；同一构建产物逐级晋升，环境差异靠外部配置注入；&lt;/li&gt;
&lt;li&gt;测试要金字塔不要冰淇淋筒：单元测试打底，验收测试讲业务语言，UI 测试最少；&lt;/li&gt;
&lt;li&gt;主干开发 + 特性开关替代长寿命分支，把&amp;quot;部署&amp;quot;和&amp;quot;发布&amp;quot;解耦；&lt;/li&gt;
&lt;li&gt;蓝绿部署和金丝雀发布把回滚能力内建在发布机制里；数据库变更走 expand-contract，每步向前向后兼容；&lt;/li&gt;
&lt;li&gt;配置和环境定义全部进版本控制，环境朝&amp;quot;可重建的牲畜&amp;quot;方向走；&lt;/li&gt;
&lt;li&gt;发布是业务决策，技术团队的职责是让那个按钮随时可按、按了能回；&lt;/li&gt;
&lt;li&gt;收尾的同构关系：客户开发是市场侧的小步试错，持续交付是工程侧的小步试错，精益创业把两者拧成一股。&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    <item>
      <title>《Unix 编程艺术》笔记：无为而治的操作系统版——不写多余的代码</title>
      <link>https://ftmao.com/blog/bookstore/art-of-unix-programming-notes/</link>
      <pubDate>Tue, 08 Aug 2023 10:40:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/bookstore/art-of-unix-programming-notes/</guid>
      <description>&lt;h1 id=&#34;unix-编程艺术笔记无为而治的操作系统版不写多余的代码&#34;&gt;《Unix 编程艺术》笔记：无为而治的操作系统版——不写多余的代码&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;目标读者：写过一两年代码、开始困惑&amp;quot;为什么有的系统越改越烂&amp;quot;的程序员。&lt;br&gt;
参考书籍：Eric S. Raymond《The Art of Unix Programming》（TAOUP，2003）&lt;br&gt;
阅读目标：把 Unix 哲学从&amp;quot;背口诀&amp;quot;变成&amp;quot;做决策的直觉&amp;quot;，知道什么时候&lt;strong&gt;不写代码&lt;/strong&gt;才是正确答案。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;0-写在前面这本书其实在讲无为&#34;&gt;0. 写在前面：这本书其实在讲&amp;quot;无为&amp;quot;&lt;/h2&gt;
&lt;p&gt;老子的&amp;quot;无为而治&amp;quot;常被误解成&amp;quot;什么都不做&amp;quot;。它的真实意思是：&lt;strong&gt;不妄为、不多为、让事物按自己的规律运转，治理者只搭最小的框架&lt;/strong&gt;。这篇博客的姊妹篇（道家经典笔记）里聊过这个，但当时我没想到，技术世界里有一套哲学和它是镜像关系——Unix 哲学。&lt;/p&gt;
&lt;p&gt;《Unix 编程艺术》这本书表面上是讲操作系统历史、讲工具、讲惯例，但通读下来，它的主线只有一句话：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;好的设计，是让每样东西只做一件事、把它做好，然后靠组合解决大问题——你写的代码越少，系统越长寿。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这就是&amp;quot;无为&amp;quot;的操作系统版：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;无为 ≠ 不写代码，而是&lt;strong&gt;不写多余的代码&lt;/strong&gt;（吝啬原则、经济原则）；&lt;/li&gt;
&lt;li&gt;无为而治 ≠ 没有规则，而是&lt;strong&gt;规则极少且稳定，万物在规则下自由组合&lt;/strong&gt;（组合原则、文本流接口）；&lt;/li&gt;
&lt;li&gt;为而不恃 ≠ 没有接口，而是&lt;strong&gt;接口低调透明，不强迫用户感知它&lt;/strong&gt;（透明原则、缄默原则）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;后面所有的原则、公案、争论，都是这一条主线的展开。先记住主线，再看细节，就不会把 17 条原则背成口诀表。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;1-一句话理解-unix-哲学&#34;&gt;1. 一句话理解 Unix 哲学&lt;/h2&gt;
&lt;p&gt;关于 Unix 哲学有很多版本的一句话总结，流传最广的是 Doug McIlroy（管道的发明人）在 1978 年《Bell System Technical Journal》里写的那段：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;让每个程序只做好一件事。要做新功能，就写个新程序，而不是给旧程序加&amp;quot;特性&amp;quot;把旧程序搞复杂。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;再加两句配套的：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;让每个程序的输出，都能成为另一个（还没写出来的）程序的输入。不要因为无关信息把输出搞得花里胡哨。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;以及后世程序员最爱引用的简版：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Do one thing and do it well.（做一件事，把它做好。）&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;注意 McIlroy 原话里最关键、也最容易被忽略的半句：&lt;strong&gt;&amp;ldquo;要做新功能，就写个新程序&amp;rdquo;&lt;/strong&gt;。这不是审美洁癖，而是工程判断——往旧程序里加功能，会把原本干净的工具变成一坨没人敢动的遗留代码。这条在后面 &lt;code&gt;cat -v&lt;/code&gt; 之争里会血淋淋地再出现一次。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;2-十七原则全景哪些是宪法哪些是实施细则&#34;&gt;2. 十七原则全景：哪些是&amp;quot;宪法&amp;quot;，哪些是&amp;quot;实施细则&amp;quot;&lt;/h2&gt;
&lt;p&gt;TAOUP 第一章把 Unix 哲学整理成了 17 条原则。我不打算逐条复述（那是目录，不是消化），先给一张全景图，再挑出真正管用的五条深讲。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;mindmap
  root((Unix哲学&amp;lt;br/&amp;gt;无为=不写多余代码))
    核心五条
      简单原则
        能简单就别复杂
        KISS
      组合原则
        程序互相连接
        文本流是通用接口
      透明原则
        一眼看穿在干嘛
        可调试可维护
      缄默原则
        没话就别说话
        输出即接口
      经济原则
        程序员时间最贵
        宁写一次脚本不重复劳动
    设计姿态
      模块原则
        清晰接口的简洁部件
      分离原则
        策略与机制分离
        接口与引擎分离
      吝啬原则
        小即是美
        除非必要别写大程序
    工程纪律
      清晰原则
        清晰胜过机巧
      稳健原则
        稳健源于透明加简单
      补救原则
        出错要吵要死得早
      优化原则
        先求跑通再求跑快
    生态智慧
      表示原则
        把知识叠进数据
      通俗原则
        最小惊讶原则
      生成原则
        能生成就不手写
      多样原则
        拒绝唯一正确方式
      扩展原则
        为将来留余地&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;21-哪些是宪法&#34;&gt;2.1 哪些是宪法？&lt;/h3&gt;
&lt;p&gt;17 条不是平权的。我自己的判断（也是 Raymond 书里的隐含排序）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;宪法级&lt;/strong&gt;：简单、组合、分离。这三条决定系统的基本骨架，违反了就救不回来。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;日常纪律级&lt;/strong&gt;：透明、缄默、吝啬、经济、清晰、补救。这些决定你每天写代码的手感。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生态智慧级&lt;/strong&gt;：表示、生成、多样、扩展、优化、通俗、稳健。这些决定系统活十年之后的样子。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下面只深讲五条最核心、也最容易被违反的。&lt;/p&gt;
&lt;h3 id=&#34;22-简单原则simplicity能简单就别复杂&#34;&gt;2.2 简单原则（Simplicity）：能简单就别复杂&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;原文：Design for simplicity; add complexity only where you must.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这是所有原则的根。复杂软件的问题不是&amp;quot;难写&amp;quot;，而是&lt;strong&gt;难改、难测、难理解&lt;/strong&gt;——复杂度是利息，每次改动都在还息。&lt;/p&gt;
&lt;p&gt;程序员最常见的自欺是：&amp;ldquo;这个功能以后肯定用得上，先设计成通用的。&amp;ldquo;结果通用框架写好了三年，那个&amp;quot;以后&amp;quot;从没来过，而每个新人都得先学懂这个没人用的框架才能干活。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;避坑指南&lt;/strong&gt;：当你设计一个系统需要超过一页纸才能向新同事解释清楚时，先怀疑设计，而不是怀疑新同事。&lt;/p&gt;
&lt;h3 id=&#34;23-组合原则composition程序要能被接起来&#34;&gt;2.3 组合原则（Composition）：程序要能被&amp;quot;接&amp;quot;起来&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;原文：Design programs to be connected with other programs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这条是 Unix 真正的独创，也是它和同时代其他操作系统最大的区别。别的系统里，程序是一个个孤岛，各干各的；Unix 里的程序天生设计成&lt;strong&gt;可以被串起来&lt;/strong&gt;——输入是文本流，输出也是文本流，于是任何两个程序都能被一根管道接起来，哪怕写第一个程序的人根本不知道第二个程序的存在。&lt;/p&gt;
&lt;p&gt;这条原则的威力在第 4 节的 McIlroy 公案里会展现得淋漓尽致。先记住一个推论：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一个程序如果不能被组合，它的能力上限就是它自己的代码量；一个能被组合的程序，能力上限是整个系统里所有程序的能力之积。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;24-透明原则transparency一眼看穿系统在干嘛&#34;&gt;2.4 透明原则（Transparency）：一眼看穿系统在干嘛&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;原文：Design for visibility to make inspection and debugging easier.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;透明性的标准是：&lt;strong&gt;系统的行为能不能被一个普通工程师&amp;quot;看出来&amp;rdquo;，而不是&amp;quot;猜出来&amp;rdquo;。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;反面教材：配置写在一个神秘的二进制数据库里、错误码只有厂商文档能查、出了问题只能重启看运气。正面教材：配置文件是文本、日志是人话、&lt;code&gt;strace&lt;/code&gt; 一看就知道进程在干什么。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;工程连接&lt;/strong&gt;：现在面试常考的&amp;quot;可观测性&amp;quot;（observability）——metrics、tracing、structured logging——本质上就是透明原则在云原生时代的重新发明。50 年前 Unix 用文本和管道做的事，今天我们用 OpenTelemetry 再做一遍。&lt;/p&gt;
&lt;h3 id=&#34;25-缄默原则silence没话就闭嘴&#34;&gt;2.5 缄默原则（Silence）：没话就闭嘴&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;原文：When a program has nothing surprising to say, it should say nothing.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这条最反直觉，新手程序员最爱犯：程序跑成功了，打印一行&amp;quot;操作成功完成！&amp;quot;；循环处理一万条数据，每条打一行进度。&lt;/p&gt;
&lt;p&gt;为什么这是错的？因为 Unix 程序的&lt;strong&gt;输出是接口&lt;/strong&gt;。&lt;code&gt;ls | wc -l&lt;/code&gt; 能数出目录里有几个文件，前提是 &lt;code&gt;ls&lt;/code&gt; 不会在末尾加一行&amp;quot;共 42 个文件，查询成功！&amp;quot;。一句多余的人话，对人是客气，对下游程序是脏数据。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;工程连接&lt;/strong&gt;：这条在今天的对应物是日志纪律——INFO 级别不该打印&amp;quot;一切正常&amp;quot;的心跳废话，告警不该在无事发生时刷屏。告警疲劳（alert fatigue）就是缄默原则被违反的现代版本：狼来了喊一千次，真的狼来了没人理。&lt;/p&gt;
&lt;h3 id=&#34;26-经济原则economy程序员的时间比机器的时间贵&#34;&gt;2.6 经济原则（Economy）：程序员的时间比机器的时间贵&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;原文：Programmer time is expensive; conserve it in preference to machine time.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;1970 年代机器慢、人便宜，这条原则在当时是革命性的；今天机器快得离谱、工程师年薪以十万计，这条原则更加正确。推论是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;能用 shell 脚本十分钟糊出来的临时工具，就别开 IDE 建工程写三天；&lt;/li&gt;
&lt;li&gt;能用高一级的语言（当年是 C vs 汇编，今天是 Python vs C++）表达，就别用低一级语言省那点机器时间；&lt;/li&gt;
&lt;li&gt;机器闲着的每一秒都不值钱，你加班的每一小时都很值钱。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这条原则是&amp;quot;无为&amp;quot;最直接的落地：&lt;strong&gt;能不做就不做，能少做就少做，把省下来的时间花在那件真正值得做的事上。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;3-管道与过滤器unix-哲学的物理化身&#34;&gt;3. 管道与过滤器：Unix 哲学的物理化身&lt;/h2&gt;
&lt;h3 id=&#34;31-一夜诞生的管道&#34;&gt;3.1 一夜诞生的管道&lt;/h3&gt;
&lt;p&gt;管道（&lt;code&gt;|&lt;/code&gt;）的故事是软件史上最著名的&amp;quot;一夜奇迹&amp;quot;之一，值得完整讲一遍：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;1964 年&lt;/strong&gt;，Doug McIlroy 就在贝尔实验室的内部备忘录里写下了类似的想法：&amp;ldquo;我们应该有某种方法，能把程序像花园水管一样接起来——把一段数据拧进另一段。&amp;quot;（&amp;ldquo;We should have some ways of coupling programs like garden hose.&amp;quot;）&lt;/li&gt;
&lt;li&gt;想法提了快十年，一直没人实现。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1973 年的一天&lt;/strong&gt;，McIlroy 又一次向 Ken Thompson 提起这个想法。Thompson 当晚回家，&lt;strong&gt;一夜之间修改了 Unix 内核和 shell&lt;/strong&gt;，把管道做了出来，顺手改了一批工具让它们能配合管道工作。&lt;/li&gt;
&lt;li&gt;第二天早上，管道就成了 Unix 的永久居民。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;McIlroy 后来回忆，那是 Unix 历史上最重要的时刻之一——一个想了十年的概念，被一个理解它的人用一晚上变成了现实。这个故事同时也是经济原则的最佳注脚：&lt;strong&gt;想清楚了再动手，动手就是一夜的事；没想清楚的团队，开会开十年。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;32-文本流最简陋也最长寿的通用接口&#34;&gt;3.2 文本流：最简陋也最长寿的通用接口&lt;/h3&gt;
&lt;p&gt;管道为什么能串起一切？因为所有程序约定了同一个数据格式：&lt;strong&gt;字节流，惯例上是按行组织的文本。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这个接口&amp;quot;简陋&amp;quot;到可笑——没有 schema、没有类型检查、没有版本号。但正因如此：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1970 年写的程序，输出可以被 2023 年写的程序消费，中间隔了半个世纪、几代程序员、无数硬件更迭；&lt;/li&gt;
&lt;li&gt;任何新程序只要读 stdin、写 stdout，立刻自动获得与系统里&lt;strong&gt;所有&lt;/strong&gt;工具组合的能力，不用跟谁谈判、不用签接口协议。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对比一下同时代的其他方案：IBM 的作业控制语言 JCL 要你先定义数据集格式，各种 RPC 框架要你先对齐接口定义。它们都更&amp;quot;严谨&amp;rdquo;，也都更快地死掉了。文本流活了 50 多年，至今是你每天 &lt;code&gt;kubectl get pods | grep xxx | awk &#39;{print $1}&#39;&lt;/code&gt; 时正在用的东西。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一句话理解&lt;/strong&gt;：文本流是接口设计里的&amp;quot;中庸之道&amp;rdquo;——结构刚好够工具处理（按行、按空格分列），又松散到谁都能产生、谁都能消费。&lt;/p&gt;
&lt;h3 id=&#34;33-过滤器模式把程序写成水管中间的一截&#34;&gt;3.3 过滤器模式：把程序写成水管中间的一截&lt;/h3&gt;
&lt;p&gt;管道催生了一种程序形态：&lt;strong&gt;过滤器（filter）&lt;/strong&gt;——从 stdin 读，变换一下，往 stdout 写。&lt;code&gt;grep&lt;/code&gt;、&lt;code&gt;sort&lt;/code&gt;、&lt;code&gt;uniq&lt;/code&gt;、&lt;code&gt;awk&lt;/code&gt;、&lt;code&gt;sed&lt;/code&gt;、&lt;code&gt;tr&lt;/code&gt; 全是过滤器。&lt;/p&gt;
&lt;p&gt;过滤器的可怕之处在于&lt;strong&gt;乘法效应&lt;/strong&gt;：系统里有 N 个过滤器，理论上就有 N! 种串法（当然实际有意义的少得多，但数量级是对的）。每新增一个过滤器，系统的总能力不是 +1，而是 ×N。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;输入: 任意文本&amp;lt;br/&amp;gt;(文件/网络/进程输出)&amp;#34;] --&amp;gt; B[&amp;#34;过滤器1&amp;lt;br/&amp;gt;grep/sed/tr&amp;#34;]
    B --&amp;gt; C[&amp;#34;过滤器2&amp;lt;br/&amp;gt;sort/awk&amp;#34;]
    C --&amp;gt; D[&amp;#34;过滤器3&amp;lt;br/&amp;gt;uniq/wc/head&amp;#34;]
    D --&amp;gt; E[&amp;#34;输出: 依然是文本&amp;lt;br/&amp;gt;可继续被消费&amp;#34;]
    B -. &amp;#34;每个程序只做一件事&amp;#34; .-&amp;gt; B
    C -. &amp;#34;接口统一为文本流&amp;#34; .-&amp;gt; C
    D -. &amp;#34;组合出无限可能&amp;#34; .-&amp;gt; D&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;工程连接&lt;/strong&gt;：这个模式的后代到处都是——MapReduce 的 map/reduce 是大规模版过滤器，Kafka 的 stream processing 是分布式版管道，React 的数据流、shell 出身的 Unix 老兵们设计的 CI/CD pipeline，全是同一张脸。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;4-经典公案六行-shell-挑战-knuth&#34;&gt;4. 经典公案：六行 shell 挑战 Knuth&lt;/h2&gt;
&lt;p&gt;这是软件史上我最喜欢的公案，没有之一，必须完整讲。&lt;/p&gt;
&lt;h3 id=&#34;41-背景bentley-出题knuth-接招&#34;&gt;4.1 背景：Bentley 出题，Knuth 接招&lt;/h3&gt;
&lt;p&gt;1986 年，Jon Bentley 在《Communications of the ACM》的&amp;quot;Programming Pearls&amp;quot;专栏出了一道题：&lt;strong&gt;给定一个文本文件和一个整数 N，输出出现频率最高的前 N 个单词及其次数。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;出题的目的不是真缺这个功能，而是想展示当时最时髦的编程方法论。Bentley 请来了 Donald Knuth——图灵奖得主、《计算机程序设计艺术》作者——用他发明的**文学编程（literate programming）**来解答。&lt;/p&gt;
&lt;p&gt;Knuth 交了一份大约&lt;strong&gt;十页&lt;/strong&gt;的 WEB 程序（Pascal 代码与散文交织），精心设计了哈希表、用 trie 变体做词频统计，数据结构精巧、行文优雅，是文学编程的样板戏。&lt;/p&gt;
&lt;h3 id=&#34;42-mcilroy-的评论六行管道&#34;&gt;4.2 McIlroy 的评论：六行管道&lt;/h3&gt;
&lt;p&gt;Bentley 随后请 Doug McIlroy 写评论。McIlroy 先客气地称赞了 Knuth 的程序，然后平静地指出：这个任务，用 Unix 工具六行就能做完，而且更好——因为每一行都是久经考验的现成工具，没有一行是新写的、可能有 bug 的代码：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;tr -cs A-Za-z &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;\n&amp;#39;&lt;/span&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;tr A-Z a-z |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sort |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;uniq -c |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sort -rn |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sed &lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;1&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;q&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;逐行翻译：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;tr -cs A-Za-z &#39;\n&#39;&lt;/code&gt;：把所有非字母字符换成换行——文本变成&amp;quot;每行一个词&amp;quot;；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tr A-Z a-z&lt;/code&gt;：全部转小写——&amp;ldquo;The&amp;quot;和&amp;quot;the&amp;quot;算同一个词；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sort&lt;/code&gt;：排序——相同的词现在挨在一起；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uniq -c&lt;/code&gt;：合并相邻重复行并计数——每行变成&amp;quot;次数 单词&amp;rdquo;；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sort -rn&lt;/code&gt;：按数字逆序排——高频词浮到顶部；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sed ${1}q&lt;/code&gt;：只保留前 N 行（N 从命令行参数来）。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    T[&amp;#34;原始文本&amp;lt;br/&amp;gt;The quick brown fox&amp;lt;br/&amp;gt;the lazy dog&amp;#34;] --&amp;gt; S1[&amp;#34;tr: 拆成单词&amp;lt;br/&amp;gt;每行一个词&amp;#34;]
    S1 --&amp;gt; S2[&amp;#34;tr: 统一小写&amp;lt;br/&amp;gt;消除大小写干扰&amp;#34;]
    S2 --&amp;gt; S3[&amp;#34;sort: 排序&amp;lt;br/&amp;gt;相同单词聚到一起&amp;#34;]
    S3 --&amp;gt; S4[&amp;#34;uniq -c: 计数&amp;lt;br/&amp;gt;2 the&amp;lt;br/&amp;gt;1 brown&amp;lt;br/&amp;gt;1 dog&amp;#34;]
    S4 --&amp;gt; S5[&amp;#34;sort -rn: 按频次降序&amp;#34;]
    S5 --&amp;gt; S6[&amp;#34;sed Nq: 取前N名&amp;#34;]
    style S1 fill:#e8f4e8
    style S2 fill:#e8f4e8
    style S3 fill:#e8f4e8
    style S4 fill:#e8f4e8
    style S5 fill:#e8f4e8
    style S6 fill:#e8f4e8&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;43-这场公案到底说明了什么&#34;&gt;4.3 这场公案到底说明了什么&lt;/h3&gt;
&lt;p&gt;Knuth 本人输了吗？没有。他的哈希 trie 在大规模数据上确实更快，而且十页程序的初衷是展示文学编程，不是交付生产工具。这场公案真正的价值在于 McIlroy 的评论里那几句话的杀伤力：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Knuth 的程序里，每个数据结构都是新的、都需要正确性证明；shell 管道里没有一个数据结构是新的，sort 和 uniq 已经被全世界的人每天用了十几年。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这就是&amp;quot;无为&amp;quot;的极致：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;不写代码，就是最高的代码质量&lt;/strong&gt;——没有新写的数据结构，就没有新的 off-by-one 错误；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;组合现成工具，胜过手工打造精品&lt;/strong&gt;——文学编程追求的&amp;quot;像文学一样优雅的程序&amp;quot;，败给了&amp;quot;根本不用写程序&amp;quot;；&lt;/li&gt;
&lt;li&gt;顺便还赢了可读性：Knuth 的程序要读十页，McIlroy 的读十秒。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;每次我看到团队为一个&amp;quot;一次性数据统计需求&amp;quot;立项、排期、写微服务，我都会想起这六行 shell。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;5-机制与策略分离x-window-的世纪教训&#34;&gt;5. 机制与策略分离：X Window 的世纪教训&lt;/h2&gt;
&lt;p&gt;分离原则（Separation）说：&lt;strong&gt;把策略（policy）和机制（mechanism）分开；把接口和引擎分开。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;机制&lt;/strong&gt;：能做什么——提供能力，比如&amp;quot;能在屏幕上画窗口、能接收鼠标点击&amp;quot;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;策略&lt;/strong&gt;：该怎么做——决定行为，比如&amp;quot;窗口标题栏长什么样、点击关闭按钮是杀进程还是最小化&amp;quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;51-x-window把不表态写进架构&#34;&gt;5.1 X Window：把&amp;quot;不表态&amp;quot;写进架构&lt;/h3&gt;
&lt;p&gt;Unix 世界对这个原则最彻底的实践是 &lt;strong&gt;X Window System（X11）&lt;/strong&gt;，1984 年起源于 MIT。X 的设计者把&amp;quot;机制，而非策略&amp;quot;（mechanism, not policy）直接写成了项目格言。&lt;/p&gt;
&lt;p&gt;具体怎么分的：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TB
    subgraph 策略层[&amp;#34;策略层(可由用户替换)&amp;#34;]
        WM[&amp;#34;窗口管理器&amp;lt;br/&amp;gt;决定标题栏样式/焦点行为&amp;#34;]
        TK[&amp;#34;控件工具包&amp;lt;br/&amp;gt;决定按钮长什么样&amp;#34;]
    end
    subgraph 机制层[&amp;#34;机制层(X服务器)&amp;#34;]
        XS[&amp;#34;X Server&amp;lt;br/&amp;gt;只管画像素/收输入事件&amp;lt;br/&amp;gt;不关心&amp;#39;窗口&amp;#39;概念&amp;#34;]
    end
    APP[&amp;#34;应用程序&amp;#34;] --&amp;gt; TK
    WM --&amp;gt; XS
    TK --&amp;gt; XS
    XS -. &amp;#34;协议(X protocol)&amp;#34; .- APP&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;X 服务器&lt;strong&gt;故意不知道&lt;/strong&gt;窗口应该长什么样、标题栏该有几个按钮。它只提供画矩形、画字、传鼠标键盘事件的机制。所有&amp;quot;外观和行为&amp;quot;都推给了可替换的窗口管理器和工具包。&lt;/p&gt;
&lt;p&gt;结果是惊人的长寿和多样性：同一套 X 协议跑了近四十年，上面先后长出过 Motif、GTK、Qt 等无数风格迥异的桌面环境，X 服务器本身几乎不用变。机制稳定，策略自由演化——这就是&amp;quot;无为而治&amp;quot;的架构版：底层定最少的规矩，上层爱怎么长就怎么长。&lt;/p&gt;
&lt;h3 id=&#34;52-反面教材策略焊死在机制里&#34;&gt;5.2 反面教材：策略焊死在机制里&lt;/h3&gt;
&lt;p&gt;对比之下，很多 GUI 系统把&amp;quot;按钮必须长这样、窗口必须这样关闭&amp;quot;直接焊死在系统底层。短期看开箱即用，长期看：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;想换一种交互风格？改不动底层，只能外挂 hack；&lt;/li&gt;
&lt;li&gt;底层想升级？牵一发而动全身，一动就破坏所有应用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;工程连接&lt;/strong&gt;：这个教训在今天依然天天上演——&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Kubernetes 的控制循环是机制，你的 CRD/Operator 是策略；&lt;/li&gt;
&lt;li&gt;HTTP 是机制，REST 风格是策略；&lt;/li&gt;
&lt;li&gt;编译器前端后端分离（LLVM 的 IR 是机制，各语言前端是策略）；&lt;/li&gt;
&lt;li&gt;你自己写的框架如果把&amp;quot;业务规则&amp;quot;和&amp;quot;执行引擎&amp;quot;揉在一个类里，就是在给三年后的自己埋雷。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;一句话理解&lt;/strong&gt;：机制回答&amp;quot;能不能&amp;quot;，策略回答&amp;quot;该不该&amp;quot;。把&amp;quot;该不该&amp;quot;的决定权留给离用户最近的那层。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;6-cat--v-之争小即是美的保卫战&#34;&gt;6. cat -v 之争：小即是美的保卫战&lt;/h2&gt;
&lt;h3 id=&#34;61-一只猫引发的血案&#34;&gt;6.1 一只猫引发的血案&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;cat&lt;/code&gt; 是 Unix 里最单纯的程序：把文件内容原样输出。它的名字来自 concatenate（拼接），&lt;code&gt;cat a b c &amp;gt; all&lt;/code&gt; 把三个文件拼成一个。就这么点功能，干净漂亮。&lt;/p&gt;
&lt;p&gt;后来 BSD（伯克利版 Unix）的开发者给 &lt;code&gt;cat&lt;/code&gt; 加了选项，比如 &lt;code&gt;-v&lt;/code&gt; 可以把不可见字符显示出来。听起来人畜无害，对吧？&lt;/p&gt;
&lt;p&gt;1983 年，Rob Pike 和 Brian Kernighan 在 USENIX 大会上发了一篇演讲，标题就很凶：&lt;strong&gt;《Program Design in the UNIX Environment》&lt;/strong&gt;，其中一节叫 &amp;ldquo;cat -v considered harmful&amp;rdquo;（化用 Dijkstra 的名篇）。核心论点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;cat&lt;/code&gt; 的职责是&lt;strong&gt;拼接字节流&lt;/strong&gt;，&amp;ldquo;可视化不可见字符&amp;quot;是&lt;strong&gt;另一件事&lt;/strong&gt;，属于另一个程序；&lt;/li&gt;
&lt;li&gt;一旦 &lt;code&gt;cat&lt;/code&gt; 开始长选项，它就会无限长下去——今天是 &lt;code&gt;-v&lt;/code&gt;，明天是显示行号、压缩空行……Unix 后来的 cat 果然长出了 &lt;code&gt;-n&lt;/code&gt;、&lt;code&gt;-s&lt;/code&gt;、&lt;code&gt;-E&lt;/code&gt;、&lt;code&gt;-T&lt;/code&gt; 一长串；&lt;/li&gt;
&lt;li&gt;正确做法是写一个专门的小程序做可视化（Pike 他们后来写了 &lt;code&gt;vis&lt;/code&gt;），让 &lt;code&gt;cat&lt;/code&gt; 保持单纯。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;62-这不是洁癖是防熵增&#34;&gt;6.2 这不是洁癖，是防熵增&lt;/h3&gt;
&lt;p&gt;回头看，Pike 他们争的从来不是 &lt;code&gt;cat&lt;/code&gt; 这一个程序，而是一个普适规律：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;软件的自然演化方向是功能蔓延（feature creep），而每一次&amp;quot;顺手加个选项&amp;quot;都在加速它。今天不加护栏，三年后它就会变成没人敢动的屎山。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;工程连接&lt;/strong&gt;：你身边一定有那个&amp;quot;本来是 200 行的小工具，三年后变成 8000 行、40 个命令行参数、没人知道所有参数组合行为&amp;quot;的内部程序。它就是一场输掉了的 cat -v 战争。McIlroy 那句话在这里闭环了：&lt;strong&gt;&amp;ldquo;要做新功能，就写个新程序，而不是给旧程序加特性。&amp;rdquo;&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;63-小即是美small-is-beautiful&#34;&gt;6.3 小即是美（Small is Beautiful）&lt;/h3&gt;
&lt;p&gt;cat -v 之争背后的正面主张，后来被 Mike Gancarz 总结成一本书的名字：《Linux and the Unix Philosophy》里的核心口号——&lt;strong&gt;Small is Beautiful（小即是美）&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;小程序容易写、容易测、容易懂、容易替换；&lt;/li&gt;
&lt;li&gt;大程序每一处都是沉没成本，坏了一个角就全盘皆输；&lt;/li&gt;
&lt;li&gt;一个 200 行的小工具，用不顺手可以扔掉重写；一个 8000 行的怪兽，你只能供着它。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;7-worse-is-betterunix-为什么能赢&#34;&gt;7. Worse is Better：Unix 为什么能赢&lt;/h2&gt;
&lt;h3 id=&#34;71-gabriel-的著名论断&#34;&gt;7.1 Gabriel 的著名论断&lt;/h3&gt;
&lt;p&gt;1989 年前后，Lisp 黑客 Richard Gabriel 写了一篇著名文章（后收录于《Lisp: Good News, Bad News, How to Win Big》，1991），提出了一个让理想主义者抓狂的概念：&lt;strong&gt;Worse is Better（更糟的就是更好的）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;他对比了两种设计哲学：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;MIT/Stanford 路线（&amp;ldquo;正确的事&amp;rdquo;，The Right Thing）&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设计必须完整、正确、一致、优雅；&lt;/li&gt;
&lt;li&gt;接口的简洁性没有实现的正确性重要；&lt;/li&gt;
&lt;li&gt;代表：Lisp 机器、Scheme 的理想主义一脉。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;新泽西路线（Worse is Better，新泽西 = 贝尔实验室所在地）&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;实现的简单性优先于一切&lt;/strong&gt;，接口简单、正确性、一致性都要为它让路；&lt;/li&gt;
&lt;li&gt;功能做 50% 能跑，好过做 90% 但没人写完；&lt;/li&gt;
&lt;li&gt;代表：C 和 Unix。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Gabriel 的观察是：那个&amp;quot;更糟&amp;quot;的新泽西方案会赢，因为它&lt;strong&gt;像病毒一样可移植&lt;/strong&gt;——简单的东西容易实现、容易移植到任何机器上，先占领世界，再靠用户社区慢慢把它补齐。而&amp;quot;正确的事&amp;quot;因为太重、太贵、太难实现，永远停留在少数精英的实验室里。&lt;/p&gt;
&lt;h3 id=&#34;72-历史判卷gabriel-说对了&#34;&gt;7.2 历史判卷：Gabriel 说对了&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;C 语言有一堆&amp;quot;糟糕&amp;quot;的设计（缓冲区溢出、未定义行为），但它简单到一个人能写个编译器，于是 C 编译器铺满了地球上每一台机器；&lt;/li&gt;
&lt;li&gt;Unix 的系统调用接口远不如 Lisp 机器优雅，但 Unix 能跑在从 PDP-11 到超算的一切东西上；&lt;/li&gt;
&lt;li&gt;反观 Lisp 机器、各种&amp;quot;设计完美&amp;quot;的操作系统，坟头草已经三米高了。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;73-但要小心这不是给烂代码发通行证&#34;&gt;7.3 但要小心：这不是给烂代码发通行证&lt;/h3&gt;
&lt;p&gt;Gabriel 本人对这篇文章的态度其实很矛盾（他甚至用化名写过反驳自己的文章）。Worse is better 描述的是&lt;strong&gt;演化的规律&lt;/strong&gt;，不是&lt;strong&gt;你写烂代码的借口&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;它说的是&amp;quot;实现简单、先跑起来、让世界帮你完善&amp;quot;会赢；&lt;/li&gt;
&lt;li&gt;它&lt;strong&gt;不是&lt;/strong&gt;说&amp;quot;不测试、不处理错误、随便糊&amp;quot;会赢。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;一句话理解&lt;/strong&gt;：先交付一个简单到不可能失败的 60 分方案，让它在真实世界里进化；不要在象牙塔里憋一个永远交付不了的 100 分方案。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;8-无用之用shell-脚本的临时救赎&#34;&gt;8. 无用之用：shell 脚本的临时救赎&lt;/h2&gt;
&lt;p&gt;道家讲&amp;quot;无用之用，方为大用&amp;rdquo;——看起来没用的东西，往往有大用。Shell 脚本就是编程世界里的&amp;quot;无用之用&amp;quot;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;它&amp;quot;不入流&amp;quot;：没有类型系统、没有 IDE 补全、面试不考、简历上不好意思写；&lt;/li&gt;
&lt;li&gt;但它解决的是&lt;strong&gt;真实世界里最高频的那类问题&lt;/strong&gt;：一次性的、临时的、不值得立项的脏活。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;举几个我自己（和每个老 Unix 用户）每周都在做的事：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 找出日志里报错最多的 10 个接口&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;grep ERROR app.log | awk &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;{print $5}&amp;#39;&lt;/span&gt; | sort | uniq -c | sort -rn | head
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 把 300 个 .jpeg 文件批量重命名成 .jpg&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; f in *.jpeg; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; mv &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$f&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;f%.jpeg&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;.jpg&amp;#34;&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 监控某个进程，挂了自动拉起（穷人版 supervisor）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; true; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; ./myserver &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; sleep 5; &lt;span style=&#34;color:#66d9ef&#34;&gt;done&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;这些脚本&amp;quot;丑陋&amp;quot;、&amp;ldquo;脆弱&amp;rdquo;、&amp;ldquo;不可维护&amp;rdquo;——都没错，但它们本来就是&lt;strong&gt;一次性用品&lt;/strong&gt;，写完跑完就扔。为它们搞工程化、写单元测试、上代码评审，恰恰违反了经济原则：程序员的时间比机器贵，也比&amp;quot;代码的体面&amp;quot;贵。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;关键判断力&lt;/strong&gt;在于分清楚两类问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;临时问题&lt;/strong&gt; → shell 糊一个，跑完即弃，这是&amp;quot;无为&amp;quot;的胜利；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;反复出现的问题&lt;/strong&gt; → 那就不是临时问题了，老老实实写成正经工具，别拿&amp;quot;反正临时用用&amp;quot;当借口让脚本混进生产系统核心路径。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;区分错了方向都很惨：给临时问题过度工程化是浪费生命，让临时脚本变成永久依赖是埋雷（无数公司的核心发布流程，起点都是某人&amp;quot;临时写一下&amp;quot;的 500 行 bash）。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;9-与无为而治的正式呼应&#34;&gt;9. 与&amp;quot;无为而治&amp;quot;的正式呼应&lt;/h2&gt;
&lt;p&gt;呼应一下开头，把这条镜像关系钉死：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;道家&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;Unix 哲学&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;无为 = 不妄为&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;不写多余的代码（吝啬/经济原则）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;治大国若烹小鲜&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;大系统由小工具组合而成（组合原则）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;道法自然&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;顺应文本流/管道这个&amp;quot;自然接口&amp;quot;，不发明私有协议&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;上善若水，水善利万物而不争&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;过滤器程序低调透明，只过数据不刷存在感（缄默原则）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;圣人无常心，以百姓心为心&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;机制不预设策略，把选择权留给用户（X Window）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;无用之用&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;shell 一次性脚本、简单的文本接口反而最长寿&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;但注意：&lt;strong&gt;这本书的主体是工程，不是玄学。&lt;/strong&gt; 道家给的是一个好用的直觉框架，真正支撑 Unix 哲学的是 50 年的工程证据——管道的胜利、文本流的长寿、cat -v 的沦陷、Lisp 机器的坟墓。直觉负责让你快速做出方向正确的决定，证据负责在你被质疑时撑住你。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;10-带走的清单&#34;&gt;10. 带走的清单&lt;/h2&gt;
&lt;p&gt;写代码或做设计之前，过一遍这十条（按 17 原则提炼后的实战版）：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;能不写就不写&lt;/strong&gt;：这个问题用现有工具组合能解决吗？（McIlroy 公案）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一次只做一件事&lt;/strong&gt;：这个程序/类/函数，能用一句话说清职责吗？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;输出即接口&lt;/strong&gt;：这个程序的输出，能被另一个还没写出来的程序直接消费吗？（缄默原则）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用文本说事&lt;/strong&gt;：这个配置/日志/接口，是人能直接读的文本吗？（透明原则）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;机制和策略分开了吗&lt;/strong&gt;：我写的这层，是提供能力，还是替用户做决定？（X Window 教训）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;先跑起来再说&lt;/strong&gt;：现在的版本是不是简单到不可能失败？（Worse is better）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;这是临时问题吗&lt;/strong&gt;：是 → 脚本糊；不是 → 正经写。别搞反。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;我在加功能还是加复杂度&lt;/strong&gt;：这个&amp;quot;顺手加的小功能&amp;quot;，是不是下一个 cat -v？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;复杂度在哪一层&lt;/strong&gt;：不得不复杂时，复杂度被隔离在最小范围了吗？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;删掉一半会怎样&lt;/strong&gt;：如果砍掉一半功能，核心用户还在吗？在 → 砍。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;11-避坑指南对-unix-哲学最常见的四种误用&#34;&gt;11. 避坑指南：对 Unix 哲学最常见的四种误用&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;坑一：把 KISS 当懒惰的挡箭牌。&lt;/strong&gt; &amp;ldquo;简单&amp;quot;指的是设计和实现的简单，不是&amp;quot;不做需求、不处理边界情况&amp;rdquo;。Knuth 的词频程序虽然输给了六行 shell，但它处理的性能边界是 shell 版处理不了的——该复杂的地方必须复杂。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;坑二：把 worse is better 当烂代码许可证。&lt;/strong&gt; 它描述的是&amp;quot;简单实现先活下来再进化&amp;quot;的演化规律，不是&amp;quot;别写测试、别管错误处理&amp;quot;。60 分方案的前提是&lt;strong&gt;边界清晰、失败模式明确&lt;/strong&gt;，不是糊。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;坑三：把&amp;quot;小即是美&amp;quot;理解成微服务越多越好。&lt;/strong&gt; 微服务拆分如果只分了部署、没分清楚接口，得到的是分布式屎山。组合原则的前提是&lt;strong&gt;每个部件接口干净&lt;/strong&gt;，否则 N 个服务只有 N 倍的运维痛苦，没有组合红利。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;坑四：用 shell 糊生产核心。&lt;/strong&gt; 一次性脚本的正确归宿是跑完即弃（或转正为正经工具）。让 500 行没人看懂的 bash 长在发布流程的心脏上，是把&amp;quot;无用之用&amp;quot;用成了&amp;quot;无用之害&amp;quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;12-结语&#34;&gt;12. 结语&lt;/h2&gt;
&lt;p&gt;《Unix 编程艺术》出版于 2003 年，讲的是 1969 年开始的故事，但它预言对了此后二十年几乎所有的技术走向：文本协议（HTTP/JSON）打败二进制协议，组合式工具（容器、K8s、CI/CD pipeline）打败单体巨兽，&amp;ldquo;简单实现先跑起来&amp;rdquo;（Python、Go 的设计取向）打败&amp;quot;正确的事&amp;quot;。&lt;/p&gt;
&lt;p&gt;回到那句主线：&lt;strong&gt;无为 = 不写多余的代码。&lt;/strong&gt; 最高明的程序员不是写得最快的人，而是最清楚哪行代码不该存在的人。就像老子说的，为学日益，为道日损——学技术是不断加法，懂设计是不断减法。&lt;/p&gt;
&lt;p&gt;这本书值得每五年重读一遍，因为你对&amp;quot;哪行代码不该存在&amp;quot;的判断力，会随踩坑数量逐年升级。&lt;/p&gt;
</description>
    </item>
    <item>
      <title>《人月神话》笔记：Brooks 用 OS/360 的学费换来的软件工程第一性原理</title>
      <link>https://ftmao.com/blog/bookstore/mythical-man-month-book-notes/</link>
      <pubDate>Tue, 08 Aug 2023 10:40:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/bookstore/mythical-man-month-book-notes/</guid>
      <description>&lt;h1 id=&#34;人月神话笔记brooks-用-os360-的学费换来的软件工程第一性原理&#34;&gt;《人月神话》笔记：Brooks 用 OS/360 的学费换来的软件工程第一性原理&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;目标读者：带过项目、估过排期、经历过&amp;quot;老板说加人就能赶上&amp;quot;的工程师和工程管理者。
参考书籍：Frederick P. Brooks Jr.《人月神话》（The Mythical Man-Month，1975 年初版，1995 年 20 周年纪念版），含 1986 年论文《没有银弹》（No Silver Bullet）。
阅读目标：不是复述一本 50 年前的书，而是把 Brooks 用 IBM OS/360 项目的巨额学费换来的几条&amp;quot;软件工程第一性原理&amp;quot;蒸馏成今天还能直接用的判断准则。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一焦油坑这本书从哪里长出来的&#34;&gt;一、焦油坑：这本书从哪里长出来的&lt;/h2&gt;
&lt;p&gt;书的第一章叫「焦油坑」（The Tar Pit），Brooks 用了一个古生物学意象：拉布雷亚沥青坑里，猛犸、剑齿虎、恐龙——越是强壮的野兽，挣扎得越猛烈，陷得越深。&lt;strong&gt;大型系统编程就是这样一个焦油坑：没有哪支队伍特别笨，但大家都在里面挣扎，而且越挣扎陷得越深。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这句话不是文学修辞，是作者的身份声明。Brooks 是 IBM System/360 大型机项目的经理，随后又是其操作系统 &lt;strong&gt;OS/360&lt;/strong&gt; 的项目经理——这是 1960 年代人类历史上最大的软件项目之一，几千人年、超支数倍、进度失控、bug 成山。1964 年 IBM 老板小沃森问他项目为什么管成这样，他答不上来；几个月后他离开 IBM 去北卡大学建计算机系，把这次失败翻来覆去想了十年，1975 年写出这本书。&lt;/p&gt;
&lt;p&gt;所以读这本书的正确姿势是：&lt;strong&gt;这不是一个管理学教授在总结别人的案例，是一个搞砸过史上最大软件项目的人在写尸检报告。&lt;/strong&gt; 书里每一条论断背后，都是 OS/360 真金白银的教训。这也是为什么这本书快 50 年了还不过时——硬件换了五代，语言换了几轮，但&amp;quot;人怎么组织起来写大软件&amp;quot;这个问题的底层结构，几乎没变。&lt;/p&gt;
&lt;p&gt;全书论点可以用一句话串起来：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;软件项目的失败，绝大多数不是技术失败，而是对&amp;quot;编程这项活动的本质&amp;quot;估计错误的失败——错估了沟通成本、错估了复杂度的来源、错估了设计与实现的相对价值。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;二人月神话人月是不可互换的&#34;&gt;二、人月神话：人月是不可互换的&lt;/h2&gt;
&lt;p&gt;书名即论点。&lt;strong&gt;人月（man-month）——用人乘以月来度量工作量——是一个神话，因为它暗含一个错误前提：人和月可以互换。&lt;/strong&gt; 10 个人干 1 个月，和 1 个人干 10 个月，不是一回事。&lt;/p&gt;
&lt;p&gt;Brooks 的论证是一个漂亮的分类学，按任务的&lt;strong&gt;可分解性&lt;/strong&gt;拆开：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;完全可分解、无需沟通的任务&lt;/strong&gt;：比如收麦子、筛数据。这类任务人月确实可互换，人加倍时间减半。可惜软件里几乎没有这种任务。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可分解但需要沟通的任务&lt;/strong&gt;：大部分编码工作属于此类。增加人手，沟通成本随之而来，收益开始打折。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可分解但需要复杂沟通与再培训的任务&lt;/strong&gt;：沟通成本开始吞噬新增人手带来的产能。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;完全不可分解、有严格次序依赖的任务&lt;/strong&gt;：比如调试（本质上是顺序的），比如生孩子——九个月，几个母亲也压缩不了。软件里的系统设计和集成调试大量属于此类。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    T[&amp;#34;软件任务&amp;#34;] --&amp;gt; Q{&amp;#34;能否分解为&amp;lt;br/&amp;gt;无需沟通的子任务?&amp;#34;}
    Q --&amp;gt;|&amp;#34;能(极少: 收麦子型)&amp;#34;| A[&amp;#34;人月近似可互换&amp;lt;br/&amp;gt;人加倍, 工期减半&amp;#34;]
    Q --&amp;gt;|&amp;#34;不能(绝大多数)&amp;#34;| Q2{&amp;#34;主要成本是什么?&amp;#34;}
    Q2 --&amp;gt;|&amp;#34;沟通与协调&amp;#34;| B[&amp;#34;加人收益递减&amp;lt;br/&amp;gt;沟通路径数 = n乘n减1再除2&amp;#34;]
    Q2 --&amp;gt;|&amp;#34;严格次序依赖&amp;lt;br/&amp;gt;设计、调试、集成&amp;#34;| C[&amp;#34;加人完全无效&amp;lt;br/&amp;gt;九个月生不出孩子&amp;#34;]
    style C fill:#ffcccc
    style A fill:#e8f5e9&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;沟通成本那条值得算一笔小账：n 个人两两沟通，路径数是 n(n-1)/2。3 个人是 3 条，7 个人是 21 条，10 个人是 45 条。&lt;strong&gt;人数翻倍，沟通路径数大约翻两番。&lt;/strong&gt; 如果每次协作都要开会、对齐、等回复，人越多，每个人写代码的时间占比越低。这就是为什么 Brooks 说&amp;quot;用人月作为度量，等于默认了一个只在收麦子时成立的假设&amp;quot;。&lt;/p&gt;
&lt;p&gt;这条原理的一个直接推论，常常被忽略：&lt;strong&gt;进度落后的项目，往往不是人太少，而是人太多&lt;/strong&gt;——沟通结构已经把产能吃掉了，这时候减人反而可能提速。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三brooks-定律向延期的项目加人只会让它更晚&#34;&gt;三、Brooks 定律：向延期的项目加人，只会让它更晚&lt;/h2&gt;
&lt;p&gt;这是全书最出名的一句话，1975 年提出，后来被叫成 Brooks 定律（Brooks&amp;rsquo;s Law）：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Adding manpower to a late software project makes it later.（向一个已经延期的软件项目增加人手，只会让它更晚。）&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;为什么&amp;quot;加人赶工&amp;quot;这个管理者最直觉的动作是错的？Brooks 拆了三层成本：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;培训成本&lt;/strong&gt;：新人不能立刻干活。他们要先学项目的目标、架构、既有代码、开发流程——而教他们的人，正是团队里本来在干活的老手。老手的时间被培训占用，团队短期产能不升反降。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;沟通成本&lt;/strong&gt;：就是上一节的 n(n-1)/2。加人后，任务要重新划分、接口要重新对齐，原本两个人口头约定的事，现在要写文档、开会。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;任务重划分成本&lt;/strong&gt;：很多任务被打断重拆时，边界上的工作要重做；有些任务根本拆不动（次序依赖的部分），能拆出去的部分往往是最不需要人的部分。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    subgraph 加人前
        A1[&amp;#34;团队产能 100%&amp;lt;br/&amp;gt;全部用于推进项目&amp;#34;]
    end
    subgraph 加人后短期
        B1[&amp;#34;老手产能下降&amp;lt;br/&amp;gt;抽时间培训新人&amp;#34;] --&amp;gt; B3[&amp;#34;净产能低于加人前&amp;#34;]
        B2[&amp;#34;新人产能为负&amp;lt;br/&amp;gt;提问、犯错、需要返工&amp;#34;] --&amp;gt; B3
        B4[&amp;#34;沟通路径数平方增长&amp;lt;br/&amp;gt;对齐成本吞噬产出&amp;#34;] --&amp;gt; B3
    end
    subgraph 加人后长期
        C1[&amp;#34;若项目不取消&amp;lt;br/&amp;gt;数月后产能才可能恢复&amp;lt;br/&amp;gt;但死线已过&amp;#34;]
    end
    A1 -.-&amp;gt;|&amp;#34;管理者直觉: 产能应线性上升&amp;#34;| B1
    B3 --&amp;gt; C1
    style B3 fill:#ffcccc&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;书里还有一个配套观察，杀伤力同样大：&lt;strong&gt;项目是怎样延期一年的？一天一天延的。&lt;/strong&gt;（How does a project get to be a year late? One day at a time.）大灾难不是突然降临的，是每天落后一点点、每次例会都&amp;quot;下周能追上&amp;quot;累积出来的。所以 Brooks 主张里程碑必须&lt;strong&gt;具体、锋利、公开&lt;/strong&gt;——&amp;ldquo;编码完成 90%&amp;ldquo;不是里程碑，是遮羞布；&amp;ldquo;模块通过全部单元测试并合入主库&amp;quot;才是。&lt;/p&gt;
&lt;p&gt;对今天的映射几乎不用翻译：每个 deadline 前两周&amp;quot;借调支援&amp;quot;的场景，都是 Brooks 定律的现场重演。正确的动作恰恰相反——&lt;strong&gt;砍掉 20% 的范围，比增加 20% 的人有效得多。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四概念完整性与外科手术队伍&#34;&gt;四、概念完整性与外科手术队伍&lt;/h2&gt;
&lt;h3 id=&#34;41-最重要的系统属性不是功能是概念完整性&#34;&gt;4.1 最重要的系统属性不是功能，是概念完整性&lt;/h3&gt;
&lt;p&gt;Brooks 认为，一个系统给用户呈现的东西里，&lt;strong&gt;概念完整性（conceptual integrity）是最重要的品质&lt;/strong&gt;——系统各处必须体现同一套设计哲学，一致、连贯、正交。用户学透三个概念就能推测出三十个行为的系统，远好过每个功能都自说自话的系统。&lt;/p&gt;
&lt;p&gt;但概念完整性有一个组织上的代价：&lt;strong&gt;它无法由委员会设计出来。&lt;/strong&gt; 民主设计出来的系统，是每个聪明人都塞进了自己最爱的特性的系统——功能很多，灵魂没有。所以 Brooks 给了一个政治不正确但极其诚实的结论：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;概念完整性要求设计出自一个人，或极少数同心同德的人。为了完整性，必须接受贵族专制（aristocracy），放弃民主。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这不等于独裁者包揽一切。Brooks 的关键区分是：&lt;strong&gt;体系结构（architecture）与实现（implementation）分离&lt;/strong&gt;。架构师定义&amp;quot;系统对用户是什么样&amp;rdquo;——接口、语义、概念模型；大量实现者在架构约束下尽情发挥创造力。OS/360 的教训是：架构师也必须克制，架构文档里不该规定任何&amp;quot;实现细节&amp;rdquo;，否则就是把实现者的创造力抢走、把架构变成瓶颈。&lt;/p&gt;
&lt;p&gt;Unix 是这条原则最著名的正面样本（Brooks 自己也拿它举例）：一切皆文件、程序是过滤器、文本流为接口——概念少到极致，组合力大到极致。&lt;/p&gt;
&lt;h3 id=&#34;42-外科手术队伍小团队怎么做大事&#34;&gt;4.2 外科手术队伍：小团队怎么做大事&lt;/h3&gt;
&lt;p&gt;如果人少才有一致性，人少又怎么完成大项目？Brooks 的回答是全书我最喜欢的组织设计：&lt;strong&gt;外科手术队伍（surgical team）&lt;/strong&gt;——不是&amp;quot;人人都是全才&amp;quot;的平等团队，而是像一台外科手术那样，一个主刀，其余所有人都是为了让主刀的每一分钟都高效而存在。&lt;/p&gt;
&lt;p&gt;10 人编制大致是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;外科医生（首席程序员）&lt;/strong&gt;：亲手定义功能与性能规格，亲手写核心代码，对系统负全责。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;副手（copilot）&lt;/strong&gt;：能替代外科医生做任何事，是他的讨论对象、保险丝和备份，但不抢方向盘。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;管理员&lt;/strong&gt;：处理钱、人、场地、设备这些非技术事务——外科医生不该为报销分心。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;编辑/秘书&lt;/strong&gt;：外科医生口述或手写，编辑负责把文档变成清晰、统一、可发布的正式文档。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;程序职员&lt;/strong&gt;：维护所有代码版本、运行记录、测试数据——机器时间时代的&amp;quot;版本与构建管家&amp;rdquo;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工具师&lt;/strong&gt;：为团队打造专用工具、脚本、测试设施。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;测试员&lt;/strong&gt;：根据规格写测试用例，并且&lt;strong&gt;从规格里找矛盾&lt;/strong&gt;——他是架构师的第一批批评者。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;语言专家&lt;/strong&gt;：精通语言犄角旮旯的顾问，帮团队写出既优雅又高效的代码。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    S[&amp;#34;外科医生 首席程序员&amp;lt;br/&amp;gt;设计 + 核心代码 + 负全责&amp;#34;]
    C[&amp;#34;副手&amp;lt;br/&amp;gt;讨论对象与备份&amp;#34;]
    AD[&amp;#34;管理员&amp;lt;br/&amp;gt;钱、人、设备&amp;#34;]
    ED[&amp;#34;编辑&amp;lt;br/&amp;gt;文档产出与统一&amp;#34;]
    CL[&amp;#34;程序职员&amp;lt;br/&amp;gt;版本与运行记录&amp;#34;]
    TL[&amp;#34;工具师&amp;lt;br/&amp;gt;内部工具与设施&amp;#34;]
    TE[&amp;#34;测试员&amp;lt;br/&amp;gt;用例设计与规格挑刺&amp;#34;]
    LX[&amp;#34;语言专家&amp;lt;br/&amp;gt;语言技艺顾问&amp;#34;]
    C --&amp;gt; S
    AD --&amp;gt; S
    ED --&amp;gt; S
    CL --&amp;gt; S
    TL --&amp;gt; S
    TE --&amp;gt; S
    LX --&amp;gt; S
    style S fill:#fff3cd&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;注意这个结构的颠覆点：&lt;strong&gt;其他人不是外科医生的下属执行者，而是他的支撑系统。&lt;/strong&gt; 今天回头看，这是&amp;quot;10x 工程师团队&amp;quot;最早的严肃版本——结论不是&amp;quot;找 10 个 10x&amp;quot;，而是&amp;quot;&lt;strong&gt;让那个 10x 的人 100% 的时间都在做只有他能做的事&lt;/strong&gt;&amp;quot;。大项目怎么办？分成若干段，每段一个外科手术队伍，段与段之间用架构对齐——又回到概念完整性。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;五第二系统效应你最危险的作品是第二个&#34;&gt;五、第二系统效应：你最危险的作品是第二个&lt;/h2&gt;
&lt;p&gt;全书最精妙的心理学洞察之一：&lt;strong&gt;第二系统效应（second-system effect）&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一个人在设计他的第二个系统时，会把它做成最危险的系统——因为他会把第一个系统里没用上的所有想法，全都塞进去。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;机制是这样的：设计第一个系统时，人是谨慎的、不自信的，处处收敛；那些&amp;quot;如果再来一次我一定要加&amp;quot;的念头，被一条条记在心里的清单上。设计第二个系统时，自信来了，约束松了，那张清单被整体倒出来——加上第一版遗留功能的修饰欲，结果是&lt;strong&gt;功能膨胀、概念混杂、复杂度失控&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;Brooks 的自白是：OS/360 就是大量设计者的&amp;quot;第二系统&amp;quot;（多数人的第一系统是 1410、7090 系列），所以它的链接器、它的各种设施，都堆满了&amp;quot;这次终于可以做了&amp;quot;的东西。而他给架构师的建议只有一句：&lt;strong&gt;为每个功能问一句&amp;quot;它值它占用的那份复杂度预算吗&amp;quot;&lt;/strong&gt;；并且始终意识到自己在设计第几个系统——第三个系统的设计师，往往才第一次有真正的克制能力。&lt;/p&gt;
&lt;p&gt;工程映射：每个&amp;quot;重写 2.0&amp;quot;项目都是第二系统效应的高危现场。旧系统的痛点清单天然会被翻译成新系统的功能清单，而旧系统&amp;quot;为什么不支持 X&amp;quot;的答案（往往是成本，不是疏忽）在重写时被遗忘。&lt;strong&gt;重写立项评审时，把&amp;quot;砍掉的功能清单&amp;quot;和&amp;quot;新增的功能清单&amp;quot;摆在一起看，能救命。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;六计划抛弃原型第一个版本注定要扔&#34;&gt;六、计划抛弃原型：第一个版本注定要扔&lt;/h2&gt;
&lt;p&gt;原话极不客气：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Plan to throw one away; you will, anyhow.（计划抛弃一个吧；反正你一定会抛弃的。）&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;意思是：你做的第一个版本，无论多用心，都是&amp;quot;试验品&amp;quot;——只有做完它，你才真正理解需求、理解约束、理解用户真正要什么。管理层如果不&amp;quot;计划&amp;quot;抛弃它，它就会被当作产品硬撑下去，背着一个为学习而生的架构进入维护地狱。&lt;/p&gt;
&lt;p&gt;这句话常被误读为&amp;quot;随便写一版就行&amp;quot;。Brooks 的本意严格得多：&lt;strong&gt;抛弃是计划内的，所以要为&amp;quot;抛弃&amp;quot;设计&lt;/strong&gt;——原型和生产系统用不同的人力配比、不同的质量预期、不同的心态；原型的使命是最大化学习，不是最大化寿命。后来的演进还有一条：&lt;strong&gt;唯一比&amp;quot;计划抛弃&amp;quot;更贵的，是假装不用抛弃。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这条思想后来开枝散叶：快速原型、MVP、以及 Brooks 自己在《没有银弹》里提出的&lt;strong&gt;增量式构建（grow, not build）&lt;/strong&gt;——软件不该是&amp;quot;建造&amp;quot;出来的，该是&amp;quot;生长&amp;quot;出来的：先让一个能跑的最小骨架活着，然后每加一个功能都保持系统可运行。1995 年纪念版里他承认，当年&amp;quot;抛弃一个&amp;quot;的说法被他自己修正为更平滑的增量演化，但内核没变：&lt;strong&gt;不要指望第一枪就命中，把迭代当成常态写进计划。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;七文档与工作手册被遗忘的主角&#34;&gt;七、文档与工作手册：被遗忘的主角&lt;/h2&gt;
&lt;p&gt;这本书有个今天读来格外超前的观点：&lt;strong&gt;项目经理最重要的日常工作是文档，不是代码。&lt;/strong&gt; Brooks 花了整整一章讲「项目工作手册」（project workbook）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;它是什么&lt;/strong&gt;：项目所有正式文档的集合——目标、规格、进度、接口定义、会议纪要、变更记录。它不是档案柜，是项目的&lt;strong&gt;活的中枢&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;为什么必须有&lt;/strong&gt;：第一，写作强制清晰——&amp;ldquo;写在纸上的决策，模糊性会自己暴露&amp;rdquo;，很多争吵在落成文字那一刻就解决了；第二，文档是沟通结构的载体，几百人的项目里，工作手册是唯一能让&amp;quot;每个人看到同一本事实&amp;quot;的装置。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;怎么维护&lt;/strong&gt;：所有变更都要进手册，手册必须人人可得、持续更新（OS/360 时代用缩微胶片分发，他特意讨论了每日更新机制——放今天就是 wiki/文档站）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;配套还有一条 OS/360 的组织创新：&lt;strong&gt;让文档成为规格的唯一权威形态&lt;/strong&gt;。架构师写正式的外部规格说明，实现中出现歧义时，由架构师裁决并&lt;strong&gt;把裁决写回规格&lt;/strong&gt;——规格永远是最新事实，不是&amp;quot;最初的美好愿望&amp;quot;。&lt;/p&gt;
&lt;p&gt;今天的映射一目了然：ADR（架构决策记录）、design doc 评审、&amp;ldquo;接口以文档为准而非以聊天为准&amp;rdquo;，全是工作手册的现代转世。Brooks 在 1975 年就说清楚了我们 2020 年代还在学的事：&lt;strong&gt;异步的、成文的沟通，是大团队唯一可扩展的沟通。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;八没有银弹本质复杂度与偶然复杂度&#34;&gt;八、没有银弹：本质复杂度与偶然复杂度&lt;/h2&gt;
&lt;p&gt;1986 年，Brooks 发表了那篇比书本身更著名的论文：「没有银弹——软件工程中本质与偶然的问题」（No Silver Bullet: Essence and Accidents of Software Engineering），1995 年纪念版收入书中。&lt;/p&gt;
&lt;p&gt;核心论断：&lt;strong&gt;没有任何一项技术或管理进步，能在十年内让软件生产率提高一个数量级。&lt;/strong&gt; 因为软件的困难要分两类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;本质复杂度（essential）&lt;/strong&gt;：问题域本身固有的复杂度——概念结构必须精确、一致、完整；软件是&amp;quot;纯粹的思想制品&amp;quot;，它的复杂度不会因为换工具而消失。就像银弹能杀狼人，但软件工程里没有狼人——没有一个叫&amp;quot;技术&amp;quot;的妖怪一枪打死就天下太平。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;偶然复杂度（accidental）&lt;/strong&gt;：我们制造过程附带的复杂度——机器慢、语言啰嗦、工具难用、环境配置地狱。&lt;strong&gt;这部分是可以消灭的&lt;/strong&gt;，而且历史上已经消灭了大部分：高级语言干掉了汇编的偶然复杂度，分时系统干掉了批处理的排队等待，统一硬件平台干掉了移植地狱。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Brooks 的关键预测逻辑是：到 1986 年，偶然复杂度已经被压缩到不是主要矛盾了——剩下的硬骨头几乎全是本质复杂度，而&lt;strong&gt;本质复杂度不响应任何&amp;quot;银弹&amp;quot;&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;那怎么办？论文给出了四条&amp;quot;有希望的铜弹&amp;quot;，条条在后来兑现：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;买，别造&lt;/strong&gt;（buy vs. build）：能买现成软件就别自己写——自己写的那部分才承载本质复杂度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;需求精炼与快速原型&lt;/strong&gt;：最难的本质问题是&amp;quot;搞清楚到底要造什么&amp;quot;，用原型和用户一起把需求逼出来。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;增量式生长&lt;/strong&gt;（grow, not build）：系统永远保持可运行，小步叠加。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;培养伟大的设计师&lt;/strong&gt;：软件生产率的最大方差来自人——顶尖设计师和普通设计师的差距是数量级的，把培养设计师当成组织的核心投资。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;1986 年之后的四十年基本在给这篇论文做注脚：面向对象、组件化、开源生态、云、低代码，每一波都消灭了新的偶然复杂度，每一波之后项目照样的难——因为难的那部分从来不在工具里，在&amp;quot;我们要造的东西本身&amp;quot;里。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;九20-周年纪念版brooks-的自我检讨&#34;&gt;九、20 周年纪念版：Brooks 的自我检讨&lt;/h2&gt;
&lt;p&gt;1995 年纪念版最珍贵的部分，是 Brooks 回头看自己二十年前的论断，一条条打分：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;仍然坚信的&lt;/strong&gt;：概念完整性是系统最重要的品质；人月不可互换；加人让延期项目更晚；工作手册必不可少；第二系统效应真实存在。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;修正的&lt;/strong&gt;：「计划抛弃一个」太粗暴，更准确的表述是增量演化（grow）；结构化编程、面向对象属于消灭偶然复杂度，不是银弹——这点他赌对了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;认错的&lt;/strong&gt;：他当年推崇&amp;quot;信息隐藏到团队之间也互相保密&amp;quot;的做法，后来承认过度了——Parnas 的信息隐藏原则适用于模块接口，不该用来隔离团队之间的知识。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;被时代证明最对的&lt;/strong&gt;：对&amp;quot;伟大设计师&amp;quot;的押注。20 年后他更确信：软件质量的天花板由少数顶尖设计者决定，组织的头等大事是发现他们、培养他们、别用流程磨平他们。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这种&amp;quot;作者亲自给旧书做 code review&amp;quot;的结构，本身就是对全书精神的践行：&lt;strong&gt;把二十年前的决策放到今天的光下重新评审，而不是假装当年的自己永远正确。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;十落地实践我自己在用的四条&#34;&gt;十、落地实践：我自己在用的四条&lt;/h2&gt;
&lt;p&gt;这本书不是读完就完的，我把其中几条固化成了自己带项目时的检查动作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;报排期时先分类任务&lt;/strong&gt;。任何估算之前，先问&amp;quot;这块工作能不能无沟通地并行拆开&amp;quot;。拆不动的部分（联调、设计、疑难 bug），单独列出来按顺序排，并且明确告诉需求方：这部分加人不解决问题，加人只会更慢。把 Brooks 定律提前说出来，比延期之后再解释有效得多。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;里程碑只写&amp;quot;可验证的事&amp;quot;&lt;/strong&gt;。排期表里禁止出现&amp;quot;开发完成 80%&amp;ldquo;这类状态词，全部换成&amp;quot;接口联调通过&amp;quot;&amp;ldquo;性能压测达标&amp;quot;这种第三方可验证的硬节点。项目是一天一天延期的，锋利的里程碑是让&amp;quot;每天的小落后&amp;quot;无处藏身。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;重写项目先审&amp;quot;砍掉清单&amp;rdquo;&lt;/strong&gt;。任何重构、2.0 立项，评审材料里必须有两栏：新系统不做什么、为什么不做。没有砍掉清单的重写方案，默认是第二系统效应发作，打回。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;护住团队里的&amp;quot;外科医生&amp;rdquo;&lt;/strong&gt;。识别出那个承担核心设计的人，然后系统性地替他挡事：杂会替他推掉、文档有人帮他整理、工具不顺有人帮他修。他的每一分钟专注时间，就是整个团队的产能上限。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;四条背后其实是同一个原则，也是这本书教我的最重要的一件事：&lt;strong&gt;软件工程里大多数&amp;quot;管理动作&amp;quot;的反面才是对的——加人不如减人，催进度不如砍范围，民主设计不如贵族专制，憋大招不如小步生长。&lt;/strong&gt; Brooks 在五十年前就把这些&amp;quot;反直觉&amp;quot;写成了定理，我们要做的只是别再交一遍学费。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;十一带走的清单&#34;&gt;十一、带走的清单&lt;/h2&gt;
&lt;p&gt;如果只能记住几件事，记这些：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;人月不可互换&lt;/strong&gt;。报工期前先问：这个任务属于&amp;quot;收麦子型&amp;quot;还是&amp;quot;生孩子型&amp;quot;？后者加人无用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Brooks 定律&lt;/strong&gt;：延期的项目加人只会更晚。赶工的正确动作是砍范围、保核心，不是借调支援。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;里程碑要锋利&lt;/strong&gt;：&amp;ldquo;完成 90%&amp;ldquo;不是进度，是幻觉；只有&amp;quot;可验证、不可含糊&amp;quot;的节点才是进度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;概念完整性 ＞ 功能数量&lt;/strong&gt;。架构出自一人或极少数人；架构师管&amp;quot;系统是什么样&amp;rdquo;，实现者管&amp;quot;怎么造出来&amp;rdquo;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;警惕第二系统效应&lt;/strong&gt;：重写立项时，先审&amp;quot;砍掉清单&amp;quot;，再审&amp;quot;新增清单&amp;quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;计划抛弃原型&lt;/strong&gt;：第一版的使命是学习；要么计划抛弃它，要么被它拖死。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;文档是项目的中枢&lt;/strong&gt;：写下来，模糊会自己暴露；规格是唯一权威，裁决要写回规格。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;没有银弹&lt;/strong&gt;：工具的进步只消灭偶然复杂度；本质复杂度只能靠更好的需求（原型）、更好的生长方式（增量）、更好的人（设计师）来对付。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;投资顶尖设计师&lt;/strong&gt;：人员方差是软件生产率里最大的方差，这件事 1975 年成立，今天更成立。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;参考资料&#34;&gt;参考资料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Frederick P. Brooks Jr., &lt;em&gt;The Mythical Man-Month: Essays on Software Engineering&lt;/em&gt;, Anniversary Edition, Addison-Wesley, 1995（含《没有银弹》全文及作者 20 年回顾）。&lt;/li&gt;
&lt;li&gt;Frederick P. Brooks Jr., &amp;ldquo;No Silver Bullet: Essence and Accidents of Software Engineering&amp;rdquo;, &lt;em&gt;Computer&lt;/em&gt;, 1987（1986 年原始论文）。&lt;/li&gt;
&lt;li&gt;Frederick P. Brooks Jr., &lt;em&gt;The Design of Design&lt;/em&gt;, Addison-Wesley, 2010（Brooks 晚年对&amp;quot;设计&amp;quot;本身的系统思考，可视作本书续篇）。&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    <item>
      <title>《凤凰项目》读书笔记：用一本小说搞懂 DevOps 的三步工作法</title>
      <link>https://ftmao.com/blog/bookstore/phoenix-project-book-notes/</link>
      <pubDate>Tue, 08 Aug 2023 10:40:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/bookstore/phoenix-project-book-notes/</guid>
      <description>&lt;h1 id=&#34;凤凰项目读书笔记用一本小说搞懂-devops-的三步工作法&#34;&gt;《凤凰项目》读书笔记：用一本小说搞懂 DevOps 的三步工作法&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;目标读者：写代码的、搞运维的、带团队的，尤其是觉得&amp;quot;我们部门天天救火、需求永远延期、发布必出事故&amp;quot;的人。&lt;br&gt;
参考书籍：Gene Kim, Kevin Behr, George Spafford《凤凰项目：一个 IT 运维的传奇故事》（The Phoenix Project）&lt;br&gt;
阅读目标：不用啃方法论大部头，通过一个故事建立 DevOps 的完整直觉——为什么 IT 问题本质上是&amp;quot;工厂管理问题&amp;quot;，以及三步工作法到底怎么用。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;0-写在前面为什么是小说体的-devops-入门&#34;&gt;0. 写在前面：为什么是&amp;quot;小说体&amp;quot;的 DevOps 入门&lt;/h2&gt;
&lt;p&gt;市面上讲 DevOps 的书大多是清单式的：CI/CD、IaC、监控、文化……知识点都对，但读完你还是不知道&lt;strong&gt;从哪一步开始改&lt;/strong&gt;，更不知道怎么跟老板解释&amp;quot;为什么要改&amp;quot;。&lt;/p&gt;
&lt;p&gt;《凤凰项目》的作者显然也意识到了这一点，所以他们写了一本小说。这本书的思路来自一个更老的先例：高德拉特（Eliyahu Goldratt）1984 年的《目标》（The Goal）——一本用工厂经理拯救濒临倒闭工厂的故事来讲&lt;strong&gt;约束理论&lt;/strong&gt;（TOC, Theory of Constraints）的小说。《凤凰项目》就是把《目标》的工厂换成了一家公司的 IT 部门，把生产线换成了&amp;quot;需求 → 开发 → 测试 → 部署 → 运维&amp;quot;这条价值流。&lt;/p&gt;
&lt;p&gt;一句话理解全书：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;IT 部门就是一座工厂，代码从流水线的这头进去、从部署那头出来。工厂的毛病 IT 全有：在制品堆积、瓶颈工位、紧急插单、返工报废。所以工厂管理那套成熟的方法（约束理论、精益生产、丰田生产方式），搬到 IT 里就是 DevOps。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;下面我们按&amp;quot;剧情主线 → 核心概念 → 现实对照&amp;quot;的顺序展开。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;1-剧情主线一个烂摊子和一个临危受命的运维-vp&#34;&gt;1. 剧情主线：一个烂摊子和一个临危受命的运维 VP&lt;/h2&gt;
&lt;p&gt;故事发生在一家虚构的汽车零部件制造公司 &lt;strong&gt;Parts Unlimited（零件公司）&lt;/strong&gt;。公司正在被竞争对手用互联网打法吊打，董事会给管理层下了死命令：两年内把公司救回来，否则 IT 部门整体外包。&lt;/p&gt;
&lt;p&gt;救亡图存的希望寄托在一个叫 &lt;strong&gt;&amp;ldquo;凤凰项目&amp;rdquo;（Project Phoenix）&lt;/strong&gt; 的大项目上——一个面向电商化的核心系统改造，号称要让公司重新获得和对手正面竞争的能力。问题是：这个项目已经严重超支、延期数年，功能越堆越多，离上线遥遥无期。&lt;/p&gt;
&lt;p&gt;开篇第一天，主角 &lt;strong&gt;Bill Palmer（比尔·帕尔默）&lt;/strong&gt; 还是个小运维经理，正在处理公司工资系统（Payroll）宕机的大事故——全公司发不出工资。就在这一天，两位高管（CIO 和运维 VP）先后被炒，CEO &lt;strong&gt;Steve Masters&lt;/strong&gt; 直接把 Bill 拎上来：从今天起，你就是 IT 运维副总裁。&lt;/p&gt;
&lt;p&gt;Bill 接手的局面堪称教科书级的灾难现场：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;凤凰项目&lt;/strong&gt;像吞金兽，吃掉全公司最优秀的工程师，却连一次完整的测试部署都做不起来；&lt;/li&gt;
&lt;li&gt;业务部门绕过 IT 自己买 SaaS、插紧急需求，IT 完全不知道有多少工作在流动；&lt;/li&gt;
&lt;li&gt;所有关键知识都集中在一个叫 &lt;strong&gt;Brent&lt;/strong&gt; 的超级工程师脑子里，任何系统的任何故障最后都得找他；&lt;/li&gt;
&lt;li&gt;每次发布都是通宵加班 + 回滚 + 更多通宵加班的恶性循环；&lt;/li&gt;
&lt;li&gt;安全审计部天天发整改单，没人理，直到差点被监管和董事会抓住把柄。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;书里还有一个关键人物：&lt;strong&gt;Erik&lt;/strong&gt;，一个董事会的准董事候选人，平时蹲在离公司总部最近的一个制造工厂里，看起来像退休技术宅，实际上是整个故事的&amp;quot;导师&amp;quot;角色。他每次出场都把 Bill 拉到工厂里参观，讲一些看似答非所问的工厂管理道理——&lt;strong&gt;Erik 就是高德拉特在书里的化身&lt;/strong&gt;，他说的每句话都是约束理论。&lt;/p&gt;
&lt;p&gt;剧情的高潮和转折大致是：一次为了赶董事会死线的&lt;strong&gt;灾难性发布&lt;/strong&gt;（凤凰被强行上线，引发支付数据泄露级别的混乱）之后，Steve 祭出狠招——&lt;strong&gt;冻结一切发布&lt;/strong&gt;。在被迫停下来的这段时间里，Bill 团队才真正看清自己的工厂长什么样：堆积如山的在制品、四处流动的紧急变更、以及那个被所有人依赖的 Brent。于是他们开始一步步疏解瓶颈、限制在制品、建立反馈回路，最后孵化出代号 &lt;strong&gt;&amp;ldquo;独角兽&amp;rdquo;（Unicorn）&lt;/strong&gt; 的独立小团队，用持续部署的方式交付电商功能，让公司起死回生。&lt;/p&gt;
&lt;p&gt;Bill 本人在故事结尾升任 CIO——这个安排也是作者的态度：&lt;strong&gt;DevOps 不是某个团队的局部优化，而是 IT 一把手工程。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;主要人物和概念可以对照着记：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;mindmap
  root((&amp;#34;凤凰项目&amp;#34;))
    人物
      Bill Palmer：运维 VP→CIO（主角视角）
      Steve Masters：CEO，冻结发布的拍板人
      Erik：导师角色，Goldratt 的化身
      Brent：超级工程师，全公司的瓶颈
      Wes/Patty：运维总监/变更管理负责人
    核心概念
      四种工作类型
      三步工作法
      约束理论 TOC
      精益与丰田生产方式
    关键事件
      工资系统宕机（开篇）
      凤凰灾难性发布（谷底）
      冻结发布（转折）
      独角兽项目（翻身）&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;11-根因开发与运维的对立目标&#34;&gt;1.1 根因：开发与运维的&amp;quot;对立目标&amp;quot;&lt;/h3&gt;
&lt;p&gt;书里把 IT 一切混乱的总根源归结为一个结构性矛盾，值得单独拎出来：&lt;strong&gt;开发和运维被两套相反的目标函数考核。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;开发的 KPI 是&amp;quot;快&amp;quot;：更多功能、更快上线、响应业务变化；&lt;/li&gt;
&lt;li&gt;运维的 KPI 是&amp;quot;稳&amp;quot;：可用性、不出事故、别动我的生产环境。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;于是开发把代码&amp;quot;扔过墙&amp;quot;就算交差，运维为了自保层层设卡（审批、冻结窗口、几十页的变更单），结果是部署越来越难、间隔越来越长、每次部署的批量越来越大——&lt;strong&gt;批量越大风险越高，风险越高越想少部署&lt;/strong&gt;，形成一个不断恶化的&amp;quot;向下的螺旋&amp;quot;。书里的凤凰项目几年憋一发，就是这条螺旋的终点。三步工作法的本质，就是把两套目标函数统一成&amp;quot;&lt;strong&gt;安全地、频繁地把价值送到用户手里&lt;/strong&gt;&amp;ldquo;这一件事——开发对生产负责，运维为交付提速。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;2-四种工作类型先看清你的工厂里在流什么&#34;&gt;2. 四种工作类型：先看清你的工厂里在流什么&lt;/h2&gt;
&lt;p&gt;Erik 给 Bill 上的第一课是：&lt;strong&gt;在解决任何问题之前，先搞清楚工厂里到底有几种工作在流动。&lt;/strong&gt; IT 里的工作只有四种：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;业务项目（Business Projects）&lt;/strong&gt;&lt;br&gt;
业务部门要的功能：凤凰项目、新的促销系统、给销售做的报表。这类工作有明确的外部客户，是&amp;quot;公司存在的原因&amp;rdquo;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;IT 内部项目（IT Internal Projects）&lt;/strong&gt;&lt;br&gt;
IT 自己发起的工作：服务器升级、数据库迁移、安全补丁、技术债偿还。它们不产生直接收入，但没有它们，业务项目迟早死在烂地基上。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;变更（Changes）&lt;/strong&gt;&lt;br&gt;
上面两类项目落地时产生的具体操作：改配置、发版本、加字段。书里的关键洞察是——&lt;strong&gt;大多数事故由变更引起，而变更的流程往往既没人看见、也没人管理&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;计划外工作（Unplanned Work）&lt;/strong&gt;&lt;br&gt;
救火、故障恢复、临时支援、&amp;ldquo;帮我看一眼这个报错&amp;rdquo;。它的特点是：&lt;strong&gt;不请自来、优先级永远最高、而且会把前三类工作的时间偷走。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;一句话理解：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;计划外工作是四种工作里唯一的&amp;quot;负资产&amp;quot;——它不光消耗资源，还打断所有正在进行的工作，制造新的错误，从而制造更多计划外工作。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;书里有个很扎心的细节：Bill 让团队统计自己的工作去向，发现几乎没有人在做&amp;quot;计划内的事&amp;quot;——每个人都在被随机的紧急请求打断。这在工厂里叫&amp;quot;紧急插单&amp;quot;，在 IT 里我们叫&amp;quot;@所有人 快帮忙看下&amp;quot;。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    subgraph 流入
        A[&amp;#34;业务项目&amp;lt;br/&amp;gt;（赚钱的功能）&amp;#34;] --&amp;gt; WIP
        B[&amp;#34;IT 内部项目&amp;lt;br/&amp;gt;（打地基）&amp;#34;] --&amp;gt; WIP
        C[&amp;#34;变更&amp;lt;br/&amp;gt;（落地动作）&amp;#34;] --&amp;gt; WIP
        D[&amp;#34;计划外工作&amp;lt;br/&amp;gt;（救火·打断一切）&amp;#34;] -.-&amp;gt;|&amp;#34;插队抢占&amp;#34;| WIP
    end

    WIP[&amp;#34;在制品队列&amp;lt;br/&amp;gt;WIP 越堆越高&amp;lt;br/&amp;gt;周期时间越来越长&amp;#34;]

    WIP --&amp;gt; E[&amp;#34;部署上线&amp;lt;br/&amp;gt;产生业务价值&amp;#34;]
    WIP -.-&amp;gt;|&amp;#34;缺陷与事故&amp;#34;| D

    style D fill:#ffcccc
    style WIP fill:#fff3cd&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;注意图里那条反向虚线：&lt;strong&gt;计划外工作不但插队，还会生出新的事故&lt;/strong&gt;——急匆匆的救火往往是又一个 bug 的种子。这就是&amp;quot;越忙越乱&amp;quot;的数学本质。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;小数字例子&lt;/strong&gt;：假设一个工程师每天 8 小时，原本排满 2 个任务。每被插入 1 次救火，打断成本大约是&amp;quot;上下文切换 + 排查 + 修复 + 切回来&amp;quot;——书里借 Erik 之口引用了一个经典结论：一个人同时挂在手上的事越多，每件事完成的周期时间就按非线性膨胀。这不是态度问题，是排队论（利特尔法则：周期时间 = 在制品数量 ÷ 吞吐率）。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;3-约束理论找到你的-brent&#34;&gt;3. 约束理论：找到你的 Brent&lt;/h2&gt;
&lt;h3 id=&#34;31-一句话理解-toc&#34;&gt;3.1 一句话理解 TOC&lt;/h3&gt;
&lt;p&gt;约束理论（TOC）来自高德拉特的《目标》，核心一句话：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;任何系统的整体产能，只由系统中最慢的那个环节（瓶颈/约束）决定。优化瓶颈之外的任何环节，都是在浪费钱，甚至帮倒忙。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;工厂例子很直观：一条流水线 5 个工位，第 3 个工位每小时只能加工 10 个零件，其余工位都能做 50 个。那你给工位 1 提速到每小时 100 个有意义吗？没有——零件只会在工位 3 前面堆成山，&lt;strong&gt;在制品增加，但出货一件都不会多&lt;/strong&gt;。工厂里的真金白银只发生在&amp;quot;零件卖出厂门&amp;quot;那一刻；IT 里对应的就是&amp;quot;功能部署到生产环境并被用户使用&amp;quot;那一刻。没上线的代码不是资产，是库存，而且是会腐烂的库存（需求变了、上下文忘了、合并冲突了）。&lt;/p&gt;
&lt;h3 id=&#34;32-brent那个什么都懂的人就是瓶颈&#34;&gt;3.2 Brent：那个什么都懂的人，就是瓶颈&lt;/h3&gt;
&lt;p&gt;《凤凰项目》里最精妙的设计就是 Brent 这个角色。Brent 是 Parts Unlimited 的传奇工程师：他知道每个系统埋在哪个角落、每条配置为什么长那样、每次故障的历史前因。于是公司里形成了一个铁律——&lt;strong&gt;重要的事情都得 Brent 参与&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;凤凰项目要部署？只有 Brent 懂环境配置，找他。&lt;/li&gt;
&lt;li&gt;工资系统挂了？只有 Brent 知道那段祖传代码，找他。&lt;/li&gt;
&lt;li&gt;网络要改造？只有 Brent 记得十年前为什么这么布线，找他。&lt;/li&gt;
&lt;li&gt;甚至业务部门自己想买软件，也要&amp;quot;Brent 看一下能不能集成&amp;quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;结果 Brent 的工作队列永远排在几个月之后，而&lt;strong&gt;所有关键路径都从 Brent 的工位前经过&lt;/strong&gt;——他就是整条流水线的瓶颈工位。更糟的是，因为 Brent 太能干，大家救火也找他，计划外工作进一步抢占他的时间，瓶颈被救火放大。&lt;/p&gt;
&lt;p&gt;识别瓶颈的方法书里也给了，非常朴素：&lt;strong&gt;看谁面前的队列最长，看谁的名字出现在所有任务的依赖列表里，看谁永远约不到会议时间。&lt;/strong&gt; 工厂里的瓶颈工位有个特征——它前面永远堆着最多的待加工零件。Brent 前面堆的，是全公司的工单。&lt;/p&gt;
&lt;h3 id=&#34;33-疏解瓶颈toc-五步法&#34;&gt;3.3 疏解瓶颈：TOC 五步法&lt;/h3&gt;
&lt;p&gt;约束理论的改进套路是五个步骤，书里完整走了一遍：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;识别约束&lt;/strong&gt;：确认 Brent（以及少数关键岗位）是系统瓶颈，而不是去怪&amp;quot;团队不努力&amp;quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;挖尽约束&lt;/strong&gt;：瓶颈的每一分钟都不能浪费。让 Brent 的时间只花在&amp;quot;只有 Brent 能做的事&amp;quot;上——别再让他回工单、开无关的会、修不需要他的机器。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;迁就约束&lt;/strong&gt;：整个系统的节奏向瓶颈看齐。在 Brent 消化完之前，不许再往上堆新工作（这在工厂里叫&amp;quot;绳子&amp;quot;机制，在 IT 里就是 WIP 限制）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;打破约束&lt;/strong&gt;：给约束扩容。书里的做法是让 Brent 的工作&lt;strong&gt;文档化、标准化&lt;/strong&gt;：每次 Brent 修东西，旁边必须有第二个人观摩记录，把他的隐性知识变成显性 runbook，逐步培养出&amp;quot;第二个、第三个 Brent&amp;quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;回到第一步&lt;/strong&gt;：约束疏解后，瓶颈会转移到别处（比如自动化测试、比如部署流程），继续循环。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    subgraph 疏解前
        direction TB
        A1[&amp;#34;所有需求&amp;#34;] --&amp;gt; B1[&amp;#34;Brent&amp;lt;br/&amp;gt;队列无限长&amp;lt;br/&amp;gt;周期时间失控&amp;#34;]
        B1 --&amp;gt; C1[&amp;#34;上线&amp;#34;]
        D1[&amp;#34;救火请求&amp;#34;] -.-&amp;gt;|&amp;#34;随时打断&amp;#34;| B1
    end

    subgraph 疏解后
        direction TB
        A2[&amp;#34;需求按优先级进入&amp;#34;] --&amp;gt; W2[&amp;#34;WIP 限制&amp;lt;br/&amp;gt;排队可见&amp;#34;]
        W2 --&amp;gt; B2[&amp;#34;Brent 只做&amp;lt;br/&amp;gt;只有他能做的事&amp;#34;]
        W2 --&amp;gt; B3[&amp;#34;文档化 + 带教&amp;lt;br/&amp;gt;复制出更多 Brent&amp;#34;]
        B2 --&amp;gt; C2[&amp;#34;上线&amp;#34;]
        B3 --&amp;gt; C2
        D2[&amp;#34;救火请求&amp;#34;] --&amp;gt; W2
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;这一条是全书对工程管理最有杀伤力的观点，值得单独抄一遍：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&amp;ldquo;把最强的工程师从所有救火中撤出来&amp;quot;听起来反直觉，但恰恰是让系统恢复流动的唯一办法。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;现实映射也很常见：几乎每个团队都有一个&amp;quot;Brent&amp;rdquo;——核心系统的唯一懂行人、所有 MR 的指定 reviewer、线上问题的最终兜底人。这个角色的存在本身就是风险（业界叫 bus factor = 1），而管理他的正确姿势不是&amp;quot;多招几个一样强的人&amp;quot;（招不到），而是&lt;strong&gt;把他的知识变成流程和文档，让他只干真正不可复制的那部分&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;4-三步工作法devops-的全部内容&#34;&gt;4. 三步工作法：DevOps 的全部内容&lt;/h2&gt;
&lt;p&gt;三步工作法（The Three Ways）是《凤凰项目》的骨架，也是后来《DevOps Handbook》的理论内核。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    subgraph S1[&amp;#34;第一步：流动原则（从左到右）&amp;#34;]
        direction LR
        Dev[&amp;#34;开发&amp;#34;] --&amp;gt; Test[&amp;#34;测试&amp;#34;] --&amp;gt; Ops[&amp;#34;运维/部署&amp;#34;] --&amp;gt; Cust[&amp;#34;客户价值&amp;#34;]
    end

    subgraph S2[&amp;#34;第二步：反馈原则（从右到左）&amp;#34;]
        direction RL
        Ops2[&amp;#34;运维/生产&amp;#34;] --&amp;gt;|监控/告警/数据| Test2[&amp;#34;测试&amp;#34;]
        Test2 --&amp;gt;|质量信号| Dev2[&amp;#34;开发&amp;#34;]
    end

    subgraph S3[&amp;#34;第三步：持续学习与实验&amp;#34;]
        L[&amp;#34;刻意练习&amp;lt;br/&amp;gt;复盘文化&amp;lt;br/&amp;gt;为改进留时间&amp;#34;]
    end

    S1 &amp;lt;--&amp;gt;|快速、可见、小批量| S2
    S2 --&amp;gt;|知识沉淀为实验| S3&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;41-第一步流动原则flow让工作从左到右永远不要停不要回流&#34;&gt;4.1 第一步：流动原则（Flow）——让工作从左到右，永远不要停、不要回流&lt;/h3&gt;
&lt;p&gt;目标是&lt;strong&gt;缩短从&amp;quot;需求提出&amp;quot;到&amp;quot;价值交付&amp;quot;的周期时间&lt;/strong&gt;。要点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;让工作可见&lt;/strong&gt;：书里第一个改革动作就是建看板（kanban）——把所有正在进行的工作贴在墙上/板上，任何插队需求必须先&amp;quot;有卡&amp;quot;再讨论。看不见的东西无法管理，&amp;ldquo;我才知道你手上还有这个&amp;quot;是大部分混乱的根源。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;限制在制品（WIP）&lt;/strong&gt;：同时开工的事越少，完成得越快。这是利特尔法则的直接推论，也是最容易被管理者忽视的一条——&amp;ldquo;开始新工作&amp;quot;有快感，&amp;ldquo;完成工作&amp;quot;才产生价值。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;小批量&lt;/strong&gt;：大项目（凤凰那种几年憋一发的）是大批量生产的 IT 版，风险随批量指数膨胀。拆小、频繁交付，风险才能线性化。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;减少交接&lt;/strong&gt;：每一次&amp;quot;扔过墙&amp;rdquo;（开发扔给测试、测试扔给运维）都是信息损失和等待。书里凤凰项目最痛的环节就是开发环境、测试环境、生产环境三套配置永远对不上，每次交接都从头踩坑。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;识别并疏解约束&lt;/strong&gt;：即上一节的 TOC 五步法，持续盯住系统里最慢的环节。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;42-第二步反馈原则feedback从右到左越早发现越便宜&#34;&gt;4.2 第二步：反馈原则（Feedback）——从右到左，越早发现、越便宜&lt;/h3&gt;
&lt;p&gt;目标是&lt;strong&gt;让质量信号在生产源头就被制造出来，而不是在用户投诉时才收到&lt;/strong&gt;。要点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;把测试和安全&amp;quot;左移&amp;rdquo;&lt;/strong&gt;：书里 Wes（运维总监）和 Bill 的最大冲突之一，就是安全审计的整改单永远是事后几百页地砸下来。改革方向是让安全要求变成自动化检查，在提交代码的那一刻就反馈，而不是发布前一周开会吵架。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;在生产中看见问题&lt;/strong&gt;：监控、日志、告警是&amp;quot;工厂的质检摄像头&amp;rdquo;。书里 IT 的至暗时刻是&amp;quot;用户先发现支付出错了，我们才知道&amp;quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;出问题就停线&lt;/strong&gt;：这条直接来自丰田的&lt;strong&gt;安灯绳（Andon Cord）&lt;/strong&gt;——流水线上任何工人发现缺陷，可以拉绳停掉整条线，全组停下来先修问题。IT 的对应物是&amp;quot;构建红了就没人能提交新代码&amp;quot;、是&amp;quot;出事故先恢复再追责&amp;quot;、是书里后来建立的规则：部署失败自动回滚，而不是边生产边抢修。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;共享的遥测与同理心&lt;/strong&gt;：开发和运维看同一套指标。书里 Bill 和 Wes 关系的转折点，是运维开始理解开发的交付压力、开发开始理解运维的稳定性压力——&lt;strong&gt;目标函数统一了，反馈才有意义&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;43-第三步持续学习与实验continual-learning高风险低信任的反面是习惯成自然的日常&#34;&gt;4.3 第三步：持续学习与实验（Continual Learning）——高风险、低信任的反面是习惯成自然的日常&lt;/h3&gt;
&lt;p&gt;目标是&lt;strong&gt;让组织把&amp;quot;改进&amp;quot;本身当成日常工作的一部分&lt;/strong&gt;。要点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;为改进偿还技术债留预算&lt;/strong&gt;：书里 Bill 争取到的关键资源之一是&amp;quot;把一部分产能固定给内部项目&amp;quot;——否则地基永远没人修，直到塌掉。业界常见的对应做法是预留 20% 左右产能给工程改进（Google SRE 书里有类似的配额思想）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;让实验变便宜&lt;/strong&gt;：能快速试错的前提是&amp;quot;错一次代价小&amp;quot;——小批量、自动回滚、灰度发布、特性开关（feature flag），全是降低实验成本的工具。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;复盘文化&lt;/strong&gt;：事故复盘（postmortem）对事不对人。书里的反面教材是每次事故后管理层的第一反应是&amp;quot;谁干的&amp;quot;，于是人人都学会隐瞒，反馈回路彻底失效——这就是为什么第三步必须建在前两步之上。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;刻意练习故障&lt;/strong&gt;：主动制造故障来训练系统韧性。这条思想后来由 Netflix 的 Chaos Monkey 发扬光大——在工作日随机关生产环境实例，逼所有服务具备自愈能力。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;44-变更管理从审批的墙到自动化的管道&#34;&gt;4.4 变更管理：从&amp;quot;审批的墙&amp;quot;到&amp;quot;自动化的管道&amp;quot;&lt;/h3&gt;
&lt;p&gt;书里 Patty（变更管理负责人）这条线是第二原则的最佳注脚。改革前，Parts Unlimited 的变更流程是典型的&lt;strong&gt;审批驱动&lt;/strong&gt;：一张变更单层层签字、CAB（变更咨询委员会）开会逐条过——看上去严谨，实际上有两个致命问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;审批者根本不了解变更内容&lt;/strong&gt;，签字只是分摊责任，出事了照样出事；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;流程太重，大家就绕开流程&lt;/strong&gt;——大量&amp;quot;紧急变更&amp;quot;和&amp;quot;标准变更&amp;quot;在暗处发生，比没有流程更危险。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;改革方向书里给得很明确：&lt;strong&gt;把人力审批换成自动验证&lt;/strong&gt;。变更信息从工单系统自动采集（谁改的、改了什么、影响哪个系统），风险低的标准变更直接自动放行，高风险的才人工介入；部署本身交给流水线，测试、安全扫描、回滚预案全部自动化。一句话：&lt;strong&gt;变更管理的目的是让正确的变更更快、让危险的变更更难，而不是让所有变更都更慢。&lt;/strong&gt; 这也是现实中 Google 等公司&amp;quot;用错误预算和 SLO 代替 CAB&amp;quot;的逻辑。&lt;/p&gt;
&lt;h3 id=&#34;45-与-sre-的关系&#34;&gt;4.5 与 SRE 的关系&lt;/h3&gt;
&lt;p&gt;如果你读过本博客的 SRE 笔记，会发现三步工作法和 SRE 是同源思想的两套表述：SRE 的&amp;quot;错误预算（error budget）&amp;ldquo;本质上是给第二步的反馈回路装了一个量化阀门——预算花光就冻结发布（这正是书里 Steve 的&amp;quot;冻结发布&amp;rdquo;）；SRE 的&amp;quot;消除琐事（toil）&amp;ldquo;就是限制计划外工作；SRE 的&amp;quot;用工程方法解决运维问题&amp;quot;就是第三步的持续学习。可以把《凤凰项目》理解为&lt;strong&gt;讲&amp;quot;为什么&amp;rdquo;&lt;/strong&gt;，《SRE 书》理解为&lt;strong&gt;讲&amp;quot;怎么做&amp;quot;&lt;/strong&gt;，先读前者再读后者，体感会好很多。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;5-devops-与精益丰田的渊源这套东西不是-it-发明的&#34;&gt;5. DevOps 与精益/丰田的渊源：这套东西不是 IT 发明的&lt;/h2&gt;
&lt;p&gt;《凤凰项目》反复提醒读者：DevOps 的每一个核心概念，在制造业都有原型，而且已经被验证了几十年。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;DevOps 概念&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;制造业原型&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;作用&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;看板（Kanban）&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;丰田的看板卡&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;让工作可见、控制 WIP&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;持续集成/小批量&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;单件流（One-piece flow）&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;降低批量风险、缩短周期&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;出故障就停构建&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;安灯绳（Andon Cord）&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;质量内建于源头，不让缺陷流向下游&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;价值流映射&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;价值流图（Value Stream Mapping）&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;找出等待与浪费藏在哪里&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;部署自动化&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;快速换模（SMED）&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;把&amp;quot;切换/部署&amp;quot;从小时级压到分钟级&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;复盘与改善&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;Kaizen（持续改善）&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;改进是日常工作，不是运动式整改&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;理解这层渊源有一个很实际的好处：&lt;strong&gt;当你需要向管理层解释 DevOps 时，不用发明新词&lt;/strong&gt;。&amp;ldquo;我们的发布流程就像一条有质检摄像头和停线机制的流水线&amp;quot;比&amp;quot;我们要做 CI/CD 文化建设&amp;quot;有说服力得多。书里 Erik 带 Bill 参观工厂、指着流水线讲 IT 的那些场景，就是在替读者做这层翻译。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;6-关键转折从冻结发布到独角兽&#34;&gt;6. 关键转折：从冻结发布到独角兽&lt;/h2&gt;
&lt;p&gt;书的中后段有几个决定性动作，连起来就是一条完整的&amp;quot;IT 翻身路径&amp;rdquo;，值得单独梳理：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;灾难性发布&lt;/strong&gt;：凤凰项目被董事会死线强推上线，结果支付系统大面积故障、敏感数据风险暴露。这是全书的谷底——&lt;strong&gt;大批量 + 弱反馈 + 瓶颈过载同时引爆&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;冻结发布（change freeze）&lt;/strong&gt;：Steve Masters 顶着业务压力叫停一切变更。这个动作极其反直觉（&amp;ldquo;业务等不了！&amp;quot;），但它强制系统&amp;quot;停下来看清自己&amp;rdquo;。类比丰田：线停下来不是损失，&lt;strong&gt;带病运转才是损失&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;盘点在制品与约束&lt;/strong&gt;：冻结期间团队把全部工作摊到看板上，才发现&amp;quot;正在做的事&amp;quot;比大家以为的多一个数量级；同时正式确认 Brent 是核心约束。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;疏解 Brent&lt;/strong&gt;：Brent 的时间被保护起来，每次排障强制带教、强制文档化；不相干的请求被挡在队列外。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;孵化独角兽（Unicorn）&lt;/strong&gt;：把凤凰项目里最急迫的电商功能拆出来，成立一个小而全的独立团队，独立代码库、独立部署流水线，绕开历史包袱，&lt;strong&gt;从&amp;quot;几年一发布&amp;quot;做到&amp;quot;每天多次发布&amp;quot;&lt;/strong&gt;。独角兽不是否定凤凰，而是用实践证明新方式可行，再反向改造凤凰。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;胜利与晋升&lt;/strong&gt;：电商功能帮公司在旺季翻身，Bill 升任 CIO，Steve 升任 COO——DevOps 的胜利最终以&lt;strong&gt;组织形态&lt;/strong&gt;的胜利收场：开发、运维、安全不再是三个互相扔锅的部门，而是围绕价值流协作的一体。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这条路径里最容易被忽略的是第 2 步：&lt;strong&gt;停下来的勇气&lt;/strong&gt;。几乎所有改进失败都死在&amp;quot;边开车边换轮子&amp;quot;——系统过载时，任何改进动作都会变成新的在制品，进一步压低吞吐。先止血，再手术。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;7-现实对照三个真实事件&#34;&gt;7. 现实对照：三个真实事件&lt;/h2&gt;
&lt;p&gt;书中的情节是虚构的，但每一个都有现实的镜像。&lt;/p&gt;
&lt;h3 id=&#34;71-knight-capital45-分钟亏掉-44-亿美元2012反面教材&#34;&gt;7.1 Knight Capital：45 分钟亏掉 4.4 亿美元（2012，反面教材）&lt;/h3&gt;
&lt;p&gt;2012 年 8 月 1 日，美国高频交易公司 Knight Capital 上线新代码。部署过程需要手动在 8 台服务器上更新，结果&lt;strong&gt;只部署了 7 台&lt;/strong&gt;——第 8 台还留着旧代码，而新旧代码复用了同一个标志位，旧代码里一个废弃多年的测试功能（Power Peg）被意外激活。这台服务器开始以每分钟数百万美元的速度下错单，&lt;strong&gt;45 分钟内亏损约 4.4 亿美元&lt;/strong&gt;，公司股价单日崩盘，数月后被收购。&lt;/p&gt;
&lt;p&gt;对照《凤凰项目》，Knight Capital 几乎踩中了书里所有的雷：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;部署是&lt;strong&gt;手工的、不一致的&lt;/strong&gt;（8 台机器人工操作，漏一台没有任何人发现）——违反第一步的自动化与小批量；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;没有自动验证与回滚&lt;/strong&gt;（旧代码带着活弹药上线，出事只能靠人肉拔网线）——违反第二步的反馈；&lt;/li&gt;
&lt;li&gt;变更过程&lt;strong&gt;不可见、无审计&lt;/strong&gt;——变更管理形同虚设。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;书里凤凰的灾难性发布如果拍成纪录片，大概就是这个样子。&lt;/p&gt;
&lt;h3 id=&#34;72-etsy每天几十次部署的软工程正面教材&#34;&gt;7.2 Etsy：每天几十次部署的&amp;quot;软工程&amp;quot;（正面教材）&lt;/h3&gt;
&lt;p&gt;Etsy 是持续部署的早期标杆，其工程师在 2010 年前后公开的实践（后被《持续交付》等书引用）几乎就是&amp;quot;独角兽项目&amp;quot;的现实版：全站每天部署几十次，每次部署只是少量提交；用特性开关把&amp;quot;部署&amp;quot;和&amp;quot;发布&amp;quot;解耦；给发布工程师配一键部署工具和全量监控大屏，新人入职第一天就完成一次真实上线（用小改动建立&amp;quot;部署不可怕&amp;quot;的肌肉记忆）。这就是第一步（流动）+ 第二步（反馈）的教科书落地。&lt;/p&gt;
&lt;h3 id=&#34;73-amazon从单体到面向服务与-two-pizza-team&#34;&gt;7.3 Amazon：从单体到面向服务与 two-pizza team&lt;/h3&gt;
&lt;p&gt;Amazon 在 2000 年代初的增长瓶颈同样是&amp;quot;单体系统 + 部门墙&amp;quot;：代码库互相纠缠、发布互相阻塞。解法是大名鼎鼎的两条——架构上转向&lt;strong&gt;面向服务的架构&lt;/strong&gt;（团队通过稳定 API 交互，可独立部署，这就是后来 AWS 内部文化和 SOA/微服务浪潮的源头之一），组织上推行 &lt;strong&gt;two-pizza team&lt;/strong&gt;（团队小到两个披萨能喂饱，端到端拥有自己的服务：谁构建，谁运行）。这正是书里独角兽团队的原型逻辑：&lt;strong&gt;把约束从&amp;quot;共享的单体&amp;quot;变成&amp;quot;自治的小团队&amp;quot;&lt;/strong&gt;，流动自然恢复。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;8-避坑指南读完书最容易犯的四个错误&#34;&gt;8. 避坑指南：读完书最容易犯的四个错误&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;把 DevOps 理解成买工具&lt;/strong&gt;&lt;br&gt;
Jenkins、Kubernetes、Terraform 都是好工具，但书里从头到尾讲的不是工具，是&lt;strong&gt;工作如何流动&lt;/strong&gt;。先画出自己的价值流、找到自己的 Brent，再谈工具选型。工具解决不了&amp;quot;计划外工作吃掉所有人&amp;quot;的问题。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;把瓶颈理解成&amp;quot;那个最忙的人&amp;quot;，然后让他更忙&lt;/strong&gt;&lt;br&gt;
给 Brent 升职加薪、让他带更多项目，只会把系统卡得更死。正确的动作是保护他的时间、榨出他的知识、复制他的能力。判断标准很简单：&lt;strong&gt;他面前那堆活变少了，全公司的交付反而变快了。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;不敢停线&lt;/strong&gt;&lt;br&gt;
&amp;ldquo;业务等不了，我们不能冻结发布&amp;quot;是改进最大的拦路虎。但书里和丰田的经验是一致的：系统带病运转时，止血（freeze）不是成本，是投资。当然，冻结要有明确的目标和时间盒——它是手术，不是疗养。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;只要前两步，不要第三步&lt;/strong&gt;&lt;br&gt;
只做流动和反馈，组织会变成一个&amp;quot;高效的救火队&amp;rdquo;：事故发现得快、恢复得快，但同样的坑反复踩。复盘、技术债预算、刻意练习，是把&amp;quot;经验&amp;quot;变成&amp;quot;组织能力&amp;quot;的唯一通道。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&#34;9-带走的清单&#34;&gt;9. 带走的清单&lt;/h2&gt;
&lt;p&gt;如果只能记住几件事，记这些：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;IT 是工厂&lt;/strong&gt;：价值只在&amp;quot;部署到生产并被使用&amp;quot;时产生，未上线的工作是库存。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;四种工作&lt;/strong&gt;：业务项目、IT 内部项目、变更、计划外工作。&lt;strong&gt;计划外工作是隐形杀手&lt;/strong&gt;，它插队、打断、还制造更多事故。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;先让工作可见&lt;/strong&gt;（看板），再限制 WIP，周期时间才会下降——利特尔法则是物理，不是态度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;找到你的 Brent&lt;/strong&gt;：系统吞吐由瓶颈决定，疏解瓶颈（保护时间、文档化、带教复制）的优先级高于一切局部优化。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;三步工作法&lt;/strong&gt;：流动（小批量、少交接、可见化）→ 反馈（测试左移、生产遥测、安灯绳式停线）→ 持续学习（复盘、技术债预算、混沌实验）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DevOps 与 SRE 同源&lt;/strong&gt;：错误预算就是量化的&amp;quot;发布冻结阀门&amp;quot;，消除琐事就是消灭计划外工作。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;现实的学费很贵&lt;/strong&gt;：Knight Capital 45 分钟 4.4 亿美元，买的是&amp;quot;手工部署 + 无回滚&amp;quot;的教训；Etsy 和 Amazon 证明另一条路走得通。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;改进从止血开始&lt;/strong&gt;：必要时冻结发布，停下来看清系统，再动手手术。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;10-延伸阅读&#34;&gt;10. 延伸阅读&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;高德拉特《目标》（The Goal）：《凤凰项目》的思想源头，约束理论的工厂原版故事。&lt;/li&gt;
&lt;li&gt;Gene Kim 等《DevOps Handbook》：《凤凰项目》的方法论展开版，三步工作法逐条落地。&lt;/li&gt;
&lt;li&gt;Google《Site Reliability Engineering》：与本博客 SRE 笔记配合食用，从&amp;quot;为什么&amp;quot;走到&amp;quot;怎么做&amp;quot;。&lt;/li&gt;
&lt;li&gt;《持续交付》（Continuous Delivery, Humble &amp;amp; Farley）：把第一步&amp;quot;流动原则&amp;quot;展开成完整的工程实践。&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    <item>
      <title>《重构》学习总结：改善既有代码的设计，把日课做成习惯</title>
      <link>https://ftmao.com/blog/bookstore/refactoring-fowler-book-notes/</link>
      <pubDate>Tue, 08 Aug 2023 10:40:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/bookstore/refactoring-fowler-book-notes/</guid>
      <description>&lt;h1 id=&#34;重构学习总结改善既有代码的设计把日课做成习惯&#34;&gt;《重构》学习总结：改善既有代码的设计，把日课做成习惯&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;目标读者：写过一两年业务代码、经常被祖传代码折磨、想搞清楚“重构到底怎么落地”的程序员。&lt;br&gt;
参考书籍：Martin Fowler《重构：改善既有代码的设计（第 2 版）》（Refactoring, 2nd Edition）&lt;br&gt;
阅读目标：建立“重构是刷牙不是大扫除”的日常习惯，把坏味道清单变成自己的启发法清单，学会在安全网保护下小步快走。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;0-写在前面重构不是大扫除是刷牙&#34;&gt;0. 写在前面：重构不是大扫除，是刷牙&lt;/h2&gt;
&lt;p&gt;很多人对重构的想象是这样的：找一个安静的下午，拉上需求方开个会，申请两周“专项重构时间”，然后把某个模块推倒重来。这种理解恰恰是 Fowler 最反对的。&lt;/p&gt;
&lt;p&gt;这本书最核心的一个比喻，可以用我自己的话总结为：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;重构不是年底大扫除，是每天刷牙。大扫除轰轰烈烈、一年一次、中间家里一直脏；刷牙小额、频繁、不痛不痒，但牙齿几十年不出大问题。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这个比喻对应到工程上有三个推论：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;小额&lt;/strong&gt;：一次重构就改一点点，编译能过、测试能绿，马上提交。不是攒一个月再交一个巨型 PR。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;频繁&lt;/strong&gt;：每次加功能、改 bug、做 code review 的时候顺手做，融进日常工作流，不需要“立项”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不改变外在行为&lt;/strong&gt;：这是定义，不是建议。下面马上展开。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;为什么这本书值得每个程序员精读？因为&lt;strong&gt;坏味道清单就是启发法清单的代码版&lt;/strong&gt;。算法书教你遇到问题时用什么启发法求解，Fowler 教你看到代码时用什么启发法判断“这里不对劲”以及“该用哪招”。读熟之后，你看代码的眼光会永久改变——这是少数几本读一遍就能立刻涨功力的书。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;1-一句话理解重构不改变可观察行为的结构调整&#34;&gt;1. 一句话理解重构：不改变可观察行为的结构调整&lt;/h2&gt;
&lt;p&gt;Fowler 给出的定义（第 2 版，第 2 章）：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;重构（名词）：对软件内部结构的一种调整，目的是在不改变软件可观察行为的前提下，提高其可理解性，降低其修改成本。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;拆开看有三个关键词：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;结构调整&lt;/strong&gt;：动的是代码的组织方式——函数怎么拆、类怎么分、数据放哪里。不是改它“做什么”，是改它“怎么摆”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不改变可观察行为&lt;/strong&gt;：用户看到的、API 返回的、数据库写入的，统统不能变。你改了行为，那叫“改功能”或“修 bug”，不叫重构。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可理解性 / 修改成本&lt;/strong&gt;：重构的目标从来不是“代码更好看”这种审美追求，而是&lt;strong&gt;让下一次修改更便宜&lt;/strong&gt;。这是经济学决策，不是艺术决策。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;11-两顶帽子加功能和重构不同时戴&#34;&gt;1.1 两顶帽子：加功能和重构不同时戴&lt;/h3&gt;
&lt;p&gt;Fowler 引用了 Kent Beck 的“两顶帽子”比喻，这是全书我最常拿来讲给同事听的概念：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;戴上“&lt;strong&gt;加功能&lt;/strong&gt;”的帽子：你只添加新代码、新测试，&lt;strong&gt;绝不修改已有代码的结构&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;戴上“&lt;strong&gt;重构&lt;/strong&gt;”的帽子：你只调整结构、保证测试全绿，&lt;strong&gt;绝不添加新功能&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;写代码的每一刻，你头上只能戴一顶帽子。发现“要加这个功能，得先把这段代码理顺”，正确动作是：摘下加功能的帽子 → 戴上重构帽子 → 重构到方便加功能为止 → 换回加功能帽子 → 加功能。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[&amp;#34;接到需求：要加新功能&amp;#34;] --&amp;gt; B{&amp;#34;现有代码结构&amp;lt;br/&amp;gt;方便加这个功能吗？&amp;#34;}
    B -- &amp;#34;方便&amp;#34; --&amp;gt; C[&amp;#34;戴加功能的帽子&amp;lt;br/&amp;gt;写新代码 + 新测试&amp;#34;]
    B -- &amp;#34;不方便&amp;#34; --&amp;gt; D[&amp;#34;戴重构的帽子&amp;lt;br/&amp;gt;只做结构调整，测试保持全绿&amp;#34;]
    D --&amp;gt; E{&amp;#34;结构理顺了吗？&amp;#34;}
    E -- &amp;#34;还没有&amp;#34; --&amp;gt; D
    E -- &amp;#34;理顺了&amp;#34; --&amp;gt; C
    C --&amp;gt; F[&amp;#34;提交，帽子归位&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;为什么这么严格？因为&lt;strong&gt;同时做两件事，出了 bug 你分不清是谁引入的&lt;/strong&gt;。重构的每一步都要求测试全绿，如果中途改了行为，红灯亮起时你就失去了判断依据。两顶帽子是重构能保持“小步”的心理学保障：它把一件模糊的、容易失控的事，拆成了两个界限分明的状态。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;2-坏味道清单程序员的启发法清单&#34;&gt;2. 坏味道清单：程序员的启发法清单&lt;/h2&gt;
&lt;p&gt;第 3 章是全书的“字典”——24 种坏味道（Bad Smells）。注意 Fowler 的用词是“味道”不是“罪状”：&lt;strong&gt;闻到味道不代表必须立刻处理，它只是提示你“这里值得看一眼”&lt;/strong&gt;。极端的教条主义本身就是一种味道。&lt;/p&gt;
&lt;p&gt;24 种味道完整清单如下，后面挑最重要的 10 个深入讲：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;神秘命名、重复代码、过长函数、过长参数列表、全局数据、可变数据、发散式变化、霰弹式修改、依恋情结、数据泥团、基本类型偏执、重复的 switch、循环、冗赘的元素、夸夸其谈未来性、临时字段、过长的消息链、中间人、内幕交易、过大的类、异曲同工的类、纯数据类、被拒绝的遗赠、注释。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;21-神秘命名mysterious-name&#34;&gt;2.1 神秘命名（Mysterious Name）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;味道&lt;/strong&gt;：变量叫 &lt;code&gt;data&lt;/code&gt;、函数叫 &lt;code&gt;process&lt;/code&gt;、类叫 &lt;code&gt;Manager&lt;/code&gt;，看完名字不知道它是干嘛的。&lt;/p&gt;
&lt;p&gt;为什么排第一？因为&lt;strong&gt;命名是程序里密度最高的文档&lt;/strong&gt;。一段代码你可能读十次改一次，名字起得好，十次阅读每次都省时间。Fowler 说得直白：如果想不到好名字，往往意味着你对这段逻辑的职责还没想清楚——命名困难本身就是设计问题的信号。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;对应手法&lt;/strong&gt;：改变函数声明（改名）、变量改名、字段改名。现代 IDE 的重命名重构是安全的，改名字没有借口留到下辈子。&lt;/p&gt;
&lt;h3 id=&#34;22-重复代码duplicated-code&#34;&gt;2.2 重复代码（Duplicated Code）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;味道&lt;/strong&gt;：同一段逻辑出现在两个以上的地方。改一处忘另一处，bug 就是这么来的。&lt;/p&gt;
&lt;p&gt;这是所有味道里&lt;strong&gt;危害最确定、修复手法最成熟&lt;/strong&gt;的一个。双胞胎函数用提炼函数收拢；兄弟子类里重复的用函数上移；类似但不完全相同的，先拆出相同的部分再收拢。&lt;/p&gt;
&lt;p&gt;一个工程界的经典注脚：NASA 的航天飞机软件团队以极低缺陷率著称，他们的经验之一就是极端重视消除重复——任何相似代码都必须解释为什么存在。日常项目虽然不必如此严苛，但“重复三次就提炼”的底线值得守住。&lt;/p&gt;
&lt;h3 id=&#34;23-过长函数long-function&#34;&gt;2.3 过长函数（Long Function）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;味道&lt;/strong&gt;：一个函数几十上百行，得靠注释分段才能读懂。&lt;/p&gt;
&lt;p&gt;Fowler 的态度非常激进：&lt;strong&gt;函数就该短&lt;/strong&gt;。短函数的好处是&lt;strong&gt;通过名字传递意图&lt;/strong&gt;——&lt;code&gt;calcOutstanding()&lt;/code&gt; 比一坨内联计算强得多，因为读者看名字就知道意图，需要细节时才钻进去。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;判断标准&lt;/strong&gt;：不是行数，而是“&lt;strong&gt;如果你需要写注释解释一段代码在干嘛，这段代码就该被提炼成函数，函数名用注释里的词&lt;/strong&gt;”。这个标准极其好用，直接把“注释”这个味道变成了提炼函数的路标。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;对应手法&lt;/strong&gt;：提炼函数（Extract Function）——全书使用频率最高的手法，没有之一。第 2 版里 Fowler 把提炼后的函数长度做到五六行甚至两三行，就是要打破“函数太短不值得”的心理障碍。&lt;/p&gt;
&lt;h3 id=&#34;24-过长参数列表long-parameter-list&#34;&gt;2.4 过长参数列表（Long Parameter List）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;味道&lt;/strong&gt;：函数签名里七八个参数，调用处像咒语。&lt;/p&gt;
&lt;p&gt;危害有两个：一是调用方每次都要记参数顺序，极易传错（尤其是同类型相邻参数）；二是参数多往往说明&lt;strong&gt;函数在做不止一件事&lt;/strong&gt;，或者这些参数本该是一个整体。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;对应手法&lt;/strong&gt;：以查询取代参数（能从参数 A 算出参数 B，就别传 B）、保持对象完整（别从对象里掏三个字段传进去，直接传对象）、引入参数对象（见 2.7 数据泥团）、移除标记参数（布尔 flag 参数往往意味着函数里有个 if 分叉，拆成两个函数更干净）。&lt;/p&gt;
&lt;h3 id=&#34;25-全局数据global-data与可变数据mutable-data&#34;&gt;2.5 全局数据（Global Data）与可变数据（Mutable Data）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;全局数据的味道&lt;/strong&gt;：任何地方都能读到、改到它，出了问题你找不到是谁改的。单例本质上也是全局数据。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;可变数据的味道&lt;/strong&gt;：一个变量在很大作用域内被反复赋值，你在第 500 行看到的值，无法信任它声明时的样子。&lt;/p&gt;
&lt;p&gt;这对兄弟是并发 bug 和“灵异 bug”的头号来源。函数式编程这些年崛起的核心卖点之一就是消灭可变状态，和 Fowler 的判断完全同频。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;对应手法&lt;/strong&gt;：封装变量（把全局变量关进函数里，访问必须经过函数）、拆分变量（一个变量被赋两次值干两件事，就拆成两个）、将引用对象改为值对象（值对象不可变，天然免疫一大类 bug）。&lt;/p&gt;
&lt;h3 id=&#34;26-发散式变化与霰弹式修改divergent-change--shotgun-surgery&#34;&gt;2.6 发散式变化与霰弹式修改（Divergent Change / Shotgun Surgery）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;发散式变化&lt;/strong&gt;：&lt;strong&gt;一个类因为不同原因被反复修改&lt;/strong&gt;。“数据库变了改它，报表格式变了也改它，业务规则变了还改它”——这个类承担了太多职责。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;霰弹式修改&lt;/strong&gt;：&lt;strong&gt;一个变化要改很多个类&lt;/strong&gt;。加一个字段类型，DAO 改、Service 改、DTO 改、前端 mock 改，漏一个就炸。&lt;/p&gt;
&lt;p&gt;这俩是&lt;strong&gt;同一枚硬币的两面&lt;/strong&gt;：前者是“多个原因指向一个类”，后者是“一个原因散到多个类”。修复方向相反——发散式变化用&lt;strong&gt;拆分阶段、搬移函数&lt;/strong&gt;把一个类拆开；霰弹式修改用&lt;strong&gt;搬移函数、提炼类&lt;/strong&gt;把散落的逻辑聚到一处。&lt;/p&gt;
&lt;p&gt;判断技巧：想象需求来了，你心里默念“要改哪几个文件”。如果答案是“又是这个巨无霸类”，是发散式变化；如果答案是“天哪又是七八个文件”，是霰弹式修改。&lt;/p&gt;
&lt;h3 id=&#34;27-依恋情结与数据泥团feature-envy--data-clumps&#34;&gt;2.7 依恋情结与数据泥团（Feature Envy / Data Clumps）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;依恋情结&lt;/strong&gt;：一个函数对另一个类的数据兴趣远大于自己所在类的——函数里 &lt;code&gt;order.getCustomer().getAddress().getCity()&lt;/code&gt; 一长串，全是别人家的 getter。治疗方式很直白：&lt;strong&gt;搬移函数&lt;/strong&gt;，让函数搬到它依恋的数据旁边去。“策略”“访问者”模式是例外，它们故意把行为和数据分开。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;数据泥团&lt;/strong&gt;：总是一起出现的三四个字段——&lt;code&gt;startDate&lt;/code&gt;/&lt;code&gt;endDate&lt;/code&gt;、&lt;code&gt;street&lt;/code&gt;/&lt;code&gt;city&lt;/code&gt;/&lt;code&gt;zip&lt;/code&gt;。它们像连体婴儿一样在各个方法签名和类字段里成群结队。&lt;strong&gt;对应手法&lt;/strong&gt;：提炼类，给这团数据一个家（比如 &lt;code&gt;DateRange&lt;/code&gt;、&lt;code&gt;Address&lt;/code&gt;）。提炼完常常有惊喜：散落各处的校验、格式化逻辑（依恋情结）会自然地搬进新类。&lt;/p&gt;
&lt;h3 id=&#34;28-基本类型偏执与重复的-switchprimitive-obsession--repeated-switches&#34;&gt;2.8 基本类型偏执与重复的 switch（Primitive Obsession / Repeated Switches）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;基本类型偏执&lt;/strong&gt;：用字符串存电话号码、用裸 double 存钱、用 int 编码状态。小钱变大钱、状态码幻数满天飞，都是这么来的。&lt;strong&gt;对应手法&lt;/strong&gt;：以对象取代基本类型——一个 &lt;code&gt;Money&lt;/code&gt; 类、&lt;code&gt;PhoneNumber&lt;/code&gt; 类，把校验和格式化的知识收拢进类型系统。业界血案：金额用 float/double 导致的分分钱对不上账，几乎每个支付团队都踩过，标准做法就是专门的 Money 类型或以最小货币单位为单位的整数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;重复的 switch&lt;/strong&gt;：同一个类型码的 &lt;code&gt;switch&lt;/code&gt;/&lt;code&gt;if-else&lt;/code&gt; 链散落多处。每加一种新类型，得把所有 switch 都找出来改一遍——这是霰弹式修改的特化版。&lt;strong&gt;对应手法&lt;/strong&gt;：&lt;strong&gt;以多态取代条件表达式&lt;/strong&gt;（见 3.6 节）。注意 Fowler 的克制：switch 本身不是罪，&lt;strong&gt;重复的&lt;/strong&gt; switch 才是；如果只有一个 switch 且类型稳定，留着无妨。&lt;/p&gt;
&lt;h3 id=&#34;29-中间人与消息链middle-man--message-chains&#34;&gt;2.9 中间人与消息链（Middle Man / Message Chains）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;中间人&lt;/strong&gt;：一个类一半以上的方法都在委托给别的类，自己什么也不干。封装是好事，但封装到这个程度就是纯粹的过路费。&lt;strong&gt;对应手法&lt;/strong&gt;：移除中间人，让调用方直接和被委托对象说话。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;消息链&lt;/strong&gt;：&lt;code&gt;a.getB().getC().getD()&lt;/code&gt; 一路要下去，调用方对整条链的结构产生了依赖，链中间任何一环变动都会传导过来。这就是“得墨忒耳定律”（Law of Demeter）说的“只和直接朋友说话”。&lt;strong&gt;对应手法&lt;/strong&gt;：隐藏委托关系。&lt;/p&gt;
&lt;p&gt;这一对味道放在一起看很有意思：&lt;strong&gt;过犹不及&lt;/strong&gt;。委托太多是中间人，穿透太多是消息链， Fowler 的词典里没有银弹，只有“闻味道、看剂量”。&lt;/p&gt;
&lt;h3 id=&#34;210-注释comments&#34;&gt;2.10 注释（Comments）&lt;/h3&gt;
&lt;p&gt;注释也上榜了，很多人第一次读会惊讶。Fowler 的逻辑是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注释本身不是坏味道，它常常是“ deodorant（除臭剂）”——用来遮盖代码本身的臭。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;当你觉得“这里得写段注释解释一下”，先问：&lt;strong&gt;能不能提炼函数用函数名说清楚？能不能改变量名说清楚？&lt;/strong&gt; 能，就别写注释。注释该写的是“为什么这么做”（业务背景、历史坑、外部约束），而不是“这段代码在干嘛”——后者是代码没写清楚的忏悔。&lt;/p&gt;
&lt;h3 id=&#34;211-其余味道速查表&#34;&gt;2.11 其余味道速查表&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;味道&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;一句话识别&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;首选手法&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;循环（Loops）&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;for 循环里干的事能用管道表达&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;以管道取代循环（filter/map/reduce）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;冗赘的元素（Lazy Element）&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;这个函数/类就只有一层转发，毫无增量&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;内联函数、内联类&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;夸夸其谈未来性（Speculative Generality）&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;“为将来预留”的抽象，现在只有一个实现&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;折叠继承体系、内联&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;临时字段（Temporary Field）&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;某个字段只在特定算法运行时才有值&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;提炼类 + 搬移函数，给它一个家&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;内幕交易（Insider Trading）&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;两个模块私下大量交换数据，耦合紧密&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;搬移函数/字段，减少私聊&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;过大的类（Large Class）&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;一个类字段太多、职责太多&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;提炼类、提炼超类&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;异曲同工的类（Alternative Classes）&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;两个类接口不同干的事一样&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;改变函数声明 + 搬移函数统一接口&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;纯数据类（Data Class）&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;只有字段和 getter/setter，没有行为&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;把操作它的行为搬进来（封装记录）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;被拒绝的遗赠（Refused Bequest）&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;子类继承了一堆用不上的东西&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;函数/字段下移，或以委托取代继承&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;“夸夸其谈未来性”值得单独敲一下黑板：&lt;strong&gt;YAGNI（You Aren&amp;rsquo;t Gonna Need It）&lt;/strong&gt;。每个“将来可能用得上”的抽象，都是要付利息的技术债——它现在就开始收阅读成本，而兑现的那一天往往永远不会来。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;3-核心重构手法七招走天下&#34;&gt;3. 核心重构手法：七招走天下&lt;/h2&gt;
&lt;p&gt;第 6 章起的重构名录有 60 多个手法，但日常高频使用的集中在少数几招。下面七招是我个人使用频率最高、也最能体现“小步安全”思想的。&lt;/p&gt;
&lt;h3 id=&#34;31-提炼函数extract-function&#34;&gt;3.1 提炼函数（Extract Function）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;场景&lt;/strong&gt;：一段代码需要注释才能看懂；或者一个函数太长。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;口诀&lt;/strong&gt;：&lt;strong&gt;意图与实现分离&lt;/strong&gt;——“这段代码做什么”（意图）交给函数名，“怎么做”（实现）交给函数体。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;小步版本&lt;/strong&gt;（Fowler 的名录里每个手法都有这种机械化步骤，这是本书最容易被低估的价值）：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;创建新函数，按意图命名（想不到好名字就先叫临时名，回头再改）。&lt;/li&gt;
&lt;li&gt;把要提炼的代码&lt;strong&gt;复制&lt;/strong&gt;过去（是复制，不是剪切——先留着原件，错了好回退）。&lt;/li&gt;
&lt;li&gt;检查局部变量：被新函数读到的 → 传参；被修改的 → 尽量变成返回值，一次只返回一个。&lt;/li&gt;
&lt;li&gt;编译。在源位置替换为对新函数的调用。&lt;/li&gt;
&lt;li&gt;测试。全绿则提交。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;避坑&lt;/strong&gt;：一次提炼只处理“只读变量多、被改变量少”的块；被改变量超过一个时，先用“拆分变量”或调整结构再提炼。&lt;/p&gt;
&lt;h3 id=&#34;32-内联函数inline-function&#34;&gt;3.2 内联函数（Inline Function）&lt;/h3&gt;
&lt;p&gt;提炼的反操作。&lt;strong&gt;场景&lt;/strong&gt;：函数体已经和函数名一样清楚（&lt;code&gt;getRating() { return moreThanFiveLateDeliveries ? 2 : 1; }&lt;/code&gt; 改名后可能直接内联掉），或者一堆小函数间接层太深、改名/重组前需要先“摊平”。&lt;/p&gt;
&lt;p&gt;重构名录里的手法大多成对出现（提炼/内联、搬入/搬出、封装/暴露），这不是凑数——&lt;strong&gt;可逆性是小步快走的前提&lt;/strong&gt;：这步走错了，反手一步就能退回来。&lt;/p&gt;
&lt;h3 id=&#34;33-搬移函数move-function&#34;&gt;3.3 搬移函数（Move Function）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;场景&lt;/strong&gt;：函数引用别的上下文的元素多于自己所在的上下文（依恋情结的特效药）；或者模块化需要重新划分边界。&lt;/p&gt;
&lt;p&gt;口诀：&lt;strong&gt;逻辑和它操作的数据住在一起&lt;/strong&gt;。搬移时先检查函数引用的所有东西——它们要么一起搬，要么变成参数。搬完旧位置可以留一个委托函数过渡，等调用方都改完再删。&lt;/p&gt;
&lt;h3 id=&#34;34-提炼变量extract-variable&#34;&gt;3.4 提炼变量（Extract Variable）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;场景&lt;/strong&gt;：表达式又长又复杂，比如：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;order&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;quantity&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;order&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;itemPrice&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Math.&lt;span style=&#34;color:#a6e22e&#34;&gt;max&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;order&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;quantity&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;500&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;order&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;itemPrice&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.05&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Math.&lt;span style=&#34;color:#a6e22e&#34;&gt;min&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;order&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;quantity&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;order&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;itemPrice&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;逐个起名字：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;basePrice&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;order&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;quantity&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;order&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;itemPrice&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;quantityDiscount&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Math.&lt;span style=&#34;color:#a6e22e&#34;&gt;max&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;order&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;quantity&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;500&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;order&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;itemPrice&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.05&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;shipping&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Math.&lt;span style=&#34;color:#a6e22e&#34;&gt;min&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;basePrice&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;basePrice&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;quantityDiscount&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;shipping&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;零风险、几秒钟、可读性立刻翻倍。&lt;strong&gt;这是性价比最高的重构手法&lt;/strong&gt;，新人最值得先养成这个习惯。变量命名还能顺带自我解释业务规则：原来那 0.05 是批量折扣，0.1 是运费比例。&lt;/p&gt;
&lt;h3 id=&#34;35-封装记录encapsulate-record&#34;&gt;3.5 封装记录（Encapsulate Record）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;场景&lt;/strong&gt;：代码里到处传裸的 object/map/dict（纯数据类的输入版），字段名靠口口相传。&lt;/p&gt;
&lt;p&gt;把记录变成类，访问走方法：字段名写错编译期就炸，而不是线上拿到 &lt;code&gt;undefined&lt;/code&gt;；还能给计算字段提供方法（&lt;code&gt;total&lt;/code&gt; 不必存，用 &lt;code&gt;get total()&lt;/code&gt; 算）。这是 TypeScript 兴起后整个行业都在做的事——&lt;strong&gt;让类型系统替你站岗&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;36-以多态取代条件表达式replace-conditional-with-polymorphism&#34;&gt;3.6 以多态取代条件表达式（Replace Conditional with Polymorphism）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;场景&lt;/strong&gt;：同一个类型码的 switch 散落多处（重复的 switch 味道）；或者一个函数内部按类型分叉，分支逻辑还会持续增加。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;步骤&lt;/strong&gt;：为每种类型建子类 → 把条件分支的逻辑搬成子类的覆写方法 → 原函数变抽象。书里的经典例子是“鸟类评级”：按鸟的种类（欧洲燕子、非洲燕子、挪威蓝鹦鹉）分支计算飞行速度和评级，重构后每种鸟一个子类，新增鸟种只需新增一个类——&lt;strong&gt;修改从“找到所有 switch”变成“新建一个文件”&lt;/strong&gt;，这正是开闭原则。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;避坑&lt;/strong&gt;：类型码&lt;strong&gt;稳定且数量少&lt;/strong&gt;时不要硬上多态。switch 只有一处、类型十年不变，多态带来的类爆炸是另一种过度设计。再强调一次：闻味道，看剂量。&lt;/p&gt;
&lt;h3 id=&#34;37-拆分阶段split-phase&#34;&gt;3.7 拆分阶段（Split Phase）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;场景&lt;/strong&gt;：一段代码同时干两件性质不同的事——典型的如“解析输入 + 业务计算 + 输出格式”。&lt;/p&gt;
&lt;p&gt;把两段拆开，中间用一个明确的数据结构传递：第一阶段产出中间对象，第二阶段消费它。好处是&lt;strong&gt;两个阶段的修改互不影响&lt;/strong&gt;——换输出格式不动计算逻辑，换输入来源不动下游。书中 statement 案例（下节）的核心一步就是拆分阶段。命令行工具、编译器（解析→语义分析→代码生成）、数据管道，全是这个手法的工业级应用。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;4-全书导读案例statement-计费重构走查&#34;&gt;4. 全书导读案例：statement 计费重构走查&lt;/h2&gt;
&lt;p&gt;第 2 版第 1 章用 JavaScript 完整走查了一个案例，非常值得复现一遍，因为它是“小步快走”的教科书级演示。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原始代码&lt;/strong&gt;：一个剧团的账单系统，&lt;code&gt;statement(invoice, plays)&lt;/code&gt; 输出客户看戏的账单文本。一个约 40 行的函数，里面一个 switch 按戏剧类型（悲剧/喜剧）算钱，外加积分计算和字符串拼接，全部揉在一起。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;需求来了&lt;/strong&gt;（这就是重构的触发器）：① 账单要支持 HTML 输出；② 剧团打算演更多类型的戏（比如“历史剧”“田园剧”），算钱和积分规则要可扩展。&lt;/p&gt;
&lt;p&gt;Fowler 没有直接动手写 HTML 版本，而是先重构。整个过程的关键步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;先补测试&lt;/strong&gt;：原代码没有任何测试。第一步不是改代码，是给现有行为写一批测试，把输出钉死。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;提炼函数&lt;/strong&gt;：把 switch 算钱的部分提炼成 &lt;code&gt;amountFor(aPerformance)&lt;/code&gt;，把积分计算提炼成 &lt;code&gt;volumeCreditsFor()&lt;/code&gt;。每提炼一个，跑一次测试。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;提炼变量 + 改名&lt;/strong&gt;：&lt;code&gt;thisAmount&lt;/code&gt; 这种模糊名改成 &lt;code&gt;result&lt;/code&gt;，消除不必要的中间变量。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;拆分阶段&lt;/strong&gt;：把“计算账单数据”和“渲染文本账单”拆成两个阶段，中间用一个数据结构传递。此时 &lt;code&gt;statement()&lt;/code&gt; 变成：&lt;code&gt;const data = createStatementData(invoice, plays); return renderPlainText(data);&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;加功能&lt;/strong&gt;：此时写 &lt;code&gt;renderHtml(data)&lt;/code&gt; 只消费中间数据，计算逻辑一行不碰——HTML 版本轻松加上。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;以多态取代条件表达式&lt;/strong&gt;：为“悲剧计算器”“喜剧计算器”建子类，switch 消失。加新戏种 = 加一个新子类。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;原始代码&amp;lt;br/&amp;gt;一个 40 行大函数&amp;lt;br/&amp;gt;无测试&amp;#34;] --&amp;gt; B[&amp;#34;补测试&amp;lt;br/&amp;gt;钉死现有行为&amp;#34;]
    B --&amp;gt; C[&amp;#34;提炼函数&amp;lt;br/&amp;gt;amountFor / volumeCreditsFor&amp;#34;]
    C --&amp;gt; D[&amp;#34;拆分阶段&amp;lt;br/&amp;gt;计算数据 vs 渲染文本&amp;#34;]
    D --&amp;gt; E[&amp;#34;中间数据结构&amp;lt;br/&amp;gt;statementData&amp;#34;]
    E --&amp;gt; F[&amp;#34;加功能：renderHtml&amp;lt;br/&amp;gt;复用同一份数据&amp;#34;]
    C --&amp;gt; G[&amp;#34;以多态取代条件表达式&amp;lt;br/&amp;gt;TragedyCalculator&amp;lt;br/&amp;gt;ComedyCalculator&amp;#34;]
    G --&amp;gt; H[&amp;#34;加功能：新戏种&amp;lt;br/&amp;gt;只需新增子类&amp;#34;]
    F --&amp;gt; I[&amp;#34;最终结构&amp;lt;br/&amp;gt;好改、好测、好扩展&amp;#34;]
    H --&amp;gt; I&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;这个案例的精髓不在于手法本身（都不难），而在于&lt;strong&gt;节奏&lt;/strong&gt;：40 行的小函数，Fowler 也花了几十个小步、每步跑测试。他要演示的是：&lt;strong&gt;重构的安全性不来自你的技术，来自你的步幅&lt;/strong&gt;。步幅足够小，每一步都可以验证，出错最多浪费一步。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;5-重构与测试先补测试再动刀&#34;&gt;5. 重构与测试：先补测试，再动刀&lt;/h2&gt;
&lt;p&gt;第 2 版相对第 1 版（1999 年）最大的时代变化，就是自动化测试从“推荐”变成了“前提”。Fowler 的态度很明确：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;没有测试保护的重构不叫重构，叫“改代码并祈祷”。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;逻辑链条是这样的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;重构的定义是“不改变可观察行为”——那你怎么&lt;strong&gt;知道&lt;/strong&gt;行为没变？只能靠测试。&lt;/li&gt;
&lt;li&gt;重构要求小步快走——每一步都要验证，人工验证一遍要十分钟，自动化只要十秒。没有自动化，小步走不起来。&lt;/li&gt;
&lt;li&gt;面对祖传代码没有测试怎么办？&lt;strong&gt;先补测试再动刀&lt;/strong&gt;。补测试本身常常需要先轻量重构（比如提炼函数让依赖可以 mock），这就回到了两顶帽子：戴着测试帽做最小限度的结构调整，只为让测试能跑起来。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Michael Feathers 在《修改代码的艺术》（Working Effectively with Legacy Code）里把“没有测试的代码”直接定义为遗留代码，和 Fowler 互为犄角。两本可以搭配读：Fowler 教你“怎么改”，Feathers 教你“改不动时怎么办”。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[&amp;#34;要动一段代码&amp;#34;] --&amp;gt; B{&amp;#34;有测试覆盖吗？&amp;#34;}
    B -- &amp;#34;有&amp;#34; --&amp;gt; C[&amp;#34;跑一遍，确认全绿&amp;#34;]
    B -- &amp;#34;没有&amp;#34; --&amp;gt; D[&amp;#34;最小限度补测试&amp;lt;br/&amp;gt;（为可测性做的微调也算）&amp;#34;]
    D --&amp;gt; C
    C --&amp;gt; E[&amp;#34;做一小步重构&amp;#34;]
    E --&amp;gt; F{&amp;#34;测试全绿？&amp;#34;}
    F -- &amp;#34;绿了&amp;#34; --&amp;gt; G[&amp;#34;立即提交&amp;lt;br/&amp;gt;（小步提交 = 免费回滚点）&amp;#34;]
    F -- &amp;#34;红了&amp;#34; --&amp;gt; H[&amp;#34;撤销这一步&amp;lt;br/&amp;gt;或立刻修复，不要带着红灯继续&amp;#34;]
    G --&amp;gt; E
    H --&amp;gt; E
    E --&amp;gt; I{&amp;#34;目标达成？&amp;#34;}
    I -- &amp;#34;没有，继续小步&amp;#34; --&amp;gt; E
    I -- &amp;#34;达成&amp;#34; --&amp;gt; J[&amp;#34;收工：行为没变，结构更好&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;这张“安全网流程图”里最反直觉也最重要的一条是：&lt;strong&gt;测试红了，默认动作是撤销这一步，而不是往前调&lt;/strong&gt;。小步走意味着你刚刚那一步的范围是已知的、可控的，退回上一步的代价几乎为零；带着红灯继续走，错误范围就不可控了。配合小步提交（每步都 commit），你就拥有了一条随时可以回滚的时间线。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;6-重构的时机与节奏融进日常而不是立项&#34;&gt;6. 重构的时机与节奏：融进日常，而不是立项&lt;/h2&gt;
&lt;h3 id=&#34;61-三次法则与顺手重构&#34;&gt;6.1 三次法则与“顺手重构”&lt;/h3&gt;
&lt;p&gt;Fowler 给的实用规则：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;事不过三，三则重构&lt;/strong&gt;（Rule of Three，源自 Don Roberts）：第一次写就写，第二次重复捏着鼻子忍，第三次必须提炼。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;加功能前重构&lt;/strong&gt;：代码结构碍事，先重构到“加这个功能很容易”，再加功能（两顶帽子）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;修 bug 时重构&lt;/strong&gt;：bug 之所以藏得深，往往因为代码难懂。把 bug 修掉的同时，把“让 bug 藏不住”的结构改出来。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;code review 时重构&lt;/strong&gt;：看到别人的坏味道，当面演示一次提炼函数，比留十句评论有用。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;62-破窗效应与童子军军规&#34;&gt;6.2 破窗效应与童子军军规&lt;/h3&gt;
&lt;p&gt;犯罪学里的“破窗效应”：一栋楼有几扇破窗没人修，很快所有窗户都会被砸——混乱会邀请更多混乱。代码库一模一样：第一处“先这样吧，回头再改”开了头，后面所有人都会照着最烂的那段代码的标准来写。&lt;/p&gt;
&lt;p&gt;对策是 Bob Martin 普及的&lt;strong&gt;童子军军规&lt;/strong&gt;（Boy Scout Rule）：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;离开营地时，要让它比你来时更干净。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;落到代码上：每次提交，都顺手改进一点点——改一个烂名字、提炼一个长函数、删掉一段死代码。不需要翻天覆地，只要&lt;strong&gt;持续为正&lt;/strong&gt;。一个团队做到这点，代码质量会随时间收敛而不是发散。这是对付“祖传代码”唯一被验证过可持续的策略：不是一次性手术，是持续的免疫反应。&lt;/p&gt;
&lt;h3 id=&#34;63-什么时候不重构&#34;&gt;6.3 什么时候&lt;strong&gt;不&lt;/strong&gt;重构&lt;/h3&gt;
&lt;p&gt;书里同样重要的克制：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;代码又烂又永远不用改&lt;/strong&gt;——让它烂着，重构它没有回报。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;重写比重构便宜&lt;/strong&gt;——偶尔成立，但门槛极高，见下节的 Netscape 教训。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;离截止日期太近且重构不能帮上眼前的忙&lt;/strong&gt;——重构的回报在下一次修改，先交付，记技术债，回头还。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;记住：&lt;strong&gt;重构是经济学决策&lt;/strong&gt;。每一步都应该能回答“这能让接下来的修改更便宜吗？”答不上来的重构是自我感动。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;7-大规模重写的教训netscape-之死&#34;&gt;7. 大规模重写的教训：Netscape 之死&lt;/h2&gt;
&lt;p&gt;聊到重构，绕不开软件工程史上最著名的一次“我们不重构了，我们重写”的惨案。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;背景&lt;/strong&gt;：Netscape Navigator 曾统治浏览器市场，但代码库在快速迭代中积重难返。1997 年前后，Netscape 决定&lt;strong&gt;抛弃旧代码，从头重写浏览器引擎&lt;/strong&gt;（即后来的 Gecko / Mozilla 项目）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;结果&lt;/strong&gt;：重写花了将近三年。这三年里 Netscape 几乎没有发布有竞争力的新版本——6.0 版本直到 2000 年底才问世。与此同时，微软的 IE 持续迭代、免费捆绑 Windows，市场份额被彻底翻盘。等 Mozilla 成品出来时，浏览器战争已经结束了。&lt;/p&gt;
&lt;p&gt;Joel Spolsky 在 2000 年那篇著名文章《Things You Should Never Do, Part I》里拿 Netscape 当反面教材，结论斩钉截铁：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;重写是你能对软件做的最糟糕的战略性错误，没有之一。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;他的论证值得背下来：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;旧代码不是垃圾，是知识的化石&lt;/strong&gt;。那些丑陋的补丁，每一个都对应一个真实用户踩过的坑、一个边界情况。重写时你看不见这些知识，于是用户会在新版本里重新踩一遍所有的坑。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;重写期间你无法交付&lt;/strong&gt;。老代码烂归烂，它在生产环境里跑着、赚着钱；新代码在成型之前一文不值。竞争对手不会等你。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;重写通常伴随着“这次一定要设计完美”的野心&lt;/strong&gt;——这正是“夸夸其谈未来性”的企业级版本，工期必然失控。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Fowler 在第 1 版就引用过这个故事来论证：&lt;strong&gt;重构的价值恰恰在于它是重写的替代品&lt;/strong&gt;。用持续的小步重构，让烂代码在“一直保持可交付”的状态下逐步变好，是风险唯一可控的路径。&lt;/p&gt;
&lt;p&gt;当然要有公允的注脚：后来 Mozilla 代码确实成了 Firefox 的底子，长期看那次重写并非全无产出。但&lt;strong&gt;作为商业决策，Netscape 公司没活到收获的那天&lt;/strong&gt;——这正是 Joel 论点最残酷的部分。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;8-带走的清单&#34;&gt;8. 带走的清单&lt;/h2&gt;
&lt;h3 id=&#34;81-核心心智模型五句话&#34;&gt;8.1 核心心智模型（五句话）&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;重构 = &lt;strong&gt;不改变可观察行为&lt;/strong&gt;的结构调整；目标不是美，是&lt;strong&gt;让下次修改更便宜&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;两顶帽子&lt;/strong&gt;：加功能和重构永不同时戴。&lt;/li&gt;
&lt;li&gt;坏味道清单 = 启发法清单：闻到味道 → 查名录 → 用对应手法。味道是提示，不是罪状。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;刷牙，不大扫除&lt;/strong&gt;：小步、频繁、顺手；童子军军规，每次提交留代码更干净一点。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;先补测试，再动刀&lt;/strong&gt;；测试红了先撤销，不带着红灯往前走。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;82-新人避坑指南&#34;&gt;8.2 新人避坑指南&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;坑一：把重构当借口拖延交付。&lt;/strong&gt; 重构是加功能的加速器不是挡箭牌。每次重构前问自己：这步能让手头这个需求更快落地吗？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;坑二：没有测试就动祖传代码。&lt;/strong&gt; 先补测试。补测试时发现代码不可测，先做最小限度的可测性重构（提炼函数、依赖注入），别一步到位。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;坑三：PR 里混着“重构 + 新功能 + 修 bug”。&lt;/strong&gt; reviewer 无法区分行为变化来自哪里。拆 PR，或者至少拆 commit。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;坑四：看到 switch 就上多态，看到简单类就套设计模式。&lt;/strong&gt; 夸夸其谈未来性也是坏味道。YAGNI。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;坑五：迷信“等我有整块时间再重构”。&lt;/strong&gt; 那一天永远不会来。能融进日常的小重构才有复利，立项式重构大概率变成 Netscape 式豪赌。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;坑六：改名字嫌麻烦。&lt;/strong&gt; IDE 的安全重命名几秒钟一次，这是全软件业性价比最高的操作，没有之一。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;83-一页速查味道--手法&#34;&gt;8.3 一页速查：味道 → 手法&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;mindmap
  root((坏味道&amp;lt;br/&amp;gt;→&amp;lt;br/&amp;gt;重构手法))
    代码冗余
      重复代码 → 提炼函数
      过长函数 → 提炼函数
      过长参数列表 → 引入参数对象
      数据泥团 → 提炼类
      冗赘元素 → 内联函数
    职责错配
      依恋情结 → 搬移函数
      发散式变化 → 拆分阶段
      霰弹式修改 → 搬移函数聚拢
      内幕交易 → 搬移函数减少私聊
      过大的类 → 提炼类
    类型与状态
      基本类型偏执 → 以对象取代基本类型
      重复的switch → 以多态取代条件表达式
      全局数据 → 封装变量
      可变数据 → 拆分变量/值对象
      临时字段 → 提炼类
    意图不清
      神秘命名 → 改名
      注释 → 提炼函数让代码自解释
      复杂表达式 → 提炼变量
      消息链 → 隐藏委托关系
      中间人 → 移除中间人&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;9-写在最后&#34;&gt;9. 写在最后&lt;/h2&gt;
&lt;p&gt;《重构》这本书最被低估的一点：它表面上是一本“手法名录”，实际上是一套&lt;strong&gt;把代码质量从“良心活”变成“机械活”的方法论&lt;/strong&gt;。坏味道给你嗅觉，名录给你机械步骤，测试给你安全网，两顶帽子给你节奏——四样东西配齐，重构就从“需要勇气和天赋的大工程”变成了“不需要下决心就能做的日常小事”。&lt;/p&gt;
&lt;p&gt;而日常小事的复利是惊人的。每天让代码干净 1%，一年后它和自然腐化的代码库就是两个物种。这本书 1999 年出第 1 版、2018 年出第 2 版，横跨二十年依然是软件工程必读，原因无他：&lt;strong&gt;代码会腐化是永恒规律，而刷牙永远有效&lt;/strong&gt;。&lt;/p&gt;
</description>
    </item>
    <item>
      <title>《领域驱动设计·精简版》详解：零基础读懂 DDD 的核心概念</title>
      <link>https://ftmao.com/blog/posts/domain-driven-design-quickly-book-notes/</link>
      <pubDate>Sat, 29 Jul 2023 14:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/domain-driven-design-quickly-book-notes/</guid>
      <description>&lt;h1 id=&#34;领域驱动设计精简版详解零基础读懂-ddd-的核心概念&#34;&gt;《领域驱动设计·精简版》详解：零基础读懂 DDD 的核心概念&lt;/h1&gt;
&lt;p&gt;DDD 要回答的问题是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;软件是为了解决现实业务问题而存在的，可为什么代码写着写着，就和业务彻底脱节了？&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Eric Evans 的答案是：问题出在&amp;quot;模型&amp;quot;上。业务在领域专家脑子里，代码在程序员手里，中间隔着层层&amp;quot;翻译&amp;quot;，每翻译一次就失真一次。DDD 的全部内容，就是围绕&amp;quot;如何建立一个好模型、并让模型直接活在代码里&amp;quot;展开的。&lt;/p&gt;
&lt;p&gt;本文面向&lt;strong&gt;从未接触过 DDD&lt;/strong&gt; 的读者，按全书结构逐章展开，配合图解和实例，帮你建立完整的知识地图。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第-1-章-简介软件为什么会偏离领域&#34;&gt;第 1 章 简介：软件为什么会偏离领域&lt;/h2&gt;
&lt;h3 id=&#34;领域与领域模型&#34;&gt;领域与领域模型&lt;/h3&gt;
&lt;p&gt;**领域（Domain）**就是软件要解决的那个现实问题。做银行系统，金融业务就是领域；做空中交通监控，航线和飞行就是领域。&lt;/p&gt;
&lt;p&gt;书里用了一个类比：造汽车。工人们各自负责某个零件，但如果没人理解整车，造出来的只是一堆零件的集合体，不是一辆好车。软件也一样——&lt;strong&gt;你不能在对金融业务一无所知的情况下写出好的银行软件&lt;/strong&gt;。谁懂业务？不是架构师，不是分析师，是&lt;strong&gt;领域专家&lt;/strong&gt;（银行的业务人员、空管的调度员）。&lt;/p&gt;
&lt;p&gt;但领域知识是海量而杂乱的，不可能全部塞进软件。我们需要从中建立&lt;strong&gt;抽象&lt;/strong&gt;——即&lt;strong&gt;领域模型（Domain Model）&lt;/strong&gt;：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;模型不是一张图，也不是专家脑子里的全部知识，而是&amp;quot;经过严格组织并进行选择性抽象的知识&amp;quot;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;建模意味着取舍。银行软件会记录客户的住址，但绝不会记录客户眼睛的颜色——前者是业务需要的，后者不是。&lt;strong&gt;选择保留什么、放弃什么，正是建模的核心工作&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;书里的案例空中交通监控系统&#34;&gt;书里的案例：空中交通监控系统&lt;/h3&gt;
&lt;p&gt;全书反复使用同一个例子：空中交通监控系统。飞机（Aircraft）按计划航线（Route）飞行，航线由一系列定位点（Fix）组成，每个定位点是一个三维坐标（3DPoint：经度、纬度、海拔）。系统接收雷达数据，计算飞机的真实飞行轨道，发现两架飞机航线可能交叉时向管制员报警。&lt;/p&gt;
&lt;p&gt;最初的设计里，Route 直接关联 3DPoint，后来团队发现：地面屏幕上显示的航线其实只需要二维投影，于是模型被重构为 Route 关联 2DPoint——&lt;strong&gt;模型随理解的加深而演进&lt;/strong&gt;，这正是 DDD 强调的动态过程。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第-2-章-通用语言ubiquitous-language&#34;&gt;第 2 章 通用语言（Ubiquitous Language）&lt;/h2&gt;
&lt;h3 id=&#34;翻译是万恶之源&#34;&gt;翻译是万恶之源&lt;/h3&gt;
&lt;p&gt;开发人员和领域专家说着两种语言：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;开发人员满脑子是类、方法、继承、多态、框架；&lt;/li&gt;
&lt;li&gt;领域专家满口是飞机、航线、海拔、偏航告警。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;于是每次沟通都在&amp;quot;翻译&amp;quot;：开发人员把业务术语翻译成技术概念去写代码，再把技术实现翻译回业务语言去汇报。&lt;strong&gt;每一次翻译都丢失信息，错误理解就这样悄悄积累&lt;/strong&gt;。更糟的是，很多时候团队连&amp;quot;翻译错了&amp;quot;都发现不了，直到软件上线才爆发。&lt;/p&gt;
&lt;h3 id=&#34;用模型当语言的骨架&#34;&gt;用模型当语言的骨架&lt;/h3&gt;
&lt;p&gt;DDD 的核心原则之一：&lt;strong&gt;使用一种基于模型的语言进行所有交流&lt;/strong&gt;——讨论用它，文档用它，代码里的类名、方法名也用它。因为语言贯穿所有交流形式，所以叫&lt;strong&gt;通用语言（Ubiquitous Language）&lt;/strong&gt;。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    subgraph 传统方式[&amp;#34;传统方式：靠翻译，层层失真&amp;#34;]
        E1[&amp;#34;领域专家&amp;lt;br/&amp;gt;（业务行话）&amp;#34;] --&amp;gt;|需求翻译| A1[&amp;#34;分析师文档&amp;#34;]
        A1 --&amp;gt;|设计翻译| D1[&amp;#34;开发人员&amp;#34;]
        D1 --&amp;gt;|代码实现| C1[(&amp;#34;代码&amp;lt;br/&amp;gt;（第三套术语）&amp;#34;)]
    end
    subgraph DDD方式[&amp;#34;DDD 方式：一种语言贯通&amp;#34;]
        E2[&amp;#34;领域专家&amp;#34;] &amp;lt;==&amp;gt;|&amp;#34;通用语言&amp;lt;br/&amp;gt;（基于领域模型）&amp;#34;| D2[&amp;#34;开发人员&amp;#34;]
        D2 --- C2[(&amp;#34;代码 = 模型&amp;lt;br/&amp;gt;术语完全一致&amp;#34;)]
        E2 --- C2
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;实例&lt;/strong&gt;：空管系统里有个概念叫&amp;quot;偏航告警&amp;quot;。如果代码里叫 &lt;code&gt;YawDeviationWarningProcessor&lt;/code&gt;，而管制员嘴里叫&amp;quot;偏航告警&amp;quot;，需求评审时大家讨论的是 A，代码里实现的是 B，三个月后没人能对上号。按 DDD 的做法，团队约定统一叫&amp;quot;偏航告警（RouteDeviationAlert）&amp;quot;，类名、数据库表、会议讨论全部用同一个词——模型和代码之间不再存在翻译层。&lt;/p&gt;
&lt;p&gt;通用语言不是一次定稿的，它随模型的演进而演进：&lt;strong&gt;当你发现某个词说不清一件事，往往不是表达问题，而是模型有问题&lt;/strong&gt;——这就是重构的信号。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第-3-章-模型驱动设计model-driven-design&#34;&gt;第 3 章 模型驱动设计（Model-Driven Design）&lt;/h2&gt;
&lt;p&gt;这是全书篇幅最大的一章，讲模型怎么落地成代码。Eric Evans 提供了一套标准化的&amp;quot;构造块&amp;quot;（Building Blocks）。&lt;/p&gt;
&lt;h3 id=&#34;分层架构&#34;&gt;分层架构&lt;/h3&gt;
&lt;p&gt;先把领域层隔离出来，别让业务逻辑散落在界面和数据库代码里：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    UI[&amp;#34;用户界面层&amp;lt;br/&amp;gt;展示信息、接收输入&amp;#34;]
    APP[&amp;#34;应用层&amp;lt;br/&amp;gt;协调任务、编排流程&amp;lt;br/&amp;gt;（很薄，不含业务规则）&amp;#34;]
    DOM[&amp;#34;领域层&amp;lt;br/&amp;gt;★ 业务概念、业务规则、业务状态&amp;lt;br/&amp;gt;模型的家&amp;#34;]
    INFRA[&amp;#34;基础设施层&amp;lt;br/&amp;gt;持久化、消息、框架支撑&amp;#34;]
    UI --&amp;gt; APP --&amp;gt; DOM
    INFRA -.支撑.- DOM&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;关键原则：&lt;strong&gt;领域层不依赖界面，不被基础设施污染&lt;/strong&gt;。判断标准很简单——把界面整个换掉（比如从网页换成 App），领域层应该一行都不用改。&lt;/p&gt;
&lt;h3 id=&#34;七个构造块&#34;&gt;七个构造块&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    subgraph 领域层[&amp;#34;领域层的构造块&amp;#34;]
        direction TB
        E[&amp;#34;实体 Entity&amp;lt;br/&amp;gt;有身份，有生命周期&amp;#34;]
        VO[&amp;#34;值对象 Value Object&amp;lt;br/&amp;gt;无身份，不可变&amp;#34;]
        S[&amp;#34;服务 Service&amp;lt;br/&amp;gt;不属于任何对象的操作&amp;#34;]
        M[&amp;#34;模块 Module&amp;lt;br/&amp;gt;按概念分组&amp;#34;]
        AGG[&amp;#34;聚合 Aggregate&amp;lt;br/&amp;gt;一致性边界 + 聚合根&amp;#34;]
        F[&amp;#34;工厂 Factory&amp;lt;br/&amp;gt;封装复杂创建&amp;#34;]
        R[&amp;#34;资源库 Repository&amp;lt;br/&amp;gt;封装查询与重建&amp;#34;]
        AGG --&amp;gt; E
        AGG --&amp;gt; VO
        F --&amp;gt; AGG
        R --&amp;gt; AGG
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h4 id=&#34;-实体entity靠身份区分&#34;&gt;① 实体（Entity）：靠&amp;quot;身份&amp;quot;区分&lt;/h4&gt;
&lt;p&gt;有些对象，即使属性全变了，它还是&amp;quot;它&amp;quot;。一架飞机换了涂装、换了座椅布局，航班号 CA1234 还是那架飞机。&lt;strong&gt;实体由身份标识（ID）定义，而不是属性&lt;/strong&gt;。人也是典型的实体——同名同姓的两个人依然是两个人。&lt;/p&gt;
&lt;h4 id=&#34;-值对象value-object靠属性区分&#34;&gt;② 值对象（Value Object）：靠&amp;quot;属性&amp;quot;区分&lt;/h4&gt;
&lt;p&gt;有些对象没有身份，只描述&amp;quot;是什么&amp;quot;。地址&amp;quot;北京市海淀区某路 1 号&amp;quot;就是值对象：两个地址属性完全相同，就是同一个地址，可以互换、可以共享。值对象应该是&lt;strong&gt;不可变的&lt;/strong&gt;——要改就新建一个。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;新手最常见的错误是把一切都建成实体&lt;/strong&gt;。书里建议：优先考虑值对象，只有当&amp;quot;身份&amp;quot;确实重要时才用实体——值对象更简单、更安全、性能更好。&lt;/p&gt;
&lt;h4 id=&#34;-服务service无家可归的操作&#34;&gt;③ 服务（Service）：无家可归的操作&lt;/h4&gt;
&lt;p&gt;有些操作天然不属于任何实体或值对象。比如&amp;quot;转账&amp;quot;：钱从 A 账户到 B 账户，这个操作该挂在 A 身上还是 B 身上？都不合适。把它做成一个领域服务 &lt;code&gt;TransferService&lt;/code&gt;。判断标准：&lt;strong&gt;操作的概念属于领域，但它本身是无状态的&lt;/strong&gt;。&lt;/p&gt;
&lt;h4 id=&#34;-模块module高内聚低耦合的分组&#34;&gt;④ 模块（Module）：高内聚、低耦合的分组&lt;/h4&gt;
&lt;p&gt;把概念上紧密相关的类放进同一个模块。&lt;strong&gt;模块名也应该进入通用语言&lt;/strong&gt;——当团队说&amp;quot;这是计费模块的事&amp;quot;，所有人都知道边界在哪。&lt;/p&gt;
&lt;h4 id=&#34;-聚合aggregate一致性的边界&#34;&gt;⑤ 聚合（Aggregate）：一致性的边界&lt;/h4&gt;
&lt;p&gt;对象之间关系复杂时，改一个对象可能牵连一片。聚合的解法是：&lt;strong&gt;把关联紧密的对象圈进一个边界，选一个实体做&amp;quot;聚合根&amp;quot;（Aggregate Root），外部只能通过根访问内部对象&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;书里的例子：客户（Customer）是聚合根，地址（Address）、联系人是内部对象。外部想要地址？找客户要，拿到的是一个拷贝，不能直接改——所有对聚合内部的修改都经过根把关，不变量（invariant）就有了守护者。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    subgraph 聚合边界[&amp;#34;聚合边界&amp;#34;]
        ROOT[&amp;#34;客户 Customer&amp;lt;br/&amp;gt;（聚合根）&amp;#34;]
        ADDR[&amp;#34;地址 Address&amp;#34;]
        CONTACT[&amp;#34;联系人 Contact&amp;#34;]
        ROOT --&amp;gt; ADDR
        ROOT --&amp;gt; CONTACT
    end
    OUT[&amp;#34;外部对象&amp;#34;] --&amp;gt;|&amp;#34;只能持有根的引用&amp;#34;| ROOT
    OUT -.-&amp;gt;|&amp;#34;禁止直接访问&amp;#34;| ADDR&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h4 id=&#34;-工厂factory封装怎么造&#34;&gt;⑥ 工厂（Factory）：封装&amp;quot;怎么造&amp;quot;&lt;/h4&gt;
&lt;p&gt;创建一个复杂聚合涉及大量知识：内部结构、对象间关系、初始规则。如果让调用方自己 &lt;code&gt;new&lt;/code&gt; 再拼装，等于&amp;quot;给你塑胶、橡胶、金属、硅，自己组装一台打印机&amp;quot;——组装知识泄漏到了所有调用点。&lt;strong&gt;工厂把创建逻辑收进一处&lt;/strong&gt;，客户只要说&amp;quot;我要一个完整的客户聚合&amp;quot;。&lt;/p&gt;
&lt;h4 id=&#34;-资源库repository封装怎么找&#34;&gt;⑦ 资源库（Repository）：封装&amp;quot;怎么找&amp;quot;&lt;/h4&gt;
&lt;p&gt;对象创建后存进数据库，之后要用时再取出来。资源库给领域对象提供一个&amp;quot;像集合一样&amp;quot;的查询接口（&lt;code&gt;findById&lt;/code&gt;、&lt;code&gt;findByName&lt;/code&gt;），把 SQL 和 ORM 细节全部藏在背后。&lt;strong&gt;领域代码面对的好像是一个内存中的对象集合&lt;/strong&gt;，至于底下是 MySQL 还是 MongoDB，领域层不关心。&lt;/p&gt;
&lt;p&gt;三者分工一句话：&lt;strong&gt;工厂管出生，资源库管找回，聚合管活着时的一致性&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第-4-章-面向深层理解的重构&#34;&gt;第 4 章 面向深层理解的重构&lt;/h2&gt;
&lt;p&gt;模型不是一次画对的。随着对业务理解的加深，团队会不断发现&amp;quot;原来的模型表达不了新需求&amp;quot;。DDD 的态度是：&lt;strong&gt;持续重构，并且专门朝&amp;quot;深层理解&amp;quot;的方向重构&lt;/strong&gt;——不是消除代码重复那种技术重构，而是当领域专家随口说出一个新概念时，敏锐地意识到&amp;quot;这个词值得成为模型里的一等公民&amp;quot;。&lt;/p&gt;
&lt;p&gt;书里称之为&lt;strong&gt;凸现关键概念&lt;/strong&gt;：把隐晦的、散落在各处的业务规则，提炼成显式的类和方法。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;实例&lt;/strong&gt;：空管系统最初把&amp;quot;两机航线交叉判断&amp;quot;写在一段几百行的过程代码里。重构后，团队把它提炼成 &lt;code&gt;FlightPathConflict&lt;/code&gt; 对象和 &lt;code&gt;conflictsWith()&lt;/code&gt; 方法——业务规则从&amp;quot;代码的副作用&amp;quot;变成了&amp;quot;模型中可以直接讨论的概念&amp;quot;。测试可以单独写，管制员和开发可以用同一个词讨论它。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第-5-章-保持模型一致性大型项目的生存法则&#34;&gt;第 5 章 保持模型一致性：大型项目的生存法则&lt;/h2&gt;
&lt;p&gt;前面讲的都是&amp;quot;一个团队、一个模型&amp;quot;的情况。企业级项目有多个团队并行开发，理想中&amp;quot;全公司统一的大模型&amp;quot;实际上根本维持不住——每个团队都知道自己后院里有什么，但对其他地方一无所知，改一处就可能破坏别人的功能。&lt;/p&gt;
&lt;p&gt;Eric Evans 的结论很反直觉：&lt;strong&gt;不要试图维持一个注定四分五裂的大模型，而是有意识地把大模型拆成几个小模型，每个模型有清晰的边界&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;界定的上下文bounded-context&#34;&gt;界定的上下文（Bounded Context）&lt;/h3&gt;
&lt;p&gt;每个模型只在一个明确的边界内有效，这个边界叫界定的上下文。同一个词在不同上下文里可以有完全不同的含义：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;实例&lt;/strong&gt;：电商系统里&amp;quot;商品&amp;quot;这个词——&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在&lt;strong&gt;销售上下文&lt;/strong&gt;里，它有价格、促销、库存；&lt;/li&gt;
&lt;li&gt;在&lt;strong&gt;物流上下文&lt;/strong&gt;里，它只有重量、体积、包装方式；&lt;/li&gt;
&lt;li&gt;在&lt;strong&gt;售后上下文&lt;/strong&gt;里，它只有保修期和维修记录。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;试图用一个统一的 &lt;code&gt;Product&lt;/code&gt; 类满足三个团队，结果一定是个臃肿的&amp;quot;上帝类&amp;quot;。承认三个上下文各有各的&amp;quot;商品&amp;quot;，反而各自清晰。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    subgraph 销售上下文[&amp;#34;销售上下文&amp;#34;]
        P1[&amp;#34;商品：价格/促销/库存&amp;#34;]
    end
    subgraph 物流上下文[&amp;#34;物流上下文&amp;#34;]
        P2[&amp;#34;商品：重量/体积/包装&amp;#34;]
    end
    subgraph 售后上下文[&amp;#34;售后上下文&amp;#34;]
        P3[&amp;#34;商品：保修期/维修记录&amp;#34;]
    end
    P1 &amp;lt;--&amp;gt;|&amp;#34;上下文映射&amp;lt;br/&amp;gt;（翻译）&amp;#34;| P2
    P2 &amp;lt;--&amp;gt;|&amp;#34;上下文映射&amp;#34;| P3&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;上下文之间的六种关系模式&#34;&gt;上下文之间的六种关系模式&lt;/h3&gt;
&lt;p&gt;上下文不能各自为战，它们之间需要集成。书里给出了一组模式，按&amp;quot;两个团队的合作意愿和权力关系&amp;quot;来选择：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    Q{&amp;#34;两个上下文如何共处？&amp;#34;}
    Q --&amp;gt;|&amp;#34;愿意深度共享代码&amp;#34;| SK[&amp;#34;共享内核 Shared Kernel&amp;lt;br/&amp;gt;划出一块双方都依赖的小模型&amp;lt;br/&amp;gt;共同维护，改动需协商&amp;#34;]
    Q --&amp;gt;|&amp;#34;上下游关系明确&amp;#34;| CS[&amp;#34;客户-供应商 Customer-Supplier&amp;lt;br/&amp;gt;上游按下游需求规划开发&amp;#34;]
    Q --&amp;gt;|&amp;#34;下游无力影响上游&amp;#34;| CF[&amp;#34;顺从者 Conformist&amp;lt;br/&amp;gt;直接采用上游模型，放弃翻译&amp;#34;]
    Q --&amp;gt;|&amp;#34;要对接脏乱差的遗留系统&amp;#34;| ACL[&amp;#34;防崩溃层 Anticorruption Layer&amp;lt;br/&amp;gt;建一层翻译，把污染挡在门外&amp;#34;]
    Q --&amp;gt;|&amp;#34;集成成本 &amp;gt; 收益&amp;#34;| SW[&amp;#34;独立方法 Separate Ways&amp;lt;br/&amp;gt;干脆不集成，各做各的&amp;#34;]
    Q --&amp;gt;|&amp;#34;一个系统被多方集成&amp;#34;| OHS[&amp;#34;开放主机服务 Open Host Service&amp;lt;br/&amp;gt;定义标准协议，免去一对一翻译&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;几个重点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;上下文映射（Context Map）&lt;/strong&gt;：把所有上下文和它们之间的关系画在一张图上。这张图是大型项目里最重要的架构文档——它让每个团队都看清&amp;quot;我的边界在哪、我和谁有契约&amp;quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;共享内核&lt;/strong&gt;：两个团队共用一小部分模型（比如通用的客户标识）。共享意味着牵一发而动全身，所以内核要&lt;strong&gt;尽量小&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;防崩溃层（ACL）&lt;/strong&gt;：对接遗留系统时，遗留模型往往概念混乱。如果直接在自己的代码里用它的概念，好模型会被污染。ACL 是一层专门的翻译器：外面进来的是遗留概念，翻成本地模型再入内。&lt;strong&gt;遗留系统的&amp;quot;腐烂&amp;quot;被挡在层外&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;顺从者 vs 防崩溃层&lt;/strong&gt;：下游对上游没有话语权时，要么顺从（直接用上游模型），要么自建翻译层。前者省成本，后者保纯洁——取决于上游模型的质量。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;开放主机服务&lt;/strong&gt;：当你的系统要被十个团队集成时，给每个团队定制翻译层会把你拖垮。定义一套标准协议（比如一套公共 API），所有集成方遵循它。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;精炼distillation找到核心域&#34;&gt;精炼（Distillation）：找到核心域&lt;/h3&gt;
&lt;p&gt;大模型拆小之后还有一个问题：&lt;strong&gt;哪里最值得投入最优秀的人力？&lt;strong&gt;答案是&lt;/strong&gt;核心域（Core Domain）&lt;/strong&gt;——代表领域本质、构成业务竞争力的那一小块。&lt;/p&gt;
&lt;p&gt;书里的例子非常精彩：空中交通监控系统里，&amp;ldquo;航线&amp;quot;看起来无处不在，但它其实是个普通概念，很多领域都有航线。系统真正的&amp;quot;心脏&amp;quot;是&lt;strong&gt;基于雷达数据实时计算飞行轨道、发现航线交叉并告警&lt;/strong&gt;的那个模块——这才是这套系统区别于任何普通航线系统的本质，也是最该投入最强团队、精雕细琢的地方。其余部分（普通子域）可以外包、可以买现成组件、可以交给初级团队。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;战略层面的启示：&lt;strong&gt;不是所有代码都值得写好&lt;/strong&gt;。把最好的资源压在核心域上，其他地方&amp;quot;够用就行&amp;rdquo;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第-6-章-专访-eric-evans&#34;&gt;第 6 章 专访 Eric Evans&lt;/h2&gt;
&lt;p&gt;书末是 InfoQ 对 Eric Evans 的访谈，几个观点值得记住：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DDD 不是新方法，而是对二十年来优秀团队实践的系统化总结；&lt;/li&gt;
&lt;li&gt;DDD 与敏捷并不冲突，恰恰相反——没有好模型的敏捷团队，重构会越来越吃力；&lt;/li&gt;
&lt;li&gt;DDD 的难点不在技术，在于团队是否愿意持续地和领域专家坐在一起打磨模型和语言。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;全书总结一张-ddd-知识地图&#34;&gt;全书总结：一张 DDD 知识地图&lt;/h2&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;mindmap
  root((领域驱动设计&amp;lt;br/&amp;gt;DDD))
    核心思想
      软件是领域的映射
      模型是选择性抽象
      模型直接活在代码里
    通用语言
      一种语言贯穿讨论与代码
      说不清 = 模型有问题
    模型驱动设计
      分层架构
        界面层
        应用层
        领域层
        基础设施层
      构造块
        实体 = 身份
        值对象 = 属性 + 不可变
        服务 = 无状态操作
        聚合 = 一致性边界
        工厂 = 管创建
        资源库 = 管存取
    深层理解
      持续重构
      凸现关键概念
    保持模型一致性
      界定的上下文
      上下文映射
        共享内核
        客户-供应商
        顺从者
        防崩溃层
        独立方法
        开放主机服务
      精炼出核心域&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;DDD 最打动人的地方在于，它把软件开发从&amp;quot;写代码&amp;quot;重新定义为&amp;quot;&lt;strong&gt;持续理解业务、并把理解精确表达出来&lt;/strong&gt;&amp;ldquo;的过程。代码只是表达模型的介质，模型才是资产。&lt;/p&gt;
&lt;p&gt;如果你是零基础读者，建议按这个顺序消化：先吃透&amp;quot;通用语言&amp;rdquo;（它不需要任何技术背景，却能立刻改善团队协作），再掌握七个构造块（写代码时随时对照），最后在有大型项目或微服务需求时再深入&amp;quot;界定上下文&amp;quot;那一套——你会发现近年火热的微服务拆分方法论，思想源头正是这本书的 Bounded Context。&lt;/p&gt;
</description>
    </item>
    <item>
      <title>《修改代码的艺术》全书导读：遗留代码不是耻辱，是一张还没铺开的防护网</title>
      <link>https://ftmao.com/blog/bookstore/working-effectively-with-legacy-code-notes/</link>
      <pubDate>Sat, 08 Jul 2023 10:40:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/bookstore/working-effectively-with-legacy-code-notes/</guid>
      <description>&lt;h1 id=&#34;修改代码的艺术全书导读遗留代码不是耻辱是一张还没铺开的防护网&#34;&gt;《修改代码的艺术》全书导读：遗留代码不是耻辱，是一张还没铺开的防护网&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;参考书籍：Michael Feathers《修改代码的艺术》（&lt;em&gt;Working Effectively with Legacy Code&lt;/em&gt;，Prentice Hall 2004，中文版由人民邮电出版社/机械工业出版社先后引进）。
目标读者：没读过这本书、正在或即将维护&amp;quot;别人写的、不敢动的&amp;quot;代码库的程序员。
阅读目标：把全书最锋利的一个定义、一个模型（seam）、一个工作法（characterization test）、一组依赖解除技术讲清楚，最后编译成可执行的日常动作。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;一这本书是什么一个定义就是全书最锋利的一刀&#34;&gt;一、这本书是什么：一个定义就是全书最锋利的一刀&lt;/h2&gt;
&lt;p&gt;这本 2004 年的书，副标题式的宣言只有一个：&lt;strong&gt;遗留代码（legacy code）就是没有测试的代码&lt;/strong&gt;（书中大意）。&lt;/p&gt;
&lt;p&gt;先把成书背景交代一句。2004 年，极限编程和 TDD 刚兴起不久，&amp;ldquo;写新代码时先写测试&amp;quot;的理念已经开始传播，但行业里沉默的大多数面对的现实是：&lt;strong&gt;手头维护的系统写出来的时候还没有单元测试这回事&lt;/strong&gt;。新书都在教&amp;quot;怎么从零写好代码&amp;rdquo;，几乎没人教&amp;quot;怎么面对已经写坏的代码&amp;quot;——而后者才是绝大多数程序员的日常。Feathers 这本书填的就是这个空档，书名里的 Working Effectively 不是修辞：它要解决的问题是&amp;quot;明天就要改这段代码，怎么办&amp;quot;，不是&amp;quot;理想情况下代码应该长什么样&amp;quot;。&lt;/p&gt;
&lt;p&gt;注意这个定义的刁钻之处。我们平常说&amp;quot;遗留代码&amp;quot;，指的是&amp;quot;前人留下的、老旧的、看不懂的代码&amp;quot;——一个时间属性。Feathers 把它换成了一个工程属性：&lt;strong&gt;没有测试覆盖的代码，哪怕昨天刚写完，也已经是遗留代码；有测试覆盖的老代码，反而不遗留。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这一刀切下去，很多习以为常的现象立刻有了解释：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;为什么你昨天写的代码今天就不敢改？因为它从出生起就没有测试，它就是遗留代码。&lt;/li&gt;
&lt;li&gt;为什么有的十年老系统改起来反而顺手？因为核心路径上有测试，行为是可见的。&lt;/li&gt;
&lt;li&gt;为什么&amp;quot;重写&amp;quot;的冲动总是错的？因为重写完的新代码依然没测试的话，你只是生产了一份新的遗留代码。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;书里还有一层更狠的推论（大意）：&lt;strong&gt;没有测试的代码就是坏代码&lt;/strong&gt;——不管它写得多漂亮。因为判断代码好坏的第一标准不是优雅，而是&amp;quot;能不能安全地改&amp;quot;，而安全的前提是你能看见改完之后行为有没有变。看不见行为，优雅就是一张裱在危房里的画。&lt;/p&gt;
&lt;p&gt;这和 &lt;a href=&#34;https://ftmao.com/blog/blog/bookstore/code-complete-learning-notes/&#34;&gt;《代码大全》笔记&lt;/a&gt; 里&amp;quot;软件的首要技术使命是管理复杂度&amp;quot;是同一件事的两面：复杂度管不住，表现就是行为不可观测；行为不可观测，改动就退化成赌博。&lt;/p&gt;
&lt;p&gt;这个定义还直接解开了遗留系统维护的头号死结——书中称之为一个两难困境（大意）：&lt;strong&gt;要安全地改代码，得先有测试；要给代码加测试，往往得先改代码&lt;/strong&gt;（把依赖解开才能放进测试夹具）。鸡生蛋蛋生鸡，多数人卡在这里就放弃了，于是继续 edit and pray。Feathers 给出的破法是这本书全部技术部分的主线：&lt;strong&gt;允许自己做&amp;quot;没有测试看护的最小改动&amp;quot;，但只限于一种改动——为了造出 seam 而做的依赖解除&lt;/strong&gt;。这类改动必须保守到近乎机械（提取一个方法、加一个参数，不碰任何逻辑），换回来的回报是：代码进了测试夹具，之后的一切修改都有了看护。先用最小风险换 leverage，再用 leverage 换安全——这是全书方法论的现金流结构。&lt;/p&gt;
&lt;p&gt;顺带一提这本书的体例：它不是一本&amp;quot;读完放书架&amp;quot;的书，而是一本&lt;strong&gt;工具手册&lt;/strong&gt;。前半本讲心法和工作流（怎么面对改不动的代码），后半本是一本&amp;quot;症状—处方&amp;quot;对照目录（&amp;ldquo;我改不动这个方法，因为它的依赖在构造函数里 new 出来&amp;rdquo;→ 查 Parameterize Constructor）。读法和读 &lt;a href=&#34;https://ftmao.com/blog/blog/posts/design-patterns-intro/&#34;&gt;《设计模式》入门&lt;/a&gt; 一样：第一遍通读建立索引，真正干活时按症状查表。&lt;/p&gt;
&lt;h2 id=&#34;二改代码的两种模式edit-and-pray-vs-cover-and-modify&#34;&gt;二、改代码的两种模式：edit and pray vs cover and modify&lt;/h2&gt;
&lt;p&gt;Feathers 把程序员修改代码的真实状态分成两种模式（书中大意）：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    subgraph 祈祷模式[&amp;#34;edit and pray&amp;lt;br/&amp;gt;编辑后祈祷&amp;#34;]
        A1[&amp;#34;读懂一部分代码&amp;#34;] --&amp;gt; A2[&amp;#34;凭感觉改&amp;#34;]
        A2 --&amp;gt; A3[&amp;#34;编译通过&amp;#34;]
        A3 --&amp;gt; A4[&amp;#34;上线前祈祷&amp;lt;br/&amp;gt;希望没碰坏别处&amp;#34;]
    end
    subgraph 覆盖模式[&amp;#34;cover and modify&amp;lt;br/&amp;gt;覆盖后修改&amp;#34;]
        B1[&amp;#34;找到改动点附近的 seam&amp;#34;] --&amp;gt; B2[&amp;#34;先写测试&amp;lt;br/&amp;gt;罩住现有行为&amp;#34;]
        B2 --&amp;gt; B3[&amp;#34;在测试看护下改代码&amp;#34;]
        B3 --&amp;gt; B4[&amp;#34;测试红了就停&amp;lt;br/&amp;gt;绿了就继续&amp;#34;]
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;两种模式的差别不在技术，在&lt;strong&gt;恐惧的分配方式&lt;/strong&gt;。edit and pray 把恐惧攒到最后一刻一次性兑现——祈祷上线别出事；cover and modify 把恐惧拆成无数次测试运行，每次只消化一小口。&lt;/p&gt;
&lt;p&gt;所以这本书对测试的定位和教科书完全不同：&lt;strong&gt;测试在这里不是 QA 工具，不是质量部门的产出物，而是改动代码的安全网&lt;/strong&gt;。它的价值排序是——先让&amp;quot;改&amp;quot;这个动作变得不恐怖，质量提升是顺带的收益。一个从没写过单元测试的团队，第一本要补的不是&amp;quot;测试覆盖率指标&amp;quot;，而是&amp;quot;下一次改动之前先罩一层网&amp;quot;的习惯。&lt;/p&gt;
&lt;p&gt;书中有句话的意思值得抄在工位上（大意）：我们之所以要在修改前写测试，是因为&lt;strong&gt;在坏代码里，修改带来的伤害往往是延迟发作的&lt;/strong&gt;——你以为只动了 A，其实 B 的行为悄悄变了，三周后才在客户现场爆出来。测试网的作用就是把&amp;quot;三周后&amp;quot;压缩成&amp;quot;三秒后&amp;quot;。&lt;/p&gt;
&lt;p&gt;既然 cover and modify 明显更好，为什么大多数团队还是常年泡在 edit and pray 里？书里给出的解释很实际（大意）：&lt;strong&gt;两种模式的成本结算周期不一样&lt;/strong&gt;。edit and pray 的成本是赊账——改动当下很快，代价在未来某次线上事故里一次性扣掉，而且常常由别人买单；cover and modify 的成本是现付——写测试、解依赖的功夫当下就要掏，收益却摊在之后每一次改动里慢慢回来。人都是短视的，团队也是，所以破局的钥匙不是讲道理，而是&lt;strong&gt;把第一次&amp;quot;现付&amp;quot;的门槛降到足够低&lt;/strong&gt;——这正是后半本依赖解除技术目录存在的意义：它们把&amp;quot;给这段代码加第一个测试&amp;quot;从一天的工程压缩到一小时的手术。&lt;/p&gt;
&lt;p&gt;还有一点容易被误读：这本书讲的测试和 TDD 不是一回事，也不冲突。TDD 是&amp;quot;先写测试驱动出新代码的设计&amp;quot;，往前看；这本书的 characterization test 是&amp;quot;先写测试固化住旧代码的行为&amp;quot;，往后看。一个管新建，一个管存量。&lt;strong&gt;遗留系统里正确的姿势是两条腿走路：存量行为用 characterization test 罩住，新增逻辑用 TDD 长出来&lt;/strong&gt;——两头往中间会师，系统的可测试面积只会越来越大。&lt;/p&gt;
&lt;h2 id=&#34;三seam-模型全书的技术核心&#34;&gt;三、seam 模型：全书的技术核心&lt;/h2&gt;
&lt;p&gt;如果这本书只带走一个词，那就是 &lt;strong&gt;seam（接缝）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;定义（书中大意）：&lt;strong&gt;seam 是程序中这样一个位置——你不需要修改这段代码本身，就能改变它的行为。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;为什么这个概念是技术核心？因为改遗留代码的全部困难可以归结成一句话：&lt;strong&gt;代码里塞满了硬编码的依赖，你没法把它放进一个测试夹具（test harness）里单独观察。&lt;/strong&gt; 而 seam 就是你在依赖丛林里找到的下刀点——庖丁解牛，顺着纹理下刀，牛的筋骨自然分开；seam 就是代码的纹理。&lt;/p&gt;
&lt;p&gt;Feathers 列出三类 seam（大意）：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    S[&amp;#34;seam&amp;lt;br/&amp;gt;不改代码就能改变行为的点&amp;#34;] --&amp;gt; O[&amp;#34;对象接缝&amp;lt;br/&amp;gt;Object Seam&amp;#34;]
    S --&amp;gt; P[&amp;#34;预处理接缝&amp;lt;br/&amp;gt;Preprocessing Seam&amp;#34;]
    S --&amp;gt; L[&amp;#34;链接接缝&amp;lt;br/&amp;gt;Linking Seam&amp;#34;]
    O --&amp;gt; O1[&amp;#34;例子：把 new 出来的依赖&amp;lt;br/&amp;gt;换成通过参数/子类注入的替身&amp;#34;]
    O --&amp;gt; O2[&amp;#34;适用：Java / C# 等&amp;lt;br/&amp;gt;面向对象语言的主战场&amp;#34;]
    P --&amp;gt; P1[&amp;#34;例子：C/C++ 用宏和条件编译&amp;lt;br/&amp;gt;在测试构建里替换掉某个调用&amp;#34;]
    P --&amp;gt; P2[&amp;#34;适用：有预处理器的语言&amp;lt;br/&amp;gt;测试代码和生产代码两份编译&amp;#34;]
    L --&amp;gt; L1[&amp;#34;例子：换掉链接的库&amp;lt;br/&amp;gt;测试时链接桩版本而不是真库&amp;#34;]
    L --&amp;gt; L2[&amp;#34;适用：编译型语言&amp;lt;br/&amp;gt;把依赖推迟到链接期&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;三种 seam 背后的原理是同一个：&lt;strong&gt;程序的行为由很多个&amp;quot;点&amp;quot;共同决定——调用点、编译点、链接点、对象创建点。每多一个这样的点，就多一次不动手术也能换行为的机会。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;三类 seam 各自的使用场景值得多说一句：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;对象 seam&lt;/strong&gt; 是日常用得最多的。书中反复强调的一个手法（大意）：当一个类在构造函数里 &lt;code&gt;new&lt;/code&gt; 出一个绕不开的依赖（数据库连接、硬件接口、全局单例），你无法实例化它去测试——这时别急着重写，先看能不能造一个 seam：把依赖变成参数传进来，或者子类化后覆写那个获取依赖的方法。&lt;strong&gt;seam 的目标不是好设计，是 leverage（杠杆点）——一个能让你把这块代码抬进测试夹具的支点。&lt;/strong&gt; 设计可以之后再收拾，先拿到支点再说。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;预处理 seam&lt;/strong&gt; 是 C/C++ 程序员的专属武器（大意）：用宏和条件编译，让测试构建里的某个函数调用指向桩实现，生产构建里指向真实实现——同一份源码，两种行为，而源码本身一行没改。它也能在 Java 等语言里以&amp;quot;测试用构建脚本替换类路径&amp;quot;的形式变相存在。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;链接 seam&lt;/strong&gt; 更偏构建层（大意）：把依赖推迟到链接期决定——测试时链接一个桩版本的库，生产时链接真库。它在遗留 C 系统里常常是把整段代码撬进测试夹具的唯一入口。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;理解 seam 之后回头看 &lt;a href=&#34;https://ftmao.com/blog/blog/posts/design-patterns-intro/&#34;&gt;《设计模式》入门笔记&lt;/a&gt; 会有新感觉：工厂、策略、依赖注入这些模式，本质都是在代码里预先埋好的 seam。遗留代码的悲剧不是没有用模式，而是当年没人在乎缝留在哪里。&lt;/p&gt;
&lt;p&gt;书里还给 seam 配了一个搭档概念——&lt;strong&gt;enabling point（使能点，大意）&lt;/strong&gt;：seam 是&amp;quot;行为可以改变的位置&amp;quot;，enabling point 是&amp;quot;你在哪儿做出那个选择&amp;quot;。同一个 seam 可能有多个使能点：一个方法调用是 seam，而选择传哪个对象的使能点，可能在调用处、在构造函数里、在配置文件里。&lt;strong&gt;找 seam 的完整动作是两步：先定位&amp;quot;哪里能换行为&amp;quot;，再决定&amp;quot;在哪儿换最顺手&amp;quot;。&lt;/strong&gt; 很多改不动代码的挫败感，其实来自只做了第一步就动手——缝找到了，但选错了下针的位置。&lt;/p&gt;
&lt;h2 id=&#34;四characterization-test先固化行为再动代码&#34;&gt;四、characterization test：先固化行为，再动代码&lt;/h2&gt;
&lt;p&gt;有了 seam，接下来的问题是：测试写什么？这是全书反直觉的地方。&lt;/p&gt;
&lt;p&gt;教科书说测试是&amp;quot;验证代码的正确性&amp;quot;。Feathers 说不对，在遗留代码场景里你要写的是 &lt;strong&gt;characterization test（特征测试/刻画测试）&lt;/strong&gt;——&lt;strong&gt;它不验证代码&amp;quot;对不对&amp;quot;，它只固化代码&amp;quot;现在是什么样&amp;quot;&lt;/strong&gt;（大意）。&lt;/p&gt;
&lt;p&gt;区别很关键。遗留代码里充满了&amp;quot;看起来是 bug 但业务已经依赖了十年&amp;quot;的行为。你没法站在上帝视角判定哪个行为是&amp;quot;正确&amp;quot;的——你能做的、必须做的，是先把当前行为原样钉在测试里，让任何意外的漂移立刻报警。正确性可以之后讨论，&lt;strong&gt;先保证&amp;quot;变化&amp;quot;这件事是可观测的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这个定位和传统的回归测试也不一样。回归测试隐含着&amp;quot;我们曾经知道什么是对的，现在防止它退化&amp;quot;；而面对一段没人说得清意图的遗留代码，你连&amp;quot;对&amp;quot;的基线都没有（大意）。characterization test 的诚实之处在于它承认这一点：&lt;strong&gt;我不知道这段代码做得对不对，但我知道它现在做成什么样，我要保证的是&amp;quot;我改完之后它还是这样&amp;quot;——或者当它必须变的时候，变的那一处是我有意为之的。&lt;/strong&gt; 把&amp;quot;无意的变化&amp;quot;降为零，把&amp;quot;有意的变化&amp;quot;收窄到一处，这就是在遗留代码上干活的全部安全学。&lt;/p&gt;
&lt;p&gt;一个常见疑问：特征测试要写多少才够？书中的回答务实得让人安心（大意）：&lt;strong&gt;不需要覆盖整个系统，只需要覆盖你这次改动的影响面&lt;/strong&gt;。用影响草图圈出&amp;quot;改动会波及哪些行为&amp;quot;，把这个圈里的行为钉住就够了。圈外的代码这次没动，它的行为本来就变不了——真正的风险从来只在影响面之内。这也意味着特征测试是按需生长的：每改一次，网就密一格，从不做&amp;quot;覆盖率大跃进&amp;quot;。&lt;/p&gt;
&lt;p&gt;完整工作法四步：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;① 选 seam&amp;lt;br/&amp;gt;找到改动点附近&amp;lt;br/&amp;gt;能下刀的接缝&amp;#34;] --&amp;gt; B[&amp;#34;② 写观测测试&amp;lt;br/&amp;gt;断言传一个值&amp;lt;br/&amp;gt;跑出什么就钉住什么&amp;#34;]
    B --&amp;gt; C[&amp;#34;③ 改代码&amp;lt;br/&amp;gt;在测试看护下&amp;lt;br/&amp;gt;做最小修改&amp;#34;]
    C --&amp;gt; D[&amp;#34;④ 重构&amp;lt;br/&amp;gt;测试全绿的前提下&amp;lt;br/&amp;gt;顺手收拾结构&amp;#34;]
    D -.-&amp;gt;|&amp;#34;下次改动从这里继续&amp;#34;| A&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;第二步有个很实用的细节（书中大意）：写特征测试时，&lt;strong&gt;断言先写一个你知道多半会失败的值，让测试跑一次，把真实输出抄进断言&lt;/strong&gt;。这不是作弊——你的目的不是证明代码正确，而是用测试当探针，观测代码此刻的行为，然后把观测结果固化下来。测试从&amp;quot;裁判&amp;quot;暂时变成&amp;quot;记录仪&amp;quot;，等它记录够了，才恢复裁判的身份。&lt;/p&gt;
&lt;p&gt;第四步的顺序同样不能乱：&lt;strong&gt;先改出功能、绿了，再重构&lt;/strong&gt;。重构和功能修改混在一次提交里，是遗留代码灾难的头号来源——测试红了，你分不清是功能改错了还是重构挪坏了。一次只动一种东西。&lt;/p&gt;
&lt;p&gt;还有一个贯穿全书的概念，解释了为什么&amp;quot;解依赖&amp;quot;这件事值得花那么大代价（大意）：把代码放进测试夹具，是为了同时获得两种能力——&lt;strong&gt;sensing（感知）和 separation（分离）&lt;/strong&gt;。感知，是你能观测到这段代码的行为，改了之后立刻知道结果变没变；分离，是你能把这段代码从整个系统里拆出来单独跑，不必拖起数据库、硬件、整个应用。很多依赖解除技术表面上是&amp;quot;为了让测试能跑&amp;quot;，实际上是在买这两样东西——sensing 买的是&amp;quot;看得见的胆子&amp;quot;，separation 买的是&amp;quot;快&amp;quot;。一个要跑三分钟的测试你不会常跑，一个不常跑的测试等于没有；所以书中对测试速度的要求近乎苛刻（大意）：&lt;strong&gt;单元测试必须快，慢测试会杀死整个反馈回路&lt;/strong&gt;——哪怕为此要解掉更多依赖。&lt;/p&gt;
&lt;h2 id=&#34;五依赖解除技术族八把常用的小刀&#34;&gt;五、依赖解除技术族：八把常用的小刀&lt;/h2&gt;
&lt;p&gt;全书后半本是一个技术目录，解决同一个问题：&lt;strong&gt;类和方法被依赖焊死了，放不进测试夹具，怎么办&lt;/strong&gt;。挑最常用、回报最快的八个讲，每个都是&amp;quot;一句话场景 + 一句话做法&amp;quot;：&lt;/p&gt;
&lt;p&gt;用之前先建立一个总认知（大意）：&lt;strong&gt;这族技术全部是&amp;quot;保守手术&amp;quot;&lt;/strong&gt;——它们中的大多数可以在没有测试看护的情况下做，因为每一步都机械到几乎不可能改变行为（提取一段代码、加一个参数、改一个可见性）。这不是说它们没有风险，而是说风险被压缩到了&amp;quot;编译器能替你检查&amp;quot;的量级。这是 Feathers 解开第一节那个两难困境的实际手法：用近乎无风险的微改动撬动 seam，seam 撬动测试，测试撬动之后一切真正的改动。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Sprout Method（新生方法）&lt;/strong&gt;：要往一个没法测试的老方法里加逻辑。做法：新逻辑写成一个独立的新方法，老方法里只加一行调用——新方法可以独立测试，老方法只承担一行的风险。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sprout Class（新生类）&lt;/strong&gt;：要加的逻辑需要一堆新状态，塞不进老类。做法：新逻辑整体放进一个新类，老类只持有它的引用并在必要时调用——新类从第一天起就有测试。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wrap Method（包裹方法）&lt;/strong&gt;：要在老方法每次执行前后加点行为（记日志、发通知），但老方法不能动。做法：把老方法改名藏起来，用原方法名建一个新方法，先做新行为再调旧实现——调用方无感知。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wrap Class（包裹类）&lt;/strong&gt;：要给整个类的行为外加一层职责（装饰器思路）。做法：新类持有一个老类实例，对外暴露同样的接口，附加行为都在新类里——老类一行不改。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extract Interface（提取接口）&lt;/strong&gt;：依赖是个具体大类，实例化就要拖起半个系统。做法：把被调用的那几个方法提取成接口，测试里传一个假的实现——接缝造在类型层面。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extract and Override（提取并覆写）&lt;/strong&gt;：方法中间嵌着一个没法在测试里执行的调用（读硬件、发网络请求）。做法：把那一小段提取成受保护的方法，测试里子类化并覆写成空实现或桩——最小的手术换最大的可测性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Subclass and Override（子类化并覆写）&lt;/strong&gt;：整个类的构造函数或关键流程里有绕不开的硬依赖。做法：测试里建一个继承它的测试子类，覆写掉获取依赖的方法——不改动生产代码就撬开了行为。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Parameterize Constructor / Method（参数化构造函数/方法）&lt;/strong&gt;：依赖是在内部 &lt;code&gt;new&lt;/code&gt; 出来的，外部完全够不着。做法：把 &lt;code&gt;new&lt;/code&gt; 挪到构造参数或方法参数上，生产代码照常传真实对象，测试传假对象——这是把隐式依赖变显式的标准动作。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这八把刀的共同哲学值得记住：&lt;strong&gt;它们的直接目的都不是&amp;quot;让设计更好&amp;quot;，而是&amp;quot;让代码能被测试&amp;quot;&lt;/strong&gt;。Feathers 把这两件事严格分开（大意）：解依赖是为了找到 leverage，一旦测试夹具立住了，设计的债可以在测试看护下慢慢还。反过来，没有测试就奔着&amp;quot;好设计&amp;quot;去大刀阔斧，那是高级版的 edit and pray。&lt;/p&gt;
&lt;p&gt;这一族技术和 &lt;a href=&#34;https://ftmao.com/blog/blog/bookstore/clean-architecture-book-notes/&#34;&gt;《整洁架构》笔记&lt;/a&gt; 里的依赖规则正好互为镜像：整洁架构讲的是&amp;quot;从第一天起就让依赖指向正确方向&amp;quot;，这本书讲的是&amp;quot;方向已经错了十年，怎么一格一格扳回来&amp;quot;。前者是规划新城，后者是老城改造，工具箱不同，底层都是同一套 seam 思维。&lt;/p&gt;
&lt;p&gt;实战中怎么选刀？有一个朴素的排序原则：&lt;strong&gt;能 Sprout 就不 Wrap，能 Wrap 就不动老代码内部，万不得已才进老代码里做 Extract / Parameterize&lt;/strong&gt;。手术越小，没有测试看护的那一段就越短，风险就越可控。另外书里的技术目录比这八把刀长得多（大意）——还有把参数降为更基础类型的 Primitivize Parameter、把一组方法整体提到抽象基类的 Pull Up Feature 等等——症状不同处方不同，真到动手时值得按&amp;quot;症状&amp;quot;去翻原书后半本的对照目录，这正是它作为工具手册的用法。&lt;/p&gt;
&lt;h2 id=&#34;六巨型类怎么拆一只上千行的怪兽&#34;&gt;六、巨型类：怎么拆一只上千行的怪兽&lt;/h2&gt;
&lt;p&gt;遗留系统里几乎必有一只巨型类——上千行、几十个方法、什么都管。Feathers 给的拆法（大意）分三步：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一步：职责识别。&lt;/strong&gt; 先别拆，先读。用笨办法给方法分组：把方法按&amp;quot;它们共同操作哪些字段、共同服务哪类调用方&amp;quot;聚类；给私有方法起更长的描述性名字，看名字里反复出现的主题；把类&amp;quot;为谁工作&amp;quot;的所有角色列出来。一个类里往往能数出三五个本可以独立存在的职责——这就是 &lt;a href=&#34;https://ftmao.com/blog/blog/bookstore/code-complete-learning-notes/&#34;&gt;《代码大全》&lt;/a&gt; 说的单一职责，只是这里你是逆着历史考古。&lt;/p&gt;
&lt;p&gt;这一步有几个书中的实用技巧（大意）：一是&lt;strong&gt;看私有方法&lt;/strong&gt;——巨型类里的私有方法往往暗示着一个可以独立出去的职责，因为它们本来就是&amp;quot;类内部的内部&amp;quot;；二是&lt;strong&gt;数调用方&lt;/strong&gt;——如果类的不同方法群服务的是完全不同的调用方（报表模块用这三个方法，同步模块用那五个方法），调用方就是职责的天然分界线；三是&lt;strong&gt;问&amp;quot;这个类在为谁做决定&amp;quot;&lt;/strong&gt;——决策点聚集的地方就是职责所在。这些技巧的共同点是：不依赖你对业务的先验理解，从代码自身的结构里把职责挖出来。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二步：责任分组。&lt;/strong&gt; 把识别出的职责画成草图：哪个方法群服务于哪个职责，哪些方法脚踩两只船。脚踩两只船的方法是拆分的难点，也是拆分的钥匙——它们往往揭示了两个职责之间真正的边界。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第三步：挤压边界。&lt;/strong&gt; 不是一次性把大类切成五个小类（那是 edit and pray 的豪华版），而是&lt;strong&gt;每次只把一个职责里的一小部分挪出去&lt;/strong&gt;——用 Sprout Class 或提取类的方式，挪一小块，测试看护下跑绿，再挪一小块。原类的边界被一圈一圈挤压收缩，像退潮一样。挤压策略的精髓是：&lt;strong&gt;任何时刻系统都是可运行的，任何一步都是可回退的。&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    G[&amp;#34;巨型类&amp;lt;br/&amp;gt;千行·几十个方法&amp;lt;br/&amp;gt;什么职责都有&amp;#34;] --&amp;gt; S1[&amp;#34;识别职责&amp;lt;br/&amp;gt;方法分组&amp;lt;br/&amp;gt;数出 3~5 个隐藏角色&amp;#34;]
    S1 --&amp;gt; S2[&amp;#34;画责任草图&amp;lt;br/&amp;gt;找到脚踩两只船的方法&amp;lt;br/&amp;gt;那才是真正的边界&amp;#34;]
    S2 --&amp;gt; S3[&amp;#34;每次挪一小块&amp;lt;br/&amp;gt;Sprout Class / 提取类&amp;lt;br/&amp;gt;测试看护下跑绿&amp;#34;]
    S3 --&amp;gt; S4[&amp;#34;边界一圈圈收缩&amp;lt;br/&amp;gt;系统时刻可运行&amp;lt;br/&amp;gt;每步可回退&amp;#34;]
    S4 -.-&amp;gt;|&amp;#34;半年后怪兽小一半&amp;#34;| S3&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;配套的两个心法（大意）：一是拆巨型类时别追求完美边界，先追求&amp;quot;可测试的边界&amp;quot;——边界丑一点没关系，测试网立住了边界可以随时再调；二是拆分过程中行为必须逐字节保持，这是 characterization test 的主场。&lt;/p&gt;
&lt;p&gt;还有一个心理层面的提醒（书中大意）：&lt;strong&gt;拆巨型类的进度要用&amp;quot;季度&amp;quot;来度量，不要用&amp;quot;冲刺&amp;quot;来度量。&lt;/strong&gt; 一个上千行的类是十年业务沉积的结果，指望一个迭代拆完，结局往往是拆到一半需求来了，留下一个比原来更糟的半拆状态。正确的节奏是&amp;quot;每次顺手&amp;quot;：每次因业务需求碰这个类，就顺手把一个职责的一小块挪出去。半年后回头看，怪兽已经小了一半——而没有任何一个迭代被&amp;quot;重构专项&amp;quot;耽误过。&lt;/p&gt;
&lt;h2 id=&#34;七改不动的时候先理解再下刀&#34;&gt;七、改不动的时候：先理解，再下刀&lt;/h2&gt;
&lt;p&gt;书里专门处理了&amp;quot;代码我完全看不懂，但明天必须改&amp;quot;的绝境。在进工具箱之前先记住一个前提（大意）：&lt;strong&gt;&amp;ldquo;改不动&amp;quot;往往不只是代码问题，还是反馈回路问题。&lt;/strong&gt; 如果构建一次要半小时、启动一次要拖起三个中间件，那再好的 seam 和测试也救不了你——反馈慢到一定程度，人就会退回 edit and pray，因为&amp;quot;跑一遍验证&amp;quot;的成本高到不划算。所以书里把&amp;quot;让代码能快速进测试夹具跑起来&amp;quot;当作和&amp;quot;解依赖&amp;quot;同等重要的事：测试夹具的意义就在于绕开整个系统的启动成本，让你在秒级的时间里看到行为变化。工具箱如下（大意）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;画草图，不写文档。&lt;/strong&gt; 读不懂就画：effect sketch（影响草图）——从你要改的方法出发，画出它影响谁、谁影响它，一张图看清代码的&amp;quot;引力范围&amp;rdquo;；feature sketch（特征草图）——按职责给方法分组。草图的价值不在图本身，在于画的过程中你被迫建立了对代码的心智模型。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;在代码上做标注。&lt;/strong&gt; 临时把大方法里的段落抽出来起个名（不提交也行），或者用注释标出&amp;quot;这一段在干嘛&amp;quot;。理解会欺骗记忆，标注不会。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;scratch refactoring（草稿式重构）&lt;/strong&gt;：一条很反直觉的建议——纯粹为了理解代码，可以随手重构它（抽方法、改名、挪位置），&lt;strong&gt;改完扔掉，不提交&lt;/strong&gt;。重构是理解代码最快的探针，比干读效率高得多；扔掉是为了不背负&amp;quot;改坏了&amp;quot;的风险。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;删掉无用代码。&lt;/strong&gt; 死代码是最大的噪音源。确认没被调用的代码就删——版本控制里它永远活着，工作区里它只会碍眼。留下来的每一行都应该是有行为意义的行。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;新代码用 TDD。&lt;/strong&gt; 遗留代码里没有测试不是你不写测试的理由。往遗留系统里加新逻辑时，能用 Sprout 就用 Sprout，新方法新类从第一行起就 TDD——让新长出来的部分永远是&amp;quot;非遗留&amp;quot;的，系统的测试覆盖率会随每次改动自然上涨。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;小步快走。&lt;/strong&gt; 每一步改动之间跑一次测试，绿了就存盘。步子小到&amp;quot;这一步不可能错&amp;quot;的程度，速度反而最快——因为省去了所有&amp;quot;改了一下午发现早就坏了、回滚到不知道哪个版本&amp;quot;的灾难恢复时间。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这套&amp;quot;先理解&amp;quot;的策略和 &lt;a href=&#34;https://ftmao.com/blog/blog/bookstore/code-complete-learning-notes/&#34;&gt;《代码大全》学习笔记&lt;/a&gt; 里的调试心法同源：&lt;strong&gt;理解是修改的前置条件，而理解本身是可以工程化的&lt;/strong&gt;——草图、标注、scratch refactoring，都是把&amp;quot;读代码&amp;quot;从玄学变成动作的工具。&lt;/p&gt;
&lt;p&gt;这个工具箱里我最想单独强调 scratch refactoring，因为它违反了多数人的直觉：花两小时重构一份代码然后全部扔掉，听起来是纯浪费。但它的账要这么算——你需要的从来不是&amp;quot;重构的结果&amp;quot;，而是&amp;quot;重构过程中被迫看清的结构&amp;quot;。干读代码是被动接收，读三小时可能还是一团雾；动手重构是主动解剖，抽第三个方法的时候你就已经知道这段代码的骨架长什么样了。&lt;strong&gt;理解是这次任务的真实交付物之一&lt;/strong&gt;，代码只是理解的副产品——既然是副产品，扔掉并不可惜，版本库里那份没动过的才是资产。这和写文档之前先画草图、做方案之前先写 spike 是同一个道理：用小成本的可丢弃物，换大决策的确定性。&lt;/p&gt;
&lt;h2 id=&#34;八全书心法遗留代码是资产恐惧来自看不见&#34;&gt;八、全书心法：遗留代码是资产，恐惧来自看不见&lt;/h2&gt;
&lt;p&gt;读到最后会发现，这本技术书的底层是一篇心理学论文。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;遗留代码不是耻辱，是资产。&lt;/strong&gt; 它丑、它乱、它没人敢碰——但它在生产环境跑了十年，每一行丑陋的代码背后都可能固化着一次真实业务的教训、一个已经交付的需求、一个被客户依赖的行为。重写派看见的是债，Feathers 看见的是&lt;strong&gt;已经兑付过的价值&lt;/strong&gt;。正确的心态不是&amp;quot;这破代码该推倒&amp;quot;，而是&amp;quot;这份资产缺一张安全网，我来给它铺上&amp;quot;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;恐惧的来源是看不见行为。&lt;/strong&gt; 程序员面对遗留代码的恐惧，本质上和走夜路的恐惧同构——不是路难走，是看不见。characterization test 做的事情，就是把行为从&amp;quot;隐式的、存在于某次生产事故记忆里的&amp;quot;变成&amp;quot;显式的、写在断言里的&amp;quot;。行为一旦可见，恐惧就有了边界，改动就有了底气。这和我在方法论里反复说的&amp;quot;写下来&amp;quot;是同构的：&lt;strong&gt;不可见的东西统治你，可见的东西服务你。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;整本书的刀法，用庖丁解牛来对照最贴切：庖丁十九年解数千牛而刀刃若新发于硎，靠的不是力气，是&amp;quot;以无厚入有间&amp;quot;——找到牛体的天然纹理，顺着纹理下刀。&lt;strong&gt;seam 就是代码的纹理，characterization test 就是下刀前先摸清纹理的那只手。&lt;/strong&gt; 对遗留代码使蛮力的人，三年换一把刀；会找 seam 的人，刀越用越快。&lt;/p&gt;
&lt;p&gt;还有一层书里没有大声说、但全书处处都在暗示的心法：&lt;strong&gt;在遗留系统里，第一个写测试的人改变的不是代码，是团队的物理环境。&lt;/strong&gt; 一张测试网一旦存在，后来者改代码的默认姿势就变了——从&amp;quot;祈祷&amp;quot;变成&amp;quot;跑一遍看看&amp;quot;。安全网和恐惧一样会传染，方向相反而已。所以不要等&amp;quot;团队有了测试文化&amp;quot;才开始写测试，顺序是反的：你写下的第一个 characterization test，就是测试文化的第一个细胞。这也是&amp;quot;资产&amp;quot;视角的推论——测试网是极少数&lt;strong&gt;只涨不跌&lt;/strong&gt;的工程资产：它一旦固化住一个行为，就永远为之后所有的改动站岗，而且每新增一条断言，存量价值的保护范围就扩大一格。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    F[&amp;#34;面对遗留代码的恐惧&amp;lt;br/&amp;gt;看不见行为，不敢改&amp;#34;] --&amp;gt; T[&amp;#34;characterization test&amp;lt;br/&amp;gt;固化当前行为&amp;lt;br/&amp;gt;让不可见变可见&amp;#34;]
    T --&amp;gt; S[&amp;#34;seam&amp;lt;br/&amp;gt;找到下刀的纹理&amp;lt;br/&amp;gt;获得 leverage&amp;#34;]
    S --&amp;gt; D[&amp;#34;依赖解除技术族&amp;lt;br/&amp;gt;Sprout / Wrap / Extract&amp;lt;br/&amp;gt;Subclass / Parameterize&amp;#34;]
    D --&amp;gt; R[&amp;#34;在测试看护下&amp;lt;br/&amp;gt;改代码 + 挤压边界&amp;#34;]
    R --&amp;gt; C[&amp;#34;遗留代码逐步变成&amp;lt;br/&amp;gt;有测试覆盖的资产&amp;#34;]
    C -.-&amp;gt;|&amp;#34;恐惧被切成小份消化&amp;#34;| F&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h2 id=&#34;九这本书值不值得读&#34;&gt;九、这本书值不值得读&lt;/h2&gt;
&lt;p&gt;把全书压成一张图，就是&amp;quot;定义 → 模型 → 工作法 → 工具箱&amp;quot;四层：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;一个定义&lt;/strong&gt;：遗留代码 = 没有测试的代码。它把&amp;quot;遗留&amp;quot;从时间属性换成工程属性，顺带解释了为什么新写的代码也会一夜之间变遗留。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一个模型&lt;/strong&gt;：seam。不改代码就能改变行为的点，是一切依赖解除技术的解剖学基础。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一个工作法&lt;/strong&gt;：characterization test 四步——选 seam、写观测测试、改代码、重构。把&amp;quot;修改遗留代码&amp;quot;从赌博变成流程。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一个工具箱&lt;/strong&gt;：Sprout / Wrap / Extract / Subclass / Parameterize 一族依赖解除技术，按症状查表使用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;优点很明确：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;问题定义得准&lt;/strong&gt;。二十多年了，&amp;ldquo;遗留代码 = 没有测试的代码&amp;quot;依然是这个领域最锋利的一刀，没有之一。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;技术可执行&lt;/strong&gt;。后半本的症状—处方目录不是空谈原则，每一条都能当天上手——这在软件工程书里并不多见。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;心态建设到位&lt;/strong&gt;。它把&amp;quot;面对烂代码的羞耻和恐惧&amp;quot;也当成工程问题处理，给出的解法是&amp;quot;让行为可见&amp;rdquo;，而不是&amp;quot;你要勇敢&amp;quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;局限也要说清楚：成书于 2004 年，例子语言以 Java/C++/C# 为主，链接 seam、预处理 seam 这些章节对纯动态语言程序员参考价值有限；书中还没有依赖注入容器、mock 框架、容器化测试环境这些今天的标配，很多当年需要手工解的依赖，现在框架已经替你解了。但它的底层模型——seam、leverage、sensing 与 separation——不过时，因为&lt;strong&gt;依赖问题不过时&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;推荐给两类人：正在维护没测试的老系统的工程师（直接读全书，后半本放案头当手册）；写代码从不写测试、想建立测试习惯的工程师（重点读前半本，先理解&amp;quot;测试是安全网&amp;quot;这个定位再谈技术）。&lt;/p&gt;
&lt;p&gt;阅读顺序上给一个实操建议：第一遍只读前半本，读到 characterization test 四步为止就停手去干活——找一个你正在维护的模块，完整地走一遍&amp;quot;找 seam → 写特征测试 → 在测试看护下改&amp;quot;，走完一遍再回来翻后半本的技术目录。这本书的全部价值都在&amp;quot;用&amp;quot;里，纯读完不用，三个月后只剩&amp;quot;遗留代码 = 没测试的代码&amp;quot;这一句口号。&lt;/p&gt;
&lt;h2 id=&#34;落地编译成工程师的日常动作&#34;&gt;落地：编译成工程师的日常动作&lt;/h2&gt;
&lt;p&gt;把这本书的方法论编译成日常动作，不用立志，只要执行五条。这五条的共同点是&lt;strong&gt;都不需要任何立项、任何批准、任何&amp;quot;重构专项&amp;quot;&lt;/strong&gt;——它们全部可以藏在日常的需求开发里完成，这正是 Feathers 式改良主义的精髓：不跟管理层要时间还债，而是让每次还债都搭在真实需求的便车上。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;接遗留系统的第一件事不是改代码，是找 seam、写特征测试。&lt;/strong&gt; 接到任何维护型任务，先把&amp;quot;我要改的地方能不能放进测试夹具&amp;quot;作为开工的门槛。放不进去就先解依赖——磨刀的时间不是浪费，是这笔任务里 ROI 最高的部分。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第二次祈祷式修改就停下来补安全网。&lt;/strong&gt; 第一次 edit and pray 可以是形势所迫；同一个模块第二次还想凭感觉改，就是明确的信号——停下来，给这个模块写 characterization test，把网补上再走。恶习都是在&amp;quot;就这一次&amp;quot;里养成的。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;新逻辑一律 Sprout，从第一行起可测。&lt;/strong&gt; 往没测试的老代码里加功能时，默认走 Sprout Method / Sprout Class：老代码只加一行调用，新逻辑活在有测试的新方法新类里。让系统的&amp;quot;非遗留面积&amp;quot;随每次提交单调递增。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;评审时多问一句：这次改动的防护网在哪？&lt;/strong&gt; Code review 除了看 diff 本身，固定问一句&amp;quot;这个行为现在有测试钉住吗&amp;quot;。没有，就讨论要不要当场补——这句话问上一年，团队的测试文化就长出来了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;重构与改功能，永远不放在同一次提交。&lt;/strong&gt; 一次只动一种东西：要么纯重构（行为逐字节不变，测试不该红），要么纯改功能（在已绿的测试看护下改）。混在一起出问题时分不清归因，等于主动放弃了测试网的保护。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;一句话总结这本书：&lt;strong&gt;测试不是成本，是让你敢动代码的许可证；seam 不是技巧，是代码的解剖学。&lt;/strong&gt; 读完它再看任何一个老系统，你看到的不再是&amp;quot;一坨烂代码&amp;quot;，而是一张还没铺开的防护网，和网上一个个等着被找到的接缝。&lt;/p&gt;
&lt;p&gt;最后再补一句这本书在我心里的真实位置：它不是&amp;quot;教你写测试&amp;quot;的书，而是&lt;strong&gt;教你在不信任代码的环境里重建信任&lt;/strong&gt;的书——对代码的信任、对改动的信任、最终是对自己手艺的信任。这份信任的重建方法，二十年来没有变过：让行为可见，让变化可测，让每一步可回退。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;相关阅读：&lt;a href=&#34;https://ftmao.com/blog/blog/bookstore/code-complete-learning-notes/&#34;&gt;《代码大全》学习笔记&lt;/a&gt; · &lt;a href=&#34;https://ftmao.com/blog/blog/bookstore/clean-architecture-book-notes/&#34;&gt;《整洁架构》读书笔记&lt;/a&gt; · &lt;a href=&#34;https://ftmao.com/blog/blog/posts/design-patterns-intro/&#34;&gt;设计模式入门&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;这三篇和本文正好凑成一条线：设计模式教你&amp;quot;缝应该留在哪&amp;quot;，整洁架构教你&amp;quot;依赖应该指向哪&amp;quot;，代码大全教你&amp;quot;复杂度应该怎么管&amp;quot;，而本文回答的是那个最狼狈也最常见的问题——前面这些都没做到，系统已经在手里了，明天还要改，怎么办。&lt;/p&gt;
</description>
    </item>
    <item>
      <title>《奔跑吧，程序员》全书导读：从零开始打造产品、技术和团队</title>
      <link>https://ftmao.com/blog/bookstore/hello-startup-book-notes/</link>
      <pubDate>Mon, 24 Feb 2020 21:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/bookstore/hello-startup-book-notes/</guid>
      <description>&lt;h1 id=&#34;奔跑吧程序员全书导读从零开始打造产品技术和团队&#34;&gt;《奔跑吧，程序员》全书导读：从零开始打造产品、技术和团队&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;参考书籍：Yevgeniy Brikman《奔跑吧，程序员：从零开始打造产品、技术和团队》（&lt;em&gt;Hello, Startup&lt;/em&gt;，O&amp;rsquo;Reilly 2016，人民邮电出版社 2018 年中文版，吴晓嘉译）。
目标读者：没读过这本书、想快速了解它讲了什么、值不值得读的程序员和创业者。
阅读目标：用一篇文章把全书三大部分、12 章的核心内容讲清楚，配结构图和真实案例。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;这本书是什么&#34;&gt;这本书是什么&lt;/h2&gt;
&lt;p&gt;作者 Yevgeniy &amp;ldquo;Jim&amp;rdquo; Brikman 是 LinkedIn 的早期工程师，后来参与创立 Gruntwork。他把这本书定位为**&amp;ldquo;给创业者的 Hello, World 教程&amp;rdquo;**——就像学一门编程语言时先写一个 &lt;code&gt;printf(&amp;quot;Hello, World&amp;quot;)&lt;/code&gt; 让程序跑起来一样，这本书教你怎么让一家创业公司的三个核心部件跑起来：&lt;strong&gt;产品、技术、团队&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;作者在序言里讲了一个自己的糗事，很能说明这本书的价值：他在 TripAdvisor 的第一个项目是给酒店列表页加一个新选项，上线后页面加载变得奇慢无比——因为他的代码在排序时每次比较都要调两次数据库，对近 2000 家巴黎酒店排序，一次页面加载就触发近 &lt;strong&gt;4 万次数据库调用&lt;/strong&gt;，差点把数据库服务器累趴下。学校教了他算法和编程，却没教他&amp;quot;代码跑在真实世界里会发生什么&amp;quot;。这本书就是要补上这一课。&lt;/p&gt;
&lt;p&gt;全书的骨架非常清晰：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[&amp;#34;奔跑吧，程序员&amp;lt;br/&amp;gt;Hello, Startup&amp;#34;] --&amp;gt; P1[&amp;#34;第一部分：产品&amp;#34;]
    A --&amp;gt; P2[&amp;#34;第二部分：技术&amp;#34;]
    A --&amp;gt; P3[&amp;#34;第三部分：团队&amp;#34;]
    P1 --&amp;gt; C1[&amp;#34;第1章 为何创业&amp;#34;]
    P1 --&amp;gt; C2[&amp;#34;第2章 创业点子&amp;#34;]
    P1 --&amp;gt; C3[&amp;#34;第3章 产品设计&amp;#34;]
    P1 --&amp;gt; C4[&amp;#34;第4章 数据与营销&amp;#34;]
    P2 --&amp;gt; C5[&amp;#34;第5章 技术栈的选择&amp;#34;]
    P2 --&amp;gt; C6[&amp;#34;第6章 整洁的代码&amp;#34;]
    P2 --&amp;gt; C7[&amp;#34;第7章 可扩展性&amp;#34;]
    P2 --&amp;gt; C8[&amp;#34;第8章 软件交付&amp;#34;]
    P3 --&amp;gt; C9[&amp;#34;第9章 创业文化&amp;#34;]
    P3 --&amp;gt; C10[&amp;#34;第10章 求职之路&amp;#34;]
    P3 --&amp;gt; C11[&amp;#34;第11章 招兵买马&amp;#34;]
    P3 --&amp;gt; C12[&amp;#34;第12章 学习&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;下面按三大部分逐一展开。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第一部分产品&#34;&gt;第一部分：产品&lt;/h2&gt;
&lt;h3 id=&#34;第-1-章-为何创业&#34;&gt;第 1 章 为何创业&lt;/h3&gt;
&lt;p&gt;作者先给&amp;quot;科技创业公司&amp;quot;下了个定义，综合了三位大牛的观点（Eric Ries、Paul Graham、Steve Blank），提炼出四要素：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    S[&amp;#34;科技创业公司&amp;#34;] --&amp;gt; A[&amp;#34;产品是技术&amp;#34;]
    S --&amp;gt; B[&amp;#34;环境极度不确定&amp;#34;]
    S --&amp;gt; C[&amp;#34;目标是大幅增长&amp;#34;]
    S --&amp;gt; D[&amp;#34;运作模式是探索&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;注意：&lt;strong&gt;和公司成立时间、员工数、估值都无关&lt;/strong&gt;。一家 500 人的公司如果还在探索可复制的商业模式，它仍然是创业公司；一个三人团队如果做的是成熟外包生意，就不是。&lt;/p&gt;
&lt;p&gt;为什么要去创业公司？作者给的理由是&lt;strong&gt;更多的机会、更多的所有权、更多的乐趣&lt;/strong&gt;。但紧接着用很大篇幅泼冷水：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;创业并不光鲜&lt;/strong&gt;：媒体塑造的&amp;quot;顿悟后一夜成功&amp;quot;是幻觉。Marc Andreessen 说创业时&amp;quot;你只能体会到两种情绪：狂喜和恐惧，而且睡眠缺乏会放大这两种情绪&amp;quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;创业是牺牲&lt;/strong&gt;：Stylefeeder 创始人 Philip Jacob 26 岁时连续八九个月每周工作 90 小时，体检时数值&amp;quot;像 60 岁的人&amp;quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;你大概率不会变富&lt;/strong&gt;：作者分析了 60 多万家创业公司，回报遵循幂律分布——仅 34 家（Facebook、Twitter、Uber 等）就占了总市值的 76%，约 75% 的风投支持创业公司以失败告终。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一个很反直觉的例子：Facebook 联合创始人 Dustin Moskovitz 说过，&lt;strong&gt;Facebook 的第 100 号员工赚的钱，比硅谷 99% 的创业者还多&lt;/strong&gt;。所以对多数人而言，&amp;ldquo;加入一家高速成长的创业公司&amp;quot;比&amp;quot;自己创业&amp;quot;是更优的赌注——同样的十年，员工可以换 3-4 家公司分散下注，而创始人从创立到退出平均要熬 7-8 年（Facebook 8 年、LinkedIn 8 年、Zappos 10 年）。&lt;/p&gt;
&lt;p&gt;经验法则：&lt;strong&gt;只有你愿意把生命的下一个十年都花在这家公司上，才去创业。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;第-2-章-创业点子&#34;&gt;第 2 章 创业点子&lt;/h3&gt;
&lt;p&gt;本章先拆穿了一个迷思：&lt;strong&gt;点子不是顿悟&lt;/strong&gt;。阿基米德喊着&amp;quot;尤里卡&amp;quot;跳出浴缸的故事出自 200 年后别人的记述，很可能是虚构；牛顿没有被苹果砸中，万有引力他研究了 20 年；爱迪生也没有&amp;quot;发明&amp;quot;灯泡——灯泡已经存在 70 多年了，他只是试验了 6000 多种灯丝材料。&lt;/p&gt;
&lt;p&gt;点子的真实产生过程是&lt;strong&gt;重新合成&lt;/strong&gt;，一切创造都是三步：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;模仿&amp;#34;] --&amp;gt; B[&amp;#34;转换&amp;#34;] --&amp;gt; C[&amp;#34;合并&amp;#34;]
    C -.-&amp;gt;|&amp;#34;案例：Google 之前至少有 10 个搜索引擎，&amp;lt;br/&amp;gt;PageRank 思想源自 60 年代的文献计量学&amp;#34;| D[&amp;#34;新点子&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;所以&amp;quot;我有一个绝妙的点子但怕人偷走&amp;quot;的秘密模式完全没必要——真正吸引竞争者的不是点子本身，而是点子受到广泛关注。事实上多数行业根本没有先发优势：1993 年的研究显示，近半数的市场先行者最终失败了。&lt;/p&gt;
&lt;p&gt;点子从哪来？作者给了七个培养创造力的方法，其中最实用的是&lt;strong&gt;记录点子日记&lt;/strong&gt;（达·芬奇、爱迪生、维珍创始人布兰森都有这个习惯）——MIT 和 CMU 的研究表明，&lt;strong&gt;提高点子的数量比提高平均质量更能带来好点子&lt;/strong&gt;，&amp;ldquo;想要好点子的关键是有很多坏点子&amp;rdquo;。&lt;/p&gt;
&lt;p&gt;有了点子之后必须&lt;strong&gt;验证&lt;/strong&gt;。CB Insights 分析 100 多家失败创业公司，头号死因是&amp;quot;没有市场需求&amp;rdquo;。Steve Blank 的&amp;quot;客户开发&amp;quot;方法论是核心：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;验证问题&amp;#34;] --&amp;gt; B[&amp;#34;验证 MVP&amp;#34;] --&amp;gt; C[&amp;#34;验证产品&amp;#34;]
    B -.-&amp;gt;|&amp;#34;不通过就退回&amp;#34;| A
    C -.-&amp;gt;|&amp;#34;不通过就退回&amp;#34;| B&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;验证问题时要跟至少 10 个真实客户交谈——&amp;ldquo;如果你找不到前十个会购买的用户，这个点子就是在哄人&amp;rdquo;。&lt;/p&gt;
&lt;p&gt;书中的反面案例很生动：Jeff Novich 做了 Patient Communicator（帮医生管理病人的系统），打了数百个推销电话、发了上万封邮件、还上了福克斯新闻，最后只得到&lt;strong&gt;一位&lt;/strong&gt;付费医生（后来也停业了）。事后他才明白：&amp;ldquo;医生想要的是更多病人，而不是更有效率的诊所。&amp;quot;——他解决的是一个根本不痛的痛点。&lt;/p&gt;
&lt;p&gt;这一章还有个著名的对照：1999 年 Webvan 烧掉 8 亿多美元做生鲜配送后破产；十几年后 Instacart 做同样的事却成功了。差别就是红杉资本爱问的那句**&amp;ldquo;为什么是现在？&amp;rdquo;**——智能手机、GPS、移动支付、用户习惯，这些&amp;quot;万事俱备&amp;quot;到 2012 年后才凑齐。&lt;/p&gt;
&lt;h3 id=&#34;第-3-章-产品设计&#34;&gt;第 3 章 产品设计&lt;/h3&gt;
&lt;p&gt;程序员最容易忽视的一章，也是本书最出彩的部分之一。核心立场是 Jeff Raskin 那句话：&lt;strong&gt;&amp;ldquo;界面即产品&amp;rdquo;&lt;/strong&gt;——用户眼里的 Google 就是一个文本框加一个结果页。Joel Spolsky 称之为&amp;quot;冰山的秘密&amp;rdquo;：UI 只占全部工作的 10%，但界面糟糕，人们就会认为你的一切都糟糕。书里有 7 名程序员在 LinkedIn 会议室被一个问题跟踪软件难住几小时、最后改用便利贴的真实故事。&lt;/p&gt;
&lt;p&gt;设计的首要障碍是&lt;strong&gt;知识之祸&lt;/strong&gt;（Curse of Knowledge）。斯坦福 1990 年的经典实验：让&amp;quot;敲打者&amp;quot;敲出歌曲节奏给&amp;quot;倾听者&amp;quot;猜，敲打者预估对方能猜中 50%，实际 120 首歌只猜中 3 首（2.5%）。程序员设计软件时脑中&amp;quot;听着完整的歌曲&amp;quot;，用户听到的只是摩尔斯码。所以 Steve Krug 才说：&amp;ldquo;没人想读说明书。&amp;rdquo;&lt;/p&gt;
&lt;p&gt;设计原则里最值得记住的几条：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;目标不等于任务&lt;/strong&gt;：NASA 花数百万美元造太空笔而苏联宇航员用铅笔（传闻），说明盯任务会跑偏；&amp;ldquo;任务随技术变化，目标很稳定&amp;rdquo;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;别为&amp;quot;平均用户&amp;quot;设计&lt;/strong&gt;：Alan Cooper 说&amp;quot;想让 50% 的人 50% 满意是行不通的，要让 10% 的人 100% 心醉神迷&amp;quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;产品要有礼貌&lt;/strong&gt;：Gmail 检测到你写了&amp;quot;附件&amp;quot;却没贴文件会弹确认框；发送后留几秒可以&amp;quot;撤销&amp;quot;。这就是考虑周到的设计。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;简单来自削减&lt;/strong&gt;：雕塑家不是往石头上加东西，而是去掉多余的部分——&amp;ldquo;达到完美并不是没有东西可以添加，而是没有东西可以去除&amp;rdquo;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下半章讲 &lt;strong&gt;MVP（最小可行产品）&lt;/strong&gt;。Eric Ries 的定义是：以最小的付出最大化地验证性地了解客户。MVP 不是一次性行为，而是一个循环：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;找出风险最大的假设&amp;#34;] --&amp;gt; B[&amp;#34;表述为可测试的假设&amp;#34;]
    B --&amp;gt; C[&amp;#34;构建最小实验 MVP&amp;#34;]
    C --&amp;gt; D[&amp;#34;分析结果&amp;#34;]
    D -.-&amp;gt;|&amp;#34;循环&amp;#34;| A&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;书中归纳了五种 MVP 类型，每种都有一个经典案例：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;MVP 类型&lt;/th&gt;
					&lt;th&gt;代表案例&lt;/th&gt;
					&lt;th&gt;做法&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;展示页面&lt;/td&gt;
					&lt;td&gt;Buffer&lt;/td&gt;
					&lt;td&gt;只有一个介绍页+价格页，用价格按钮的点击量验证付费意愿&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;介绍视频&lt;/td&gt;
					&lt;td&gt;Dropbox&lt;/td&gt;
					&lt;td&gt;4 分钟演示视频放 Hacker News，24 小时内 7 万人注册等候&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;众筹&lt;/td&gt;
					&lt;td&gt;Pebble&lt;/td&gt;
					&lt;td&gt;只拿一个原型上 Kickstarter，从 6.8 万赞助人处筹到 1000 万美元&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;绿野仙踪&lt;/td&gt;
					&lt;td&gt;Zappos&lt;/td&gt;
					&lt;td&gt;前台接单，后台&amp;quot;人肉&amp;quot;去鞋店买鞋寄出，假装系统存在&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;拼凑式&lt;/td&gt;
					&lt;td&gt;Groupon&lt;/td&gt;
					&lt;td&gt;WordPress 博客 + FileMaker 生成优惠券 PDF + Apple Mail 群发&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;做 MVP 要&lt;strong&gt;关注差异性&lt;/strong&gt;：要的不是 10% 的改进，而是 10 倍的改进。Gmail 推出时别家邮箱只有 4MB 容量，它直接给 1GB，外加 Ajax 对话视图——至于富文本编辑器这些&amp;quot;标配&amp;quot;，反而都没有。初代 iPhone 同理：没有应用商店、没有 GPS、没有 3G，甚至没有复制粘贴，但多指触控的体验比别人好 10 倍。&lt;/p&gt;
&lt;p&gt;最后是 Y Combinator 最著名的建议：&lt;strong&gt;创业须从无法规模化的事情做起&lt;/strong&gt;。Airbnb 创始人挨家挨户招募房东并亲自帮拍公寓照片；Pinterest 创始人在咖啡店里挨个拉人试用；Wufoo 给每个客户手写感谢信。早期亲力亲为，是让公司这台小机器转动起来的唯一办法。&lt;/p&gt;
&lt;h3 id=&#34;第-4-章-数据与营销&#34;&gt;第 4 章 数据与营销&lt;/h3&gt;
&lt;p&gt;数据部分的核心工具是 Dave McClure 的&lt;strong&gt;海盗指标 AARRR&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;获取&amp;lt;br/&amp;gt;用户从哪来&amp;#34;] --&amp;gt; B[&amp;#34;激活&amp;lt;br/&amp;gt;首次体验好吗&amp;#34;]
    B --&amp;gt; C[&amp;#34;留存&amp;lt;br/&amp;gt;用户会回来吗&amp;#34;]
    C --&amp;gt; D[&amp;#34;推荐&amp;lt;br/&amp;gt;用户会传播吗&amp;#34;]
    D --&amp;gt; E[&amp;#34;收益&amp;lt;br/&amp;gt;能赚到钱吗&amp;#34;]
    F[&amp;#34;神奇数字&amp;lt;br/&amp;gt;先行指标&amp;#34;] -.-&amp;gt; C&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;每个指标都要按渠道分解，且&lt;strong&gt;客户生命周期价值（CLV）必须大于获客成本&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&amp;ldquo;神奇数字&amp;quot;是本章最有趣的概念：找到那个&amp;quot;用户一旦突破就会粘住&amp;quot;的先行指标。Facebook 的是注册 10 天内加 7 个朋友；Twitter 的是关注 30 人；Slack 的是团队累计交换 2000 条消息（达到的团队 93% 会留存）。找到它的方法：定成功指标 → 找相关行为 → 回归分析 → 用 A/B 测试验证因果（相关不等于因果）。&lt;/p&gt;
&lt;p&gt;数据驱动开发的正确姿势，是把功能当实验：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;开发 MVP&amp;#34;] --&amp;gt; B[&amp;#34;A/B 测试&amp;#34;] --&amp;gt; C{&amp;#34;分析结果&amp;#34;}
    C --&amp;gt;|&amp;#34;有效但不够好&amp;#34;| A
    C --&amp;gt;|&amp;#34;有效&amp;#34;| D[&amp;#34;发布&amp;#34;]
    C --&amp;gt;|&amp;#34;无效&amp;#34;| E[&amp;#34;放弃&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;书中的例子：LinkedIn 订阅页做过一次分桶测试，4 张不同的人物照片随机分 4 组，结果&amp;quot;灰白头发男士&amp;quot;那张显著胜出——&amp;ldquo;仅用一个测试代替直觉，就让我们赚到更多钱&amp;rdquo;。反例是 Etsy 式的做法：花大力气做完功能开庆祝派对，20 个月后默默删掉。&lt;/p&gt;
&lt;p&gt;营销部分的第一个认知冲击是：&lt;strong&gt;最好的产品未必胜出，是&amp;quot;客户认为最好的产品&amp;quot;胜出&lt;/strong&gt;。TiVo 发明了 DVR、用户忠诚度极高，2008 年市占率却只有 6%——94% 在有线电视公司手里。Peter Thiel 说得很直白：&amp;ldquo;没有销售方法的发明就是糟糕的生意。&amp;rdquo;&lt;/p&gt;
&lt;p&gt;四大营销渠道：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;口口相传&lt;/strong&gt;：做出值得谈论的产品（Seth Godin 的&amp;quot;紫色的牛&amp;rdquo;）；极致客服——Zappos 全员接客服电话，Stripe 工程师每两周轮班客服，KAYAK 干脆把客服电话线装在工程师座位中间；病毒式循环。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;病毒系数的数学&lt;/strong&gt;：K = 每用户邀请数 × 转化率。K=0.5 时用户量翻倍，K=0.67 三倍，K≥1 才是指数增长但现实中不可持续（消费互联网产品 K 值 0.15-0.25 已属不错）。经典案例：PayPal 推荐朋友双方各得 10 美元，用户日增 7%-10%；Hotmail 每封邮件底部自动附一句&amp;quot;来 Hotmail 获取你的免费邮箱&amp;quot;，6 个月增 100 万用户。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;市场推广&lt;/strong&gt;：广告、公关、邮件、SEO、社交媒体、集客式营销。亮点案例：LinkedIn 2011 年的&amp;quot;一年回顾&amp;quot;个性化邮件，点击率超过 100%（有人点了不止一次）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;销售&lt;/strong&gt;：按客单价分层——1000 美元以下自动化销售，1000-10 万美元内部销售，10 万美元以上外部销售。招人之前，创始人应该先亲自销售。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;品牌化&lt;/strong&gt;：品牌不是 logo，是客户与你所有交互的总和。红牛卖的不是饮料而是极限运动（赞助 Felix Baumgartner 从 38 公里高空跳伞，时速超 1280 公里）。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第二部分技术&#34;&gt;第二部分：技术&lt;/h2&gt;
&lt;h3 id=&#34;第-5-章-技术栈的选择&#34;&gt;第 5 章 技术栈的选择&lt;/h3&gt;
&lt;p&gt;本章的黄金法则只有一句：&lt;strong&gt;好的技术栈，其扩展速度要快于维护所需的投入。&lt;/strong&gt; 如果用户翻倍只需要加几台服务器，就是好状态；如果需要把团队人数翻倍才能应付，就该重新评估了。&lt;/p&gt;
&lt;p&gt;初始技术栈怎么选？答案朴素得让人意外：&lt;strong&gt;熟悉什么就用什么&lt;/strong&gt;。LinkedIn 用 Java、GitHub 用 Ruby、Twitter 用 Rails、Pinterest 用 Python——全因为创始团队最精通它。早期用户和代码都少，可扩展性根本不是挑战，快速迭代才是王道。&lt;/p&gt;
&lt;p&gt;技术栈是&lt;strong&gt;进化&lt;/strong&gt;出来的，不是设计出来的。LinkedIn 2011 年用增量式四阶段法迁移到 Play 框架（找痛点和早期采纳者 → 最小集成点 → 重写一个服务 → 循环），最终 80 多个服务完成迁移；Twitter 从 Ruby 迁到 JVM 后搜索延迟降到 1/3。作者严厉警告&lt;strong&gt;不要&amp;quot;停止一切、彻底重写&amp;quot;&lt;/strong&gt;——这是&amp;quot;所有软件公司都可能犯的单一最糟糕的战略性错误&amp;quot;，正确做法是渐进主义：把大改动拆成每步都有独立价值的小步骤。&lt;/p&gt;
&lt;p&gt;自研、买商业产品还是用开源？书中给出了现成的决策树：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[&amp;#34;需要一项新技术&amp;#34;] --&amp;gt; B{&amp;#34;它是你的商业&amp;lt;br/&amp;gt;竞争优势吗？&amp;#34;}
    B --&amp;gt;|&amp;#34;是&amp;#34;| C[&amp;#34;内部实现&amp;lt;br/&amp;gt;（或实现后开源）&amp;#34;]
    B --&amp;gt;|&amp;#34;否&amp;#34;| D{&amp;#34;有开源项目满足&amp;lt;br/&amp;gt;大部分需求？&amp;#34;}
    D --&amp;gt;|&amp;#34;是&amp;#34;| E[&amp;#34;使用开源&amp;#34;]
    D --&amp;gt;|&amp;#34;部分满足&amp;#34;| F[&amp;#34;使用开源&amp;lt;br/&amp;gt;并贡献补丁/插件&amp;#34;]
    D --&amp;gt;|&amp;#34;否&amp;#34;| G{&amp;#34;有商业产品&amp;lt;br/&amp;gt;可满足？&amp;#34;}
    G --&amp;gt;|&amp;#34;是&amp;#34;| H[&amp;#34;购买&amp;#34;]
    G --&amp;gt;|&amp;#34;否&amp;#34;| C&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;还有一份&amp;quot;&lt;strong&gt;永远不要自己实现&lt;/strong&gt;&amp;ldquo;的清单：安全（加密、密码存储）、Web 框架、数据库、消息队列、版本控制、构建部署工具、通用数据格式库、日期时间库、操作系统、编程语言。例外只有两种：为了学习，或者这就是你业务的核心竞争力。密码存储务必&amp;quot;盐 + bcrypt&amp;rdquo;，这类基础错误不值得任何公司再犯一次。&lt;/p&gt;
&lt;p&gt;数据库的结论同样朴素：&lt;strong&gt;关系型数据库是默认答案&lt;/strong&gt;。理由有三：通用数据存储技术需要十年才能成熟（NoSQL 在成书时平均年龄只有 6 岁）；数据是业务中寿命最长的部分，比任何功能甚至公司本身活得久；&amp;ldquo;好的数据存储是无聊的&amp;rdquo;。Facebook 用 4000 个 MySQL 节点每秒处理 6000 万次查询，证明关系型数据库可以走得很远；反例是 Pinterest 在月页面访问从零到上百亿的过程中放弃了 MongoDB 和 Cassandra——打败它的不是性能，而是复杂的故障模式。真要扩展，按复杂度逐级升级：优化查询 → 加缓存 → 主从复制 → 垂直分区 → 水平分片。&lt;/p&gt;
&lt;h3 id=&#34;第-6-章-整洁的代码&#34;&gt;第 6 章 整洁的代码&lt;/h3&gt;
&lt;p&gt;本章开篇点题：&lt;strong&gt;程序员的工作不是写代码，而是理解代码&lt;/strong&gt;。编程时间中读代码与写代码的时间比超过 10:1，写代码的时间里 80% 以上是在维护旧代码——一天 8 小时，能写 5 分钟新代码就不错了。所以整洁代码就是&amp;quot;为人的理解而优化&amp;quot;的代码。&lt;/p&gt;
&lt;p&gt;书中用一段 30 行的糟糕 Java 代码（把 CSV 转 JSON 的 BookParser）作为贯穿案例，逐节重构，覆盖：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;代码布局&lt;/strong&gt;：2014 年 Apple 的 &amp;ldquo;goto fail&amp;rdquo; SSL 漏洞是最好的反面教材——一行重复的 &lt;code&gt;goto fail;&lt;/code&gt; 因为缺大括号、缩进误导，跳过了关键的安全验证，让数以百万计的设备暴露于中间人攻击。布局不是美观问题，是安全问题。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;命名四原则&lt;/strong&gt;：回答所有重要问题、要精确、要全面、遵循约定。一个好例子是把变量命名为 &lt;code&gt;totalWeightInLbs&lt;/code&gt;——单位编码进名字里，&amp;ldquo;千克加磅&amp;quot;的 bug 自己就会现形。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;错误处理&lt;/strong&gt;：绝不静默吞异常，空的 catch 块会让调试变成猜谜。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DRY&lt;/strong&gt;：最大的重复是重新发明轮子，用久经考验的开源库替代手写解析。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;单一职责原则&lt;/strong&gt;：函数名长到可笑（&lt;code&gt;convertCsvToJsonAndDeleteCsvAndGenerateReport&lt;/code&gt;）就是职责过多的信号。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;函数式编程三件套&lt;/strong&gt;：不可变数据、高阶函数、纯函数。副作用无法消灭，但要隔离到应用入口。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;松耦合与高内聚&lt;/strong&gt;：依赖反转原则——高层与低层模块都依赖抽象；类名带 &amp;ldquo;Util&amp;rdquo; 是低内聚的典型特征。判断耦合紧不紧的快速方法：试着写单元测试，测试要复制多少实现细节，耦合就有多紧。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;关于注释和重构的两句话值得抄下来：&amp;ldquo;不要为糟糕的代码注释——重新写吧。&amp;quot;（Kernighan &amp;amp; Plauger）；重构不是单独的&amp;quot;清理阶段&amp;rdquo;（这样的阶段永远不会到来），而是编码的核心活动——先做纯粹重构，再做纯粹扩展。&lt;/p&gt;
&lt;h3 id=&#34;第-7-章-可扩展性&#34;&gt;第 7 章 可扩展性&lt;/h3&gt;
&lt;p&gt;可扩展性不是布尔属性，它分两个层面：&lt;strong&gt;编码实践的扩展&lt;/strong&gt;和&lt;strong&gt;性能的扩展&lt;/strong&gt;。创业像开手动挡：太早扩展（低速挂高挡）会熄火，太晚扩展（低挡猛踩油门）会过热。早期重点应放在编码实践上。&lt;/p&gt;
&lt;p&gt;四大编码实践：&lt;strong&gt;自动化测试、代码分离、代码评审、文档&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;自动化测试&lt;/strong&gt;：TDD 的真正价值是设计反馈——先写测试会提前暴露设计问题。但作者很务实：创业早期可以合理地跳过测试（早期产品 10 款里 9 款会被抛弃），但支付、安全、数据存储类代码从第一天起就必须大量测试。权衡三要素：bug 的成本、bug 的可能性、测试的成本。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;代码评审&lt;/strong&gt;：数据惊人——有评审的代码每 100 行 0.82 个错误，无评审 4.5 个（降低 80% 以上）；设计+代码评审能抓住 55%-60% 的 bug，而单元测试和集成测试只能抓 30%-35%。结对编程多花约 15% 的时间，换约 15% 更少的缺陷。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;文档&lt;/strong&gt;：RDD（Readme 驱动开发）——写代码之前先写 Readme，写作是更严密的思考形式，能在写几千行代码之前暴露计划的瑕疵。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;性能扩展的方法论是&amp;quot;测量 → 优化&amp;quot;的迭代，顺序是 Kent Beck 那句&amp;rdquo;&lt;strong&gt;让它工作，让它正确，让它快速&lt;/strong&gt;&amp;quot;——顺序不可颠倒。书中用链表 vs 数组的实验证明直觉和大 O 分析都会骗人（数组因 CPU 缓存一致性快了 50-100 倍）。记住一组&amp;quot;每名程序员都应知道的延迟数字&amp;quot;（内存引用约 100ns、磁盘寻道约 4ms、同数据中心往返约 0.5ms），很多优化决策用算术就能算出来——何况&lt;strong&gt;程序员的时间比 CPU 时间贵三个数量级&lt;/strong&gt;，买硬件常常比改代码便宜。&lt;/p&gt;
&lt;h3 id=&#34;第-8-章-软件交付&#34;&gt;第 8 章 软件交付&lt;/h3&gt;
&lt;p&gt;本章两个核心观点：&lt;strong&gt;&amp;ldquo;完成&amp;quot;意味着交付&lt;/strong&gt;（代码跑在你电脑上不算完成，交到用户手里才算）；以及 Martin Fowler 那句反直觉的方法论——&lt;strong&gt;&amp;ldquo;如果你觉得痛苦，就多去做&amp;rdquo;&lt;/strong&gt;。降低交付痛苦的办法不是回避，而是更频繁地面对：频繁合并（持续集成）、频繁部署（持续交付）。&lt;/p&gt;
&lt;p&gt;完整流水线长这样：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;版本控制&amp;#34;] --&amp;gt; B[&amp;#34;构建工具&amp;#34;]
    B --&amp;gt; C[&amp;#34;持续集成&amp;lt;br/&amp;gt;CI 服务器守门&amp;#34;]
    C --&amp;gt; D[&amp;#34;配置管理&amp;lt;br/&amp;gt;容器 / 编排&amp;#34;]
    D --&amp;gt; E[&amp;#34;自动化部署&amp;lt;br/&amp;gt;金丝雀 → 滚动全量&amp;#34;]
    E --&amp;gt; F[&amp;#34;监控&amp;lt;br/&amp;gt;日志 / 指标 / 报警&amp;#34;]
    F -.-&amp;gt;|&amp;#34;反馈&amp;#34;| A&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;本章最震撼的案例是 &lt;strong&gt;LinkedIn 的手工交付恐怖故事&lt;/strong&gt;：2011 年 LinkedIn 上市、会员每秒增加 2 个，但发布流程是&amp;quot;火车模型&amp;quot;两周一班；发布前三周，十几个团队合并功能分支如同噩梦；构建一次要 10 小时且测试间歇性失败；部署计划是手工维护的 wiki 页面，人肉在几千台服务器上滚动部署几百个服务，发布常拖到深夜甚至中途取消。到 2011 年底，公司几乎&lt;strong&gt;无法发布软件&lt;/strong&gt;了。于是启动&amp;quot;项目反转&amp;rdquo;（Project Inversion），全面转向基于主干的开发、自动化和持续交付，最终做到一小时内多次部署且 bug 更少。&lt;/p&gt;
&lt;p&gt;支撑这种规模的两个关键技术：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;基于主干的开发&lt;/strong&gt;：不用功能分支，大改动靠&amp;quot;抽象分支&amp;quot;（先定义接口、增量迁移、再切换实现）和&amp;quot;功能开关&amp;quot;（未完成代码默认关闭，可 1% → 10% → 100% 渐进放量）。LinkedIn 借此从 100 名工程师扩到 500+；Google 支撑 15000+ 开发者、每分钟 20-60 次提交。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;监控三支柱&lt;/strong&gt;：日志（格式化、含时间戳和唯一请求 ID、对 grep 友好）、指标（可用性 → 业务 → 应用 → 进程 → 代码 → 服务器六个层次）、报警（监控数据没人看就等于没有）。LinkedIn 高级副总裁 David Henke 的话：&amp;ldquo;如果你无法测量，你就无法修复。&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第三部分团队&#34;&gt;第三部分：团队&lt;/h2&gt;
&lt;h3 id=&#34;第-9-章-创业文化&#34;&gt;第 9 章 创业文化&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;文化胜过战略&lt;/strong&gt;：点子、规划、产品都会失败，伟大公司的核心是伟大文化——一种能不断产生新点子、新规划、新产品的环境。注意，文化不是桌上足球和圣诞派对，&amp;ldquo;文化是行动，而非口号&amp;rdquo;。&lt;/p&gt;
&lt;p&gt;核心理念 = 使命 + 核心价值。好的使命宣言要&lt;strong&gt;简洁、清晰、永恒、鼓舞人心&lt;/strong&gt;。Simon Sinek 的&amp;quot;黄金圆&amp;quot;是最好的解释工具：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    subgraph 普通公司[&amp;#34;普通公司：由外而内&amp;#34;]
        A1[&amp;#34;做什么&amp;#34;] --&amp;gt; A2[&amp;#34;怎么做&amp;#34;] --&amp;gt; A3[&amp;#34;为什么&amp;#34;]
    end
    subgraph 伟大公司[&amp;#34;伟大公司：由内而外&amp;#34;]
        B1[&amp;#34;为什么&amp;#34;] --&amp;gt; B2[&amp;#34;怎么做&amp;#34;] --&amp;gt; B3[&amp;#34;做什么&amp;#34;]
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&amp;ldquo;人们不想买你做的产品，想买的是你的信念。&amp;ldquo;马丁·路德·金讲的是&amp;quot;我有一个梦想&amp;rdquo;，而不是&amp;quot;我有一个计划&amp;rdquo;。&lt;/p&gt;
&lt;p&gt;核心价值不是拍脑袋选出来的，而是从团队身上&lt;strong&gt;发现&lt;/strong&gt;的——判断标准是&amp;quot;即便不再缺钱、即便一百年后、即便因此被华尔街惩罚也要坚持&amp;quot;。&amp;ldquo;宽松着装规定&amp;quot;过不了这个测试，&amp;ldquo;透明沟通&amp;quot;可能过得了（Buffer 公开所有员工薪水、股权甚至投资人协议，公开薪酬后那个月收到的简历翻倍）。&lt;/p&gt;
&lt;p&gt;关于激励，本章引用了经典的&amp;quot;蜡烛问题&amp;quot;实验：给金钱奖励的一组比无奖励组平均慢 3-5 分钟——&lt;strong&gt;外在奖励对创造性工作适得其反&lt;/strong&gt;。正确的环境要放大三个内在要素：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;自主权&amp;lt;br/&amp;gt;Google 20% 时间&amp;lt;br/&amp;gt;LinkedIn 黑客日&amp;#34;] --&amp;gt; M[&amp;#34;内在激励&amp;#34;]
    B[&amp;#34;专业能力&amp;lt;br/&amp;gt;刻意练习&amp;lt;br/&amp;gt;导师制&amp;#34;] --&amp;gt; M
    C[&amp;#34;目标&amp;lt;br/&amp;gt;使命感&amp;lt;br/&amp;gt;任期制&amp;#34;] --&amp;gt; M&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;几个让人印象深刻的公司实践：Valve 无管理、无头衔、桌子带轮子让员工&amp;quot;用脚投票&amp;quot;选项目，400 名员工人均产值超过 Google；Travis CI 规定每人每年&lt;strong&gt;至少&lt;/strong&gt;休 25 天带薪假，因为&amp;quot;休假比离职补偿金便宜&amp;rdquo;；Zappos 给新员工开 2000 美元&amp;quot;退出奖金&amp;rdquo;，最终不到 1% 的人拿钱走人。&lt;/p&gt;
&lt;h3 id=&#34;第-10-章-求职之路&#34;&gt;第 10 章 求职之路&lt;/h3&gt;
&lt;p&gt;这一章是写给打工人的，核心事实：&lt;strong&gt;80% 的工作不登广告&lt;/strong&gt;。招聘网站投递的录用率约 1/100，而人脉引荐的录用率约 1/7——所以找工作的时间应该 80% 花在人脉上。&lt;/p&gt;
&lt;p&gt;LinkedIn 三度人脉的数学很有说服力：50 个联系人 × 50 × 50 = 125,000 个三度关系。发展人脉的途径包括 Meetup、黑客马拉松（&amp;ldquo;几乎每次编程竞赛实质上都是一次招募活动&amp;rdquo;）、演讲、博客、开源。一个经典案例是 Meebo 联创 Elaine Wherry 的&amp;quot;招聘蜜罐&amp;quot;实验：她虚构了一份 JavaScript 工程师简历，挂在网上无人问津，一放上 LinkedIn 立刻爆炸——总计收到 172 个组织、382 位招聘人员的 530 封邮件，平均每 40 小时一封。&lt;/p&gt;
&lt;p&gt;评估工作机会的顺序是&lt;strong&gt;人 → 业务 → 经济回报&lt;/strong&gt;（&amp;ldquo;人们不是离开公司，而是离开经理&amp;rdquo;）。报酬部分最有价值的是&lt;strong&gt;股权科普&lt;/strong&gt;，典型的 4 年兑现结构：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;入职 0%&amp;#34;] --&amp;gt; B[&amp;#34;满 1 年 cliff&amp;lt;br/&amp;gt;一次性兑现 25%&amp;#34;]
    B --&amp;gt; C[&amp;#34;余下 75%&amp;lt;br/&amp;gt;分 36 个月均分&amp;#34;]
    C --&amp;gt; D[&amp;#34;行权&amp;lt;br/&amp;gt;按授予时 409A 价格&amp;#34;]
    D --&amp;gt; E[&amp;#34;IPO 或收购退出&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;作者用同一笔投入（年薪差 1.5 万 × 4 年 + 行权 1 万 = 7 万美元）算了三档退出回报：公司像 Flickr 那样 2200 万被收购 → 回报 8.55 万；像 Photobucket 那样 2.5 亿被收购 → 92 万；像 Instagram 那样 10 亿被收购 → 373 万。再结合 75% 的失败率——这就是&amp;quot;股权是一张彩票&amp;quot;的全部含义。Facebook 工程师 Daniel Kim 坦白：他差点因为 Oracle 薪水更高而拒绝 Facebook，因为&amp;quot;我不知道股权是什么&amp;quot;。&lt;/p&gt;
&lt;p&gt;谈判要点：想要 +10% 就开口要 +15%；不要透露当前薪水；没写进合同的承诺当作不存在；最强的筹码是竞争性 offer 和&amp;quot;离开&amp;quot;的底气——&lt;strong&gt;面试工作的最好时机，是你不需要工作的时候&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;第-11-章-招兵买马&#34;&gt;第 11 章 招兵买马&lt;/h3&gt;
&lt;p&gt;站在创业者视角的镜像章节。第一条建议出人意料：&lt;strong&gt;先别招&lt;/strong&gt;。更多人 = 资金消耗更快、决策更慢；关键问题不是&amp;quot;招了人能做什么&amp;quot;，而是&amp;quot;不招人就做不到什么&amp;quot;。&lt;/p&gt;
&lt;p&gt;关于合伙人：Paul Graham&amp;quot;杀死创业公司的 18 个错误&amp;quot;第一条就是单一创始人；两个创始人最好，超过三个易生内耗；最佳合伙人是共同战斗过的前同事；&lt;strong&gt;股权应平均分配并加兑现计划&lt;/strong&gt;（YC 最好的公司中没有一家股权分割显著不均）。&lt;/p&gt;
&lt;p&gt;关于&amp;quot;10 倍程序员&amp;quot;，作者给出了全书最清醒的解读：研究确实发现程序员效率差一个数量级，但 10 倍程序员不是打字快 10 倍，而是&lt;strong&gt;10 倍能力的决策者&lt;/strong&gt;——做更好的技术决策，避免几个月的无效苦干。而且表现因任务和环境而异（文化契合决定他是 10x 还是 0.1x），人可以变好。所以正确的策略是&lt;strong&gt;成为培养 10 倍程序员的公司&lt;/strong&gt;，而不是妄想只招摇滚明星。&lt;/p&gt;
&lt;p&gt;面试环节的数据同样颠覆：Google 研究 1 万次面试后发现面试官评分与后续工作表现&amp;quot;一点关系都没有&amp;quot;，智力题（&amp;ldquo;一架飞机能装多少高尔夫球&amp;rdquo;）完全是浪费时间。更真实的替代方式是一个谱系：BYOL 自带笔记本实操 → 带回家的挑战 → 真实业务问题 → 结对编程 → 试用一天。招聘流程上，每位面试官只准说&amp;quot;可以&amp;quot;或&amp;quot;不可以&amp;quot;，不允许&amp;quot;也许&amp;quot;，一票否决——&lt;strong&gt;录用错的人比错过对的人更糟&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;两个暖心的案例收尾：Airbnb 创始人花近 6 个月、面试数百人才招到最初的员工；Zappos 所有新员工（含会计、律师）都要上 4 周客服培训并实际接 2 周电话——文化不是贴在墙上的，是筛选出来的。&lt;/p&gt;
&lt;h3 id=&#34;第-12-章-学习&#34;&gt;第 12 章 学习&lt;/h3&gt;
&lt;p&gt;最后一章讲的是程序员职业的底层操作系统。软件行业知识每年都在淘汰，Erlang 之父 Joe Armstrong 的建议是：&lt;strong&gt;花 20% 的时间学习——这是一种复利&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;一万小时定律人人听过，但作者强调光有时间不够，还要&lt;strong&gt;刻意练习&lt;/strong&gt;：有反馈机制 + 每次挑战超出当前能力的任务。刻意练习&amp;quot;是乐趣的对立面&amp;quot;。&lt;/p&gt;
&lt;p&gt;&amp;ldquo;没时间学习&amp;quot;是伪命题——说这话其实是在说&amp;quot;我把别的事排在学习前面&amp;rdquo;。作者亲证：每晚 11 点学 20-40 分钟，2006 年学 Ajax → 2007 年进 TripAdvisor；2008 年研究雪碧图 → 2009 年进 LinkedIn；2010 年研究 Web 框架 → 2011 年负责重建 LinkedIn 的 Web 框架基础设施。&lt;/p&gt;
&lt;p&gt;学习的三个技巧构成一个闭环：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;研究&amp;lt;br/&amp;gt;读书 / 论文 / 课程&amp;lt;br/&amp;gt;读开源代码&amp;#34;] --&amp;gt; B[&amp;#34;实现&amp;lt;br/&amp;gt;业余项目 / 开源贡献&amp;lt;br/&amp;gt;刻意练习&amp;#34;]
    B --&amp;gt; C[&amp;#34;分享&amp;lt;br/&amp;gt;写作 / 演讲 / 开源&amp;#34;]
    C -.-&amp;gt;|&amp;#34;反馈与新机会&amp;lt;br/&amp;gt;驱动新的研究&amp;#34;| A&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&amp;ldquo;分享&amp;quot;是回报最被低估的一环，作者列了五重回报：&lt;strong&gt;专业性&lt;/strong&gt;（教是最好的学）、&lt;strong&gt;质量&lt;/strong&gt;（家里最干净的时候是客人来之前——开源前你会补测试写文档）、&lt;strong&gt;劳动力&lt;/strong&gt;（用户报 bug 是免费 QA，提交补丁是免费开发）、&lt;strong&gt;市场推广&lt;/strong&gt;、&lt;strong&gt;所有权&lt;/strong&gt;（博客和开源成果一辈子跟着你）。作者和同事做过&amp;quot;30 天博客挑战&amp;rdquo;，每人每天写一篇，一个月后博客流量涨了 10 倍，文章两次登上 Hacker News 首页，甚至推动 Play/Scala 社区根据他的反馈改了 API。&lt;/p&gt;
&lt;p&gt;全书的落脚点很朴素：学习的目标不是成为世界上最出色的程序员，而是&lt;strong&gt;成为比昨天更出色的程序员&lt;/strong&gt;。&amp;ldquo;好程序员与差程序员的差别和天赋关系不大，更多与坚持有关。&amp;rdquo;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;总结这本书值不值得读&#34;&gt;总结：这本书值不值得读&lt;/h2&gt;
&lt;p&gt;如果把全书压缩成一张图，创业公司的打造过程就是三个循环的叠加：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    subgraph 产品循环
        P1[&amp;#34;验证问题&amp;#34;] --&amp;gt; P2[&amp;#34;构建 MVP&amp;#34;] --&amp;gt; P3[&amp;#34;数据验证&amp;#34;] --&amp;gt; P1
    end
    subgraph 技术循环
        T1[&amp;#34;整洁代码&amp;#34;] --&amp;gt; T2[&amp;#34;测试与评审&amp;#34;] --&amp;gt; T3[&amp;#34;持续交付&amp;#34;] --&amp;gt; T1
    end
    subgraph 团队循环
        M1[&amp;#34;定义文化&amp;#34;] --&amp;gt; M2[&amp;#34;招兵买马&amp;#34;] --&amp;gt; M3[&amp;#34;持续学习&amp;#34;] --&amp;gt; M1
    end
    产品循环 --- 技术循环
    技术循环 --- 团队循环&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;这本书的优点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;全面&lt;/strong&gt;：产品、技术、团队三大块一次讲透，是名副其实的&amp;quot;创业者的 Hello, World&amp;quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;案例扎实&lt;/strong&gt;：几乎每一个论点都有真实公司、真实数据支撑，LinkedIn 的一手经验尤其珍贵。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;立场清醒&lt;/strong&gt;：不贩卖创业鸡汤，幂律分布、75% 失败率、&amp;ldquo;你大概率不会变富&amp;quot;都摆在明面上。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;局限也很明显：成书于 2016 年，技术栈部分的具体工具（如 Chef/Puppet 时代的配置管理）已有年代感，但其方法论——渐进主义、持续交付、决策树——至今没过时。&lt;/p&gt;
&lt;p&gt;推荐给三类人：想创业但不知从何开始的程序员（读全书）；在大公司想理解创业公司运作方式的工程师（读第 1、4、8 章）；要带团队或正在带团队的技术管理者（读第 6-9、11 章）。&lt;/p&gt;
</description>
    </item>
    <item>
      <title>设计模式全景图：六大原则、23 种 GoF 模式与 J2EE 模式</title>
      <link>https://ftmao.com/blog/posts/design-patterns-intro/</link>
      <pubDate>Tue, 28 May 2019 14:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/design-patterns-intro/</guid>
      <description>&lt;h1 id=&#34;设计模式全景图六大原则23-种-gof-模式与-j2ee-模式&#34;&gt;设计模式全景图：六大原则、23 种 GoF 模式与 J2EE 模式&lt;/h1&gt;
&lt;p&gt;设计模式是面向对象设计中反复出现问题的&lt;strong&gt;可复用解决方案&lt;/strong&gt;。掌握它们能够帮助我们写出更易扩展、更易维护、更清晰的代码。本文从六大设计原则出发，完整梳理 GoF 23 种设计模式以及 8 种 J2EE 模式，并配合类图、意图说明和 JDK/Spring 中的典型应用，帮助建立系统性的知识地图。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一设计模式的六大原则&#34;&gt;一、设计模式的六大原则&lt;/h2&gt;
&lt;h3 id=&#34;1-开闭原则open-close-principle&#34;&gt;1. 开闭原则（Open Close Principle）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;对扩展开放，对修改关闭。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;当程序需要新增功能时，应通过增加新代码实现，而不是修改已有代码。核心手段是抽象化：使用接口和抽象类。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[业务调用方] --&amp;gt; B[抽象接口]
    B --&amp;gt; C[旧实现]
    B --&amp;gt; D[新实现]
    style D fill:#e1f5e1&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;2-里氏代换原则liskov-substitution-principle&#34;&gt;2. 里氏代换原则（Liskov Substitution Principle）&lt;/h3&gt;
&lt;p&gt;任何基类可以出现的地方，子类一定可以出现。子类替换基类后，程序功能不应受影响。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Animal { +eat() }
    class Dog { +eat() }
    class Cat { +eat() }
    Animal &amp;lt;|-- Dog
    Animal &amp;lt;|-- Cat&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;3-依赖倒转原则dependence-inversion-principle&#34;&gt;3. 依赖倒转原则（Dependence Inversion Principle）&lt;/h3&gt;
&lt;p&gt;高层模块和低层模块都应依赖抽象；要针对接口编程，而不是针对实现编程。&lt;/p&gt;
&lt;h3 id=&#34;4-接口隔离原则interface-segregation-principle&#34;&gt;4. 接口隔离原则（Interface Segregation Principle）&lt;/h3&gt;
&lt;p&gt;使用多个职责单一的接口，优于使用一个庞大臃肿的接口，降低类之间的耦合。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    A[大接口] --&amp;gt;|拆分为| B[接口A]
    A --&amp;gt;|拆分为| C[接口B]
    A --&amp;gt;|拆分为| D[接口C]
    B --&amp;gt; E[实现类1]
    C --&amp;gt; F[实现类2]
    D --&amp;gt; G[实现类3]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;5-迪米特法则--最少知道原则demeter-principle&#34;&gt;5. 迪米特法则 / 最少知道原则（Demeter Principle）&lt;/h3&gt;
&lt;p&gt;一个实体应尽量少地与其他实体发生直接交互，降低模块耦合，提高独立性。&lt;/p&gt;
&lt;h3 id=&#34;6-合成复用原则composite-reuse-principle&#34;&gt;6. 合成复用原则（Composite Reuse Principle）&lt;/h3&gt;
&lt;p&gt;优先使用组合/聚合来复用功能，而不是继承。组合更灵活，耦合更低。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Car { -Engine engine; +run() }
    class Engine { +start() }
    Car *-- Engine : 组合&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;二设计模式分类概览&#34;&gt;二、设计模式分类概览&lt;/h2&gt;
&lt;p&gt;设计模式通常分为三大类：创建型、结构型、行为型。J2EE 模式则专注于企业应用和表示层。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;mindmap
  root((设计模式))
    创建型模式 5种
      工厂模式
      抽象工厂模式
      单例模式
      建造者模式
      原型模式
    结构型模式 8种
      适配器模式
      桥接模式
      过滤器/标准模式
      组合模式
      装饰器模式
      外观模式
      享元模式
      代理模式
    行为型模式 13种 
      责任链模式
      命令模式
      解释器模式
      迭代器模式
      中介者模式
      备忘录模式
      观察者模式
      状态模式
      空对象模式
      策略模式
      模板模式
      访问者模式
    J2EE模式 8种 
      MVC模式
      业务代表模式
      组合实体模式
      数据访问对象模式
      前端控制器模式
      拦截过滤器模式
      服务定位器模式
      传输对象模式&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;三创建型模式creational-patterns&#34;&gt;三、创建型模式（Creational Patterns）&lt;/h2&gt;
&lt;p&gt;创建型模式关注对象的创建过程，隐藏创建逻辑，使程序在判断需要创建哪些对象时更灵活。&lt;/p&gt;
&lt;h3 id=&#34;1-工厂模式factory-pattern&#34;&gt;1. 工厂模式（Factory Pattern）&lt;/h3&gt;
&lt;p&gt;定义一个创建对象的接口，让子类决定实例化哪一个类。工厂方法使类的实例化延迟到子类。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Product { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +use() }
    class ConcreteProductA { +use() }
    class ConcreteProductB { +use() }
    class Creator { +factoryMethod() Product }
    class ConcreteCreatorA { +factoryMethod() Product }
    class ConcreteCreatorB { +factoryMethod() Product }
    Product &amp;lt;|.. ConcreteProductA
    Product &amp;lt;|.. ConcreteProductB
    Creator &amp;lt;|-- ConcreteCreatorA
    Creator &amp;lt;|-- ConcreteCreatorB
    Creator ..&amp;gt; Product
    ConcreteCreatorA ..&amp;gt; ConcreteProductA
    ConcreteCreatorB ..&amp;gt; ConcreteProductB&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;JDK 应用：&lt;/strong&gt; &lt;code&gt;java.text.DateFormat.getDateInstance()&lt;/code&gt;、&lt;code&gt;KeyGenerator.getInstance()&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&#34;2-抽象工厂模式abstract-factory-pattern&#34;&gt;2. 抽象工厂模式（Abstract Factory Pattern）&lt;/h3&gt;
&lt;p&gt;提供一个创建一系列相关或相互依赖对象的接口，而无需指定它们具体的类。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class AbstractFactory { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +createProductA() +createProductB() }
    class ConcreteFactory1 { +createProductA() +createProductB() }
    class ConcreteFactory2 { +createProductA() +createProductB() }
    class AbstractProductA
    class AbstractProductB
    AbstractFactory &amp;lt;|.. ConcreteFactory1
    AbstractFactory &amp;lt;|.. ConcreteFactory2
    ConcreteFactory1 ..&amp;gt; AbstractProductA
    ConcreteFactory1 ..&amp;gt; AbstractProductB
    ConcreteFactory2 ..&amp;gt; AbstractProductA
    ConcreteFactory2 ..&amp;gt; AbstractProductB&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;3-单例模式singleton-pattern&#34;&gt;3. 单例模式（Singleton Pattern）&lt;/h3&gt;
&lt;p&gt;确保一个类只有一个实例，并提供一个全局访问点。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Singleton {
        -static Singleton instance
        -Singleton()
        +static getInstance() Singleton
    }&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;JDK 应用：&lt;/strong&gt; &lt;code&gt;Runtime.getRuntime()&lt;/code&gt;。&lt;strong&gt;Spring 应用：&lt;/strong&gt; 默认 Bean 作用域为单例。&lt;/p&gt;
&lt;h3 id=&#34;4-建造者模式builder-pattern&#34;&gt;4. 建造者模式（Builder Pattern）&lt;/h3&gt;
&lt;p&gt;将一个复杂对象的构建与它的表示分离，使得同样的构建过程可以创建不同的表示。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Product { +partA +partB +partC }
    class Builder { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +buildPartA() +buildPartB() +getResult() }
    class ConcreteBuilder { +buildPartA() +buildPartB() +getResult() }
    class Director { +construct(builder) }
    Builder &amp;lt;|.. ConcreteBuilder
    ConcreteBuilder ..&amp;gt; Product
    Director ..&amp;gt; Builder&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;5-原型模式prototype-pattern&#34;&gt;5. 原型模式（Prototype Pattern）&lt;/h3&gt;
&lt;p&gt;通过复制现有对象来创建新对象，而不是新建实例。适用于创建成本较高的对象。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Prototype { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +clone() }
    class ConcretePrototype { +clone() }
    Prototype &amp;lt;|.. ConcretePrototype&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;JDK 应用：&lt;/strong&gt; &lt;code&gt;Object.clone()&lt;/code&gt;、&lt;code&gt;Cloneable&lt;/code&gt; 接口。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四结构型模式structural-patterns&#34;&gt;四、结构型模式（Structural Patterns）&lt;/h2&gt;
&lt;p&gt;结构型模式关注对象之间的组合和关系，解决如何构建灵活且可复用的类和对象结构。&lt;/p&gt;
&lt;h3 id=&#34;1-适配器模式adapter-pattern&#34;&gt;1. 适配器模式（Adapter Pattern）&lt;/h3&gt;
&lt;p&gt;将一个类的接口转换成客户希望的另外一个接口，使得原本由于接口不兼容而不能一起工作的类可以协同工作。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Target { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +request() }
    class Adapter { +request() }
    class Adaptee { +specificRequest() }
    Target &amp;lt;|.. Adapter
    Adapter --&amp;gt; Adaptee&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;JDK 应用：&lt;/strong&gt; &lt;code&gt;Arrays.asList()&lt;/code&gt;、&lt;code&gt;InputStreamReader&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&#34;2-桥接模式bridge-pattern&#34;&gt;2. 桥接模式（Bridge Pattern）&lt;/h3&gt;
&lt;p&gt;将抽象部分与它的实现部分分离，使它们都可以独立地变化。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Abstraction { -Implementor impl +operation() }
    class RefinedAbstraction { +operation() }
    class Implementor { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +operationImpl() }
    class ConcreteImplementorA { +operationImpl() }
    class ConcreteImplementorB { +operationImpl() }
    Abstraction &amp;lt;|-- RefinedAbstraction
    Abstraction o-- Implementor
    Implementor &amp;lt;|.. ConcreteImplementorA
    Implementor &amp;lt;|.. ConcreteImplementorB&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;3-过滤器模式--标准模式filter--criteria-pattern&#34;&gt;3. 过滤器模式 / 标准模式（Filter / Criteria Pattern）&lt;/h3&gt;
&lt;p&gt;使用不同的标准来过滤一组对象，并通过逻辑运算以解耦的方式把它们连接起来。常用于集合筛选。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Criteria { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +meetCriteria(list) }
    class CriteriaMale { +meetCriteria(list) }
    class CriteriaFemale { +meetCriteria(list) }
    class AndCriteria { +meetCriteria(list) }
    class OrCriteria { +meetCriteria(list) }
    Criteria &amp;lt;|.. CriteriaMale
    Criteria &amp;lt;|.. CriteriaFemale
    Criteria &amp;lt;|.. AndCriteria
    Criteria &amp;lt;|.. OrCriteria
    AndCriteria --&amp;gt; Criteria
    OrCriteria --&amp;gt; Criteria&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;4-组合模式composite-pattern&#34;&gt;4. 组合模式（Composite Pattern）&lt;/h3&gt;
&lt;p&gt;将对象组合成树形结构以表示&amp;quot;部分-整体&amp;quot;的层次结构，使得用户对单个对象和组合对象的使用具有一致性。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Component { +operation() }
    class Leaf { +operation() }
    class Composite { -children +add() +remove() +operation() }
    Component &amp;lt;|-- Leaf
    Component &amp;lt;|-- Composite
    Composite o-- Component&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;JDK 应用：&lt;/strong&gt; &lt;code&gt;java.awt.Container&lt;/code&gt; 与 &lt;code&gt;Component&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&#34;5-装饰器模式decorator-pattern&#34;&gt;5. 装饰器模式（Decorator Pattern）&lt;/h3&gt;
&lt;p&gt;动态地给一个对象添加一些额外的职责，比生成子类更灵活。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Component { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +operation() }
    class ConcreteComponent { +operation() }
    class Decorator { -Component component +operation() }
    class ConcreteDecoratorA { +operation() }
    class ConcreteDecoratorB { +operation() }
    Component &amp;lt;|.. ConcreteComponent
    Component &amp;lt;|.. Decorator
    Decorator &amp;lt;|-- ConcreteDecoratorA
    Decorator &amp;lt;|-- ConcreteDecoratorB
    Decorator o-- Component&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;JDK 应用：&lt;/strong&gt; Java IO 流 &lt;code&gt;BufferedInputStream&lt;/code&gt;、&lt;code&gt;DataInputStream&lt;/code&gt;、&lt;code&gt;ZipOutputStream&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&#34;6-外观模式facade-pattern&#34;&gt;6. 外观模式（Facade Pattern）&lt;/h3&gt;
&lt;p&gt;为子系统中的一组接口提供一个一致的入口，定义了一个高层接口，使子系统更容易使用。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Facade { +simplifiedOperation() }
    class SubSystemA { +operationA() }
    class SubSystemB { +operationB() }
    class SubSystemC { +operationC() }
    Facade --&amp;gt; SubSystemA
    Facade --&amp;gt; SubSystemB
    Facade --&amp;gt; SubSystemC&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;7-享元模式flyweight-pattern&#34;&gt;7. 享元模式（Flyweight Pattern）&lt;/h3&gt;
&lt;p&gt;运用共享技术有效地支持大量细粒度对象，减少内存占用。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class FlyweightFactory { -pool +getFlyweight(key) }
    class Flyweight { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +operation() }
    class ConcreteFlyweight { +operation() }
    FlyweightFactory o-- Flyweight
    Flyweight &amp;lt;|.. ConcreteFlyweight&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;JDK 应用：&lt;/strong&gt; &lt;code&gt;Integer.valueOf()&lt;/code&gt; 缓存（-128-127）、&lt;code&gt;String&lt;/code&gt; 常量池。&lt;/p&gt;
&lt;h3 id=&#34;8-代理模式proxy-pattern&#34;&gt;8. 代理模式（Proxy Pattern）&lt;/h3&gt;
&lt;p&gt;为其他对象提供一种代理以控制对这个对象的访问。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Subject { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +request() }
    class RealSubject { +request() }
    class Proxy { -RealSubject real +request() }
    Subject &amp;lt;|.. RealSubject
    Subject &amp;lt;|.. Proxy
    Proxy --&amp;gt; RealSubject&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;JDK 应用：&lt;/strong&gt; JDK 动态代理 &lt;code&gt;Proxy.newProxyInstance()&lt;/code&gt;。&lt;strong&gt;Spring 应用：&lt;/strong&gt; AOP 使用 JDK 动态代理和 CGLIB。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;五行为型模式behavioral-patterns&#34;&gt;五、行为型模式（Behavioral Patterns）&lt;/h2&gt;
&lt;p&gt;行为型模式关注对象之间的通信和交互，解决对象之间的责任分配和算法封装。&lt;/p&gt;
&lt;h3 id=&#34;1-责任链模式chain-of-responsibility-pattern&#34;&gt;1. 责任链模式（Chain of Responsibility Pattern）&lt;/h3&gt;
&lt;p&gt;使多个对象都有机会处理请求，从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链，并沿着这条链传递请求。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Handler { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +setNext(handler) +handle(request) }
    class ConcreteHandlerA { +handle(request) }
    class ConcreteHandlerB { +handle(request) }
    Handler &amp;lt;|.. ConcreteHandlerA
    Handler &amp;lt;|.. ConcreteHandlerB
    Handler --&amp;gt; Handler : next&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;2-命令模式command-pattern&#34;&gt;2. 命令模式（Command Pattern）&lt;/h3&gt;
&lt;p&gt;将请求封装为对象，从而可以用不同的请求、队列或日志来参数化其他对象，同时支持可撤销操作。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Command { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +execute() }
    class ConcreteCommand { -receiver +execute() }
    class Invoker { -command +setCommand() +invoke() }
    class Receiver { +action() }
    Command &amp;lt;|.. ConcreteCommand
    Invoker --&amp;gt; Command
    ConcreteCommand --&amp;gt; Receiver&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;3-解释器模式interpreter-pattern&#34;&gt;3. 解释器模式（Interpreter Pattern）&lt;/h3&gt;
&lt;p&gt;给定一个语言，定义它的文法表示，并定义一个解释器，这个解释器使用该表示来解释语言中的句子。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class AbstractExpression { +interpret(context) }
    class TerminalExpression { +interpret(context) }
    class NonterminalExpression { +interpret(context) }
    AbstractExpression &amp;lt;|-- TerminalExpression
    AbstractExpression &amp;lt;|-- NonterminalExpression
    NonterminalExpression o-- AbstractExpression&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;JDK 应用：&lt;/strong&gt; Java 正则表达式 &lt;code&gt;Pattern&lt;/code&gt; 与 &lt;code&gt;Matcher&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&#34;4-迭代器模式iterator-pattern&#34;&gt;4. 迭代器模式（Iterator Pattern）&lt;/h3&gt;
&lt;p&gt;提供一种方法顺序访问一个聚合对象中的各个元素，而又无需暴露该对象的内部表示。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Iterator { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +hasNext() +next() }
    class ConcreteIterator { +hasNext() +next() }
    class Aggregate { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +createIterator() }
    class ConcreteAggregate { +createIterator() }
    Iterator &amp;lt;|.. ConcreteIterator
    Aggregate &amp;lt;|.. ConcreteAggregate
    ConcreteAggregate ..&amp;gt; ConcreteIterator&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;JDK 应用：&lt;/strong&gt; &lt;code&gt;java.util.Iterator&lt;/code&gt;、&lt;code&gt;java.util.Enumeration&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&#34;5-中介者模式mediator-pattern&#34;&gt;5. 中介者模式（Mediator Pattern）&lt;/h3&gt;
&lt;p&gt;用一个中介对象来封装一系列对象交互，使对象不需要显式地相互引用，从而使其耦合松散。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Mediator { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +send(message, colleague) }
    class ConcreteMediator { +send(message, colleague) }
    class Colleague { -Mediator mediator }
    class ConcreteColleagueA
    class ConcreteColleagueB
    Mediator &amp;lt;|.. ConcreteMediator
    Colleague &amp;lt;|-- ConcreteColleagueA
    Colleague &amp;lt;|-- ConcreteColleagueB
    Colleague --&amp;gt; Mediator
    ConcreteMediator --&amp;gt; ConcreteColleagueA
    ConcreteMediator --&amp;gt; ConcreteColleagueB&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;6-备忘录模式memento-pattern&#34;&gt;6. 备忘录模式（Memento Pattern）&lt;/h3&gt;
&lt;p&gt;在不破坏封装性的前提下，捕获一个对象的内部状态，并在该对象之外保存这个状态，以便以后恢复。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Originator { +createMemento() +restore(memento) }
    class Memento { -state }
    class Caretaker { -mementos }
    Originator ..&amp;gt; Memento
    Caretaker o-- Memento&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;7-观察者模式observer-pattern&#34;&gt;7. 观察者模式（Observer Pattern）&lt;/h3&gt;
&lt;p&gt;定义对象间的一对多依赖关系，当一个对象状态发生改变时，所有依赖于它的对象都得到通知并自动更新。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Subject { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +registerObserver() +removeObserver() +notifyObservers() }
    class ConcreteSubject { -state +notifyObservers() }
    class Observer { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +update() }
    class ConcreteObserverA { +update() }
    class ConcreteObserverB { +update() }
    Subject &amp;lt;|.. ConcreteSubject
    Observer &amp;lt;|.. ConcreteObserverA
    Observer &amp;lt;|.. ConcreteObserverB
    ConcreteSubject --&amp;gt; Observer&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;JDK 应用：&lt;/strong&gt; &lt;code&gt;java.util.Observer&lt;/code&gt;（已弃用）和 &lt;code&gt;Observable&lt;/code&gt;。&lt;strong&gt;Spring 应用：&lt;/strong&gt; Spring 事件驱动模型 &lt;code&gt;ApplicationEvent&lt;/code&gt;、&lt;code&gt;ApplicationListener&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&#34;8-状态模式state-pattern&#34;&gt;8. 状态模式（State Pattern）&lt;/h3&gt;
&lt;p&gt;允许对象在其内部状态改变时改变它的行为，对象看起来好像修改了它的类。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Context { -State state +request() }
    class State { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +handle() }
    class ConcreteStateA { +handle() }
    class ConcreteStateB { +handle() }
    Context o-- State
    State &amp;lt;|.. ConcreteStateA
    State &amp;lt;|.. ConcreteStateB&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;9-空对象模式null-object-pattern&#34;&gt;9. 空对象模式（Null Object Pattern）&lt;/h3&gt;
&lt;p&gt;用一个空对象取代 &lt;code&gt;null&lt;/code&gt; 检查，提供默认的&amp;quot;什么也不做&amp;quot;的行为，避免空指针异常。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Animal { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +makeSound() }
    class Dog { +makeSound() }
    class NullAnimal { +makeSound() }
    Animal &amp;lt;|.. Dog
    Animal &amp;lt;|.. NullAnimal&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;10-策略模式strategy-pattern&#34;&gt;10. 策略模式（Strategy Pattern）&lt;/h3&gt;
&lt;p&gt;定义一系列算法，把它们一个个封装起来，并且使它们可以互相替换。策略模式让算法的变化独立于使用算法的客户。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Strategy { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +execute() }
    class ConcreteStrategyA { +execute() }
    class ConcreteStrategyB { +execute() }
    class Context { -Strategy strategy +setStrategy() +executeStrategy() }
    Strategy &amp;lt;|.. ConcreteStrategyA
    Strategy &amp;lt;|.. ConcreteStrategyB
    Context o-- Strategy&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;JDK 应用：&lt;/strong&gt; &lt;code&gt;Comparator&lt;/code&gt; 接口与 &lt;code&gt;Collections.sort()&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&#34;11-模板模式template-pattern&#34;&gt;11. 模板模式（Template Pattern）&lt;/h3&gt;
&lt;p&gt;定义一个操作中的算法骨架，而将一些步骤延迟到子类中。模板方法使得子类可以不改变算法结构即可重定义该算法的某些特定步骤。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class AbstractClass { +templateMethod() +primitiveOperation() }
    class ConcreteClass { +primitiveOperation() }
    AbstractClass &amp;lt;|-- ConcreteClass&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;JDK 应用：&lt;/strong&gt; &lt;code&gt;Arrays.sort()&lt;/code&gt; 配合 &lt;code&gt;Comparable&lt;/code&gt;。&lt;strong&gt;Spring 应用：&lt;/strong&gt; &lt;code&gt;RestTemplate&lt;/code&gt;、&lt;code&gt;JmsTemplate&lt;/code&gt;、&lt;code&gt;JpaTemplate&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&#34;12-访问者模式visitor-pattern&#34;&gt;12. 访问者模式（Visitor Pattern）&lt;/h3&gt;
&lt;p&gt;表示一个作用于某对象结构中的各元素的操作，它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Element { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +accept(visitor) }
    class ConcreteElementA { +accept(visitor) }
    class ConcreteElementB { +accept(visitor) }
    class Visitor { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +visitA() +visitB() }
    class ConcreteVisitor { +visitA() +visitB() }
    Element &amp;lt;|.. ConcreteElementA
    Element &amp;lt;|.. ConcreteElementB
    Visitor &amp;lt;|.. ConcreteVisitor
    ConcreteElementA ..&amp;gt; Visitor
    ConcreteElementB ..&amp;gt; Visitor&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;六j2ee-模式&#34;&gt;六、J2EE 模式&lt;/h2&gt;
&lt;p&gt;J2EE 模式专注于企业应用，尤其是表示层和业务层的架构设计。这些模式由 Sun Java Center 鉴定。&lt;/p&gt;
&lt;h3 id=&#34;1-mvc-模式mvc-pattern&#34;&gt;1. MVC 模式（MVC Pattern）&lt;/h3&gt;
&lt;p&gt;将应用程序分为 Model（模型）、View（视图）、Controller（控制器）三层，实现关注点分离。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    V[View] --&amp;gt; C[Controller]
    C --&amp;gt; M[Model]
    M --&amp;gt; V&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;Spring 应用：&lt;/strong&gt; Spring MVC 是典型实现。&lt;/p&gt;
&lt;h3 id=&#34;2-业务代表模式business-delegate-pattern&#34;&gt;2. 业务代表模式（Business Delegate Pattern）&lt;/h3&gt;
&lt;p&gt;为表示层提供对业务服务的访问抽象，减少表示层与业务层之间的耦合。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[Client/Controller] --&amp;gt; B[Business Delegate]
    B --&amp;gt; C[Business Service]
    C --&amp;gt; D[DAO/数据层]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;3-组合实体模式composite-entity-pattern&#34;&gt;3. 组合实体模式（Composite Entity Pattern）&lt;/h3&gt;
&lt;p&gt;将一组相关的持久化对象组合成一个粗粒度实体，减少网络调用次数，提高性能。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class CompositeEntity { -DependentObjectA a -DependentObjectB b +getData() }
    class DependentObjectA
    class DependentObjectB
    CompositeEntity *-- DependentObjectA
    CompositeEntity *-- DependentObjectB&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;4-数据访问对象模式data-access-object-pattern&#34;&gt;4. 数据访问对象模式（Data Access Object Pattern）&lt;/h3&gt;
&lt;p&gt;将底层数据访问逻辑与高层业务逻辑分离，提供一个统一的接口访问数据库。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[Business Object] --&amp;gt; B[DAO Interface]
    B --&amp;gt; C[DAO Implementation]
    C --&amp;gt; D[Data Source/Database]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;Spring 应用：&lt;/strong&gt; Spring Data JPA、MyBatis Mapper 都是 DAO 模式的具体实现。&lt;/p&gt;
&lt;h3 id=&#34;5-前端控制器模式front-controller-pattern&#34;&gt;5. 前端控制器模式（Front Controller Pattern）&lt;/h3&gt;
&lt;p&gt;提供一个集中的入口点来处理所有请求，通常负责认证、日志、路由等横切关注点。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[所有请求] --&amp;gt; B[Front Controller]
    B --&amp;gt; C[Dispatcher]
    C --&amp;gt; D[具体 Handler]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;Spring 应用：&lt;/strong&gt; &lt;code&gt;DispatcherServlet&lt;/code&gt; 是前端控制器模式的实现。&lt;/p&gt;
&lt;h3 id=&#34;6-拦截过滤器模式intercepting-filter-pattern&#34;&gt;6. 拦截过滤器模式（Intercepting Filter Pattern）&lt;/h3&gt;
&lt;p&gt;在请求到达目标处理器之前，通过一系列过滤器链对请求进行预处理（如编码、认证、日志）。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[Request] --&amp;gt; F1[Filter 1]
    F1 --&amp;gt; F2[Filter 2]
    F2 --&amp;gt; F3[Filter N]
    F3 --&amp;gt; T[Target]
    T --&amp;gt; F3
    F3 --&amp;gt; F2
    F2 --&amp;gt; F1
    F1 --&amp;gt; R[Response]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;Spring 应用：&lt;/strong&gt; Spring 的 &lt;code&gt;HandlerInterceptor&lt;/code&gt;、Servlet Filter。&lt;/p&gt;
&lt;h3 id=&#34;7-服务定位器模式service-locator-pattern&#34;&gt;7. 服务定位器模式（Service Locator Pattern）&lt;/h3&gt;
&lt;p&gt;提供一个中心化的注册表，用于获取服务实例，减少服务依赖的直接查找。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class ServiceLocator { +getService(name) }
    class Cache { -services }
    class InitialContext { +lookup(name) }
    class Service { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +execute() }
    ServiceLocator --&amp;gt; Cache
    ServiceLocator --&amp;gt; InitialContext
    ServiceLocator ..&amp;gt; Service&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;8-传输对象模式transfer-object-pattern&#34;&gt;8. 传输对象模式（Transfer Object Pattern）&lt;/h3&gt;
&lt;p&gt;通过简单的 POJO 对象在不同层之间传输数据，减少远程调用的网络开销，也称为 DTO（Data Transfer Object）。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class UserDTO { -id -name -email +getters/setters }
    class UserService { +getUserDTO(id) }
    class UserController { +showUser(dto) }
    UserService ..&amp;gt; UserDTO
    UserController ..&amp;gt; UserDTO&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;七jdkjava-io-与-spring-中的模式速查&#34;&gt;七、JDK、Java IO 与 Spring 中的模式速查&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;模式&lt;/th&gt;
					&lt;th&gt;JDK 示例&lt;/th&gt;
					&lt;th&gt;Spring 示例&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;工厂模式&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;DateFormat.getDateInstance()&lt;/code&gt;、&lt;code&gt;KeyGenerator.getInstance()&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;BeanFactory&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;单例模式&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;Runtime.getRuntime()&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;默认 Bean 作用域&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;原型模式&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;Object.clone()&lt;/code&gt;、&lt;code&gt;Cloneable&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;@Scope(&amp;quot;prototype&amp;quot;)&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;适配器模式&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;Arrays.asList()&lt;/code&gt;、&lt;code&gt;InputStreamReader&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;MVC 的 Controller 适配器&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;装饰器模式&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;BufferedInputStream&lt;/code&gt;、&lt;code&gt;ZipOutputStream&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;-&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;代理模式&lt;/td&gt;
					&lt;td&gt;JDK 动态代理&lt;/td&gt;
					&lt;td&gt;AOP&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;组合模式&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;java.awt.Container&lt;/code&gt; / &lt;code&gt;Component&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;-&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;享元模式&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;Integer.valueOf()&lt;/code&gt;、&lt;code&gt;String&lt;/code&gt; 常量池&lt;/td&gt;
					&lt;td&gt;-&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;模板方法模式&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;Arrays.sort()&lt;/code&gt; + &lt;code&gt;Comparable&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;RestTemplate&lt;/code&gt;、&lt;code&gt;JpaTemplate&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;策略模式&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;Comparator&lt;/code&gt;、&lt;code&gt;Collections.sort()&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;ResourceLoader&lt;/code&gt; 策略&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;观察者模式&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;Observer&lt;/code&gt; / &lt;code&gt;Observable&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Spring 事件驱动模型&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;解释器模式&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;Pattern&lt;/code&gt; / &lt;code&gt;Matcher&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;SpEL 表达式&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;责任链模式&lt;/td&gt;
					&lt;td&gt;Servlet Filter Chain&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;HandlerInterceptor&lt;/code&gt; 链&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;MVC 模式&lt;/td&gt;
					&lt;td&gt;-&lt;/td&gt;
					&lt;td&gt;Spring MVC&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;DAO 模式&lt;/td&gt;
					&lt;td&gt;JDBC Template&lt;/td&gt;
					&lt;td&gt;Spring Data JPA / MyBatis&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;前端控制器&lt;/td&gt;
					&lt;td&gt;-&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;DispatcherServlet&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;java-io-中使用的模式&#34;&gt;Java IO 中使用的模式&lt;/h3&gt;
&lt;p&gt;Java IO 同时使用了&lt;strong&gt;适配器模式&lt;/strong&gt;和&lt;strong&gt;装饰器模式&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;适配器模式：&lt;/strong&gt; &lt;code&gt;InputStreamReader&lt;/code&gt; 将 &lt;code&gt;InputStream&lt;/code&gt; 字节流适配为 &lt;code&gt;Reader&lt;/code&gt; 字符流。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;装饰器模式：&lt;/strong&gt; &lt;code&gt;BufferedInputStream&lt;/code&gt;、&lt;code&gt;DataInputStream&lt;/code&gt;、&lt;code&gt;ZipOutputStream&lt;/code&gt; 等通过包装 &lt;code&gt;InputStream&lt;/code&gt; 动态增强功能。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 装饰器模式：逐层包装&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;InputStream fis &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; FileInputStream(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;file.txt&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;InputStream bis &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; BufferedInputStream(fis);     &lt;span style=&#34;color:#75715e&#34;&gt;// 增加缓冲&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;DataInputStream dis &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; DataInputStream(bis);      &lt;span style=&#34;color:#75715e&#34;&gt;// 增加基本类型读取&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 适配器模式：字节流转字符流&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Reader reader &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; InputStreamReader(fis, StandardCharsets.&lt;span style=&#34;color:#a6e22e&#34;&gt;UTF_8&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;八如何选择设计模式&#34;&gt;八、如何选择设计模式&lt;/h2&gt;
&lt;p&gt;设计模式不是越复杂越好。选择时可以从以下角度思考：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;问题类型&lt;/strong&gt;：是对象创建、结构组织，还是行为交互？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;变化方向&lt;/strong&gt;：系统中哪些部分经常变化？把变化的部分封装起来。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;开闭原则&lt;/strong&gt;：新增功能时是否能不修改已有代码？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;复杂度收益&lt;/strong&gt;：不要为了用模式而增加不必要的抽象。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;一张简化的决策地图：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[遇到设计问题] --&amp;gt; B{对象创建复杂?}
    B --&amp;gt;|是| C[创建型模式]
    B --&amp;gt;|否| D{对象结构复杂?}
    D --&amp;gt;|是| E[结构型模式]
    D --&amp;gt;|否| F{对象交互复杂?}
    F --&amp;gt;|是| G[行为型模式]
    F --&amp;gt;|否| H[保持简单，遵循原则]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;总结&#34;&gt;总结&lt;/h2&gt;
&lt;p&gt;设计模式是前人总结的设计经验，核心目标是让软件更易扩展、更易维护、更易理解。掌握它们的关键不是背诵每个类图，而是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;理解六大原则，它们是选择模式的判断依据；&lt;/li&gt;
&lt;li&gt;熟悉 23 种 GoF 模式和 J2EE 模式的适用场景；&lt;/li&gt;
&lt;li&gt;多读优秀源码（JDK、Spring），在真实代码中感受模式的应用；&lt;/li&gt;
&lt;li&gt;避免过度设计，合适才是最好的。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;从 &lt;code&gt;BeanFactory&lt;/code&gt; 到 &lt;code&gt;DispatcherServlet&lt;/code&gt;，从 Java IO 的装饰器到 Spring AOP 的代理，设计模式无处不在。理解这些模式，能帮助我们在面对复杂系统时做出更清晰的结构选择。&lt;/p&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;p&gt;参考：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.runoob.com/design-pattern/factory-pattern.html&#34;&gt;菜鸟教程 - 设计模式&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.cnblogs.com/dailyprogrammer/articles/12272717.html&#34;&gt;dailyprogrammer - JDK 类库常用的设计模式&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Design Patterns: Elements of Reusable Object-Oriented Software&lt;/em&gt; (GoF)&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
  </channel>
</rss>
