Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

最近更新 | Last Updated

Prenote

NOTICE: This content is presented as git diff.

更新记录(2026-09-20 09:02:09 +0000 | e7143ab3)

Summary

  • Generated at: 2026-09-20 09:02:09 +0000
  • Base commit: e7143ab3
  • Diff source: f745f47e4d710d24fba797f551f9e1b8a86b0dea..e7143ab32ace98e3550d1a827ce7621b926fbe67
  • Changed files: 17
  • Total lines: +1492 / -0

Index

  1. src/SUMMARY.md +16 / -0
  2. src/学习与进步/计算机科学极简入门指南/软件构造/第两百九十一章:构建与依赖.md +92 / -0
  3. src/学习与进步/计算机科学极简入门指南/软件构造/第两百九十七章:从一个问题到一个软件系统(全系列总装).md +138 / -0
  4. src/学习与进步/计算机科学极简入门指南/软件构造/第两百九十三章:性能分析.md +86 / -0
  5. src/学习与进步/计算机科学极简入门指南/软件构造/第两百九十二章:数据持久化:何时文件已经不够.md +89 / -0
  6. src/学习与进步/计算机科学极简入门指南/软件构造/第两百九十五章:安全是共同责任.md +88 / -0
  7. src/学习与进步/计算机科学极简入门指南/软件构造/第两百九十六章:维护与演化.md +92 / -0
  8. src/学习与进步/计算机科学极简入门指南/软件构造/第两百九十四章:可靠性与故障处理.md +91 / -0
  9. src/学习与进步/计算机科学极简入门指南/软件构造/第两百九十章:协作与代码审查.md +90 / -0
  10. src/学习与进步/计算机科学极简入门指南/软件构造/第两百八十七章:单元测试.md +88 / -0
  11. src/学习与进步/计算机科学极简入门指南/软件构造/第两百八十三章:需求与边界.md +88 / -0
  12. src/学习与进步/计算机科学极简入门指南/软件构造/第两百八十九章:版本控制.md +90 / -0
  13. src/学习与进步/计算机科学极简入门指南/软件构造/第两百八十二章:从算法到软件.md +89 / -0
  14. src/学习与进步/计算机科学极简入门指南/软件构造/第两百八十五章:契约、错误与异常.md +91 / -0
  15. src/学习与进步/计算机科学极简入门指南/软件构造/第两百八十八章:集成测试与系统测试.md +86 / -0
  16. src/学习与进步/计算机科学极简入门指南/软件构造/第两百八十六章:日志与调试.md +91 / -0
  17. src/学习与进步/计算机科学极简入门指南/软件构造/第两百八十四章:分解、模块与接口.md +87 / -0

Diffs

src/SUMMARY.md

+16 / -0 Click to expand diff
diff --git a/src/SUMMARY.md b/src/SUMMARY.md
index 63567599..7f1f8bff 100644
--- a/src/SUMMARY.md
+++ b/src/SUMMARY.md
@@ -833,6 +833,22 @@
   - [第两百七十九章:字节码与虚拟机](学习与进步/计算机科学极简入门指南/编译原理/第两百七十九章:字节码与虚拟机.md)
   - [第两百八十章:即时编译(进阶)](学习与进步/计算机科学极简入门指南/编译原理/第两百八十章:即时编译(进阶).md)
   - [第两百八十一章:编译器总装](学习与进步/计算机科学极简入门指南/编译原理/第两百八十一章:编译器总装.md)
+  - [第两百八十二章:从算法到软件](学习与进步/计算机科学极简入门指南/软件构造/第两百八十二章:从算法到软件.md)
+  - [第两百八十三章:需求与边界](学习与进步/计算机科学极简入门指南/软件构造/第两百八十三章:需求与边界.md)
+  - [第两百八十四章:分解、模块与接口](学习与进步/计算机科学极简入门指南/软件构造/第两百八十四章:分解、模块与接口.md)
+  - [第两百八十五章:契约、错误与异常](学习与进步/计算机科学极简入门指南/软件构造/第两百八十五章:契约、错误与异常.md)
+  - [第两百八十六章:日志与调试](学习与进步/计算机科学极简入门指南/软件构造/第两百八十六章:日志与调试.md)
+  - [第两百八十七章:单元测试](学习与进步/计算机科学极简入门指南/软件构造/第两百八十七章:单元测试.md)
+  - [第两百八十八章:集成测试与系统测试](学习与进步/计算机科学极简入门指南/软件构造/第两百八十八章:集成测试与系统测试.md)
+  - [第两百八十九章:版本控制](学习与进步/计算机科学极简入门指南/软件构造/第两百八十九章:版本控制.md)
+  - [第两百九十章:协作与代码审查](学习与进步/计算机科学极简入门指南/软件构造/第两百九十章:协作与代码审查.md)
+  - [第两百九十一章:构建与依赖](学习与进步/计算机科学极简入门指南/软件构造/第两百九十一章:构建与依赖.md)
+  - [第两百九十二章:数据持久化:何时文件已经不够](学习与进步/计算机科学极简入门指南/软件构造/第两百九十二章:数据持久化:何时文件已经不够.md)
+  - [第两百九十三章:性能分析](学习与进步/计算机科学极简入门指南/软件构造/第两百九十三章:性能分析.md)
+  - [第两百九十四章:可靠性与故障处理](学习与进步/计算机科学极简入门指南/软件构造/第两百九十四章:可靠性与故障处理.md)
+  - [第两百九十五章:安全是共同责任](学习与进步/计算机科学极简入门指南/软件构造/第两百九十五章:安全是共同责任.md)
+  - [第两百九十六章:维护与演化](学习与进步/计算机科学极简入门指南/软件构造/第两百九十六章:维护与演化.md)
+  - [第两百九十七章:从一个问题到一个软件系统(全系列总装)](学习与进步/计算机科学极简入门指南/软件构造/第两百九十七章:从一个问题到一个软件系统(全系列总装).md)
   - [附加章一:大数据](学习与进步/计算机科学极简入门指南/附加章/大数据.md)
   - [附加章二:数据加密](学习与进步/计算机科学极简入门指南/附加章/数据加密.md)
   - [附加章三:区块链](学习与进步/计算机科学极简入门指南/附加章/区块链.md)

src/学习与进步/计算机科学极简入门指南/软件构造/第两百九十一章:构建与依赖.md

