目录
- [1. 为什么学习 Software Construction](#1. 为什么学习 Software Construction)
- [2. 软件工程最重要的三个目标](#2. 软件工程最重要的三个目标)
- [2.1 Safe from Bugs](#2.1 Safe from Bugs)
- [2.2 Easy to Understand](#2.2 Easy to Understand)
- [2.3 Ready for Change](#2.3 Ready for Change)
- [3. Specification:先定义"软件应该做什么"](#3. Specification:先定义“软件应该做什么”)
- [3.1 一个好的功能应该明确](#3.1 一个好的功能应该明确)
- [4. Abstraction:抽象](#4. Abstraction:抽象)
- [5. Interface:接口](#5. Interface:接口)
- [5.1 接口解决什么问题?](#5.1 接口解决什么问题?)
- [6. ADT:抽象数据类型](#6. ADT:抽象数据类型)
- [7. Modularity:模块化](#7. Modularity:模块化)
- [8. Cohesion:内聚](#8. Cohesion:内聚)
- [9. Coupling:耦合](#9. Coupling:耦合)
- [10. 高内聚 + 低耦合](#10. 高内聚 + 低耦合)
- [11. Immutability:不可变性](#11. Immutability:不可变性)
- [12. Defensive Programming:防御式编程](#12. Defensive Programming:防御式编程)
- [13. Testing:测试](#13. Testing:测试)
- [14. Unit Test:单元测试](#14. Unit Test:单元测试)
- [15. Regression Test:回归测试](#15. Regression Test:回归测试)
- [16. Code Review](#16. Code Review)
- [17. Git / Version Control](#17. Git / Version Control)
- [18. Debugging:Debug 的正确思维](#18. Debugging:Debug 的正确思维)
- [19. Concurrency:并发](#19. Concurrency:并发)
- [20. Event-driven:事件驱动](#20. Event-driven:事件驱动)
- [21. 状态设计](#21. 状态设计)
- [22. Error Handling:错误处理](#22. Error Handling:错误处理)
- [23. API / Module Boundary](#23. API / Module Boundary)
- [24. 设计的一个核心问题:变化在哪里?](#24. 设计的一个核心问题:变化在哪里?)
- [25. 一个非常重要的工程原则:封装变化](#25. 一个非常重要的工程原则:封装变化)
- [26. AI Coding 应该如何使用这些思想?](#26. AI Coding 应该如何使用这些思想?)
- [27. AI 时代最重要的工作流](#27. AI 时代最重要的工作流)
- [28. 如何把 MIT Software Construction 应用到你的 GIS 项目](#28. 如何把 MIT Software Construction 应用到你的 GIS 项目)
- [29. 第一阶段学习清单](#29. 第一阶段学习清单)
- [30. 与后续四个资源的关系](#30. 与后续四个资源的关系)
- [31. 最终应该掌握的核心思想](#31. 最终应该掌握的核心思想)
- [32. 最终目标](#32. 最终目标)
1. 为什么学习 Software Construction
AI 让"写出代码"越来越容易,但真正困难的问题逐渐变成:
- 需求应该如何拆分?
- 一个模块应该负责什么?
- 模块之间应该如何通信?
- 如何降低耦合?
- 如何让代码容易修改?
- 如何避免修改一个功能导致其他功能崩溃?
- 如何证明代码是正确的?
- 如何让别人能够快速理解代码?
- 如何让 AI 在大型项目中稳定地修改代码?
因此:
Software Construction 的核心不是某一种编程语言,而是如何把代码构建成可靠、可理解、可维护、可演进的软件。
2. 软件工程最重要的三个目标
MIT Software Construction 中非常值得建立的三个目标是:
text
Software Quality
│
┌────────────────┼────────────────┐
↓ ↓ ↓
Safe from bugs Easy to understand Ready for change
少 Bug 易理解 易修改
也可以理解为:
2.1 Safe from Bugs
软件应该尽可能:
- 正确
- 可验证
- 异常情况可控
- 修改后不容易引入新的 Bug
2.2 Easy to Understand
代码应该:
- 命名清晰
- 结构清晰
- 职责明确
- 模块边界明确
- 尽量减少隐式行为
因为:
代码最重要的读者通常不是编译器,而是未来的自己和其他开发者。
AI Coding 时代这一点更加重要。
如果代码结构混乱,AI 也更容易:
- 理解错误
- 修改错误位置
- 产生重复代码
- 破坏原有逻辑
2.3 Ready for Change
软件需求一定会变化。
因此应该问:
"如果三个月后需求发生变化,我需要修改多少地方?"
优秀的设计应该让:
text
需求变化
↓
局部修改
↓
其他模块基本不受影响
而不是:
text
需求变化
↓
修改 A
↓
A 影响 B
↓
B 影响 C
↓
整个项目开始出现 Bug
3. Specification:先定义"软件应该做什么"
Software Construction 很重要的一部分是 Specification。
可以把它理解为:
在写代码之前,明确程序应该满足什么条件。
很多开发者的问题是:
text
需求
↓
直接写代码
更好的方式是:
text
需求
↓
定义行为
↓
定义输入
↓
定义输出
↓
定义异常情况
↓
再开始实现
3.1 一个好的功能应该明确
例如:
加载一个 3D 模型。
不要只描述:
"把模型加载进 Cesium。"
而应该进一步定义:
输入
- 模型路径
- 模型类型
- 坐标信息
输出
- 加载成功
- 加载失败
- 加载进度
异常
- 文件不存在
- 文件格式错误
- 坐标系不支持
- 模型损坏
- 用户取消加载
状态
text
Idle
↓
Loading
↓
Success
或
Loading
↓
Error
或
Loading
↓
Cancelled
这一步实际上已经开始进入:
产品设计 + 软件设计。
4. Abstraction:抽象
软件工程中最核心的能力之一:
不要让代码过度关注具体实现,而应该关注"它提供什么能力"。
例如模型加载。
不好的方式:
text
业务代码
↓
if OSGB
...
else if 3DTiles
...
else if GLTF
...
随着格式增加,代码越来越复杂。
更好的抽象:
text
ModelLoader
├── OSGBLoader
├── Tiles3DLoader
├── GLTFLoader
└── ...
业务代码只需要知道:
text
loader.load()
而不需要知道具体怎么加载。
5. Interface:接口
接口的作用之一:
明确模块之间的契约。
例如:
text
ModelLoader
load(path)
cancel()
getProgress()
dispose()
具体实现:
text
OSGBLoader
Tiles3DLoader
GLTFLoader
只要它们遵守同一个接口,上层业务就不需要关心具体实现。
5.1 接口解决什么问题?
主要解决:
text
业务逻辑
↓
具体实现
之间过度绑定的问题。
理想状态:
text
业务逻辑
↓
抽象接口
↓
具体实现
这样以后更换底层实现时,上层代码可以尽量不动。
6. ADT:抽象数据类型
Abstract Data Type 可以理解为:
把数据和操作这些数据的行为封装起来,只暴露必要的能力。
例如一个项目管理器:
text
ProjectManager
openProject()
closeProject()
saveProject()
getProjectInfo()
调用方不应该直接操作:
text
内部数据结构
内部缓存
内部文件格式
内部状态
而应该通过:
text
ProjectManager
提供的接口访问。
这实际上是在建立:
封装 + 边界。
7. Modularity:模块化
一个大型软件不能所有代码放在一起。
应该拆成多个具有明确职责的模块。
例如一个 Electron + GIS 项目:
text
Application
│
├── UI
│
├── Project
│
├── Model
│
├── GIS
│
├── File
│
├── Config
│
└── Communication
进一步:
text
Model
│
├── ModelService
├── ModelLoader
├── ModelRegistry
└── ModelCache
8. Cohesion:内聚
内聚关注:
一个模块内部的东西是不是都属于同一类职责。
高内聚:
text
ModelService
├── loadModel()
├── removeModel()
├── getModel()
└── updateModel()
低内聚:
text
ModelService
├── loadModel()
├── login()
├── saveDatabase()
├── changeTheme()
└── sendEmail()
一般应该追求:
一个模块内部的功能应该高度相关。
9. Coupling:耦合
耦合关注:
模块之间依赖得有多紧。
例如:
text
UI
↓
ModelService
↓
Cesium API
↓
File System
↓
Database
如果每一层都直接依赖其他层,修改起来会非常困难。
更好的结构:
text
UI
↓
Application Service
↓
Domain / Interface
↓
Infrastructure
核心思想:
减少模块之间不必要的直接依赖。
10. 高内聚 + 低耦合
这是软件设计中最值得长期记住的一组原则:
text
高内聚
+
低耦合
=
更容易维护的软件
可以用一个问题判断:
"如果我修改这个模块,会不会被迫修改很多其他模块?"
如果经常出现:
"改这里必须改那里。"
通常说明模块边界或者依赖关系存在问题。
11. Immutability:不可变性
不可变思想的核心:
一个数据创建以后,尽量不要到处修改它。
例如:
text
state
↓
多个模块共同修改
↓
不知道是谁改的
↓
Bug
不可变思维:
text
oldState
↓
newState
这样状态变化更加明确。
它对于:
- 前端状态管理
- 并发
- 多模块协作
- Debug
都非常有帮助。
12. Defensive Programming:防御式编程
不要假设:
"用户一定会按照我想的方式操作。"
需要考虑:
text
正常输入
异常输入
空值
错误格式
超大数据
重复操作
取消操作
网络失败
文件不存在
权限不足
例如:
text
loadModel(path)
不能只考虑:
text
path 正确
↓
加载
还要考虑:
text
path == null
文件不存在
格式错误
文件损坏
文件过大
加载超时
用户取消
重复加载
13. Testing:测试
测试不是为了"证明代码完全没有 Bug"。
测试更重要的作用是:
建立对代码行为的信心。
尤其是 AI Coding。
AI 修改代码之后,如果没有测试:
text
AI 修改
↓
看起来能运行
↓
不知道有没有破坏其他功能
有测试:
text
AI 修改
↓
运行测试
↓
通过
↓
信心增加
14. Unit Test:单元测试
一个模块应该能够被独立测试。
例如:
text
CoordinateService
可以测试:
text
WGS84 → WebMercator
WGS84 → GCJ02
Invalid Coordinate
Null Input
而不需要启动整个 GIS 系统。
这说明:
好的模块通常也是容易测试的模块。
15. Regression Test:回归测试
回归测试解决的问题:
新代码有没有破坏旧功能?
例如:
text
原来:
模型加载 ✓
增加:
3D Tiles ✓
但是:
OSGB ✗
如果有自动测试:
text
修改代码
↓
测试
↓
发现 OSGB 测试失败
↓
立即定位问题
这对 AI Coding 非常重要。
16. Code Review
Code Review 不只是检查:
"有没有语法错误?"
更应该检查:
结构
- 模块职责是否清晰?
- 有没有高耦合?
- 有没有重复代码?
设计
- 抽象是否合理?
- 是否过度设计?
- 接口是否稳定?
可靠性
- 异常是否处理?
- 资源是否释放?
- 并发是否安全?
可维护性
- 三个月以后还能看懂吗?
- 需求变化后容易修改吗?
17. Git / Version Control
版本控制不仅是:
text
git add
git commit
git push
更重要的是:
让软件开发过程可以被安全地回退和比较。
AI Coding 尤其应该重视 Git。
推荐:
text
一个功能
↓
一个 Branch
↓
AI 修改
↓
查看 Diff
↓
测试
↓
Commit
不要让 AI 一次修改几百个文件以后才开始看:
"到底发生了什么?"
18. Debugging:Debug 的正确思维
不要:
text
看到 Bug
↓
直接让 AI 改
应该:
text
Bug
↓
复现
↓
定位
↓
建立假设
↓
验证假设
↓
找到根因
↓
修复
↓
测试
特别是 AI Debug 时,要让 AI:
先解释 Bug 的根因,再修改代码。
例如:
text
请先分析这个 Bug 的根本原因。
不要修改代码。
请告诉我:
1. Bug 如何产生
2. 涉及哪些模块
3. 哪个状态发生了异常
4. 为什么现有代码没有阻止它
5. 有哪些修复方案
19. Concurrency:并发
虽然目前不一定马上用到,但这是工程能力的重要组成部分。
典型问题:
text
用户点击加载
↓
任务 A 开始
用户再次点击
↓
任务 B 开始
A、B 同时修改同一个状态
↓
状态混乱
需要考虑:
- Race Condition
- Thread Safety
- Shared State
- Lock
- Atomic Operation
- Cancellation
在 Electron + ZMQ + GIS + AI 的项目中,这类问题会越来越常见。
20. Event-driven:事件驱动
很多复杂应用可以通过事件解耦。
例如:
text
ModelService
↓
model:loading
ModelService
↓
model:progress
ModelService
↓
model:loaded
ModelService
↓
model:error
其他模块只订阅自己需要的事件:
text
UI
↓
监听 model:progress
Logger
↓
监听 model:error
SceneManager
↓
监听 model:loaded
这样模块之间不需要全部直接调用。
21. 状态设计
复杂软件最容易出 Bug 的地方之一就是:
状态。
例如模型:
text
Idle
Loading
Loaded
Failed
Cancelled
Disposing
Disposed
不要让代码中到处出现:
text
isLoading
isLoaded
isError
isCancelled
然后互相组合产生几十种未知状态。
应该明确:
text
ModelState
并定义合法状态转换:
text
Idle
↓
Loading
├── Loaded
├── Failed
└── Cancelled
这会显著降低复杂度。
22. Error Handling:错误处理
错误不是简单的:
text
try {
...
} catch {
console.log(error)
}
应该思考:
错误在哪里产生?
text
File System
↓
Model Loader
↓
Service
↓
UI
谁负责处理?
例如:
text
底层
↓
产生技术错误
Service
↓
转换成业务错误
UI
↓
展示用户能够理解的信息
例如:
text
ENOENT
用户不一定需要看到。
可以转换为:
text
模型文件不存在,请重新选择文件。
23. API / Module Boundary
一个模块应该明确:
text
输入什么
输出什么
可以调用什么
不能调用什么
例如:
text
ModelService
允许:
load()
remove()
get()
不允许:
直接修改 UI DOM
直接修改数据库
直接操作 Renderer 状态
这就是:
边界意识。
架构能力本质上很大一部分就是:
建立边界。
24. 设计的一个核心问题:变化在哪里?
这是非常值得培养的思维。
每次设计一个模块,都问:
"未来什么地方最可能发生变化?"
例如:
text
模型格式
可能变化:
text
OSGB
3D Tiles
GLTF
未来的新格式
因此应该把:
text
模型格式相关代码
隔离起来。
又例如:
text
GIS 引擎
未来可能从:
text
Cesium
换成:
text
其他 GIS Engine
那么就应该尽量避免整个业务层直接依赖 Cesium。
25. 一个非常重要的工程原则:封装变化
可以记住:
把容易变化的东西封装起来。
例如:
text
业务逻辑
│
↓
ModelLoader
│
┌────────┼────────┐
↓ ↓ ↓
OSGB 3DTiles GLTF
这样未来增加:
text
OBJ
主要增加:
text
OBJLoader
而不是修改整个业务系统。
26. AI Coding 应该如何使用这些思想?
以后不要只问:
text
"帮我实现 XXX。"
可以逐渐变成:
需求分析
text
请先分析这个需求涉及哪些模块。
不要写代码。
架构分析
text
请分析当前模块之间的依赖关系,
指出高耦合和职责混乱的位置。
不要修改代码。
方案设计
text
请给出 2~3 个设计方案,
分析各自的优缺点。
暂时不要实现。
实现
text
按照确定的方案实现。
要求:
1. 保持现有功能
2. 尽量减少修改范围
3. 每完成一个阶段运行测试
4. 不要引入不必要的抽象
Review
text
请 Review 刚才的修改。
重点检查:
1. Bug 风险
2. 高耦合
3. 低内聚
4. 重复代码
5. 状态问题
6. 异常处理
7. 资源泄漏
8. 可测试性
9. 可维护性
10. 是否过度设计
27. AI 时代最重要的工作流
最终建议形成:
text
需求
↓
Specification
↓
模块划分
↓
接口 / 边界
↓
方案设计
↓
AI 实现
↓
Test
↓
Code Review
↓
Refactor
↓
Commit
而不是:
text
需求
↓
AI 写代码
↓
Bug
↓
AI 修
↓
Bug
↓
AI 再修
↓
代码越来越乱
28. 如何把 MIT Software Construction 应用到你的 GIS 项目
以:
"实现本地 3D 模型加载"
为例。
第一层:Specification
定义:
text
输入:
模型路径 + 模型类型
输出:
加载成功 / 失败 / 进度
异常:
文件不存在 / 格式错误 / 坐标错误 / 用户取消
第二层:模块
text
UI
↓
ModelService
↓
ModelLoader
↓
FileSystem
第三层:抽象
text
ModelLoader
├── OSGBLoader
├── Tiles3DLoader
└── GLTFLoader
第四层:状态
text
Idle
↓
Loading
├── Success
├── Failed
└── Cancelled
第五层:测试
text
正常模型
错误路径
错误格式
超大模型
重复加载
取消加载
加载失败
第六层:Review
检查:
text
是否高耦合?
是否职责混乱?
是否存在重复代码?
是否容易增加新的模型格式?
是否容易测试?
是否存在资源泄漏?
这整个过程,就是 Software Construction 的思想在实际项目中的落地。
29. 第一阶段学习清单
不需要 Java,也可以按照下面的顺序学习。
Level 1:软件质量
- Safe from Bugs
- Easy to Understand
- Ready for Change
Level 2:Specification
- 明确输入
- 明确输出
- 明确状态
- 明确异常
- 明确边界
Level 3:模块化
- Module
- Cohesion
- Coupling
- Interface
- Abstraction
- Encapsulation
Level 4:可靠性
- Defensive Programming
- Error Handling
- State Management
- Resource Management
Level 5:测试
- Unit Test
- Integration Test
- Regression Test
- Testable Design
Level 6:工程实践
- Git
- Code Review
- Debugging
- Refactoring
Level 7:复杂系统基础
- Concurrency
- Event-driven
- Thread Safety
- Networking
30. 与后续四个资源的关系
MIT Software Construction 不需要单独学完所有"架构"。
它更像是整个学习路线的地基:
text
MIT Software Construction
│
│ 软件工程基本功
↓
Refactoring
│
│ 代码如何持续变好
↓
Design Patterns
│
│ 如何组织复杂代码
↓
Clean Architecture
│
│ 如何建立系统边界
↓
DDIA
│
│ 如何设计复杂系统
↓
System Design
所以:
MIT Software Construction 的任务不是教你成为架构师,而是建立"工程师思维"。
31. 最终应该掌握的核心思想
如果最后只保留十条,请记住:
1. 先定义问题,再写代码
text
Specification → Implementation
2. 一个模块应该有清晰的职责
text
高内聚
3. 模块之间尽量少互相依赖
text
低耦合
4. 用接口隔离具体实现
text
业务 → Interface → Implementation
5. 把变化隔离起来
text
容易变化的东西 → 独立封装
6. 让代码容易测试
text
好的设计 → 更容易测试
7. 不要害怕重构
text
Working Code ≠ Good Code
8. 用测试保护重构
text
Refactor
↓
Test
↓
Confidence
9. 不要为了架构而架构
text
复杂度来自需求
而不是来自设计者
10. AI 是施工队,你是设计者
text
你:
需求 / 判断 / 设计 / 架构
AI:
实现 / 测试 / Debug / Review / 重构
32. 最终目标
学习 MIT Software Construction 最终不是为了:
"掌握 Java。"
也不是为了:
"把课程作业做完。"
真正应该获得的是:
一种构建软件的思维方式。
以后面对一个功能时,你会自然开始思考:
text
这个功能的边界是什么?
↓
谁负责?
↓
依赖谁?
↓
谁依赖它?
↓
什么东西可能变化?
↓
状态有哪些?
↓
异常怎么办?
↓
怎么测试?
↓
以后怎么扩展?
当这些问题逐渐变成你的习惯之后,你的 AI Coding 能力也会发生本质变化。
你不再只是:
"让 AI 写代码。"
而会变成:
"我负责设计软件,AI 帮我高效率完成软件构建。"
这才是 AI 编程时代真正值得培养的工程能力。