+92 / -0 Click to expand diff
diff --git "a/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\344\271\235\345\215\201\344\270\200\347\253\240\357\274\232\346\236\204\345\273\272\344\270\216\344\276\235\350\265\226.md" "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\344\271\235\345\215\201\344\270\200\347\253\240\357\274\232\346\236\204\345\273\272\344\270\216\344\276\235\350\265\226.md"
new file mode 100644
index 00000000..64e940c3
--- /dev/null
+++ "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\344\271\235\345\215\201\344\270\200\347\253\240\357\274\232\346\236\204\345\273\272\344\270\216\344\276\235\350\265\226.md"
@@ -0,0 +1,92 @@
+# 构建与依赖
+
+## 复习
+
+- 版本控制:保存与合并代码变更
+- 编译器总装:从源码到可执行文件
+- 从源代码到可执行文件:编译、汇编、链接的流水线
+
+## TL;DR
+
+- 构建把源文件、库和配置变成可发布的产物
+- 依赖管理记录并获取程序用到的外部库
+- 自动化构建保证结果一致、可重复
+- “在我机器上能跑”正是构建要消灭的问题
+
+## 正文
+
+  代码写好了,也通过了测试。但它还不能直接交付——它需要经过**构建**(build),才能变成用户能用的东西。
+
+### 构建:从源码到产物
+
+  **构建**,就是把源代码、库、配置等各种原料,加工成可发布产物的过程。它大致包括:
+
+- **编译**:把源码编译成目标文件(还记得前面讲的那条流水线吗)
+- **链接**:把目标文件和库连成可执行文件
+- **打包**:整理成安装包、镜像等可交付形态
+- 有时还有代码生成、资源处理等步骤
+
+  这些步骤,如果全靠人手动敲,既繁琐又容易出错。所以实践中通常用**自动化构建**:一条命令,把整套流程跑完。
+
+### 依赖:程序也用别人的库
+
+  现代程序很少“自给自足”,几乎都会用到**第三方库**。这些外部库,就是程序的**依赖**(dependency)。
+
+  依赖管理要处理两个问题:
+
+- **记录**:我用了哪些库、各自哪个版本
+- **获取**:怎么把这些库下载、安装到本地
+
+  依赖管理工具会自动完成这些,让你不必手动去找、去装。但这也带来了新的麻烦:**版本兼容**——库升级了,可能和你的代码不兼容;不同库之间,也可能要求同一个库的不同版本。
+
+### 可重现的构建
+
+  “构建”最重要的目标之一,是**可重现**:**同样的源代码,在任何时候、任何机器上构建,都应该得到相同的结果。**
+
+  为什么这很重要?因为如果构建结果不可重现,就会出现经典的一幕——“**在我机器上能跑啊**”:开发者本地一切正常,换台机器或换个环境就崩了。原因往往是构建环境的差异:缺了某个依赖、版本不对、配置不同……
+
+  解决之道,是把构建所需的**一切**都明确记录下来(依赖的精确版本、构建配置等),让构建过程像一份精确的配方:**照着做,无论谁来、在哪做,都得到同样的产物。** 这样,问题才能被稳定地复现和解决。
+
+  至此,“怎么把代码变成能交付的东西”就清楚了。接下来,要面对的是软件长期运行中最实际的两件事:**数据往哪存、性能怎么调。**
+
+ **思考题 1** 
+
+>   “构建”大致包括哪些步骤?
+
+ **思考题 2** 
+
+>   为什么“能重现的构建”很重要?
+
+## 小结
+
+### 知识点
+
+- 构建把源码、库与配置加工成可交付产物
+- 步骤包括编译、链接、打包等
+- 依赖管理记录并获取外部库及其版本
+- 可重现的构建消除环境差异带来的问题
+
+### 参考资料
+
+1. [Wikipedia(zh):软件构建](https://zh.wikipedia.org/wiki/%E8%BD%AF%E4%BB%B6%E6%9E%84%E5%BB%BA):把源码加工为可运行产物的过程
+2. [Wikipedia(zh):依赖管理](https://zh.wikipedia.org/wiki/%E4%BE%9D%E8%B5%96%E7%AE%A1%E7%90%86):管理软件所依赖的外部组件
+
+### 思考题答案(仅供参考)
+
+#### 思考题 1
+
+  大致包括:编译源码、链接库与目标文件、按需进行代码生成或资源处理,最后打包成安装包或镜像等可交付产物。实践中通常由自动化构建工具一次完成。
+
+#### 思考题 2
+
+  因为它保证同样的源码在任何时间、任何机器上都得到相同的结果。若构建不可重现,就会出现“在我机器上能跑”的问题:问题难以复现、难以排查。可重现的构建让环境差异不再捣乱,问题能被稳定定位。
+
+## 协议
+
+  本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)进行许可。
+
+## 封面图
+
+![](https://raw.githubusercontent.com/TinySnow/computer-science-guide-resources/master/computer-science-guide/cover/软件构造/构建与依赖.png)
+
+> 设计师 | 南国微雪

src/学习与进步/计算机科学极简入门指南/软件构造/第两百九十七章:从一个问题到一个软件系统(全系列总装).md

+138 / -0 Click to expand diff
diff --git "a/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\344\271\235\345\215\201\344\270\203\347\253\240\357\274\232\344\273\216\344\270\200\344\270\252\351\227\256\351\242\230\345\210\260\344\270\200\344\270\252\350\275\257\344\273\266\347\263\273\347\273\237\357\274\210\345\205\250\347\263\273\345\210\227\346\200\273\350\243\205\357\274\211.md" "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\344\271\235\345\215\201\344\270\203\347\253\240\357\274\232\344\273\216\344\270\200\344\270\252\351\227\256\351\242\230\345\210\260\344\270\200\344\270\252\350\275\257\344\273\266\347\263\273\347\273\237\357\274\210\345\205\250\347\263\273\345\210\227\346\200\273\350\243\205\357\274\211.md"
new file mode 100644
index 00000000..c8fd948e
--- /dev/null
+++ "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\344\271\235\345\215\201\344\270\203\347\253\240\357\274\232\344\273\216\344\270\200\344\270\252\351\227\256\351\242\230\345\210\260\344\270\200\344\270\252\350\275\257\344\273\266\347\263\273\347\273\237\357\274\210\345\205\250\347\263\273\345\210\227\346\200\273\350\243\205\357\274\211.md"
@@ -0,0 +1,138 @@
+# 从一个问题到一个软件系统(全系列总装)
+
+## 复习
+
+- 需求与边界、分解与接口:把问题变成设计
+- 测试、构建、维护:让软件能被信赖地交付与长期使用
+- 编译原理:把源码变成可运行的机器码
+
+## TL;DR
+
+- 从“一个问题”到“一个软件系统”,是一条完整的闭环
+- 需求、程序、编译器、操作系统、网络、算法、硬件层层接力
+- 每一层都建立在下面几层之上
+- 整个系列的主线:分层、抽象与取舍
+
+## 正文
+
+  这是整部指南的最后一章。让我们把从第一枚比特开始、一路走来的所有内容,串成一条完整的线:**从一个真实的问题出发,直到它在机器上运行起来,并长期被使用。**
+
+### 从问题开始
+
+  一切都从一个**问题**开始:有人想解决某件事。于是:
+
+1. **理解问题**:弄清需求,划清边界——不做这一步,后面全是白费
+2. **设计**:把它分解成模块,定好接口,约定契约
+3. **写程序**:用高级语言把设计写成代码
+
+  到这里,你手里有一份源码。但它还只是文字。
+
+### 让它成为可运行的程序
+
+  接着,**编译器**接过源码(还记得它的一生吗):
+
+- 前端读懂它:词法、语法、语义
+- 中端优化它:常量折叠、死循环删除、循环优化……
+- 后端落成机器码:指令选择、寄存器分配、栈帧
+- 再经过汇编、链接,成为可执行文件
+
+  这份可执行文件,最终由**操作系统**装载进内存、分配资源、开始运行。而它在内存里的样子、它的地址空间、它用的设备与文件——正是操作系统那一部分的内容。
+
+### 让它连上世界
+
+  如果程序需要和远方的机器通信,就轮到**网络**:
+
+- 数据从应用出发,经传输层、网络层、链路层一路封装
+- 跨过交换机、路由器,一跳一跳接力
+- 在另一台机器上被层层拆开,交给对应的程序
+
+  从一根线传一个比特,到一次完整的网页访问,网络把“远方”接了进来。
+
+### 让它跑得聪明
+
+  而无论哪一层,**算法与数据结构**都在背后提供支撑:
+
+- 操作系统调度进程、缓存数据、管理内存
+- 网络计算路由、缓存内容、解析域名
+- 编译器做词法语法分析、优化、寄存器分配
+
+  每一个“怎样更快、怎样更好”的问题,最终都会落到“**怎样组织数据、怎样安排步骤**”上——这正是算法与数据结构那一部分的主题。
+
+### 让它能长期活着
+
+  最后,一个程序要成为真正的**软件系统**,还要:
+
+- 被**测试**,保证它是可信的
+- 被**构建**,变成可交付的产物
+- 被**维护**,在变化中长久演化
+- 在**安全**与**可靠**上有基本的保障
+
+  这些,就是软件构造那一部分补上的最后一段路。
+
+### 回到最初
+
+  把这一切串起来看:
+
+```text
+问题 → 需求 → 设计 → 代码
+  → 编译器(词法·语法·语义·优化·后端)
+  → 操作系统(装载·内存·进程·文件·设备)
+  → 网络(封装·转发·端到端·应用)
+  → 软件(测试·构建·部署·维护)
+  → 运行在硬件上(逻辑门·寄存器·时钟)
+```
+
+  你看,**每一层都站在下面几层之上**:软件依赖操作系统,操作系统依赖硬件,网络把一台台机器连成世界,而算法让每一层都更高效。整部教程反复讲的那几句话,此刻应该都能体会了:
+
+- 懒是人类进步的第一动力
+- 在计算机科学里,没有什么问题是加一层解决不了的
+- 人类所有知识都来源于好奇心和解决问题
+- 所有庞大学科知识体量均由积累而成,不会一蹴而就
+
+  计算机科学看起来庞大,但拆开看,不过是一层层简单的约定,一次次务实的取舍,一代代人积累的结果。
+
+  而这门学科的未来,以及下一个问题的答案,不在任何一本书里——它在下一个愿意好奇、愿意动手解决问题的人身上。
+
+  也许,就是你。
+
+ **思考题 1** 
+
+>   从“一个问题”到“一个软件系统”,大致经历了哪些环节?
+
+ **思考题 2** 
+
+>   回顾整个系列,你觉得贯穿始终的主线是什么?
+
+## 小结
+
+### 知识点
+
+- 从问题到软件系统是一条完整闭环
+- 需求、设计、代码、编译、运行、网络、维护层层接力
+- 每一层都建立在下层之上
+- 全程主线是分层、抽象与取舍
+
+### 参考资料
+
+1. [Wikipedia(zh):计算机科学](https://zh.wikipedia.org/wiki/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6):研究计算与信息处理的学科
+2. [Wikipedia(zh):系统开发生命周期](https://zh.wikipedia.org/wiki/%E7%B3%BB%E7%BB%9F%E5%BC%80%E5%8F%91%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F):从需求到维护的完整过程
+
+### 思考题答案(仅供参考)
+
+#### 思考题 1
+
+  大致经历:理解需求与边界、设计模块与接口、编写代码、编译(词法·语法·语义·优化·后端)与链接、由操作系统装载运行、必要时通过网络通信、以及测试、构建、部署、维护等软件工程环节,最终运行在硬件之上。
+
+#### 思考题 2
+
+  参考答案:**分层、抽象与取舍。** 用一层层抽象把复杂性封装起来(硬件→操作系统→网络→应用),每遇到难题就“加一层”;而每一层、每个设计又都在性能、空间、复杂度之间做取舍。整部教程,就是这条主线的不断重演。
+
+## 协议
+
+  本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)进行许可。
+
+## 封面图
+
+![](https://raw.githubusercontent.com/TinySnow/computer-science-guide-resources/master/computer-science-guide/cover/软件构造/从一个问题到一个软件系统.png)
+
+> 设计师 | 南国微雪

src/学习与进步/计算机科学极简入门指南/软件构造/第两百九十三章:性能分析.md

+86 / -0 Click to expand diff
diff --git "a/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\344\271\235\345\215\201\344\270\211\347\253\240\357\274\232\346\200\247\350\203\275\345\210\206\346\236\220.md" "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\344\271\235\345\215\201\344\270\211\347\253\240\357\274\232\346\200\247\350\203\275\345\210\206\346\236\220.md"
new file mode 100644
index 00000000..f7e6f0c0
--- /dev/null
+++ "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\344\271\235\345\215\201\344\270\211\347\253\240\357\274\232\346\200\247\350\203\275\345\210\206\346\236\220.md"
@@ -0,0 +1,86 @@
+# 性能分析
+
+## 复习
+
+- 日志与调试:先观察,再动手
+- 大 O 记号:描述代价随规模的增长
+- 循环优化:优化优先盯热点
+
+## TL;DR
+
+- 优化之前先测量,别凭感觉
+- 性能分析用来找出真正的瓶颈
+- 瓶颈往往集中在少数热点上
+- 先测再改,改后再测
+
+## 正文
+
+  程序能跑,但可能有点慢。想让它更快,第一反应往往是“我觉得这里慢,改改看”。这恰恰是最容易走弯路的地方。
+
+### 别凭感觉优化
+
+  有一条很有名的忠告:**过早优化是万恶之源。** 它想说的不是“不要优化”,而是**不要凭直觉优化**。
+
+  因为人对性能瓶颈的直觉,常常是错的。你以为最耗时的那个函数,实际可能根本没被调用几次;真正拖慢程序的,也许藏在一个你从没注意的角落。**在没有数据的情况下优化,很可能是在做无用功,甚至把代码改得更复杂、更慢。**
+
+### 先测量,找瓶颈
+
+  正确的做法是**先测量**。**性能分析**(profiling)就是干这个的:它统计出程序把时间都花在了哪里——哪个函数调用最多、哪段代码最耗时。
+
+  有了分析结果,你会发现一个很普遍的现象:**瓶颈往往高度集中**——可能 80% 的时间,都花在 20% 的代码上。这就是所谓的“热点”。
+
+  这和前面讲算法、讲循环优化时的思路完全一致:**把力气花在真正的热头上。** 区别只在于,这里的“热点”是靠实测找出来的,而不是靠猜。
+
+### 测—改—再测
+
+  找到热点之后,优化它,然后**再测量一次**:
+
+- 确实变快了吗?
+- 会不会把瓶颈转移到了别处?
+- 有没有引入新的问题?
+
+  因为优化常常是“按下葫芦浮起瓢”:这里快了,别处成了新瓶颈。所以要**循环往复:测量 → 优化 → 再测量**,直到达到目标。
+
+  **用数据说话,而不是用感觉。** 这条原则,从调试一直贯穿到性能优化。
+
+ **思考题 1** 
+
+>   为什么优化前要先“测量”?
+
+ **思考题 2** 
+
+>   为什么性能瓶颈常常集中在少数热点上?
+
+## 小结
+
+### 知识点
+
+- 不要凭直觉优化,应先测量
+- 性能分析找出时间花在哪里
+- 瓶颈常集中在少数热点代码
+- 采用“测量—优化—再测量”的循环
+
+### 参考资料
+
+1. [Wikipedia(zh):性能分析](https://zh.wikipedia.org/wiki/%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90):测量程序各部分的资源消耗
+2. [Wikipedia(zh):性能工程](https://zh.wikipedia.org/wiki/%E6%80%A7%E8%83%BD%E5%B7%A5%E7%A8%8B):以测量为基础改进系统性能
+
+### 思考题答案(仅供参考)
+
+#### 思考题 1
+
+  因为人对瓶颈的直觉常常不准,可能优化了根本不影响性能的地方,白费力气甚至弄糟代码。先测量才能知道时间真正花在哪里,让优化有的放矢。
+
+#### 思考题 2
+
+  因为程序各部分被执行的频率和耗时差异很大:少数代码会被大量重复执行(热点),占据了大部分时间,而多数代码很少运行。因此把优化集中在这些热头上,收益最高。
+
+## 协议
+
+  本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)进行许可。
+
+## 封面图
+
+![](https://raw.githubusercontent.com/TinySnow/computer-science-guide-resources/master/computer-science-guide/cover/软件构造/性能分析.png)
+
+> 设计师 | 南国微雪

src/学习与进步/计算机科学极简入门指南/软件构造/第两百九十二章:数据持久化:何时文件已经不够.md

+89 / -0 Click to expand diff
diff --git "a/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\344\271\235\345\215\201\344\272\214\347\253\240\357\274\232\346\225\260\346\215\256\346\214\201\344\271\205\345\214\226\357\274\232\344\275\225\346\227\266\346\226\207\344\273\266\345\267\262\347\273\217\344\270\215\345\244\237.md" "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\344\271\235\345\215\201\344\272\214\347\253\240\357\274\232\346\225\260\346\215\256\346\214\201\344\271\205\345\214\226\357\274\232\344\275\225\346\227\266\346\226\207\344\273\266\345\267\262\347\273\217\344\270\215\345\244\237.md"
new file mode 100644
index 00000000..3014d1c7
--- /dev/null
+++ "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\344\271\235\345\215\201\344\272\214\347\253\240\357\274\232\346\225\260\346\215\256\346\214\201\344\271\205\345\214\226\357\274\232\344\275\225\346\227\266\346\226\207\344\273\266\345\267\262\347\273\217\344\270\215\345\244\237.md"
@@ -0,0 +1,89 @@
+# 数据持久化:何时文件已经不够
+
+## 复习
+
+- 构建与依赖:把代码变成可交付产物
+- 文件:把数据长期保存到磁盘
+- 契约、错误与异常:明确地处理失败
+
+## TL;DR
+
+- 数据要长期保存,就需要持久化
+- 简单场景下,用文件就够了
+- 但当需要并发、按条件查询、一致性时,文件会很吃力
+- 数据库作为外部服务,负责保存、查询和更新数据
+
+## 正文
+
+  程序运行时的数据大多在内存里,一关就没了。要让它**长久保存**,就得**持久化**(persistence)——把数据写到磁盘上。最开始,我们会想到**文件**。
+
+### 文件:够用的起点
+
+  很多场景下,文件是完全够用的:
+
+- 保存配置、日志
+- 存一份程序自己读写、格式自定的数据
+- 数据量不大、访问简单
+
+  这时候,直接读写文件简单直接,不必引入更复杂的东西。**能用简单办法解决,就别急着上复杂工具。**
+
+### 什么时候文件不够了
+
+  但随着需求变复杂,文件会越来越吃力:
+
+- **并发**:多个人(或多个程序)同时读写同一个文件,很容易互相覆盖、写坏
+- **查询**:想要“找出所有满足某条件的记录”,文件只能一遍遍读取、自己过滤
+- **一致性**:一次修改牵涉多处,中途出错就可能留下“改了一半”的坏数据
+- **规模与效率**:数据一大,全文件扫描慢得难以接受
+
+  这些要求加在一起,用文件硬撑,会把自己写成一个难以维护、bug 频出的“土数据库”。
+
+### 数据库作为外部服务
+
+  于是,人们把“保存、查询、更新数据”这件事,交给专门的软件——**数据库**(database)。
+
+  这里我们只把它当成一个**外部服务**来理解:程序通过它**保存**数据、按条件**查询**数据、**更新**数据。至于它内部怎么组织文件、怎么保证并发和一致性,暂时当作黑箱——那是另一个庞大的领域。
+
+  关键是认识到:**当“保存数据”的需求,升级成“可靠、并发、可查询地管理数据”时,专业的事就该交给专业的工具。** 这和整部教程里“加一层、用现成的抽象”是同一个思路。
+
+ **思考题 1** 
+
+>   什么情况下,用文件保存数据就够了?
+
+ **思考题 2** 
+
+>   当需要“按条件查询”和“多人同时修改”时,为什么文件会很吃力?
+
+## 小结
+
+### 知识点
+
+- 持久化把数据长期保存到磁盘
+- 简单场景用文件足够
+- 并发、查询、一致性与规模会让文件吃力
+- 数据库作为外部服务负责保存、查询与更新
+
+### 参考资料
+
+1. [Wikipedia(zh):持久化](https://zh.wikipedia.org/wiki/%E6%8C%81%E4%B9%85%E5%8C%96):把数据长期保存下来
+2. [Wikipedia(zh):数据库](https://zh.wikipedia.org/wiki/%E6%95%B0%E6%8D%AE%E5%BA%93):以结构化管理数据的系统
+
+### 思考题答案(仅供参考)
+
+#### 思考题 1
+
+  当数据量不大、访问方式简单、基本不存在多人并发修改时,用文件保存就足够,例如配置、日志或格式自定的本地数据。这时引入数据库反而增加复杂度。
+
+#### 思考题 2
+
+  因为文件缺乏对并发的控制,多人同时读写容易互相覆盖;而按条件查询只能一遍遍读取并自行过滤,数据一大就非常慢。此外,牵涉多处的修改也难以保证“要么全成、要么全不做”,容易留下不一致的数据。
+
+## 协议
+
+  本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)进行许可。
+
+## 封面图
+
+![](https://raw.githubusercontent.com/TinySnow/computer-science-guide-resources/master/computer-science-guide/cover/软件构造/数据持久化.png)
+
+> 设计师 | 南国微雪

src/学习与进步/计算机科学极简入门指南/软件构造/第两百九十五章:安全是共同责任.md

+88 / -0 Click to expand diff
diff --git "a/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\344\271\235\345\215\201\344\272\224\347\253\240\357\274\232\345\256\211\345\205\250\346\230\257\345\205\261\345\220\214\350\264\243\344\273\273.md" "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\344\271\235\345\215\201\344\272\224\347\253\240\357\274\232\345\256\211\345\205\250\346\230\257\345\205\261\345\220\214\350\264\243\344\273\273.md"
new file mode 100644
index 00000000..1d2f5701
--- /dev/null
+++ "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\344\271\235\345\215\201\344\272\224\347\253\240\357\274\232\345\256\211\345\205\250\346\230\257\345\205\261\345\220\214\350\264\243\344\273\273.md"
@@ -0,0 +1,88 @@
+# 安全是共同责任
+
+## 复习
+
+- 契约、错误与异常:明确校验输入、处理失败
+- 可靠性与故障处理:假设问题会发生
+- 网络:攻击者会不断寻找突破口
+
+## TL;DR
+
+- 安全不是某一个人的事,而是每个环节的共同责任
+- 输入验证、最小权限、纵深防御是基本功
+- 安全应在设计之初就考虑,而非事后打补丁
+- 任何便利,都可能带来风险
+
+## 正文
+
+  软件要长期服务,就必须面对一个不友好的现实:**总有人在试图利用它。** 而安全这件事,最忌讳的想法是“安全是安全工程师的事”。事实是——**它是每一个参与者的共同责任。**
+
+### 谁都有份
+
+  安全的责任,散落在每个环节里:
+
+- **开发者**:不能写出可被利用的漏洞
+- **运维者**:要正确配置、及时打补丁
+- **使用者**:要保持警惕、不乱点乱填
+
+  任何一个环节掉链子,整条防线都会被打穿。所以,“安全是共同责任”不是一句口号,而是这套系统的真实结构。
+
+### 三样基本功
+
+  软件安全有一些反复出现的基本功:
+
+- **输入验证**:永远不要相信外部输入。用户填的、网络传的、文件读的,都要校验、过滤,别直接拿来用
+- **最小权限**:只给程序或账号完成工作所必需的权限,出了事破坏范围也小
+- **纵深防御**:不要指望一道防线。多层设防,任何一层失守,还有下一层兜底
+
+  这三样,我们在讲网络安全时都见过。它们之所以反复出现,是因为**真正有效的安全,靠的从来不是某个绝招,而是一套扎实的习惯。**
+
+### 安全要“从设计开始”
+
+  还有一个关键观念:**安全要在设计之初就考虑,而不是等到出事再打补丁。**
+
+  事后补漏往往很被动:漏洞可能已经被利用,修复还可能牵一发而动全身。而在设计时就考虑信任边界、数据保护、权限控制,代价小得多,效果也好得多。
+
+  同时要牢记:**没有绝对的安全。** 你能做的,是不断提高攻击者的成本,把风险控制到可接受的程度。这又是那句老话——**任何便利都伴随风险,关键是权衡。**
+
+ **思考题 1** 
+
+>   为什么说“安全是共同责任”?
+
+ **思考题 2** 
+
+>   “最小权限”和“输入验证”,分别防的是什么?
+
+## 小结
+
+### 知识点
+
+- 安全是开发者、运维者与使用者共同的责任
+- 基本功:输入验证、最小权限、纵深防御
+- 安全应在设计之初考虑
+- 没有绝对安全,只能控制风险
+
+### 参考资料
+
+1. [Wikipedia(zh):计算机安全](https://zh.wikipedia.org/wiki/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%AE%89%E5%85%A8):保护系统免受攻击
+2. [Wikipedia(zh):最小权限原则](https://zh.wikipedia.org/wiki/%E6%9C%80%E5%B0%8F%E6%9D%83%E9%99%90%E5%8E%9F%E5%88%99):只授予完成工作所必需的权限
+
+### 思考题答案(仅供参考)
+
+#### 思考题 1
+
+  因为软件的安全依赖多个环节:开发者不写漏洞、运维者正确配置并及时修补、使用者保持警惕。任何一环出问题都可能被打穿,所以安全不是某个角色的专属,而是所有参与者共同的责任。
+
+#### 思考题 2
+
+  最小权限防的是“被攻破后的破坏范围”:即使某部分被利用,它也做不了超出职责的事。输入验证防的是“不可信的外部数据”:用户、网络、文件传来的内容都要校验,避免恶意或非法输入被直接使用而引发问题。
+
+## 协议
+
+  本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)进行许可。
+
+## 封面图
+
+![](https://raw.githubusercontent.com/TinySnow/computer-science-guide-resources/master/computer-science-guide/cover/软件构造/安全是共同责任.png)
+
+> 设计师 | 南国微雪

src/学习与进步/计算机科学极简入门指南/软件构造/第两百九十六章:维护与演化.md

+92 / -0 Click to expand diff
diff --git "a/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\344\271\235\345\215\201\345\205\255\347\253\240\357\274\232\347\273\264\346\212\244\344\270\216\346\274\224\345\214\226.md" "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\344\271\235\345\215\201\345\205\255\347\253\240\357\274\232\347\273\264\346\212\244\344\270\216\346\274\224\345\214\226.md"
new file mode 100644
index 00000000..a322a0ac
--- /dev/null
+++ "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\344\271\235\345\215\201\345\205\255\347\253\240\357\274\232\347\273\264\346\212\244\344\270\216\346\274\224\345\214\226.md"
@@ -0,0 +1,92 @@
+# 维护与演化
+
+## 复习
+
+- 分解、模块与接口:低耦合便于修改
+- 单元测试:修改不破坏既有功能
+- 版本控制:保存变更历史
+
+## TL;DR
+
+- 软件的大部分成本,花在它上线之后的维护上
+- 需求与环境会不断变化,软件必须随之演化
+- 可读、低耦合、有测试,是能被维护的前提
+- 好的设计,是为了让将来的修改更容易
+
+## 正文
+
+  很多人以为软件开发到“上线”就结束了。实际上,**上线只是它漫长生命的开始**。此后它要被使用、被修改、被适应新的需求,这段过程叫**维护与演化**(maintenance and evolution)。
+
+### 维护,才是大头
+
+  一个常被提到的观察是:**软件生命周期里,大部分成本花在上线之后的维护上,而不是最初的开发。**
+
+  为什么?因为软件所处的世界一直在变:
+
+- 用户想要新功能
+- 环境变了(依赖升级、平台更替)
+- 发现了 bug
+- 业务规则调整了
+
+  于是代码必须不断被修改。而“改得动”本身,就是一种能力。
+
+### 什么样的软件“改得动”
+
+  回头看前面几章,你会发现它们其实都在为“可维护”服务:
+
+- **模块化、低耦合**:改一处,不会牵动全身
+- **清晰的接口**:内部可以换,外部不受影响
+- **单元测试**:改完能立刻验证有没有弄坏别的
+- **版本控制**:改坏了能回退,历史可追溯
+
+  这些实践,在开发时也许显得“多此一举”,但正是它们,让软件在几年后还**有人敢改、改得动**。**可维护性,是设计时埋下的种子。**
+
+### 为变化而设计
+
+  所以,写代码时多花的那点心思——起清楚的名字、划清模块、写好测试——并不是浪费。它们是在为**未来的自己和同事**铺路。
+
+  这也回到最初的一句话:**算法解决“怎么算对”,而软件构造解决“怎么让它长久地用下去”。** 一个“能跑”的程序,和一个“能一直改下去”的软件之间,隔着这几章讲的这一切。
+
+  接下来是最后一章。我们将把需求、程序、编译器、操作系统、网络、算法和硬件全部串起来,走完从“一个问题”到“一个软件系统”的完整闭环。
+
+ **思考题 1** 
+
+>   为什么说软件的大部分成本在“维护”上?
+
+ **思考题 2** 
+
+>   模块化、测试、版本控制等实践,是怎样帮助“以后更容易修改”的?
+
+## 小结
+
+### 知识点
+
+- 软件的大部分成本在上线后的维护
+- 需求与环境变化迫使软件持续演化
+- 可读、低耦合、有测试是易维护的前提
+- 好设计是为将来的修改铺路
+
+### 参考资料
+
+1. [Wikipedia(zh):软件维护](https://zh.wikipedia.org/wiki/%E8%BD%AF%E4%BB%B6%E7%BB%B4%E6%8A%A4):软件交付后的修改与演进
+2. [Wikipedia(zh):可维护性](https://zh.wikipedia.org/wiki/%E5%8F%AF%E7%BB%B4%E6%8A%A4%E6%80%A7):软件被修改的容易程度
+
+### 思考题答案(仅供参考)
+
+#### 思考题 1
+
+  因为软件上线后还要长期被使用和修改:用户要新功能、环境会变化、bug 要修、业务规则会调整。这些持续发生的改动,累积起来占用了软件生命周期中的大部分成本。
+
+#### 思考题 2
+
+  模块化与低耦合让改动局限在一处、不易牵连全局;清晰的接口让内部实现可替换;单元测试让改动后能立刻验证旧功能未被破坏;版本控制让改坏可回退。这些共同使软件在将来“敢改、改得动”。
+
+## 协议
+
+  本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)进行许可。
+
+## 封面图
+
+![](https://raw.githubusercontent.com/TinySnow/computer-science-guide-resources/master/computer-science-guide/cover/软件构造/维护与演化.png)
+
+> 设计师 | 南国微雪

src/学习与进步/计算机科学极简入门指南/软件构造/第两百九十四章:可靠性与故障处理.md

+91 / -0 Click to expand diff
diff --git "a/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\344\271\235\345\215\201\345\233\233\347\253\240\357\274\232\345\217\257\351\235\240\346\200\247\344\270\216\346\225\205\351\232\234\345\244\204\347\220\206.md" "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\344\271\235\345\215\201\345\233\233\347\253\240\357\274\232\345\217\257\351\235\240\346\200\247\344\270\216\346\225\205\351\232\234\345\244\204\347\220\206.md"
new file mode 100644
index 00000000..1bd667a7
--- /dev/null
+++ "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\344\271\235\345\215\201\345\233\233\347\253\240\357\274\232\345\217\257\351\235\240\346\200\247\344\270\216\346\225\205\351\232\234\345\244\204\347\220\206.md"
@@ -0,0 +1,91 @@
+# 可靠性与故障处理
+
+## 复习
+
+- 契约、错误与异常:明确地处理失败
+- 日志与调试:保留现场,便于定位
+- 从算法到软件:软件要长期使用
+
+## TL;DR
+
+- 真实系统一定会出故障,要假设故障会发生
+- 超时、重试、降级、恢复是常见手段
+- 重试要小心:要有限度、要退避,并考虑重复执行的影响
+- 目标不是“永不出错”,而是“出错也能撑住”
+
+## 正文
+
+  软件要在真实世界里长期运行,就一定会遇到各种意外:网络断了、磁盘满了、依赖的服务挂了、流量突然暴涨。**可靠性**(reliability)要面对的,正是这些“一定会发生”的故障。
+
+### 假设它会坏
+
+  可靠系统设计的第一条心态是:**不要假设一切正常,而要假设故障迟早会发生。**
+
+  如果某个操作可能永远卡住,就不能“一直等”;如果某个服务可能暂时不可用,就要想好“它挂了怎么办”。把故障当成常态来设计,系统才能在出问题时依然可用。
+
+### 几种常见手段
+
+  应对故障,有几种常用手段:
+
+- **超时**:给操作设一个时限,别无限等待。卡住的操作,往往会拖垮整个系统
+- **重试**:暂时性失败,可以再试几次。但重试有讲究
+- **降级**:放弃次要功能,保住核心功能。比如推荐服务挂了,商品列表照常展示
+- **恢复/熔断**:把出问题的部分隔离、快速失败,避免故障扩散
+
+### 重试要小心
+
+  重试看起来简单,其实暗藏陷阱。想想看:如果一个操作其实**已经成功**,只是**确认**没收到,你重试一次,会不会把同一件事做了两遍?
+
+  所以,重试要配合几件事:
+
+- **有限度**:不能无限重试,否则故障时反而雪上加霜
+- **退避**:每次重试间隔逐渐拉长,别一股脑猛冲
+- **可重复执行**:让操作“多执行一次也没关系”(幂等),重试才真正安全
+
+  这说明:**处理故障的手段本身,也可能带来新问题。** 可靠性设计,处处是这种需要细致权衡的地方。
+
+### 目标:撑住,而不是不出错
+
+  可靠性的目标,从来不是“永不出错”——那不可能。真正的目标是:**即使出了错,系统也能保持基本可用,并且能恢复。** 这又回到了整部教程反复出现的那句话:**没有绝对不出问题的系统,只有不断提高的可承受能力。**
+
+ **思考题 1** 
+
+>   为什么可靠系统要“假设故障一定会发生”?
+
+ **思考题 2** 
+
+>   “重试”为什么需要小心设计?
+
+## 小结
+
+### 知识点
+
+- 可靠系统假设故障必然发生
+- 常见手段:超时、重试、降级、熔断与恢复
+- 重试要有节制、退避,并考虑可重复执行
+- 目标是故障时仍可用、可恢复
+
+### 参考资料
+
+1. [Wikipedia(zh):可靠性工程](https://zh.wikipedia.org/wiki/%E5%8F%AF%E9%9D%A0%E6%80%A7%E5%B7%A5%E7%A8%8B):提高系统可靠性的工程方法
+2. [Wikipedia(zh):熔断器模式](https://zh.wikipedia.org/wiki/%E7%86%94%E6%96%AD%E5%99%A8%E6%A8%A1%E5%BC%8F):故障时快速失败以避免扩散
+
+### 思考题答案(仅供参考)
+
+#### 思考题 1
+
+  因为故障在真实环境中无法避免:网络会断、服务会挂、磁盘会满。只有把故障当作常态来设计(超时、降级、恢复等),才能在问题出现时保持可用,而不是一遇意外就整体崩溃。
+
+#### 思考题 2
+
+  因为盲目重试可能雪上加霜或造成重复执行:如果操作其实已成功、只是确认丢失,重试就会把同一件事做两遍;故障时无限重试还会加重系统负担。因此重试要有次数上限、采用退避,并尽量让操作可安全重复执行。
+
+## 协议
+
+  本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)进行许可。
+
+## 封面图
+
+![](https://raw.githubusercontent.com/TinySnow/computer-science-guide-resources/master/computer-science-guide/cover/软件构造/可靠性与故障处理.png)
+
+> 设计师 | 南国微雪

src/学习与进步/计算机科学极简入门指南/软件构造/第两百九十章:协作与代码审查.md

+90 / -0 Click to expand diff
diff --git "a/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\344\271\235\345\215\201\347\253\240\357\274\232\345\215\217\344\275\234\344\270\216\344\273\243\347\240\201\345\256\241\346\237\245.md" "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\344\271\235\345\215\201\347\253\240\357\274\232\345\215\217\344\275\234\344\270\216\344\273\243\347\240\201\345\256\241\346\237\245.md"
new file mode 100644
index 00000000..a77473bd
--- /dev/null
+++ "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\344\271\235\345\215\201\347\253\240\357\274\232\345\215\217\344\275\234\344\270\216\344\273\243\347\240\201\345\256\241\346\237\245.md"
@@ -0,0 +1,90 @@
+# 协作与代码审查
+
+## 复习
+
+- 版本控制:保存变更、支持多人并行
+- 分解、模块与接口:模块化分工
+- 契约、错误与异常:明确责任
+
+## TL;DR
+
+- 多人协作需要规则:谁改了什么、如何合入
+- 代码审查让代码在被合并前被别人看过
+- 审查能发现错误、传播知识、统一风格
+- 它是质量与团队成长的共同手段
+
+## 正文
+
+  版本控制让多人可以并行工作。可代码终究是人写的、给人看的——**一群人如何高效、可靠地合作**,还有不少讲究。
+
+### 协作的挑战
+
+  多人一起写代码,会遇到一些天然的问题:
+
+- **理解**:别人写的代码,你看得懂吗?
+- **风格**:每个人习惯不同,代码会不会乱成一团?
+- **冲突**:两人改了同一处,怎么处理?
+- **质量**:谁能保证每一份改动都是靠谱的?
+
+  版本控制解决了“合并”层面的问题,但“质量”和“理解”,还需要另一项实践——**代码审查**(code review)。
+
+### 代码审查:让代码被看见
+
+  代码审查的做法是:**一份改动在正式合入之前,先由其他人看过、提出意见。**
+
+  它带来的好处,往往超出“找 bug”本身:
+
+- **发现错误**:多一双眼睛,就多一层把关
+- **传播知识**:作者讲清思路,审阅者学到新东西,团队的知识不再只在一个人脑子里
+- **统一风格**:大家逐渐形成一致的写法,代码更整齐
+- **集体负责**:审查过的代码,是团队共同认可的,而不是某个人的“私产”
+
+  可以说,代码审查既提升了代码质量,也提升了**团队本身**。
+
+### 有些约定是必要的
+
+  多人协作,离不开一些共同的约定:代码风格、提交规范、审查流程、分支策略……这些约定可能显得琐碎,却能让协作顺畅很多。
+
+  这又回到了我们熟悉的主线:**接口与约定,让独立的个体能够高效地拼接在一起。** 只不过这次,被“约定”连接起来的,是人。
+
+ **思考题 1** 
+
+>   代码审查有哪些好处?
+
+ **思考题 2** 
+
+>   多人协作时,为什么需要一些共同约定?
+
+## 小结
+
+### 知识点
+
+- 多人协作面临理解、风格、冲突与质量挑战
+- 代码审查在合入前由他人检查改动
+- 审查能找错、传知识、统一风格、促进集体负责
+- 共同约定让协作更顺畅
+
+### 参考资料
+
+1. [Wikipedia(zh):代码审查](https://zh.wikipedia.org/wiki/%E4%BB%A3%E7%A0%81%E5%AE%A1%E6%9F%A5):合并前由他人检查代码改动
+2. [Wikipedia(zh):版本控制](https://zh.wikipedia.org/wiki/%E7%89%88%E6%9C%AC%E6%8E%A7%E5%88%B6):支持多人协作的基础工具
+
+### 思考题答案(仅供参考)
+
+#### 思考题 1
+
+  它能发现错误(多一层把关)、传播知识(作者与审阅者互相学习)、统一风格(形成一致的写法),并让代码成为团队共同认可的成果。既提升了代码质量,也促进了团队成长。
+
+#### 思考题 2
+
+  因为一群人的习惯和思路各不相同。约定代码风格、提交流程、审查与分支规则等,能减少误解与冲突,让各自独立的改动顺利拼接、协同推进,正如接口让模块能协作一样。
+
+## 协议
+
+  本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)进行许可。
+
+## 封面图
+
+![](https://raw.githubusercontent.com/TinySnow/computer-science-guide-resources/master/computer-science-guide/cover/软件构造/协作与代码审查.png)
+
+> 设计师 | 南国微雪

src/学习与进步/计算机科学极简入门指南/软件构造/第两百八十七章:单元测试.md

+88 / -0 Click to expand diff
diff --git "a/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\345\205\253\345\215\201\344\270\203\347\253\240\357\274\232\345\215\225\345\205\203\346\265\213\350\257\225.md" "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\345\205\253\345\215\201\344\270\203\347\253\240\357\274\232\345\215\225\345\205\203\346\265\213\350\257\225.md"
new file mode 100644
index 00000000..6191a00a
--- /dev/null
+++ "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\345\205\253\345\215\201\344\270\203\347\253\240\357\274\232\345\215\225\345\205\203\346\265\213\350\257\225.md"
@@ -0,0 +1,88 @@
+# 单元测试
+
+## 复习
+
+- 契约、错误与异常:明确的前提与承诺
+- 从算法到软件:软件需要长期维护
+- 日志与调试:问题要能被定位
+
+## TL;DR
+
+- 单元测试针对最小的功能单元验证行为
+- 它小而快,可以反复自动运行
+- 它保护模块的行为不被后续修改破坏
+- 好的单元测试聚焦“一个行为”,输入输出清晰
+
+## 正文
+
+  软件要长期维护,就意味着代码会不断被修改。怎么保证“改这里,不会弄坏那里”?一个核心手段是**单元测试**(unit testing)。
+
+### 测最小的单元
+
+  单元测试的“单元”,通常指**最小的可测试功能块**——一个函数、一个方法。
+
+  测试的写法很直接:**准备输入,调用它,然后检查输出是否符合预期。** 比如测一个“求最大值”的函数:给 `[3, 1, 2]`,期望得到 `3`;给它一个空数组,期望按约定处理。
+
+  它和上一章的“契约”正好呼应:**单元测试,就是在验证这个单元有没有履行它的承诺。**
+
+### 小而快,还能反复跑
+
+  单元测试有几个讨喜的特点:
+
+- **小**:只测一个功能,目的明确
+- **快**:不依赖复杂的运行环境,几毫秒就能跑完
+- **可自动化、可反复**:改完代码,一键全跑一遍
+
+  前两点让它写起来轻松,第三点才是它最大的价值。
+
+### 它保护“已有功能”
+
+  单元测试最强大的作用,其实是**保护已经写好的行为**:一旦某个改动破坏了原有功能,那个对应的测试就会立刻失败,把问题**当场**暴露出来。这就是所谓的**回归保护**。
+
+  于是,你在重构或加功能时,就有了底气:**只要测试还是绿的,就说明原来的承诺依然成立。**
+
+  它还能当**活文档**:一个测试用例,等于一个“这个函数该怎么用”的清晰例子。比注释更可靠,因为它能被真正执行、被验证。
+
+  **把“我改的对不对”这件事,交给机器反复替你确认**——这正是单元测试的价值。
+
+ **思考题 1** 
+
+>   单元测试的“单元”指什么?它有什么好处?
+
+ **思考题 2** 
+
+>   为什么单元测试能“保护”已有功能?
+
+## 小结
+
+### 知识点
+
+- 单元测试针对最小的功能单元验证行为
+- 它准备输入、检查输出,验证契约
+- 小而快、可自动化反复运行
+- 能提供回归保护,并充当活文档
+
+### 参考资料
+
+1. [Wikipedia(zh):单元测试](https://zh.wikipedia.org/wiki/%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95):针对最小功能单元的测试
+2. [Wikipedia(zh):回归测试](https://zh.wikipedia.org/wiki/%E5%9B%9E%E5%BD%92%E6%B5%8B%E8%AF%95):确认修改未破坏既有功能
+
+### 思考题答案(仅供参考)
+
+#### 思考题 1
+
+  “单元”通常指最小的可测试功能块,例如一个函数或方法。好处是目的明确、编写与运行都很轻快,可自动化反复执行,能及早发现问题,也便于验证函数是否履行契约。
+
+#### 思考题 2
+
+  因为它把该单元应有的行为固化成了可反复运行的检查。一旦后续修改破坏了原有行为,对应测试就会立刻失败,从而当场暴露问题,起到“回归保护”的作用。
+
+## 协议
+
+  本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)进行许可。
+
+## 封面图
+
+![](https://raw.githubusercontent.com/TinySnow/computer-science-guide-resources/master/computer-science-guide/cover/软件构造/单元测试.png)
+
+> 设计师 | 南国微雪

src/学习与进步/计算机科学极简入门指南/软件构造/第两百八十三章:需求与边界.md

+88 / -0 Click to expand diff
diff --git "a/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\345\205\253\345\215\201\344\270\211\347\253\240\357\274\232\351\234\200\346\261\202\344\270\216\350\276\271\347\225\214.md" "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\345\205\253\345\215\201\344\270\211\347\253\240\357\274\232\351\234\200\346\261\202\344\270\216\350\276\271\347\225\214.md"
new file mode 100644
index 00000000..aaf42d24
--- /dev/null
+++ "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\345\205\253\345\215\201\344\270\211\347\253\240\357\274\232\351\234\200\346\261\202\344\270\216\350\276\271\347\225\214.md"
@@ -0,0 +1,88 @@
+# 需求与边界
+
+## 复习
+
+- 从算法到软件:软件要长期使用与维护
+- 抽象:加一层,把复杂藏起来
+- 输入与输出:程序处理数据并给出结果
+
+## TL;DR
+
+- 需求是“要解决什么问题”
+- 边界是“明确不解决什么”
+- 说不清边界,就容易做不完、做错方向
+- 先弄清需求,再动手
+
+## 正文
+
+  做软件的第一步,不是写代码,而是搞清楚**要做什么**。这听起来是废话,却是最多项目栽跟头的地方。
+
+### 需求:到底要解决什么
+
+  **需求**(requirement)描述的是:**用户真正想要解决的问题,以及希望达到的效果。**
+
+  这和算法题很不一样。算法题里,输入、输出、规则都被出题人规定好了;而真实的软件里,用户往往只说一句“我想要个方便记账的东西”——细节全是模糊的。**把模糊的期望,变成清晰、可实现的目标,就是需求分析。**
+
+### 边界:明确不做什么
+
+  比“要做什么”更容易被忽略的,是“**不做什么**”。
+
+  任何一个软件都不可能承担无限多的功能。如果不划清**边界**(scope),就很容易陷入一种困境:
+
+- 功能越加越多,永远做不完
+- 中途发现方向不对,返工代价巨大
+- 该做的没做,不该做的却花了大功夫
+
+  所以,**明确“本次不做 X”,和明确“要做 Y”同样重要。** 边界不是偷懒,而是把有限的精力集中在真正重要的目标上。
+
+### 说不清的代价
+
+  需求不清、边界不明,会带来一连串问题:
+
+- 你以为要做 A,用户以为要做 B,最后谁都不满意
+- 做着做着发现漏了关键前提,推倒重来
+- 无法判断“什么时候算做完了”
+
+  **“解决问题,先从理解问题开始。”** 这也是整个指南反复强调的一句话——在写第一行代码之前,先花时间把问题想清楚,往往能省下后面成倍的返工。
+
+ **思考题 1** 
+
+>   为什么“明确不做什么”和“明确做什么”同样重要?
+
+ **思考题 2** 
+
+>   需求没弄清就动手,可能带来什么后果?
+
+## 小结
+
+### 知识点
+
+- 需求是用户真正要解决的问题与目标
+- 边界明确本次要做什么、不做什么
+- 需求模糊、边界不清会导致返工与失控
+- 动手前先把问题想清楚
+
+### 参考资料
+
+1. [Wikipedia(zh):需求分析](https://zh.wikipedia.org/wiki/%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90):明确并整理用户的需求
+2. [Wikipedia(zh):范围 (项目管理)](https://zh.wikipedia.org/wiki/%E8%8C%83%E5%9B%B4_(%E9%A1%B9%E7%9B%AE%E7%AE%A1%E7%90%86)):项目要完成与不完成的内容
+
+### 思考题答案(仅供参考)
+
+#### 思考题 1
+
+  因为资源与时间都有限。若不划清“不做什么”,功能会无限膨胀,导致做不完、方向失控;明确边界才能把精力集中在真正重要的目标上,也才能判断“何时算完成”。
+
+#### 思考题 2
+
+  可能出现做出来的不是用户想要的、漏掉关键前提而返工、或者无法判断何时完成等问题。理解错了问题,越努力反而错得越远,返工的代价也更大。
+
+## 协议
+
+  本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)进行许可。
+
+## 封面图
+
+![](https://raw.githubusercontent.com/TinySnow/computer-science-guide-resources/master/computer-science-guide/cover/软件构造/需求与边界.png)
+
+> 设计师 | 南国微雪

src/学习与进步/计算机科学极简入门指南/软件构造/第两百八十九章:版本控制.md

+90 / -0 Click to expand diff
diff --git "a/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\345\205\253\345\215\201\344\271\235\347\253\240\357\274\232\347\211\210\346\234\254\346\216\247\345\210\266.md" "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\345\205\253\345\215\201\344\271\235\347\253\240\357\274\232\347\211\210\346\234\254\346\216\247\345\210\266.md"
new file mode 100644
index 00000000..766a9a96
--- /dev/null
+++ "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\345\205\253\345\215\201\344\271\235\347\253\240\357\274\232\347\211\210\346\234\254\346\216\247\345\210\266.md"
@@ -0,0 +1,90 @@
+# 版本控制
+
+## 复习
+
+- 从算法到软件:软件需要长期维护
+- 单元测试:修改不应破坏已有功能
+- 文件:数据可以长期保存
+
+## TL;DR
+
+- 版本控制保存代码变化的历史
+- 它让你可以随时回到任意一个过去的状态
+- 分支让你安全地尝试修改
+- 它是多人协作的基础
+
+## 正文
+
+  软件会被反复修改。这就带来一个问题:**改了之后要是改坏了,怎么退回去?多个人同时改,怎么不打架?**
+
+  解决这些问题的工具,叫**版本控制**(version control)。
+
+### 给代码保存历史
+
+  版本控制最基本的作用,是**给代码的每一次变化留个记录**。你可以把它想成一个“无限撤销”的存档系统:
+
+- 每次修改,提交一次,形成一份**历史记录**
+- 任何时候,都能查看“某文件昨天长什么样”“这行是谁、什么时候改的”
+- 出了问题,可以回到任意一个过去的版本
+
+  有了它,“改坏了回不去”这个噩梦就消失了。**每一次修改都是安全的,因为随时可以退回。**
+
+### 分支:安全的试验田
+
+  版本控制还有个很强大的能力:**分支**(branch)。
+
+  分支让你从主线“岔”出一条独立的线路,在上面大胆改动,**完全不影响主线**。改得满意,就把它合并回去;改得不满意,直接丢掉,主线毫发无损。
+
+  这让“尝试一个不确定的想法”变得毫无压力:反正搞砸了也不影响别人。**先在一个隔离的副本上大胆试,成功了再合入**——又一次是“隔离变化”的思路。
+
+### 协作的基础
+
+  版本控制还解决了多人协作的核心难题:
+
+- **谁改了什么**:每次提交都带着作者与说明
+- **冲突怎么解**:两人改了同一处,工具会提示并协助合并
+- **如何并行**:各人在自己的分支上工作,最后统一合入
+
+  可以说,**现代软件开发,没有版本控制是无法想象的。** 它保存的不只是代码,更是整个团队协作的“历史与秩序”。
+
+ **思考题 1** 
+
+>   版本控制解决了什么问题?
+
+ **思考题 2** 
+
+>   分支为什么能让修改更安全?
+
+## 小结
+
+### 知识点
+
+- 版本控制保存代码的变更历史
+- 可查看历史、追溯责任、回退版本
+- 分支提供隔离的试验空间
+- 支持多人协作:记录、合并与冲突处理
+
+### 参考资料
+
+1. [Wikipedia(zh):版本控制](https://zh.wikipedia.org/wiki/%E7%89%88%E6%9C%AC%E6%8E%A7%E5%88%B6):记录与管理文件的变更历史
+2. [Wikipedia(zh):分支 (版本控制)](https://zh.wikipedia.org/wiki/%E5%88%86%E6%94%AF_(%E7%89%88%E6%9C%AC%E6%8E%A7%E5%88%B6)):从主线分离出的独立开发线
+
+### 思考题答案(仅供参考)
+
+#### 思考题 1
+
+  它保存代码每次变更的历史,让你能查看过去的状态、追溯某行改动的来源、在出问题时回退到旧版本,并为多人协作提供记录、合并与冲突处理的基础。
+
+#### 思考题 2
+
+  因为分支是一条独立的开发线,在其上所做的修改不会影响主线。你可以在分支上大胆尝试,成功就合并、失败就丢弃,主线始终不受牵连,因此修改更安全。
+
+## 协议
+
+  本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)进行许可。
+
+## 封面图
+
+![](https://raw.githubusercontent.com/TinySnow/computer-science-guide-resources/master/computer-science-guide/cover/软件构造/版本控制.png)
+
+> 设计师 | 南国微雪

src/学习与进步/计算机科学极简入门指南/软件构造/第两百八十二章:从算法到软件.md

+89 / -0 Click to expand diff
diff --git "a/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\345\205\253\345\215\201\344\272\214\347\253\240\357\274\232\344\273\216\347\256\227\346\263\225\345\210\260\350\275\257\344\273\266.md" "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\345\205\253\345\215\201\344\272\214\347\253\240\357\274\232\344\273\216\347\256\227\346\263\225\345\210\260\350\275\257\344\273\266.md"
new file mode 100644
index 00000000..ed40689e
--- /dev/null
+++ "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\345\205\253\345\215\201\344\272\214\347\253\240\357\274\232\344\273\216\347\256\227\346\263\225\345\210\260\350\275\257\344\273\266.md"
@@ -0,0 +1,89 @@
+# 从算法到软件
+
+## 复习
+
+- 编译器总装:源码能被编译、链接并运行
+- 数据结构与算法:用合适的结构与算法高效解决问题
+- 测试:正确之外,还要考虑出错与验证
+
+## TL;DR
+
+- 能算出答案,不等于软件已经完成
+- 软件还要被人使用、阅读、修改和维护
+- 它面对真实输入、真实用户与长期变化
+- 算法是软件的核心,但不是全部
+
+## 正文
+
+  走到这里,你已经能让一段程序正确地跑起来,也能为它挑选合适的算法。但一个很现实的问题摆在面前:**“能算出答案的程序”,离一个真正的“软件”,还差多远?**
+
+### 一次性程序 vs 长期使用的软件
+
+  很多练习里的程序是**一次性**的:输入给定、输出正确,就完事了。可真实的软件不是这样。它会:
+
+- 被很多人**长期使用**
+- 收到各种**不规范的输入**(错的、空的、超大的)
+- 被反复**阅读和修改**
+- 在需求和环境不断变化中**存活很多年**
+
+  这就要求它不只是“算得对”,还要**易读、易改、不易坏、出错时能查**。这些,正是**软件构造**(software construction)关心的东西。
+
+### 差在哪
+
+  一个算法正确的程序,可能还缺这些:
+
+- **没弄清需求**:做出来的不是用户真正要的
+- **一团乱麻**:没人看得懂,也没人敢改
+- **一碰就碎**:边界输入、异常情况就崩
+- **出了问题查不出**:没有日志,无法定位
+- **多人没法协作**:代码无法合并、无法维护
+
+  **算法解决“怎么算”,软件构造解决“怎么把它做成一个能长期用的东西”。** 前者是核心,后者是让核心真正落地的过程。
+
+### 这一部分要讲什么
+
+  接下来的章节,会依次覆盖:从需求到设计、找错与验证、协作与构建、运行与维护。它们大多不是“算法”,却决定了一个软件能不能真正被用起来。
+
+  这不意味着算法不重要——恰恰相反,**好的算法是软件的骨架**。只是当程序要走向真实世界时,还需要在骨架上,装上能让它长久运转的血肉。
+
+ **思考题 1** 
+
+>   “能算出正确答案”的程序,为什么还不等于一个软件?
+
+ **思考题 2** 
+
+>   软件与一次性的程序,最大的区别是什么?
+
+## 小结
+
+### 知识点
+
+- 算法正确不等于软件完成
+- 软件需长期使用、阅读与维护
+- 要应对真实输入与异常情况
+- 软件构造关注“如何做成可长期使用的东西”
+
+### 参考资料
+
+1. [Wikipedia(zh):软件工程](https://zh.wikipedia.org/wiki/%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B):系统化地开发与维护软件
+2. [Wikipedia(zh):软件构造](https://zh.wikipedia.org/wiki/%E8%BD%AF%E4%BB%B6%E6%9E%84%E5%BB%BA):把设计落实为可运行软件的过程
+
+### 思考题答案(仅供参考)
+
+#### 思考题 1
+
+  因为它可能只对给定的输入算得对,却未必能应对真实世界的各种输入,也未必易读、易改、易维护,更未必在出错时便于排查。软件不仅要“算得对”,还要能长期、可靠地被使用和维护。
+
+#### 思考题 2
+
+  一次性程序只求某次运行结果正确;软件则要被长期使用、反复修改,面对真实用户与不完美输入,还要多人协作、可读可维护。也就是说,软件要在“时间和变化”中存活,而不只是算对一次。
+
+## 协议
+
+  本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)进行许可。
+
+## 封面图
+
+![](https://raw.githubusercontent.com/TinySnow/computer-science-guide-resources/master/computer-science-guide/cover/软件构造/从算法到软件.png)
+
+> 设计师 | 南国微雪

src/学习与进步/计算机科学极简入门指南/软件构造/第两百八十五章:契约、错误与异常.md

+91 / -0 Click to expand diff
diff --git "a/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\345\205\253\345\215\201\344\272\224\347\253\240\357\274\232\345\245\221\347\272\246\343\200\201\351\224\231\350\257\257\344\270\216\345\274\202\345\270\270.md" "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\345\205\253\345\215\201\344\272\224\347\253\240\357\274\232\345\245\221\347\272\246\343\200\201\351\224\231\350\257\257\344\270\216\345\274\202\345\270\270.md"
new file mode 100644
index 00000000..f13c8d39
--- /dev/null
+++ "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\345\205\253\345\215\201\344\272\224\347\253\240\357\274\232\345\245\221\347\272\246\343\200\201\351\224\231\350\257\257\344\270\216\345\274\202\345\270\270.md"
@@ -0,0 +1,91 @@
+# 契约、错误与异常
+
+## 复习
+
+- 分解、模块与接口:模块通过接口交流
+- 异常处理:错误跨调用传播
+- 类型系统:约束合法操作
+
+## TL;DR
+
+- 每个函数或模块,都有一份“契约”:前提与承诺
+- 输入不符合预期时,应当明确地失败
+- 错误有不同类型:调用方错误、可恢复、不可恢复
+- 明确失败,远好过悄悄出错
+
+## 正文
+
+  模块之间通过接口交流。可接口上说的,不只是“函数叫什么、参数是什么类型”,还有一份更深层的约定——**契约**(contract)。
+
+### 契约:前提与承诺
+
+  一份契约,通常包含:
+
+- **前置条件**(前提):调用方必须满足什么(比如“这个参数不能为负”)
+- **后置条件**(承诺):只要前提满足,被调方保证做到什么
+- **不变式**:在整个过程中始终成立的性质
+
+  可以把函数看成一份“合同”:**你按前提调用我,我按承诺给你结果。** 双方各守其责,合作才顺利。
+
+### 前提不满足时怎么办
+
+  现实里,调用方未必守约——传了非法参数、给了坏数据。此时,被调方不能装作没事,而应**明确地失败**。
+
+  具体怎么做,取决于错误的性质:
+
+- **调用方的错误**:比如参数非法。通常应当明确报错(抛出异常或返回错误),让问题立刻暴露
+- **可恢复的错误**:比如文件暂时打不开。可以返回错误,让调用方决定重试或走别的路
+- **不可恢复的错误**:比如内存耗尽。可能只能终止,但要尽量留下信息
+
+  关键原则是:**错误要被明确地处理,而不是被悄悄吞掉。**
+
+### 为什么“明确失败”更重要
+
+  有两类糟糕的做法,都和“不明确”有关:
+
+- **悄悄出错**:出了问题却装作正常,返回一个看似合理其实是错的结果。调用方毫不知情,错误会一路扩散,最后在一个离得很远的地方爆发,极难排查
+- **吞掉错误**:捕获了异常却什么都不做。问题被“藏”起来,下次再犯还是查不出原因
+
+  **让错误尽早、明确地暴露,比让它悄悄蔓延安全得多。** 这也呼应了异常处理的思路:错误的代价,往往不在于它发生了,而在于它被掩盖了。
+
+ **思考题 1** 
+
+>   一个函数的“契约”,通常包括什么?
+
+ **思考题 2** 
+
+>   为什么“悄悄出错”,比“明确失败”更危险?
+
+## 小结
+
+### 知识点
+
+- 契约含前置条件、后置条件与不变式
+- 输入不符合预期时应当明确失败
+- 错误可分为调用方错误、可恢复与不可恢复
+- 明确失败优于悄悄出错或吞掉错误
+
+### 参考资料
+
+1. [Wikipedia(zh):契约式设计](https://zh.wikipedia.org/wiki/%E5%A5%91%E7%BA%A6%E5%BC%8F%E8%AE%BE%E8%AE%A1):以契约描述模块的责任
+2. [Wikipedia(zh):异常处理](https://zh.wikipedia.org/wiki/%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86):明确处理错误的方式
+
+### 思考题答案(仅供参考)
+
+#### 思考题 1
+
+  通常包括前置条件(调用方必须满足的约束)、后置条件(满足前提后被调方保证的结果),有的还包含不变式(过程中始终成立的性质)。它们共同约定双方的责任。
+
+#### 思考题 2
+
+  因为悄悄出错会让调用方以为一切正常,错误随之向远处扩散,最终在一个不相干的地方爆发,极难定位;而明确失败能让问题在发生处立刻暴露,便于及时发现和处理。被掩盖的错误,代价往往更大。
+
+## 协议
+
+  本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)进行许可。
+
+## 封面图
+
+![](https://raw.githubusercontent.com/TinySnow/computer-science-guide-resources/master/computer-science-guide/cover/软件构造/契约、错误与异常.png)
+
+> 设计师 | 南国微雪

src/学习与进步/计算机科学极简入门指南/软件构造/第两百八十八章:集成测试与系统测试.md

+86 / -0 Click to expand diff
diff --git "a/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\345\205\253\345\215\201\345\205\253\347\253\240\357\274\232\351\233\206\346\210\220\346\265\213\350\257\225\344\270\216\347\263\273\347\273\237\346\265\213\350\257\225.md" "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\345\205\253\345\215\201\345\205\253\347\253\240\357\274\232\351\233\206\346\210\220\346\265\213\350\257\225\344\270\216\347\263\273\347\273\237\346\265\213\350\257\225.md"
new file mode 100644
index 00000000..cddd8555
--- /dev/null
+++ "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\345\205\253\345\215\201\345\205\253\347\253\240\357\274\232\351\233\206\346\210\220\346\265\213\350\257\225\344\270\216\347\263\273\347\273\237\346\265\213\350\257\225.md"
@@ -0,0 +1,86 @@
+# 集成测试与系统测试
+
+## 复习
+
+- 单元测试:验证最小功能单元
+- 分解、模块与接口:模块通过接口交流
+- 契约、错误与异常:明确的前提与承诺
+
+## TL;DR
+
+- 集成测试验证多个模块组合后能否协作
+- 系统测试从整体验证端到端流程
+- 问题常出在“结合处”,单元测试抓不到
+- 不同层次的测试各有分工
+
+## 正文
+
+  单元测试保证了每个模块“自己是对的”。可多个正确的模块放到一起,就一定能正常工作吗?不一定。因为**问题常常出在它们交接的地方。**
+
+### 结合处的麻烦
+
+  模块之间通过接口交流。哪怕每个模块都通过了单元测试,它们组合起来仍可能出问题:
+
+- A 以为传给 B 的是整数,B 却以为是字符串
+- A 在 B 还没准备好时就调用了它
+- 数据在一连串传递中,被某一方悄悄改坏
+
+  这些“结合处”的毛病,单元测试抓不到——因为单元测试时,其他模块还不在场。要抓它们,就需要**集成测试**(integration testing):**把若干模块连起来,验证它们真的能协作。**
+
+### 从整体验证端到端
+
+  再往上,还有**系统测试**(system testing):不再盯着某些模块,而是把整个软件当作一个黑盒,从用户的角度走一遍**完整的流程**——输入一个请求,看最终输出对不对。
+
+  它验证的是“**端到端**”的效果:这一整条路,从入口到出口,通不通、对不对。
+
+### 不同层次的测试
+
+  把几层测试放在一起看,它们各管一段:
+
+- **单元测试**:这个模块自己对不对
+- **集成测试**:几个模块拼起来协不协作
+- **系统测试**:整个软件端到端对不对
+
+  这和网络故障诊断里的“分层排查”是一个味道:**越往上,范围越大、越接近真实使用,但定位问题也越难。** 所以实践中的常见做法是——底层测试写得多、跑得勤,越往上的测试相对越少但更贴近实际。上面出问题时,往往要靠下面的测试来缩小范围。
+
+ **思考题 1** 
+
+>   为什么要单独做集成测试,而不是只做单元测试?
+
+ **思考题 2** 
+
+>   单元测试、集成测试、系统测试各关注什么?
+
+## 小结
+
+### 知识点
+
+- 集成测试验证多个模块的协作
+- 结合处的问题单元测试抓不到
+- 系统测试从整体验证端到端流程
+- 各层测试分工不同,越往上范围越大
+
+### 参考资料
+
+1. [Wikipedia(zh):集成测试](https://zh.wikipedia.org/wiki/%E9%9B%86%E6%88%90%E6%B5%8B%E8%AF%95):验证多个模块组合后的行为
+2. [Wikipedia(zh):系统测试](https://zh.wikipedia.org/wiki/%E7%B3%BB%E7%BB%9F%E6%B5%8B%E8%AF%95):从整体验证整个系统
+
+### 思考题答案(仅供参考)
+
+#### 思考题 1
+
+  因为单元测试只验证单个模块自己是否正确,无法覆盖模块之间的配合。接口不匹配、时序、数据传递等问题都发生在“结合处”,只有在模块组合起来之后才会暴露,因此需要专门的集成测试。
+
+#### 思考题 2
+
+  单元测试关注单个模块自己的行为是否正确;集成测试关注多个模块连接后能否正确协作;系统测试则把整个软件当作整体,从用户角度验证端到端流程是否通、结果是否正确。
+
+## 协议
+
+  本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)进行许可。
+
+## 封面图
+
+![](https://raw.githubusercontent.com/TinySnow/computer-science-guide-resources/master/computer-science-guide/cover/软件构造/集成测试与系统测试.png)
+
+> 设计师 | 南国微雪

src/学习与进步/计算机科学极简入门指南/软件构造/第两百八十六章:日志与调试.md

+91 / -0 Click to expand diff
diff --git "a/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\345\205\253\345\215\201\345\205\255\347\253\240\357\274\232\346\227\245\345\277\227\344\270\216\350\260\203\350\257\225.md" "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\345\205\253\345\215\201\345\205\255\347\253\240\357\274\232\346\227\245\345\277\227\344\270\216\350\260\203\350\257\225.md"
new file mode 100644
index 00000000..d95f1c27
--- /dev/null
+++ "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\345\205\253\345\215\201\345\205\255\347\253\240\357\274\232\346\227\245\345\277\227\344\270\216\350\260\203\350\257\225.md"
@@ -0,0 +1,91 @@
+# 日志与调试
+
+## 复习
+
+- 契约、错误与异常:错误应明确暴露
+- 网络故障诊断:分层排查问题
+- 从算法到软件:软件要正确,也要能被验证
+
+## TL;DR
+
+- 调试是从现象出发,逐步缩小错误范围
+- 日志是程序留下的“现场记录”
+- 先提出假设,再用证据验证
+- 好的日志让问题可被重现和定位
+
+## 正文
+
+  无论写得多小心,程序总会有 bug。**调试**(debugging)就是找出并修掉它们的过程。而**日志**(logging),是让这件事变得容易的关键工具。
+
+### 调试的思路
+
+  调试最忌讳的,是“凭感觉乱改代码”。更有效的做法是**科学一点**:
+
+1. **观察现象**:到底哪里不对、什么时候不对
+2. **提出假设**:猜一个可能的原因
+3. **设计验证**:怎么用最小的实验,证明或推翻这个假设
+4. **缩小范围**:根据结果,把问题可能存在的范围砍掉一半,重复
+
+  这和前面网络故障诊断里的“分层排查”是一个思路:**每次都把可能性砍掉一大块,而不是四处瞎猜。** 范围越小,离真相越近。
+
+### 让问题可重现
+
+  调试里有一句话:**能稳定重现的问题,就等于解决了一半。** 因为只有能重现,你才能反复试验、验证假设。
+
+  如果一个 bug 时有时无,那多半和某些**特定条件**有关——特定的输入、特定的时序、特定的环境。找到这些条件,让它稳定重现,往往比直接改代码更重要。
+
+### 日志:程序的现场记录
+
+  如果程序已经跑在用户那里、无法直接调试呢?这时**日志**就派上用场了。
+
+  日志是程序运行时留下的“现场记录”:它记下发生了什么、在哪一步、关键数据是什么。有了它,即使问题发生在别人的机器上,你也能“回放”出事发前后的情况。
+
+  好的日志有几个要点:
+
+- **有级别**:比如“普通信息 / 警告 / 错误”,便于按需查看
+- **有上下文**:记录足够的信息(时间、位置、关键变量),而不只是“出错了”
+- **不泄露敏感信息**:密码、密钥之类绝不能写进日志
+
+  **日志的价值,在于它把“事后无法复现的问题”,变成“有据可查的线索”。** 平时可能不起眼,出事时就是救命稻草。
+
+ **思考题 1** 
+
+>   调试时,为什么“先提出假设再验证”比“乱改代码”更有效?
+
+ **思考题 2** 
+
+>   为什么说“好的日志”能大幅缩短排查时间?
+
+## 小结
+
+### 知识点
+
+- 调试是观察、假设、验证、缩小范围的过程
+- 让问题稳定重现往往事半功倍
+- 日志记录程序运行时的关键信息
+- 日志应有级别、有上下文且不泄露敏感信息
+
+### 参考资料
+
+1. [Wikipedia(zh):调试](https://zh.wikipedia.org/wiki/%E8%B0%83%E8%AF%95):定位并修复程序错误
+2. [Wikipedia(zh):日志文件](https://zh.wikipedia.org/wiki/%E6%97%A5%E5%BF%97%E6%96%87%E4%BB%B6):程序运行时记录的信息
+
+### 思考题答案(仅供参考)
+
+#### 思考题 1
+
+  因为“先假设再验证”能让每一步都有依据地缩小问题范围,方向明确、可累积。乱改代码则可能掩盖真因、引入新问题,即便偶然修好也不知道为什么,效率低且不可靠。
+
+#### 思考题 2
+
+  因为日志记录了程序运行时的现场信息。当问题难以直接复现(比如发生在用户处)时,日志能帮助还原出事的条件与经过,从而快速定位线索,避免“两眼一抹黑”地猜测。
+
+## 协议
+
+  本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)进行许可。
+
+## 封面图
+
+![](https://raw.githubusercontent.com/TinySnow/computer-science-guide-resources/master/computer-science-guide/cover/软件构造/日志与调试.png)
+
+> 设计师 | 南国微雪

src/学习与进步/计算机科学极简入门指南/软件构造/第两百八十四章:分解、模块与接口.md

+87 / -0 Click to expand diff
diff --git "a/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\345\205\253\345\215\201\345\233\233\347\253\240\357\274\232\345\210\206\350\247\243\343\200\201\346\250\241\345\235\227\344\270\216\346\216\245\345\217\243.md" "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\345\205\253\345\215\201\345\233\233\347\253\240\357\274\232\345\210\206\350\247\243\343\200\201\346\250\241\345\235\227\344\270\216\346\216\245\345\217\243.md"
new file mode 100644
index 00000000..8eb44641
--- /dev/null
+++ "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\350\275\257\344\273\266\346\236\204\351\200\240/\347\254\254\344\270\244\347\231\276\345\205\253\345\215\201\345\233\233\347\253\240\357\274\232\345\210\206\350\247\243\343\200\201\346\250\241\345\235\227\344\270\216\346\216\245\345\217\243.md"
@@ -0,0 +1,87 @@
+# 分解、模块与接口
+
+## 复习
+
+- 需求与边界:先弄清要做什么、不做什么
+- 抽象:加一层,把复杂藏起来
+- 函数:把一段工作命名、封装起来
+
+## TL;DR
+
+- 把大程序切成可独立理解、替换的模块
+- 模块之间通过接口交流
+- 好的分解能降低耦合、便于并行开发
+- 这和前面反复出现的“分层与封装”一脉相承
+
+## 正文
+
+  需求清楚了,接下来要面对的是“怎么把它搭出来”。一个稍大的软件,动辄成千上万行代码,直接从头写到尾,是没法维护的。办法还是那个老朋友:**分解**。
+
+### 切成人能理解的小块
+
+  把一个大程序,切成若干个**模块**(module):每个模块负责一块相对独立的职责。
+
+  切分的目标有两条,常被概括成一句话:
+
+- **高内聚**(high cohesion):一个模块内部做的事,应该彼此相关、围绕一个明确的职责
+- **低耦合**(low coupling):模块之间尽量少依赖、少纠缠
+
+  高内聚让一个模块“专注”,容易理解;低耦合让改动一个模块时,不容易牵连别的模块,容易维护。**这两条,是模块划分好不好的重要标准。**
+
+### 接口:模块之间怎么说话
+
+  模块之间总不能互相看透对方的内部。它们通过**接口**(interface)交流:**我对外承诺能提供什么,你需要我做什么,就按这个来。**
+
+  这正是我们前面讲过无数次的思想:**接口与实现分离**。只要接口不变,一个模块的内部怎么改,都不影响其他模块。于是:
+
+- 不同的人可以**并行开发**不同的模块(只要接口先定好)
+- 一个模块的实现可以**被替换**,只要还遵守原接口
+- 出了问题,可以**限定在某个模块内部**去查
+
+### 又一次呼应主线
+
+  分解、模块、接口,说到底还是那几件事:**把复杂问题拆小,用清晰的边界隔开,再用稳定的接口连接。**
+
+  从逻辑门到芯片、从函数到操作系统、从协议分层到编译器阶段——**“分层、封装、接口”这条主线,贯穿了整部教程。** 软件构造不过是把它用在了“代码组织”上而已。
+
+ **思考题 1** 
+
+>   为什么要把大程序分解成模块?
+
+ **思考题 2** 
+
+>   “高内聚、低耦合”大概是什么意思?
+
+## 小结
+
+### 知识点
+
+- 模块化把大程序切成独立的职责单元
+- 高内聚指模块内部职责集中
+- 低耦合指模块之间依赖少
+- 模块通过接口交流,接口稳定则内部可替换
+
+### 参考资料
+
+1. [Wikipedia(zh):模块化编程](https://zh.wikipedia.org/wiki/%E6%A8%A1%E5%9D%97%E5%8C%96%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1):把程序划分为独立模块
+2. [Wikipedia(zh):耦合性 (计算机科学)](https://zh.wikipedia.org/wiki/%E8%80%A6%E5%90%88%E6%80%A7_(%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6)):模块之间依赖的程度
+
+### 思考题答案(仅供参考)
+
+#### 思考题 1
+
+  因为一个稍大的程序直接从头写到尾难以理解和维护。分解成模块后,每块职责相对独立、便于单独理解和测试;接口稳定时还能并行开发和替换实现,出问题也更容易定位。
+
+#### 思考题 2
+
+  高内聚指一个模块内部的功能彼此相关、围绕一个明确职责,显得专注;低耦合指模块之间尽量少相互依赖。前者让模块好理解,后者让改动不易牵连他处,两者共同决定了模块划分的质量。
+
+## 协议
+
+  本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)进行许可。
+
+## 封面图
+
+![](https://raw.githubusercontent.com/TinySnow/computer-science-guide-resources/master/computer-science-guide/cover/软件构造/分解、模块与接口.png)
+
+> 设计师 | 南国微雪

更新记录(2026-09-20 08:46:53 +0000 | f745f47e)

Summary

  • Generated at: 2026-09-20 08:46:53 +0000
  • Base commit: f745f47e
  • Diff source: e1afb271f91fea4f8fb6332085536d65f837695f..f745f47e4d710d24fba797f551f9e1b8a86b0dea
  • Changed files: 5
  • Total lines: +364 / -0

Index

  1. src/SUMMARY.md +4 / -0
  2. src/学习与进步/计算机科学极简入门指南/编译原理/第两百七十九章:字节码与虚拟机.md +83 / -0
  3. src/学习与进步/计算机科学极简入门指南/编译原理/第两百七十八章:解释器.md +85 / -0
  4. src/学习与进步/计算机科学极简入门指南/编译原理/第两百八十一章:编译器总装.md +103 / -0
  5. src/学习与进步/计算机科学极简入门指南/编译原理/第两百八十章:即时编译(进阶).md +89 / -0

Diffs

src/SUMMARY.md

+4 / -0 Click to expand diff
diff --git a/src/SUMMARY.md b/src/SUMMARY.md
index b50c2ba5..63567599 100644
--- a/src/SUMMARY.md
+++ b/src/SUMMARY.md
@@ -829,6 +829,10 @@
   - [第两百七十五章:引用计数](学习与进步/计算机科学极简入门指南/编译原理/第两百七十五章:引用计数.md)
   - [第两百七十六章:追踪式垃圾回收](学习与进步/计算机科学极简入门指南/编译原理/第两百七十六章:追踪式垃圾回收.md)
   - [第两百七十七章:分代垃圾回收(进阶)](学习与进步/计算机科学极简入门指南/编译原理/第两百七十七章:分代垃圾回收(进阶).md)
+  - [第两百七十八章:解释器](学习与进步/计算机科学极简入门指南/编译原理/第两百七十八章:解释器.md)
+  - [第两百七十九章:字节码与虚拟机](学习与进步/计算机科学极简入门指南/编译原理/第两百七十九章:字节码与虚拟机.md)
+  - [第两百八十章:即时编译(进阶)](学习与进步/计算机科学极简入门指南/编译原理/第两百八十章:即时编译(进阶).md)
+  - [第两百八十一章:编译器总装](学习与进步/计算机科学极简入门指南/编译原理/第两百八十一章:编译器总装.md)
   - [附加章一:大数据](学习与进步/计算机科学极简入门指南/附加章/大数据.md)
   - [附加章二:数据加密](学习与进步/计算机科学极简入门指南/附加章/数据加密.md)
   - [附加章三:区块链](学习与进步/计算机科学极简入门指南/附加章/区块链.md)

src/学习与进步/计算机科学极简入门指南/编译原理/第两百七十九章:字节码与虚拟机.md

+83 / -0 Click to expand diff
diff --git "a/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\347\274\226\350\257\221\345\216\237\347\220\206/\347\254\254\344\270\244\347\231\276\344\270\203\345\215\201\344\271\235\347\253\240\357\274\232\345\255\227\350\212\202\347\240\201\344\270\216\350\231\232\346\213\237\346\234\272.md" "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\347\274\226\350\257\221\345\216\237\347\220\206/\347\254\254\344\270\244\347\231\276\344\270\203\345\215\201\344\271\235\347\253\240\357\274\232\345\255\227\350\212\202\347\240\201\344\270\216\350\231\232\346\213\237\346\234\272.md"
new file mode 100644
index 00000000..1d6f6417
--- /dev/null
+++ "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\347\274\226\350\257\221\345\216\237\347\220\206/\347\254\254\344\270\244\347\231\276\344\270\203\345\215\201\344\271\235\347\253\240\357\274\232\345\255\227\350\212\202\347\240\201\344\270\216\350\231\232\346\213\237\346\234\272.md"
@@ -0,0 +1,83 @@
+# 字节码与虚拟机
+
+## 复习
+
+- 解释器:直接读取程序结构并执行
+- 中间表示:与机器无关的通用形式
+- 语言运行时:程序运行时的支撑
+
+## TL;DR
+
+- 字节码是一种贴近执行、却与具体硬件无关的简化指令
+- 虚拟机负责解释执行字节码
+- 它比直接解释源码快,又能保持跨平台
+- “一次编译,到处运行”正是这个思路
+
+## 正文
+
+  直接解释源码比较慢,因为源码离机器太远。一个折中的办法是:先把源码翻译成一种**更接近执行、但又与具体机器无关**的形式,再交给一个“虚拟的机器”去跑。这种形式叫**字节码**(bytecode),执行它的软件叫**虚拟机**(VM,Virtual Machine)。
+
+### 字节码:比源码更“贴身”,比机器码更“通用”
+
+  字节码的设计,处在一个很舒服的中间位置:
+
+- 它比源码**简单、规整**:没有复杂的语法,是一条条简单的指令,解释起来快
+- 它又比机器码**通用**:不绑定某个具体的 CPU,任何装了对应虚拟机的地方都能跑
+
+  可以说,字节码就是我们前面讲的**中间表示**,在“执行”场景下的一个分身。
+
+### 一次编译,到处运行
+
+  这带来一个很受欢迎的好处:**“一次编译,到处运行”。**
+
+  程序源码只需要编译成一份字节码,之后在 Windows、Linux、各种机器上,只要有对应的虚拟机,就能执行同一份字节码——**不必为每种平台分别编译。** 这是把“跨平台”这件事,从“每个平台编译一遍”里抽了出来。
+
+### 虚拟机的角色
+
+  **虚拟机**是一个用软件模拟出来的“机器”:它认识字节码,逐条解释执行。对使用者来说,就像有一台专门为这门语言打造的、到处都有的虚拟计算机。
+
+  它比“直接解释源码”快(因为字节码简单、预处理好),又比“为每台机器静态编译”灵活(因为一份字节码走天下)。当然,它也多了一层:**虚拟机本身的解释开销**。
+
+  所以,字节码 + 虚拟机,是“灵活性”与“效率”之间的又一次折中。但它还能更进一步——下一章的即时编译,会让它在运行时也快起来。
+
+ **思考题 1** 
+
+>   字节码与真正的机器指令,有什么不同?
+
+ **思考题 2** 
+
+>   “一次编译,到处运行”是怎样通过字节码和虚拟机实现的?
+
+## 小结
+
+### 知识点
+
+- 字节码是贴近执行、与硬件无关的简化指令
+- 虚拟机解释执行字节码
+- 它比解释源码快,比静态编译灵活
+- 支持“一次编译,到处运行”
+
+### 参考资料
+
+1. [Wikipedia(zh):字节码](https://zh.wikipedia.org/wiki/%E5%AD%97%E8%8A%82%E7%A0%81):一种与硬件无关的指令形式
+2. [Wikipedia(zh):虚拟机](https://zh.wikipedia.org/wiki/%E8%99%9A%E6%8B%9F%E6%9C%BA):用软件模拟的计算机
+
+### 思考题答案(仅供参考)
+
+#### 思考题 1
+
+  真正的机器指令面向某个具体的 CPU,只有该硬件能直接执行;字节码则与具体硬件无关,是一种更简单、更规整的指令,需要由虚拟机来解释执行,因此可以在多种平台上通用。
+
+#### 思考题 2
+
+  源码只需编译成一份与平台无关的字节码,再由各平台上对应的虚拟机解释执行。因为字节码不绑定具体硬件,所以这份字节码能在装有虚拟机的不同平台上运行,从而实现“一次编译,到处运行”。
+
+## 协议
+
+  本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)进行许可。
+
+## 封面图
+
+![](https://raw.githubusercontent.com/TinySnow/computer-science-guide-resources/master/computer-science-guide/cover/编译原理/字节码与虚拟机.png)
+
+> 设计师 | 南国微雪

src/学习与进步/计算机科学极简入门指南/编译原理/第两百七十八章:解释器.md

+85 / -0 Click to expand diff
diff --git "a/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\347\274\226\350\257\221\345\216\237\347\220\206/\347\254\254\344\270\244\347\231\276\344\270\203\345\215\201\345\205\253\347\253\240\357\274\232\350\247\243\351\207\212\345\231\250.md" "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\347\274\226\350\257\221\345\216\237\347\220\206/\347\254\254\344\270\244\347\231\276\344\270\203\345\215\201\345\205\253\347\253\240\357\274\232\350\247\243\351\207\212\345\231\250.md"
new file mode 100644
index 00000000..7d2f4177
--- /dev/null
+++ "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\347\274\226\350\257\221\345\216\237\347\220\206/\347\254\254\344\270\244\347\231\276\344\270\203\345\215\201\345\205\253\347\253\240\357\274\232\350\247\243\351\207\212\345\231\250.md"
@@ -0,0 +1,85 @@
+# 解释器
+
+## 复习
+
+- 语言运行时:程序运行时的支撑
+- 编译器:把源码翻译成目标代码
+- 抽象语法树:程序结构的表示
+
+## TL;DR
+
+- 解释器直接读取程序结构并执行,不先生成机器码
+- 它逐条处理,灵活但通常较慢
+- 编译与解释,是“翻译”的两种时机
+- 两者各有适用场景,也常混合使用
+
+## 正文
+
+  前面一路讲的都是**编译**:先把源码翻译成机器码,再运行。但“把程序变成能跑的东西”,其实还有另一条路——**解释**。
+
+### 另一种做法:边读边执行
+
+  **解释器**(interpreter)不把程序整体翻译成机器码,而是**直接读取程序的结构,逐条执行**。它可以读源码,也可以读某种中间形式,然后一条一条地处理下去。
+
+  这就像两种翻译方式:
+
+- **编译**:先把整本书**翻译**好,再交给读者(生成完整的目标程序)
+- **解释**:请一位**同声传译**,你读到哪,它翻到哪(边读边执行)
+
+### 各自的脾气
+
+  解释执行有它的好处:
+
+- **灵活**:改一行代码就能立刻重新跑,适合开发和调试
+- **启动快**:不必先花时间编译整个程序
+- **跨平台**:只要有解释器,同一份程序就能跑
+
+  但代价也很明显:**慢。** 因为程序每次运行都要被“解释”一遍,同样的逻辑可能被反复分析,却无法像编译那样一次性生成高效的机器码。**灵活性,是用速度换来的。**
+
+### 编译与解释并不是对立的
+
+  别把两者看成非此即彼。现实中很多系统是**混合**的:先把源码编译成一种中间形式,再由解释器执行;甚至在执行过程中,把“跑得最勤”的部分进一步编译成机器码——那就是后面的**即时编译**。
+
+  所以,编译和解释不是谁取代谁,而是**在“提前翻译”与“边跑边翻”之间,各取所长**。接下来的两章,看看这种混合是怎么做的。
+
+ **思考题 1** 
+
+>   解释器和编译器,在“翻译时机”上有什么不同?
+
+ **思考题 2** 
+
+>   解释执行通常比编译执行慢,为什么?
+
+## 小结
+
+### 知识点
+
+- 解释器直接读取程序结构并逐条执行
+- 优点:灵活、启动快、跨平台
+- 代价:通常比编译执行慢
+- 编译与解释可以混合使用
+
+### 参考资料
+
+1. [Wikipedia(zh):解释器](https://zh.wikipedia.org/wiki/%E8%A7%A3%E9%87%8A%E5%99%A8):直接执行程序的软件
+2. [Wikipedia(zh):编译器](https://zh.wikipedia.org/wiki/%E7%B7%A8%E8%AD%AF%E5%99%A8):把源码翻译为目标代码的程序
+
+### 思考题答案(仅供参考)
+
+#### 思考题 1
+
+  编译器在程序运行**之前**就把整个程序翻译成目标代码,运行时直接执行机器码;解释器则在程序**运行时**边读边执行,不提前生成完整的机器码。区别在于翻译发生在运行前还是运行中。
+
+#### 思考题 2
+
+  因为解释执行需要边运行边分析、逐条处理,同一段逻辑每次运行都要再解释一遍,无法像编译那样一次性生成高效的机器码。额外的分析开销使它通常比编译后的机器码执行慢。
+
+## 协议
+
+  本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)进行许可。
+
+## 封面图
+
+![](https://raw.githubusercontent.com/TinySnow/computer-science-guide-resources/master/computer-science-guide/cover/编译原理/解释器.png)
+
+> 设计师 | 南国微雪

src/学习与进步/计算机科学极简入门指南/编译原理/第两百八十一章:编译器总装.md

+103 / -0 Click to expand diff
diff --git "a/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\347\274\226\350\257\221\345\216\237\347\220\206/\347\254\254\344\270\244\347\231\276\345\205\253\345\215\201\344\270\200\347\253\240\357\274\232\347\274\226\350\257\221\345\231\250\346\200\273\350\243\205.md" "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\347\274\226\350\257\221\345\216\237\347\220\206/\347\254\254\344\270\244\347\231\276\345\205\253\345\215\201\344\270\200\347\253\240\357\274\232\347\274\226\350\257\221\345\231\250\346\200\273\350\243\205.md"
new file mode 100644
index 00000000..f7d99b1c
--- /dev/null
+++ "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\347\274\226\350\257\221\345\216\237\347\220\206/\347\254\254\344\270\244\347\231\276\345\205\253\345\215\201\344\270\200\347\253\240\357\274\232\347\274\226\350\257\221\345\231\250\346\200\273\350\243\205.md"
@@ -0,0 +1,103 @@
+# 编译器总装
+
+## 复习
+
+- 解释器与即时编译:在“提前编译”与“边跑边编译”之间取舍
+- 编译器的分工:前端读懂、中端优化、后端生成
+- 从源代码到可执行文件:完整的流水线
+
+## TL;DR
+
+- 编译器把源码一步步变成可运行的机器码
+- 前端读懂、中端优化、后端生成,最后链接装载
+- 编译、解释、即时编译,各有取舍
+- 至此,从字符到运行的整条路已经打通
+
+## 正文
+
+  这是编译原理部分的最后一章。让我们像前面几个“总装”一样,把这段旅程完整地走一遍。
+
+### 一段源码的一生
+
+  从你敲下一段源代码,到它真正在机器上跑起来,它经历了许多形态:
+
+1. **字符**:源码首先是一串字符
+2. **词法单元**:词法分析把它切成一个个词
+3. **语法树**:语法分析把词组织成结构,再简化成抽象语法树
+4. **带类型的树**:语义分析补上类型、名字等信息
+5. **中间表示**:降低成三地址码等与机器无关的形式
+6. **优化后的 IR**:经过常量折叠、死代码删除、循环优化等
+7. **汇编**:后端选指令、分配寄存器、安排栈帧,生成汇编
+8. **目标文件**:汇编器把它变成机器码加符号、重定位信息
+9. **可执行文件**:链接器合并文件、完成重定位
+10. **运行**:操作系统装载它,机器开始执行
+
+  绕了一大圈,我们终于回到最开始那台**由逻辑门、寄存器、时钟搭起来的机器**上——只不过这次,它跑的是我们自己写的程序。**整部教程的前后,在这里接上了。**
+
+### 三种“让它跑起来”的方式
+
+  把这一部分的最后几章连起来看,让程序运行的方式主要有三种:
+
+- **编译**:提前翻译成机器码,运行快,但不灵活、且要针对平台
+- **解释**:运行中边读边执行,灵活,但慢
+- **即时编译**:运行中把热点编译成机器码,兼顾两者,但更复杂
+
+  它们没有绝对优劣,而是**在灵活、速度、复杂度之间,各取一个平衡点**。选择哪一种,取决于这门语言想服务什么场景。
+
+### 一些不变的东西
+
+  回头看,编译原理虽然庞杂,主线却很清晰:
+
+- **分层**:前端、中端、后端各司其职,一层层的表示逐级转换
+- **等价**:无论怎么改写,程序的行为不能变——这是优化的底线
+- **取舍**:快与灵活、全局与代价、编译时间与运行性能,处处都在权衡
+
+  这些,和整部教程里反复出现的主线,是同一回事。
+
+### 接下来
+
+  现在,机器已经能执行高级语言写成的软件,算法也让程序能跑得更快。但真正的软件,还要能被人**阅读、修改、测试和长期维护**。一个正确又高效的算法,距离一个可靠耐用的软件,还有最后一段路。
+
+  那就是整个指南的最后一个部分——**软件构造**。
+
+ **思考题 1** 
+
+>   回顾整个编译过程,程序大致经历了哪些形态?
+
+ **思考题 2** 
+
+>   编译、解释、即时编译三种方式,各自的取舍是什么?
+
+## 小结
+
+### 知识点
+
+- 源码在编译过程中经历字符、词、语法树、IR、汇编等多种形态
+- 前端读懂、中端优化、后端生成,链接与装载后运行
+- 编译、解释、即时编译各有取舍
+- 编译原理的主线是分层、等价与取舍
+
+### 参考资料
+
+1. [Wikipedia(zh):编译器](https://zh.wikipedia.org/wiki/%E7%B7%A8%E8%AD%AF%E5%99%A8):编译流程的总览
+2. [Wikipedia(zh):编译原理](https://zh.wikipedia.org/wiki/%E7%B7%A8%E8%AD%AF%E5%8E%9F%E7%90%86):编译器构造的理论与阶段
+
+### 思考题答案(仅供参考)
+
+#### 思考题 1
+
+  大致经历:字符 → 词法单元 → 语法树(抽象语法树)→ 带类型与名字信息的树 → 中间表示 → 优化后的中间表示 → 汇编 → 目标文件(机器码)→ 可执行文件,最后被装载运行。
+
+#### 思考题 2
+
+  编译提前翻译成机器码,运行快但不够灵活、需针对平台;解释在运行时边读边执行,灵活、跨平台、启动快,但通常较慢;即时编译在运行时把热点代码编译成机器码,兼顾灵活与速度,但实现更复杂、有启动预热和运行开销。
+
+## 协议
+
+  本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)进行许可。
+
+## 封面图
+
+![](https://raw.githubusercontent.com/TinySnow/computer-science-guide-resources/master/computer-science-guide/cover/编译原理/编译器总装.png)
+
+> 设计师 | 南国微雪

src/学习与进步/计算机科学极简入门指南/编译原理/第两百八十章:即时编译(进阶).md

+89 / -0 Click to expand diff
diff --git "a/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\347\274\226\350\257\221\345\216\237\347\220\206/\347\254\254\344\270\244\347\231\276\345\205\253\345\215\201\347\253\240\357\274\232\345\215\263\346\227\266\347\274\226\350\257\221\357\274\210\350\277\233\351\230\266\357\274\211.md" "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\347\274\226\350\257\221\345\216\237\347\220\206/\347\254\254\344\270\244\347\231\276\345\205\253\345\215\201\347\253\240\357\274\232\345\215\263\346\227\266\347\274\226\350\257\221\357\274\210\350\277\233\351\230\266\357\274\211.md"
new file mode 100644
index 00000000..7b204ccd
--- /dev/null
+++ "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\347\274\226\350\257\221\345\216\237\347\220\206/\347\254\254\344\270\244\347\231\276\345\205\253\345\215\201\347\253\240\357\274\232\345\215\263\346\227\266\347\274\226\350\257\221\357\274\210\350\277\233\351\230\266\357\274\211.md"
@@ -0,0 +1,89 @@
+# 即时编译(进阶)
+
+## 复习
+
+- 字节码与虚拟机:解释执行字节码
+- 编译器:把代码编译成机器码
+- 循环优化:优化优先盯热点
+
+> 本章为进阶内容,零基础读者可以跳过,不影响后续阅读。
+
+## TL;DR
+
+- 即时编译(JIT)在程序运行时把热点代码编译成本地机器码
+- 它结合了解释的灵活与编译的速度
+- 只编译热点,避免整体编译的开销
+- 代价是运行时编译的复杂与启动时的“预热”
+
+## 正文
+
+  纯解释执行太慢,而完全静态编译又不够灵活。**即时编译**(JIT,Just-In-Time compilation)想两头都占:**程序运行起来之后,一边跑,一边把最值得编译的部分编译成机器码。**
+
+### 运行中“抓热点”
+
+  JIT 的做法,和我们前面讲循环优化时的思路一脉相承——**盯着热点**:
+
+1. 程序先以解释(或字节码)的方式跑起来
+2. 运行时**监控**:哪些方法、哪些循环被反复执行(热点)
+3. 把热点代码**编译成本地机器码**
+4. 之后这些热点就改走机器码,快得多
+
+  冷门代码就继续解释执行,不必为它们花编译时间。**把编译的力气,花在真正经常跑的地方。**
+
+### 还能“看情况”优化
+
+  JIT 有一个静态编译比不了的优势:**它知道程序运行时到底发生了什么。**
+
+  比如,它可以观察到“这个变量实际上一直是整数类型”,于是按整数来优化这段代码,生成更快的机器码。这种“根据运行时的实际情况做优化”,是纯静态编译做不到的——静态编译器只能靠猜测。**运行时信息,让优化更有底气。**
+
+### 代价:预
+
+  但 JIT 不是没有代价:
+
+- **运行时编译要花时间**,程序刚启动时可能偏慢(常叫“预热”)
+- 虚拟机和 JIT 本身很复杂,占用额外的内存与资源
+- 优化得越激进,编译开销和不确定性也越大
+
+  于是,很多系统采用**混合模式**:先解释、或者快速编译一版,等热点出现再深度优化。**先跑起来,再越跑越快。** 这也是现代很多语言运行时的常见策略。
+
+ **思考题 1** 
+
+>   即时编译相比纯解释,快在哪里?
+
+ **思考题 2** 
+
+>   即时编译为什么只编译“热点”代码?
+
+## 小结
+
+### 知识点
+
+- JIT 在运行时把热点代码编译成本地机器码
+- 它结合解释的灵活与编译的速度
+- 可利用运行时信息做更贴合实际的优化
+- 代价是编译开销、复杂度与启动预热
+
+### 参考资料
+
+1. [Wikipedia(zh):即时编译](https://zh.wikipedia.org/wiki/%E5%8D%B3%E6%97%B6%E7%BC%96%E8%AF%91):运行时把代码编译为机器码
+2. [Wikipedia(zh):虚拟机](https://zh.wikipedia.org/wiki/%E8%99%9A%E6%8B%9F%E6%9C%BA):JIT 通常运行于其中的环境
+
+### 思考题答案(仅供参考)
+
+#### 思考题 1
+
+  因为纯解释每次执行都要逐条分析指令,而 JIT 会把反复执行的热点代码一次性编译成高效的本地机器码,之后直接执行机器码,省去了反复解释的开销,因此快得多。
+
+#### 思考题 2
+
+  因为编译要花时间和资源。若把所有代码都编译,既拖慢启动、又可能为很少执行的代码浪费力气。只编译热点,能把编译成本集中到真正影响性能的那部分代码上,收益最高、代价最小。
+
+## 协议
+
+  本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)进行许可。
+
+## 封面图
+
+![](https://raw.githubusercontent.com/TinySnow/computer-science-guide-resources/master/computer-science-guide/cover/编译原理/即时编译.png)
+
+> 设计师 | 南国微雪

更新记录(2026-09-20 08:43:52 +0000 | e1afb271)

Summary

  • Generated at: 2026-09-20 08:43:52 +0000
  • Base commit: e1afb271
  • Diff source: d9de80d76bfe3eb69966057ff4b2f3b88f5161ee..e1afb271f91fea4f8fb6332085536d65f837695f
  • Changed files: 8
  • Total lines: +645 / -0

Index

  1. src/SUMMARY.md +7 / -0
  2. src/学习与进步/计算机科学极简入门指南/编译原理/第两百七十一章:语言运行时.md +91 / -0
  3. src/学习与进步/计算机科学极简入门指南/编译原理/第两百七十七章:分代垃圾回收(进阶).md +88 / -0
  4. src/学习与进步/计算机科学极简入门指南/编译原理/第两百七十三章:对象布局与方法调用(进阶).md +89 / -0
  5. src/学习与进步/计算机科学极简入门指南/编译原理/第两百七十二章:闭包.md +98 / -0
  6. src/学习与进步/计算机科学极简入门指南/编译原理/第两百七十五章:引用计数.md +94 / -0
  7. src/学习与进步/计算机科学极简入门指南/编译原理/第两百七十六章:追踪式垃圾回收.md +87 / -0
  8. src/学习与进步/计算机科学极简入门指南/编译原理/第两百七十四章:异常处理(进阶).md +91 / -0

Diffs

src/SUMMARY.md

+7 / -0 Click to expand diff
diff --git a/src/SUMMARY.md b/src/SUMMARY.md
index 0496ca91..b50c2ba5 100644
--- a/src/SUMMARY.md
+++ b/src/SUMMARY.md
@@ -822,6 +822,13 @@
   - [第两百六十八章:重定位](学习与进步/计算机科学极简入门指南/编译原理/第两百六十八章:重定位.md)
   - [第两百六十九章:静态链接](学习与进步/计算机科学极简入门指南/编译原理/第两百六十九章:静态链接.md)
   - [第两百七十章:动态链接与装载](学习与进步/计算机科学极简入门指南/编译原理/第两百七十章:动态链接与装载.md)
+  - [第两百七十一章:语言运行时](学习与进步/计算机科学极简入门指南/编译原理/第两百七十一章:语言运行时.md)
+  - [第两百七十二章:闭包](学习与进步/计算机科学极简入门指南/编译原理/第两百七十二章:闭包.md)
+  - [第两百七十三章:对象布局与方法调用(进阶)](学习与进步/计算机科学极简入门指南/编译原理/第两百七十三章:对象布局与方法调用(进阶).md)
+  - [第两百七十四章:异常处理(进阶)](学习与进步/计算机科学极简入门指南/编译原理/第两百七十四章:异常处理(进阶).md)
+  - [第两百七十五章:引用计数](学习与进步/计算机科学极简入门指南/编译原理/第两百七十五章:引用计数.md)
+  - [第两百七十六章:追踪式垃圾回收](学习与进步/计算机科学极简入门指南/编译原理/第两百七十六章:追踪式垃圾回收.md)
+  - [第两百七十七章:分代垃圾回收(进阶)](学习与进步/计算机科学极简入门指南/编译原理/第两百七十七章:分代垃圾回收(进阶).md)
   - [附加章一:大数据](学习与进步/计算机科学极简入门指南/附加章/大数据.md)
   - [附加章二:数据加密](学习与进步/计算机科学极简入门指南/附加章/数据加密.md)
   - [附加章三:区块链](学习与进步/计算机科学极简入门指南/附加章/区块链.md)

src/学习与进步/计算机科学极简入门指南/编译原理/第两百七十一章:语言运行时.md

+91 / -0 Click to expand diff
diff --git "a/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\347\274\226\350\257\221\345\216\237\347\220\206/\347\254\254\344\270\244\347\231\276\344\270\203\345\215\201\344\270\200\347\253\240\357\274\232\350\257\255\350\250\200\350\277\220\350\241\214\346\227\266.md" "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\347\274\226\350\257\221\345\216\237\347\220\206/\347\254\254\344\270\244\347\231\276\344\270\203\345\215\201\344\270\200\347\253\240\357\274\232\350\257\255\350\250\200\350\277\220\350\241\214\346\227\266.md"
new file mode 100644
index 00000000..39983a17
--- /dev/null
+++ "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\347\274\226\350\257\221\345\216\237\347\220\206/\347\254\254\344\270\244\347\231\276\344\270\203\345\215\201\344\270\200\347\253\240\357\274\232\350\257\255\350\250\200\350\277\220\350\241\214\346\227\266.md"
@@ -0,0 +1,91 @@
+# 语言运行时
+
+## 复习
+
+- 动态链接与装载:运行时才把库接上
+- 程序在内存里的样子:代码区、数据区、栈和堆
+- 编译器:把程序翻译成可运行的形式
+
+## TL;DR
+
+- 语言运行时是高级语言背后的一组隐藏服务
+- 它负责内存管理、对象模型、异常、并发等
+- 有些工作由编译器直接生成代码,有些由运行时库提供
+- 语言越好用,往往运行时越“重”
+
+## 正文
+
+  一路走到这里,编译器似乎已经把所有事都做完了:读懂、优化、生成机器码、链接。可高级语言用起来那么“舒服”——你不用手动管理内存、不用关心对象在哪、还能随手抛个异常——这些便利,总得有人买单。
+
+  买单的,就是**语言运行时**(language runtime)。
+
+### 高级语言的“甜”是谁给的
+
+  想一想,哪些事你从没亲手做过:
+
+- 申请和释放对象的内存
+- 检查数组越界
+- 抛出和捕获异常
+- 记录每个对象的类型
+- 支持多线程
+
+  这些都不是“凭空”发生的。它们要么由编译器**在生成的代码里插入**检查与调用,要么由一组**运行时库**在程序运行时提供服务。这整套支撑,就是语言运行时。
+
+### 编译器和运行时怎么分工
+
+  两者的分工大致是这样:
+
+- **编译器**:能静态确定的,就直接生成代码(比如类型检查、部分内存布局)
+- **运行时**:需要“运行时才知道”的,交给运行时库(比如动态分配、垃圾回收、动态派发、异常展开)
+
+  两者配合,才让高级语言既好用又能跑。
+
+### 越“甜”,越“重”
+
+  这里有一条很有意思的规律:**语言提供的便利越多,运行时往往越庞大、越复杂。**
+
+  比如带自动垃圾回收的语言,需要一整套垃圾回收器;支持反射、动态派发的,要维护类型信息;有异常机制的,要维护异常处理数据。相比之下,贴近底层的语言,运行时可能非常小。
+
+  这又回到了那个熟悉的味道:**没有免费的便利。** 语言帮你做的事越多,背后替你干活的运行时就越重。接下来几章,就看几项典型的运行时服务。
+
+ **思考题 1** 
+
+>   语言运行时大致负责哪些工作?
+
+ **思考题 2** 
+
+>   为什么说“语言越好用,运行时往往越重”?
+
+## 小结
+
+### 知识点
+
+- 语言运行时提供高级语言背后的隐藏服务
+- 职责包括内存管理、对象模型、异常、并发等
+- 编译器生成部分代码,运行时库提供其余服务
+- 语言特性越丰富,运行时通常越大
+
+### 参考资料
+
+1. [Wikipedia(zh):运行时系统](https://zh.wikipedia.org/wiki/%E8%BF%90%E8%A1%8C%E6%97%B6%E7%B3%BB%E7%BB%9F):程序运行期间提供支持的系统
+2. [Wikipedia(zh):垃圾回收](https://zh.wikipedia.org/wiki/%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6_(%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6)):运行时自动管理内存的机制
+
+### 思考题答案(仅供参考)
+
+#### 思考题 1
+
+  它负责程序运行时需要的各种支持,例如内存分配与垃圾回收、对象与类型信息的维护、异常处理、动态方法派发、线程与并发支持等。部分是编译器插入的代码,部分是运行时库提供的服务。
+
+#### 思考题 2
+
+  因为语言提供的每项便利通常都需要运行时的支撑:自动内存管理要有垃圾回收器,异常机制要有异常处理数据,动态派发要有类型信息。便利越多,需要的运行时服务就越多,运行时也就越大越复杂。
+
+## 协议
+
+  本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)进行许可。
+
+## 封面图
+
+![](https://raw.githubusercontent.com/TinySnow/computer-science-guide-resources/master/computer-science-guide/cover/编译原理/语言运行时.png)
+
+> 设计师 | 南国微雪

src/学习与进步/计算机科学极简入门指南/编译原理/第两百七十七章:分代垃圾回收(进阶).md

+88 / -0 Click to expand diff
diff --git "a/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\347\274\226\350\257\221\345\216\237\347\220\206/\347\254\254\344\270\244\347\231\276\344\270\203\345\215\201\344\270\203\347\253\240\357\274\232\345\210\206\344\273\243\345\236\203\345\234\276\345\233\236\346\224\266\357\274\210\350\277\233\351\230\266\357\274\211.md" "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\347\274\226\350\257\221\345\216\237\347\220\206/\347\254\254\344\270\244\347\231\276\344\270\203\345\215\201\344\270\203\347\253\240\357\274\232\345\210\206\344\273\243\345\236\203\345\234\276\345\233\236\346\224\266\357\274\210\350\277\233\351\230\266\357\274\211.md"
new file mode 100644
index 00000000..610eed48
--- /dev/null
+++ "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\347\274\226\350\257\221\345\216\237\347\220\206/\347\254\254\344\270\244\347\231\276\344\270\203\345\215\201\344\270\203\347\253\240\357\274\232\345\210\206\344\273\243\345\236\203\345\234\276\345\233\236\346\224\266\357\274\210\350\277\233\351\230\266\357\274\211.md"
@@ -0,0 +1,88 @@
+# 分代垃圾回收(进阶)
+
+## 复习
+
+- 追踪式垃圾回收:从根出发判断可达性
+- 时间与空间的交换:用空间换时间
+- 局部性:刚用过的、附近的更可能再用
+
+> 本章为进阶内容,零基础读者可以跳过,不影响后续阅读。
+
+## TL;DR
+
+- 大多数对象“朝生夕死”
+- 分代 GC 把对象按年龄分成几代
+- 优先回收年轻代,减少扫描成本
+- 它利用“多数对象很快消亡”的经验
+
+## 正文
+
+  追踪式 GC 很可靠,却有个痛点:每次回收都要暂停程序、扫描大量对象。能不能让它**少扫一点、快一点**?分代垃圾回收给出了一个漂亮的答案——它建立在一个很实用的经验之上。
+
+### 一条经验:大多数对象活不久
+
+  观察大量真实程序后,人们总结出一条规律(常称**分代假说**):
+
+- **大多数对象,诞生后很快就死了**(临时变量、中间结果……)
+- **越老的对象,越可能继续活下去**
+
+  如果这是真的,那么每次回收都去仔细扫描那些“老资格”对象,就很浪费——它们大概率还要活着。真正值得频繁检查的,是那些**刚出生、很可能马上就没用**的对象。
+
+### 按年龄分代
+
+  于是,分代 GC 把对象按“年龄”分成几代:
+
+- **年轻代**:新创建的对象
+- **老年代**:熬过了若干次回收、还活着的对象
+
+  回收时,**优先、频繁地回收年轻代**。因为年轻代通常很小,扫起来快;而老年代又大又稳定,就少去扫它。这样,每次回收的成本大大降低。
+
+  当一个年轻代对象熬过足够多次回收,就被“晋升”到老年代,从此少被打扰。
+
+### 它和前面呼应
+
+  分代 GC 的背后,还是那条熟悉的**局部性**经验:**刚出现的更可能很快消失、存在久了更可能继续存在。** 这和缓存的“时间局部性”、DNS 的 TTL 一样,都是在用“数据在时间上的分布不均”来省力气。
+
+  **先观察规律,再让机制顺着规律走**,往往比一味蛮干高效得多。分代 GC 就是这条思路在内存管理上的一次漂亮应用。
+
+ **思考题 1** 
+
+>   分代 GC 基于什么经验?它带来什么好处?
+
+ **思考题 2** 
+
+>   分代 GC 为什么优先回收年轻代?
+
+## 小结
+
+### 知识点
+
+- 分代假说:多数对象很快消亡,越老越可能存活
+- 对象按年龄分为年轻代与老年代
+- 优先回收年轻代以降低成本
+- 熬过多次回收的对象晋升到老年代
+
+### 参考资料
+
+1. [Wikipedia(zh):分代垃圾回收](https://zh.wikipedia.org/wiki/%E5%88%86%E4%BB%A3%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6):按年龄分代的垃圾回收
+2. [Wikipedia(zh):垃圾回收](https://zh.wikipedia.org/wiki/%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6_(%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6)):自动内存管理的机制
+
+### 思考题答案(仅供参考)
+
+#### 思考题 1
+
+  它基于“大多数对象很快消亡、越老越可能存活”的经验(分代假说)。好处是只频繁扫描很可能已死的年轻代,而不反复检查稳定的老年代,从而显著降低每次回收的成本、缩短停顿。
+
+#### 思考题 2
+
+  因为年轻代里的对象大多是刚创建、很可能马上就没用的,回收它们收益高、数量相对少、扫描快。而老年代对象大且通常还要继续存活,频繁扫描不划算。优先回收年轻代正是顺着这条经验省力气。
+
+## 协议
+
+  本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)进行许可。
+
+## 封面图
+
+![](https://raw.githubusercontent.com/TinySnow/computer-science-guide-resources/master/computer-science-guide/cover/编译原理/分代垃圾回收.png)
+
+> 设计师 | 南国微雪

src/学习与进步/计算机科学极简入门指南/编译原理/第两百七十三章:对象布局与方法调用(进阶).md

+89 / -0 Click to expand diff
diff --git "a/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\347\274\226\350\257\221\345\216\237\347\220\206/\347\254\254\344\270\244\347\231\276\344\270\203\345\215\201\344\270\211\347\253\240\357\274\232\345\257\271\350\261\241\345\270\203\345\261\200\344\270\216\346\226\271\346\263\225\350\260\203\347\224\250\357\274\210\350\277\233\351\230\266\357\274\211.md" "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\347\274\226\350\257\221\345\216\237\347\220\206/\347\254\254\344\270\244\347\231\276\344\270\203\345\215\201\344\270\211\347\253\240\357\274\232\345\257\271\350\261\241\345\270\203\345\261\200\344\270\216\346\226\271\346\263\225\350\260\203\347\224\250\357\274\210\350\277\233\351\230\266\357\274\211.md"
new file mode 100644
index 00000000..98cc2ddd
--- /dev/null
+++ "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\347\274\226\350\257\221\345\216\237\347\220\206/\347\254\254\344\270\244\347\231\276\344\270\203\345\215\201\344\270\211\347\253\240\357\274\232\345\257\271\350\261\241\345\270\203\345\261\200\344\270\216\346\226\271\346\263\225\350\260\203\347\224\250\357\274\210\350\277\233\351\230\266\357\274\211.md"
@@ -0,0 +1,89 @@
+# 对象布局与方法调用(进阶)
+
+## 复习
+
+- 语言运行时:提供对象模型等隐藏服务
+- 类型系统:类型约束运算
+- 程序在内存里的样子:数据如何布局
+
+> 本章为进阶内容,零基础读者可以跳过,不影响后续阅读。
+
+## TL;DR
+
+- 对象在内存里由字段数据加类型信息组成
+- 方法调用需要根据对象的实际类型找到正确方法
+- 动态派发常借助类型信息(如虚表)实现
+- 布局设计影响性能与内存
+
+## 正文
+
+  面向对象语言里,我们写 `对象.方法()` 时,运行时到底做了什么?要回答它,先得知道对象在内存里长什么样。
+
+### 对象里装了什么
+
+  一个对象,除了它自己的**字段数据**,往往还要携带一点**类型信息**——因为它得知道自己“是什么类型”,才能在调用方法时找对地方。
+
+  具体的布局,视语言而定,常见的组成部分有:
+
+- 各个字段的值
+- 一个指向**类型信息**的指针(有时叫虚表指针)
+- (在某些语言里)用于同步、哈希等的额外数据
+
+### 方法调用:静态还是动态
+
+  方法调用的“找方法”,有两种情形:
+
+- **静态派发**:调用哪个方法,编译时就定死了(比如非虚方法)。直接生成调用,很快
+- **动态派发**:调哪个方法,要看对象的**实际类型**,运行时才确定。比如父类引用指向子类对象,`对象.方法()` 该调子类重写的版本
+
+  动态派发怎么实现?常见办法是**虚表**(vtable):类型信息里存一张方法地址表,每个类型一张。调用时,先从对象找到它的类型,再从这个类型的虚表里取出对应方法的地址,跳过去执行。
+
+  这样,**同一个调用点,随着对象实际类型的不同,就会跳到不同实现**——这正是“多态”在机器层面的样子。
+
+### 代价与设计
+
+  动态派发很灵活,却也有成本:每次调用都要先查表、再跳转,比静态调用多几步;而且对象里要额外存类型信息,占一点空间。
+
+  所以,语言和编译器会尽量在“能静态确定”的地方,就用静态派发,把动态派发留给真正需要多态的地方。**能用确定信息时就用,省下运行时的开销**——又是一次熟悉的权衡。
+
+ **思考题 1** 
+
+>   对象在内存里通常包含哪些内容?
+
+ **思考题 2** 
+
+>   “动态派发”为什么需要额外的类型信息?
+
+## 小结
+
+### 知识点
+
+- 对象通常由字段数据加类型信息组成
+- 方法调用分静态派发与动态派发
+- 动态派发常借助虚表按实际类型查找
+- 动态派发更灵活,但开销更大
+
+### 参考资料
+
+1. [Wikipedia(zh):虚函数表](https://zh.wikipedia.org/wiki/%E8%99%9A%E5%87%BD%E6%95%B0%E8%A1%A8):支持动态派发的方法地址表
+2. [Wikipedia(zh):动态分派](https://zh.wikipedia.org/wiki/%E5%8A%A8%E6%80%81%E5%88%86%E6%B4%BE):根据运行时类型选择方法
+
+### 思考题答案(仅供参考)
+
+#### 思考题 1
+
+  通常包含它各个字段的值,以及一份类型信息(常见形式是指向类型信息或虚表的指针),有的语言还会带上用于同步、哈希等的额外数据。这些使对象既保存数据,又能在运行时确定自己是什么类型。
+
+#### 思考题 2
+
+  因为动态派发要在运行时根据对象的实际类型选择正确的方法实现。没有类型信息,就无从知道该调哪个版本,因此需要保存对象的类型(以及该类型对应的方法地址表),运行时据此查表跳转。
+
+## 协议
+
+  本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)进行许可。
+
+## 封面图
+
+![](https://raw.githubusercontent.com/TinySnow/computer-science-guide-resources/master/computer-science-guide/cover/编译原理/对象布局与方法调用.png)
+
+> 设计师 | 南国微雪

src/学习与进步/计算机科学极简入门指南/编译原理/第两百七十二章:闭包.md

+98 / -0 Click to expand diff
diff --git "a/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\347\274\226\350\257\221\345\216\237\347\220\206/\347\254\254\344\270\244\347\231\276\344\270\203\345\215\201\344\272\214\347\253\240\357\274\232\351\227\255\345\214\205.md" "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\347\274\226\350\257\221\345\216\237\347\220\206/\347\254\254\344\270\244\347\231\276\344\270\203\345\215\201\344\272\214\347\253\240\357\274\232\351\227\255\345\214\205.md"
new file mode 100644
index 00000000..3f3e52f4
--- /dev/null
+++ "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\347\274\226\350\257\221\345\216\237\347\220\206/\347\254\254\344\270\244\347\231\276\344\270\203\345\215\201\344\272\214\347\253\240\357\274\232\351\227\255\345\214\205.md"
@@ -0,0 +1,98 @@
+# 闭包
+
+## 复习
+
+- 语言运行时:高级语言背后的隐藏服务
+- 函数调用与运行栈:局部变量的生命周期
+- 作用域:名字可见的范围
+
+## TL;DR
+
+- 闭包是“函数 + 它捕获的外部变量”
+- 它让函数离开定义位置后,仍能访问那些变量
+- 被捕获的变量通常要放到堆上,延长生命周期
+- 它是许多语言的重要特性
+
+## 正文
+
+  前面讲函数调用时说过:函数返回后,它在栈上的局部变量就该被销毁了。可有时候,我们偏偏想让一个函数**记住**它出生地的某些变量——即使已经离开了那里。实现这件事的,就是**闭包**(closure)。
+
+### 一个“记仇”的函数
+
+  看个例子。一个生成计数器的函数:
+
+```text
+函数 造计数器():
+    count = 0
+    函数 加一():
+        count = count + 1
+        返回 count
+    返回 加一
+
+c = 造计数器()
+c()   → 1
+c()   → 2
+```
+
+  奇妙的地方来了:`造计数器` 已经返回了,可它内部的 `count` 竟然还“活着”——每次调用 `c()`,`count` 都记得上次的值。
+
+  这是因为,返回的 `加一` 不只是一个函数,它还**带着自己捕获的环境**——这个 `count` 变量。函数加环境,就是**闭包**。
+
+### 捕获的是“变量”,不是“值”
+
+  要强调:闭包捕获的是那个**变量本身**,而不是它某一刻的取值。所以 `count` 才能在多次调用之间持续累积。
+
+  这也解释了为什么闭包的实现有讲究:
+
+- 如果 `count` 还按普通局部变量放在栈上,函数一返回,栈帧就没了,`count` 也无处安放
+- 所以,**被闭包捕获的变量,通常要“搬到”堆上**(或者放进一块专门的环境记录里),让它的生命周期不再受栈帧的束缚
+
+  于是运行时要在背后做一件事:**判断哪些变量被捕获了,把它们分配到能“活得更久”的地方。** 这正是语言运行时的一部分工作。
+
+### 它是一种“带记忆的函数”
+
+  闭包的价值,在于把“函数”从单纯的“一段代码”,升级成了“**一段代码 + 一份只属于它的记忆**”。这让很多写法变得自然:回调、生成器、函数式风格的数据处理……
+
+  而这背后,是编译器和运行时在悄悄解决一个难题:**当变量的生命超出了它出生的函数,该把它安置在哪里。** 答案,往往就是堆。
+
+ **思考题 1** 
+
+>   闭包“捕获”的是什么?为什么需要它?
+
+ **思考题 2** 
+
+>   为什么闭包往往要求把某些变量放到堆上?
+
+## 小结
+
+### 知识点
+
+- 闭包是函数加上它捕获的外部变量
+- 闭包捕获的是变量本身,可跨调用保持
+- 被捕获的变量生命周期超出栈帧
+- 因此常被分配到堆上,由运行时安排
+
+### 参考资料
+
+1. [Wikipedia(zh):闭包 (计算机科学)](https://zh.wikipedia.org/wiki/%E9%97%AD%E5%8C%85_(%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6)):携带环境的函数
+2. [Wikipedia(zh):运行时系统](https://zh.wikipedia.org/wiki/%E8%BF%90%E8%A1%8C%E6%97%B6%E7%B3%BB%E7%BB%9F):为闭包等特性提供支持
+
+### 思考题答案(仅供参考)
+
+#### 思考题 1
+
+  它捕获的是函数定义处的外部变量(环境),使函数在离开定义位置之后仍能访问、修改这些变量。没有它,函数返回后这些局部变量会随栈帧销毁,函数就“失忆”了。
+
+#### 思考题 2
+
+  因为被捕获的变量的生命周期超出了函数调用:函数已经返回、栈帧已经销毁,但变量还要继续存在供闭包使用。栈上的空间随调用结束就没了,所以必须把它放到堆上(或专门的环境记录)以延长寿命。
+
+## 协议
+
+  本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)进行许可。
+
+## 封面图
+
+![](https://raw.githubusercontent.com/TinySnow/computer-science-guide-resources/master/computer-science-guide/cover/编译原理/闭包.png)
+
+> 设计师 | 南国微雪

src/学习与进步/计算机科学极简入门指南/编译原理/第两百七十五章:引用计数.md

+94 / -0 Click to expand diff
diff --git "a/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\347\274\226\350\257\221\345\216\237\347\220\206/\347\254\254\344\270\244\347\231\276\344\270\203\345\215\201\344\272\224\347\253\240\357\274\232\345\274\225\347\224\250\350\256\241\346\225\260.md" "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\347\274\226\350\257\221\345\216\237\347\220\206/\347\254\254\344\270\244\347\231\276\344\270\203\345\215\201\344\272\224\347\253\240\357\274\232\345\274\225\347\224\250\350\256\241\346\225\260.md"
new file mode 100644
index 00000000..c75f000c
--- /dev/null
+++ "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\347\274\226\350\257\221\345\216\237\347\220\206/\347\254\254\344\270\244\347\231\276\344\270\203\345\215\201\344\272\224\347\253\240\357\274\232\345\274\225\347\224\250\350\256\241\346\225\260.md"
@@ -0,0 +1,94 @@
+# 引用计数
+
+## 复习
+
+- 语言运行时:负责内存管理等服务
+- 对象布局:对象在内存中的表示
+- 时间与空间的交换:用空间换时间
+
+## TL;DR
+
+- 引用计数为每个对象记录被引用的次数
+- 计数归零就回收,及时而且简单
+- 代价是每次引用变化都要维护计数
+- 最大的问题是无法回收“循环引用”
+
+## 正文
+
+  自动内存管理有两条主要路线,先看比较直观的一条:**引用计数**(reference counting)。
+
+### 数一数有多少人在用它
+
+  引用计数的想法很朴素:**给每个对象记一个数,表示“当前有多少地方在引用它”。**
+
+- 有人引用它(比如赋给一个变量、放进一个容器),计数加一
+- 有人不再引用它,计数减一
+- 计数**减到 0**,说明再也没人用它了,立刻回收
+
+  它的优点是**及时**:没人用了马上回收,不用等;实现也相对简单。程序一旦不再引用某对象,内存立刻就能腾出来。
+
+### 代价
+
+  但“每次引用变化都要改计数”,本身就是开销:
+
+- 每次赋值、传参、出作用域……都可能触发计数增减
+- 频繁的计数操作会拖慢程序
+- 多线程下,计数还得同步,开销更大
+- 每个对象还要多存一个计数字段,占空间
+
+  所以,引用计数是“**用运行时的计数开销,换及时、简单的回收**”。又是一次交换。
+
+### 循环引用:它的死穴
+
+  引用计数有一个著名的问题:**循环引用。**
+
+  设想两个对象互相引用:A 引用 B,B 也引用 A,此外再没人用它们了。此时:
+
+- A 的计数是 1(被 B 引用),不是 0
+- B 的计数也是 1(被 A 引用),不是 0
+
+  于是它们**谁也不会被回收**,即使整个程序已经用不到它们了。这对引用计数来说,是死结——因为它是靠“局部的计数”判断的,看不到“整体都已无用”这个事实。
+
+  要解决循环引用,就需要一种**全局视角**的回收方式:从“谁还在用”出发,判断哪些对象真正还能被到达。这就是下一章的**追踪式垃圾回收**。
+
+ **思考题 1** 
+
+>   引用计数在什么时候回收对象?
+
+ **思考题 2** 
+
+>   引用计数的“循环引用”问题,是什么?
+
+## 小结
+
+### 知识点
+
+- 引用计数为每个对象记录被引用次数
+- 计数减到 0 时立即回收
+- 优点是及时、简单,代价是计数开销
+- 无法回收互相引用的对象
+
+### 参考资料
+
+1. [Wikipedia(zh):引用计数](https://zh.wikipedia.org/wiki/%E5%BC%95%E7%94%A8%E8%AE%A1%E6%95%B0):按引用数量管理内存
+2. [Wikipedia(zh):垃圾回收](https://zh.wikipedia.org/wiki/%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6_(%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6)):自动内存管理的机制
+
+### 思考题答案(仅供参考)
+
+#### 思考题 1
+
+  当某个对象的引用计数减到 0 时,说明已经没有任何地方引用它,引用计数机制便会立即回收它。回收是即时的,不必等待其他时机。
+
+#### 思考题 2
+
+  当两个或多个对象互相引用、形成环,而外部已不再引用它们时,它们彼此的计数都不为 0,于是都不会被回收。即使实际已无人使用,内存也无法释放,这就是循环引用问题。
+
+## 协议
+
+  本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)进行许可。
+
+## 封面图
+
+![](https://raw.githubusercontent.com/TinySnow/computer-science-guide-resources/master/computer-science-guide/cover/编译原理/引用计数.png)
+
+> 设计师 | 南国微雪

src/学习与进步/计算机科学极简入门指南/编译原理/第两百七十六章:追踪式垃圾回收.md

+87 / -0 Click to expand diff
diff --git "a/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\347\274\226\350\257\221\345\216\237\347\220\206/\347\254\254\344\270\244\347\231\276\344\270\203\345\215\201\345\205\255\347\253\240\357\274\232\350\277\275\350\270\252\345\274\217\345\236\203\345\234\276\345\233\236\346\224\266.md" "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\347\274\226\350\257\221\345\216\237\347\220\206/\347\254\254\344\270\244\347\231\276\344\270\203\345\215\201\345\205\255\347\253\240\357\274\232\350\277\275\350\270\252\345\274\217\345\236\203\345\234\276\345\233\236\346\224\266.md"
new file mode 100644
index 00000000..e6787043
--- /dev/null
+++ "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\347\274\226\350\257\221\345\216\237\347\220\206/\347\254\254\344\270\244\347\231\276\344\270\203\345\215\201\345\205\255\347\253\240\357\274\232\350\277\275\350\270\252\345\274\217\345\236\203\345\234\276\345\233\236\346\224\266.md"
@@ -0,0 +1,87 @@
+# 追踪式垃圾回收
+
+## 复习
+
+- 引用计数:循环引用问题
+- 图:用节点和边表示关系
+- 语言运行时:负责内存管理
+
+## TL;DR
+
+- 追踪式 GC 从“根”出发,找出所有还能到达的对象
+- 到不了的对象就是垃圾,可以回收
+- 常见做法分“标记”与“清除”两个阶段
+- 它能处理引用计数搞不定的循环引用
+
+## 正文
+
+  引用计数的死穴是循环引用。要解决它,就得换一种**全局视角**:不再只看“谁引用了谁”这个局部计数,而是从“程序现在还能触及哪些对象”出发。这就是**追踪式垃圾回收**(tracing GC)。
+
+### 从“根”出发,看能到达谁
+
+  追踪式 GC 的思路是:
+
+1. 找出所有**根**(root)——程序当前直接能访问到的地方,比如栈上的变量、全局变量
+2. 从根出发,沿着引用关系一路“走”下去,把能到达的对象都标记为“活着”
+3. 走不到的,就是垃圾,可以回收
+
+  注意第 2 步:**“从根出发遍历所有能到达的对象”,本身就是一次图的遍历。** 我们在数据结构部分学过的遍历,在这里派上了大用场。**可达的活着,不可达的回收**——一句话就是它的核心。
+
+### 为什么它能解决循环引用
+
+  回头看看那个死结:A 和 B 互相引用,但再没有外部引用它们。
+
+  追踪式 GC 会怎么判断?它从根出发,发现**根本走不到 A 和 B**——因为没有任何根指向它们。于是它们被判定为“不可达”,即使它们彼此引用,也照样被回收。**问题不在“有没有人引用它”,而在“根还能不能到达它”。** 视角一换,死结就解开了。
+
+### 标记与清除
+
+  最常见的实现叫**标记—清除**(mark and sweep):
+
+- **标记**:从头遍历,把可达对象打上标记
+- **清除**:扫一遍内存,把没标记的对象回收掉
+
+  它的代价,是回收时往往要**暂停程序**(stop the world),集中做一轮标记和清除——这会带来可感知的卡顿。此外,回收后内存可能变得零碎,还需要“整理”或“复制”来改善(这些属于更细的取舍)。
+
+  **追踪式 GC 用“运行时的停顿”,换来了对循环引用的正确处理和整体视角的简洁。** 下一次,我们看看怎样把这种停顿变得更短、更聪明。
+
+ **思考题 1** 
+
+>   追踪式 GC 怎样判断一个对象是垃圾?
+
+ **思考题 2** 
+
+>   追踪式 GC 为什么能回收引用计数处理不了的循环引用?
+
+## 小结
+
+### 知识点
+
+- 追踪式 GC 从根出发做可达性遍历
+- 可达对象存活,不可达对象回收
+- 标记—清除是常见实现
+- 它能处理循环引用,代价是回收时可能暂停
+
+### 参考资料
+
+1. [Wikipedia(zh):追踪垃圾回收](https://zh.wikipedia.org/wiki/%E8%BF%BD%E8%B8%AA%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6):基于可达性的垃圾回收
+2. [Wikipedia(zh):标记-清除算法](https://zh.wikipedia.org/wiki/%E6%A0%87%E8%AE%B0-%E6%B8%85%E9%99%A4%E7%AE%97%E6%B3%95):先标记存活再清除垃圾
+
+### 思考题答案(仅供参考)
+
+#### 思考题 1
+
+  它从根(栈上变量、全局变量等)出发,沿引用关系遍历,把能到达的对象标记为存活;那些从根出发无法到达的对象就是垃圾,可以被回收。
+
+#### 思考题 2
+
+  因为它判断的是“从根是否可达”,而不只看引用计数。循环引用的对象虽然彼此引用,但若没有任何根能到达它们,就会被判定为不可达而回收。所以它能解开引用计数的死结。
+
+## 协议
+
+  本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)进行许可。
+
+## 封面图
+
+![](https://raw.githubusercontent.com/TinySnow/computer-science-guide-resources/master/computer-science-guide/cover/编译原理/追踪式垃圾回收.png)
+
+> 设计师 | 南国微雪

src/学习与进步/计算机科学极简入门指南/编译原理/第两百七十四章:异常处理(进阶).md

+91 / -0 Click to expand diff
diff --git "a/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\347\274\226\350\257\221\345\216\237\347\220\206/\347\254\254\344\270\244\347\231\276\344\270\203\345\215\201\345\233\233\347\253\240\357\274\232\345\274\202\345\270\270\345\244\204\347\220\206\357\274\210\350\277\233\351\230\266\357\274\211.md" "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\347\274\226\350\257\221\345\216\237\347\220\206/\347\254\254\344\270\244\347\231\276\344\270\203\345\215\201\345\233\233\347\253\240\357\274\232\345\274\202\345\270\270\345\244\204\347\220\206\357\274\210\350\277\233\351\230\266\357\274\211.md"
new file mode 100644
index 00000000..b6aa25eb
--- /dev/null
+++ "b/src/\345\255\246\344\271\240\344\270\216\350\277\233\346\255\245/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246\346\236\201\347\256\200\345\205\245\351\227\250\346\214\207\345\215\227/\347\274\226\350\257\221\345\216\237\347\220\206/\347\254\254\344\270\244\347\231\276\344\270\203\345\215\201\345\233\233\347\253\240\357\274\232\345\274\202\345\270\270\345\244\204\347\220\206\357\274\210\350\277\233\351\230\266\357\274\211.md"
@@ -0,0 +1,91 @@
+# 异常处理(进阶)
+
+## 复习
+
+- 语言运行时:提供异常等隐藏服务
+- 函数调用与运行栈:调用栈的结构
+- 控制流:程序的执行路径
+
+> 本章为进阶内容,零基础读者可以跳过,不影响后续阅读。
+
+## TL;DR
+
+- 异常让错误可以跨多层调用向上传播
+- 抛出异常时,运行时要“展开栈”,找到能处理它的地方
+- 运行时通常维护一张异常表,记录哪里能处理什么
+- 展开过程中还要正确执行清理工作
+
+## 正文
+
+  程序出错时,错误可能发生在很深的调用里,而真正能处理它的代码却在很外层。用返回值一层层往上传递,太累。**异常**(exception)就是为了解决这件事:**让错误“跳”过多层调用,直接送到能处理它的地方。**
+
+### 抛出与捕获
+
+  异常机制有两个关键动作:
+
+- **抛出**(throw):在出错处抛出一个异常
+- **捕获**(catch):在某处处理匹配的异常
+
+  当异常被抛出,运行时会沿着调用栈**从内到外**查找:哪个函数能处理这种异常?找到后,控制权就转到那里。
+
+### 展开栈
+
+  这个“查找并转移”的过程,叫**栈展开**(stack unwinding):把中间那些“不负责处理”的函数栈帧,一层层弹掉。
+
+  但栈帧不能随便一弹了事。因为那些被跳过的函数,可能有需要**清理**的东西:
+
+- 打开的文件要关闭
+- 申请的资源要释放
+- 对象要析构
+
+  所以展开过程中,运行时必须一边退栈,一边把每一层的清理工作做掉。**“跳过”不是“扔掉”,该做的收尾还得做。**
+
+### 怎么知道哪里能处理
+
+  运行时凭什么知道“哪一层能处理这个异常”?常见做法是维护一张**异常表**:记录某段代码范围对应哪个处理入口。抛出异常时,就查这张表,找到匹配的处理者。
+
+  这种基于表格的方式,好处是**正常执行时几乎没有额外开销**——平时不必检查什么,只有真正抛异常时,才去查表。这也算一种“把开销留给异常路径、让正常路径更快”的设计取舍。
+
+  **用异常处理错误,代码更清晰;而这份清晰,背后是一整套栈展开与异常表的机制。**
+
+ **思考题 1** 
+
+>   异常处理为什么需要“展开栈”?
+
+ **思考题 2** 
+
+>   抛出异常时,为什么还要做“清理”工作?
+
+## 小结
+
+### 知识点
+
+- 异常让错误跨多层调用向上传播
+- 抛出时沿调用栈查找能处理它的地方
+- 查找过程需要展开栈,逐层退栈
+- 展开时要执行各层的清理,并借助异常表定位处理者
+
+### 参考资料
+
+1. [Wikipedia(zh):异常处理](https://zh.wikipedia.org/wiki/%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86):跨调用传播错误的机制
+2. [Wikipedia(zh):调用栈](https://zh.wikipedia.org/wiki/%E8%B0%83%E7%94%A8%E6%A0%88):栈展开所依赖的结构
+
+### 思考题答案(仅供参考)
+
+#### 思考题 1
+
+  因为异常可能从很深的调用处抛出,而处理者在外层。要让控制权转移到处理者,就必须把中间那些不处理它的函数栈帧逐层弹出,也就是展开栈,才能回到能处理它的那一层。
+
+#### 思考题 2
+
+  因为被跳过的那些函数可能持有需要释放的资源,比如打开的文件、申请的内存、需要析构的对象。若不清理,这些资源就会泄漏。所以展开时每一步都要把该做的收尾做完,做到“跳过但不遗漏”。
+
+## 协议
+
+  本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)进行许可。
+
+## 封面图
+
+![](https://raw.githubusercontent.com/TinySnow/computer-science-guide-resources/master/computer-science-guide/cover/编译原理/异常处理.png)
+
+> 设计师 | 南国微雪