MIT《The Missing Semester》2026 Coding Agents 相关新章节
MIT《The Missing Semester》
- Coding Agents(AI 编程智能体):如何利用 AI 编写、修改、测试和理解代码。
- Code Quality(代码质量):如何通过代码检查、自动化测试和 CI 保证软件质量。
- 软件工程沟通与协作(Communication & Collaboration):如何写好技术文档、提交记录、Issue 和 Pull Request,以及如何进行 Code Review。
这三节课其实有非常紧密的联系:
Coding Agents 帮助你更高效地编写代码,Code Quality 帮助你验证代码,Communication & Collaboration 帮助整个团队理解、审查和维护代码。
下面分别讲解技术原理、具体例子、实际使用方法以及课程练习。
第一节:Coding Agents(AI 编程智能体)
一、什么是 Coding Agent?
Coding Agent 是一种能够使用外部工具完成编程任务的 AI 系统。
普通聊天 AI 通常主要通过对话提供解释和代码建议。
Coding Agent 除了生成文本,还可以在得到相应权限的情况下:
- 读取项目中的源代码。
- 修改文件、创建新文件。
- 在终端执行命令。
- 运行自动化测试和类型检查。
- 分析错误并尝试修复。
- 搜索相关文档。
- 检查 Git 修改记录。
因此,它不只是告诉你如何修改代码,而是可以实际执行相关开发步骤。
1. 普通 AI 与 Coding Agent 的区别
| 能力 | 普通 AI 对话 | Coding Agent |
|---|---|---|
| 解释代码 | 可以 | 可以 |
| 生成代码 | 可以 | 可以 |
| 自动读取项目文件 | 取决于工具 | 通常支持 |
| 直接修改项目 | 取决于工具 | 通常支持 |
| 执行测试 | 需要执行工具 | 通常支持 |
| 根据报错迭代修复 | 需要反馈 | 可以通过工具循环完成 |
课程建议采用一个理解模型:
把 Coding Agent 想象成一名能力较强、但仍然需要指导和监督的初级工程师。
你负责告诉它目标、限制和验收标准,它负责执行具体任务,你则要检查结果。
二、Coding Agent 如何工作?
这是课程最重要的技术原理。
传统 LLM(Large Language Model,大语言模型)主要根据输入上下文生成输出。
但 Coding Agent 在此基础上增加了一个负责执行工具调用的程序,教材称为 Agent Harness。
User Prompt
用户提出编程需求
LLM
分析上下文,生成下一步动作
Agent Harness(智能体运行框架)
文件读写
终端命令
运行测试
工具结果反馈给模型,继续分析和修正
例如,你告诉 Agent:
将这个 Python 脚本改造成命令行工具,使用 argparse 解析参数,添加类型注解,并确保类型检查通过。
Agent 可能执行这样的流程:
- 读取 Python 文件。
- 分析程序当前结构。
- 修改代码,增加
argparse。 - 添加 Type Hints(类型注解)。
- 执行
mypy类型检查。 - 如果有错误,就继续修改。
- 检查通过后向你报告结果。
这个过程最关键的地方,是 Feedback Loop(反馈循环)。
它不是只生成一段代码,而是利用真实的工具运行结果验证自己的工作。
三、LLM 的 Context Window 是什么?
Context Window(上下文窗口)指模型在一次推理中能够处理的上下文长度限制。
例如,一个项目可能包含:
project/
├── app.py
├── database.py
├── utils.py
├── tests/
├── docs/
└── README.md
如果 AI 同时读取大量文件、文档、终端输出和历史对话,就会消耗上下文容量。
这会带来两个问题:
- 上下文可能超过模型的容量限制。
- 即使没有超过限制,大量无关信息也可能干扰模型完成当前任务。
因此,有效使用 Coding Agent,不只是写出好的 Prompt,还需要管理提供给它的上下文。
课程介绍了三种常见方法:
| 方法 | 含义 | 适用场景 |
|---|---|---|
| Clear Context | 开始新的对话 | 切换到完全不同的任务 |
| Rewind | 回退之前的对话步骤 | Agent 沿错误方向工作 |
| Compaction | 压缩已有对话信息 | 对话过长,需要保留重要上下文 |
这部分还有一个重要结论:
提供更多上下文不一定更好,提供准确且相关的上下文才更重要。
四、Coding Agent 有哪些实际用途?
1. Implementing Features:实现新功能
假设你有一个 Python 项目,需要增加 CSV 导出功能。
你可以要求 Agent:
在现有项目中增加导出 CSV 的功能。保留现有 API,增加单元测试,运行全部测试,并列出修改的文件。
这比简单要求“帮我写一个 CSV 函数”更完整。
因为你不仅告诉它要做什么,还明确了不能破坏什么,以及如何验收。
2. Fixing Errors:修复错误
例如:
mypy .
报告:
error: Incompatible return value type
你可以要求 Agent 找出原因、修复问题,再重新运行 mypy。
这种能够自动运行检查工具的任务非常适合 Agent。
3. Refactoring:重构代码
Refactoring 指在尽量保持外部行为不变的前提下,改善代码结构。
例如,将一个 500 行的 Python 文件拆分成:
project/
├── api.py
├── models.py
├── services.py
└── utils.py
Agent 可以帮助调整导入路径、函数位置以及相关测试。
但是必须验证重构没有破坏原有功能。
4. Code Review:代码审查
可以让 Agent 检查:
- 潜在 Bug。
- 不合理的异常处理。
- 缺失的测试。
- 安全风险。
- 重复代码。
例如:
Review the changes since the last commit. Focus on correctness, security and missing tests.
但这不意味着 AI 的审查结果一定正确。它可能遗漏真实问题,也可能把正常代码误判为 Bug。
5. Code Understanding:理解陌生代码库
这对新加入项目的开发者尤其有用。
例如:
Explain how authentication works in this repository. Identify the main entry points and relevant files.
Agent 可以阅读相关文件,帮助你理解认证流程。
这比漫无目的地逐个阅读文件通常更高效。
6. Vibe Coding
教材还介绍了 Vibe Coding。
其含义是:开发者主要通过自然语言描述目标,由 AI 完成绝大部分实际代码编写。
这种方式适合快速原型和探索性项目,但对于长期维护的软件,开发者仍然需要理解代码、安全边界和测试结果。
五、进阶 Agent:AGENTS.md、Skills、Subagents 和 MCP
这四个术语很容易混淆。
1. AGENTS.md:项目级指导文件
假设你的项目中有:
project/
├── AGENTS.md
├── src/
└── tests/
AGENTS.md 可以用于告诉兼容的 Coding Agent 如何在这个项目中工作。
例如:
# Project Instructions
- Use Python 3.12.
- Format code with Ruff.
- Run pytest after changing code.
- Do not modify generated files.
- Do not commit API keys.
它的作用类似于一份给 AI 开发者阅读的项目说明。
不同 Agent 对指令文件的名称、作用范围和加载规则可能不同。
2. Skills:按需加载的专门工作流程
AGENTS.md 一般用于项目级约定。
Skill 则可以把某项专门任务的操作方法单独封装。
例如:
skills/
└── code-review/
└── SKILL.md
里面可以说明:
- 如何分析代码变更。
- 重点检查哪些风险。
- 如何运行测试。
- 如何组织审查结果。
Agent 在需要进行代码审查时,再按自身支持的机制加载对应的 Skill。
这样可以减少每次对话都加载大量无关指导信息的问题。
3. Subagent:子智能体
Subagent 是由主 Agent 调用、负责特定任务的智能体。
例如,主 Agent 正在实现新功能,同时可以将代码检查任务交给专门的子智能体。
Main Agent
负责整体开发任务
Code Agent
实现功能
Checking Agent
运行 mypy、ruff
教材提供的练习就是创建一个 Python Code Checking Subagent,使它能够:
- 对上一次 Git Commit 后修改过的 Python 文件执行
mypy。 - 运行
ruff。 - 检查代码格式和静态分析问题。
- 向主 Agent 返回检查结果。
4. MCP:Model Context Protocol
MCP 是一种用于连接 AI 应用与外部工具或数据源的开放协议。
例如,Agent 可以通过支持的 MCP 服务访问文档或其他工作系统。
因此,它和前面的概念并不相同:
| 概念 | 主要解决的问题 |
|---|---|
| AGENTS.md | Agent 应当遵守哪些项目规则? |
| Skill | Agent 如何完成某类专门任务? |
| Subagent | 哪个专门 Agent 来完成子任务? |
| MCP | Agent 如何连接外部工具和数据源? |
5. Parallel Agents:并行智能体
教材还介绍了让多个 Agent 同时开发。
例如:
- Agent A 修改前端。
- Agent B 修改后端。
- Agent C 增加测试。
如果多个 Agent 直接修改同一工作目录,就可能发生冲突。
因此课程推荐结合 Git Worktree,为不同 Agent 提供相对独立的工作目录。
六、Coding Agents 的风险
教材特别强调了几个风险:
第一,AI 可能产生错误代码。即使代码可以运行,也不代表它满足业务要求。
第二,AI 可能陷入调试循环。它不断尝试修改,但没有真正定位问题,甚至造成更多修改。
第三,隐私与安全。部分 Coding Agent 会把代码、上下文或工具结果传输给云端模型。使用前需要理解相关权限和数据处理方式。
第四,过度依赖。如果完全不理解生成的代码,短期看似节省了时间,长期却可能降低你独立开发和排查问题的能力。
因此,教材特别提醒:不要把允许 Agent 不经确认执行危险操作的模式当作日常默认配置。即使使用隔离容器或虚拟机,也必须考虑网络、文件挂载和凭证的安全边界。
这一节的核心不是“让 AI 替代程序员”,而是:
学会如何提出明确任务、提供正确上下文,并通过真实工具验证 AI 的工作结果。
第二节:Code Quality(代码质量)
这一节讨论一个非常实际的问题:
程序可以运行,是否意味着代码质量很好?
答案是否定的。
例如,下面两段 Python 代码在正常输入下可以产生相同结果:
def add(a,b):return a+b
def add(a: int, b: int) -> int: return a + b
第二段代码结构更清晰,而且具有类型注解。
但即使第二段代码写得很整洁,也不能证明它没有逻辑错误。
因此,课程介绍了一系列不同层次的质量保证工具:
Formatting、Linting、Testing、Pre-commit Hooks、Continuous Integration 和 Command Runners。
一、Formatting:代码格式化
Formatting 是自动统一代码风格的过程。
例如,你写了:
def calculate(a,b): return a+b
格式化工具可以调整为:
def calculate(a, b): return a + b
通常可以自动处理:
- 空格与缩进。
- 引号风格。
- 过长代码行。
- 换行方式。
- 一些语法布局规则。
常见工具包括:
| 语言 | 工具 |
|---|---|
| Python | Ruff Formatter、Black |
| JavaScript / TypeScript | Prettier |
| Go | gofmt |
| 多种文件格式 | Prettier 等 |
例如,Python 项目使用 Ruff:
ruff format .
就可以按照配置格式化项目。
还可以配置 IDE 在保存文件时自动格式化。
为什么这很重要?
假设一个团队有十个人,每个人都习惯不同的缩进、换行和引号方式。
代码审查时,就会出现大量无意义的格式变化。
统一格式的好处,是让程序员把注意力集中在真正重要的逻辑修改上。
二、Linting:静态代码检查
Formatting 和 Linting 的区别非常重要。
Formatting 关注代码怎么写得整齐;Linting 关注代码中是否存在可疑或不合理的写法。
例如:
import osdef calculate(): x = 10 return 42
这里的 os 没有使用,x 也没有被使用。
代码能够运行,但存在没有必要的内容。
Linter 可能报告:
Unused import: os
Local variable x is assigned but never used
使用 Ruff:
ruff check .
可以运行规则检查。
某些可安全自动修复的问题可以使用:
ruff check --fix .
但是,不是所有 Lint 警告都意味着代码一定错误。
部分规则可能产生 False Positive(误报),而且自动修复仍需要检查结果。
Formatter、Linter 和 Type Checker 的区别
| 工具 | 主要检查内容 | 示例 |
|---|---|---|
| Formatter | 格式与排版 | 空格、缩进、换行 |
| Linter | 可疑写法和代码规范 | 未使用变量、复杂条件 |
| Type Checker | 类型是否一致 | 函数返回 str 却声明为 int |
例如:
def get_age() -> int: return "twenty"
mypy 这类类型检查工具可以指出返回值与声明的类型不一致。
但类型检查通过,也不意味着程序逻辑一定正确。
Semgrep 是什么?
教材还介绍了 Semgrep。
普通文本搜索主要匹配字符,而 Semgrep 可以根据程序语言结构搜索代码模式。
例如:
semgrep -l python \
-e "subprocess.Popen(..., shell=True, ...)"
它可以帮助找出符合特定调用模式的 Python 代码。
这种能力对于自定义安全检查、查找危险 API 用法和执行团队代码规则非常实用。
三、Testing:自动化测试
这是代码质量部分最重要的概念之一。
假设你写了:
def divide(a, b): return a / b
你手动运行:
print(divide(10, 2))
输出:
5.0
于是你认为程序正确。
但这只验证了一个输入组合。
例如:
divide(10, 0)
会发生什么?
它会抛出 ZeroDivisionError。
这不一定意味着实现错误,因为函数的设计可能本来就要求分母非零。但它说明,我们需要明确函数的预期行为并测试。
1. Unit Test:单元测试
单元测试通常针对单个函数或较小的功能单元。
例如:
# calculator.pydef add(a: int, b: int) -> int: return a + b
使用 pytest:
# test_calculator.pyfrom calculator import adddef test_add(): assert add(2, 3) == 5def test_add_negative(): assert add(-2, 3) == 1
执行:
pytest
如果实现符合断言,测试就会通过。
assert 的意思是:断言后面的条件必须成立。
2. Integration Test:集成测试
单元测试主要检查单个组件。
集成测试则关注多个组件协同工作时是否正常。
例如:
API
↓
Business Logic
↓
Database
一个 API 的业务逻辑可能完全正确,但数据库连接配置错误,导致整个功能不可用。
集成测试可以覆盖这种跨组件的问题。
3. Functional / End-to-End Test
端到端测试会从用户使用场景出发,测试一条较完整的业务流程。
例如,一个购物网站:
用户登录
↓
选择商品
↓
加入购物车
↓
提交订单
↓
确认结果
这类测试通常更接近用户实际使用系统的方式。
4. 其他测试方法
教材还介绍了:
- Test-Driven Development(TDD):先编写测试,再实现功能。
- Regression Testing(回归测试):为已经修复的 Bug 增加测试,防止再次出现。
- Property-Based Testing(基于性质的测试):描述程序应当始终满足的性质,并自动生成输入进行验证。
- Mocking(模拟依赖):在测试中用可控对象代替数据库、网络 API 等外部依赖。
例如,除法函数的一项性质是:
对于任意满足条件的数 a 和非零数 b:
[ (a/b)\times b\approx a ]
这里通常要考虑浮点数误差。
Property-Based Testing 工具可以根据这种性质生成大量不同的输入,而不局限于几个手写测试案例。
四、Code Coverage:代码覆盖率
假设你有一个函数:
def classify(x): if x > 0: return "positive" elif x < 0: return "negative" else: return "zero"
你只测试:
classify(10)
虽然测试通过,但你并没有测试负数和零。
Code Coverage(代码覆盖率)用于衡量执行测试时,哪些代码被运行过。
假设某个项目的行覆盖率为 75%
75% 已执行
25% 未执行
使用 pytest-cov 可以生成覆盖率报告,例如:
pytest --cov=. --cov-report=html
然后查看 HTML 报告,找到哪些代码没有得到测试。
但是教材特别提醒:
高覆盖率不等于高质量测试。
假设一个测试只是调用函数,却完全不检查结果,那么它也可能提高覆盖率。
因此,比起盲目追求 100%,更重要的是测试关键行为、边界情况和错误处理是否正确。
五、Pre-commit Hooks:提交前自动检查
你已经学过 Git,因此这一部分比较容易理解。
正常提交流程:
修改代码
↓
git add
↓
git commit
如果我们希望每次提交前都自动检查代码格式和 Lint 问题,就可以使用 Git Hook。
例如:
修改代码
↓
git add
↓
Pre-commit Checks
├── Formatter
├── Linter
└── Other Checks
↓
git commit
Pre-commit Hook 可以在提交前运行配置好的检查任务。
如果检查不通过,提交通常会被阻止,直到问题得到修复。
它主要解决:
防止不符合项目规范的代码轻易进入版本历史。
不过本地 Hook 可以被跳过,团队也不能假设每个人都正确安装了 Hook。
这就是为什么还需要 CI。
六、Continuous Integration(CI):持续集成
这是现代软件开发中非常重要的工程实践。
假设你的项目托管在 GitHub。
每次开发者提交代码或创建 Pull Request 时,CI 系统都可以自动运行检查。
Git Push / Pull Request
GitHub Actions (CI)
Format
Lint
Test
PASS
检查通过
FAIL
反馈错误
例如,一个 GitHub Actions 工作流可以写成:
name: Python CI
on: [push, pull_request]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install tools
run: python -m pip install ruff pytest
- name: Lint
run: ruff check .
- name: Format check
run: ruff format --check .
- name: Test
run: pytest
这是一个用于理解 CI 的简化示例。
它会在 push 或 Pull Request 事件发生时检查项目。
真实项目还需要根据依赖安装方式、测试配置和安全策略调整工作流。
CI 和 CD 的区别
- CI(Continuous Integration):持续集成,自动构建、检查和测试代码。
- Continuous Deployment(持续部署):在满足条件后,将修改自动部署到目标环境。
例如,课程网站可以在提交代码后自动运行检查,再构建并部署到 GitHub Pages。
七、Command Runners:命令运行器
项目变大之后,开发者可能需要记住很多命令:
uv run ruff check .
uv run ruff format .
uv run pytest
uv run mypy .
教材介绍了 just 这样的 Command Runner。
你可以在项目中定义:
lint:
uv run ruff check .
format:
uv run ruff format .
test:
uv run pytest
然后执行:
just lint
just test
它的本质是:
给复杂或常用的项目命令提供统一、易记的入口。
这也能够降低新成员加入项目时的学习成本。
八、Regular Expressions:正则表达式
这部分虽然看起来与代码质量不同,但在代码搜索、日志分析和批量替换中非常实用。
Regex(Regular Expression,正则表达式)是一种描述文本匹配模式的语言。
1. 最基础的正则表达式
例如:
abc
匹配字符串中的 abc。
\d+
匹配一个或多个数字。
\d{4}-\d{2}-\d{2}
匹配类似:
2026-01-14
的日期形式。
但注意,它同样可能匹配:
2026-01-99
因为这个正则表达式检查的是字符格式,而不是日期是否合法。
2. 常见正则语法
| 符号 | 意思 | 示例 |
|---|---|---|
. |
匹配一个字符 | a.c |
* |
前面的模式重复 0 次或多次 | a* |
+ |
前面的模式重复 1 次或多次 | a+ |
? |
前面的模式出现 0 次或 1 次 | a? |
\d |
数字字符 | \d{4} |
\w |
单词字符 | \w+ |
[abc] |
a、b、c 中的一个 | [abc] |
^ |
字符串或行的开头,取决于模式 | ^Hello |
$ |
字符串或行的结尾,取决于模式 | end$ |
(...) |
捕获分组 | (\d+) |
a\|b |
两个模式二选一 | cat\|dog |
3. Capture Group:捕获组
假设你有一个日期:
2026-01-14
只想获取月份 01。
可以使用:
import retext = "2026-01-14"match = re.match( r"\d{4}-(\d{2})-\d{2}", text)print(match.group(1))
输出:
01
其中括号:
(\d{2})
表示捕获两位数字。
.group(1) 表示获取第一个捕获组的内容。
4. Greedy 和 Non-greedy:贪婪与非贪婪匹配
这是正则表达式中非常容易出错的地方。
假设字符串为:
"name": "Alice", "college": "MIT"
如果使用:
"name": "(.*)"
.* 默认是贪婪匹配,可能匹配尽可能长的内容,把后面的字段也包含进去。
而:
"name": "(.*?)"
使用非贪婪匹配,会尽量在第一个符合条件的位置停止。
但教材特别提醒:
对于 JSON、HTML 等具有复杂结构的数据,不应该依赖简单正则表达式进行完整解析。
例如,要提取 JSON 中的 name:
import jsondata = '{"name": "Alice", "college": "MIT"}'person = json.loads(data)print(person["name"])
这比尝试编写一个处理所有 JSON 转义情况的正则表达式更加可靠。
第三节:Communication & Collaboration(工程沟通与团队协作)
这一节与前面两节有所不同。
前两节主要讲技术工具,而这节课强调:
优秀的软件工程师不仅需要写出正确的代码,还要让其他人能够理解、审查、使用和维护这些代码。
课程开头提出了一个非常重要的原则:
记录和传达 Why(为什么),而不仅仅是 What(做了什么)。
因为代码通常已经能告诉别人“程序做了什么”,但往往无法告诉别人“当初为什么这样设计”。
一、Code Comments:怎样写有价值的注释?
先看一个没有太大价值的注释:
# Increase counter by onecounter += 1
这段注释几乎没有增加任何信息。
因为代码已经能够清楚表达它的行为。
再看另一种:
# Retry at most three times because the upstream# service rate-limits repeated failed requests.MAX_RETRIES = 3
第二段注释不仅解释了常量的含义,还解释了为什么选择这个值。
这就更有价值。
教材推荐的注释类型
| 类型 | 应该解释什么 |
|---|---|
| TODO | 哪些工作尚未完成,为什么暂时没有做 |
| References | 算法、规范或实现依据来自哪里 |
| Correctness Arguments | 为什么这段复杂代码能够产生正确结果 |
| Hard-learned Lessons | 过去遇到过哪些不明显的问题 |
| Rationale for Constants | 某个固定数值为什么这样设置 |
| Load-bearing Choices | 哪些看似普通的实现细节不能随意修改 |
| Why not | 为什么没有使用一个看起来更简单的方案 |
例如:
# Do not replace this ordered collection# with a set: later processing depends# on preserving the original input order.
它不是在描述代码执行了什么,而是在提醒未来的维护者:
这里有一个容易被忽略的设计约束。
如果没有这段注释,其他程序员可能出于性能考虑将其替换为普通集合,从而意外改变程序行为。
这里还涉及一个非常重要的写作原则:
注释应该补充代码无法充分传达的信息,而不是重复代码。
二、README:让其他人理解你的项目
README 通常是别人接触一个项目时看到的第一份文档。
教材建议 README 尽早回答四个问题:
- What does this do? —— 这个项目做什么?
- Why should I care? —— 为什么它对我有用?
- How do I use it? —— 如何使用它?
- How do I install it? —— 如何安装它?
注意它的顺序。
很多开发者习惯先写一大段安装教程。
但用户首先想知道的是:这个工具是否解决自己的问题。
例如,一个 Python CSV 清洗工具的 README 可以这样组织:
README.md 示例结构
CSV Cleaner
A command-line tool for cleaning duplicate rows in CSV files.
首先说明项目的用途
Why use it?
Remove duplicate rows automatically without manually editing spreadsheets.
解释项目的实际价值
Usage
csv-cleaner input.csv --output clean.csv
展示用户如何使用
Installation
pip install csv-cleaner
最后说明安装过程,命令仅作示例
一个好的 README 应该让用户能够快速判断项目是否适合自己,并在需要时找到详细信息。
三、Commit Messages:如何写高质量的 Git 提交信息?
你之前学过:
git commit -m "fix bug"
这虽然是有效命令,但提交信息的价值很低。
因为它没有解释:
- 修复了什么 Bug?
- 为什么 Bug 会发生?
- 为什么采用当前修复方式?
- 是否存在副作用?
教材推荐使用:
Problem → Solution → Implications
即:
问题 → 解决方案 → 影响
例如,假设系统中的付款请求偶尔被重复处理。
一个较好的提交信息可以写成:
Prevent duplicate payment processing
Problem:
Network retries may submit the same payment
request multiple times.
Solution:
Use an idempotency key to identify repeated
requests and reuse the existing result.
Implications:
Payment requests now require a stable
idempotency key from the caller.
这个提交信息不仅说明了修改内容,还解释了问题背景以及可能产生的影响。
这在几个月后调查历史问题时会非常有用。
Atomic Commits:原子提交
教材还推荐将代码按照独立的逻辑变化组织成 Commit。
例如:
较好的提交组织
Commit A: Add user authentication
Commit B: Add authentication tests
Commit C: Update authentication docs
不理想的提交组织
Commit A: Fix login, update payment, rename files, format code, change README
把无关修改塞进同一个 Commit,会让代码审查和后续排查问题变得更加困难。
你之前学过的:
git add -p
就可以帮助你将文件中的不同修改块分开暂存,创建逻辑上更清晰的提交。
四、Bug Reports:如何写好错误报告?
假设你发现某个软件有问题。
你提交了一个 Issue:
This doesn't work. Please fix it.
维护者很可能不知道问题发生在哪里。
课程建议,一个好的 Bug Report 至少应该说明:
- Environment:操作系统、软件版本及相关配置。
- Expected Behavior:预期行为。
- Actual Behavior:实际发生的结果。
- Reproduction Steps:如何复现问题。
- Attempted Solutions:你已经尝试过哪些解决方式。
例如:
Bug Report 示例
Title: CSV export fails when the input is empty
Environment
Python 3.12, Ubuntu 24.04, application v1.2.0
Expected Behavior
The command should produce an empty CSV file containing the header row.
Actual Behavior
The program raises an IndexError.
Steps to Reproduce
- Create an empty input CSV file.
- Run
csv-cleaner input.csv --output result.csv. - Observe the exception.
What I Tried
Tested with a non-empty CSV file; the problem did not occur.
这种报告让维护者更容易理解和复现问题。
Minimal Reproducible Example:最小可复现示例
这是软件工程中非常重要的能力。
假设你的程序有 10,000 行代码,某个功能发生异常。
你可以尝试将导致错误的代码逐渐简化:
10,000 行项目
↓
500 行相关模块
↓
50 行相关逻辑
↓
10 行最小复现代码
最终得到一个只包含问题核心的示例。
这样的示例非常有价值,因为它可以排除不相关因素,帮助维护者快速定位错误。
有时你在缩小问题范围的过程中,就会自己发现根本原因。
五、Pull Request:如何向项目贡献代码?
Pull Request(PR)是 GitHub 等平台中常见的协作机制。
它让开发者提出代码修改,并邀请维护者审查、讨论和合并。
一个典型的工作流程是:
1
2
建立开发分支
3
实现一个明确的修改
4
运行格式检查与测试
5
创建 Pull Request
6
进行 Code Review
7
根据反馈修改并合并
教材建议你在提交 PR 前,先确认项目是否已有类似的 Issue 或正在进行的工作。
还要尽量控制 PR 的范围,不要为了实现一个小功能而同时修改大量无关文件。
一个好的 PR 描述不仅要说明修改了什么,还要解释:
为什么需要这个修改,以及为什么当前实现值得被项目接受。
维护者需要长期承担代码维护责任,因此合理的技术方案也可能因为维护成本过高或不符合项目方向而被拒绝。
六、Code Review:如何审查别人的代码?
Code Review 不只是寻找 Bug。
它还帮助团队共享知识、发现不合理的设计以及提高代码可维护性。
教材提出了几个很实用的原则。
1. Review the Code, Not the Person
评价代码,而不是评价程序员。
不理想:
You wrote confusing code.
更好的方式:
This function is difficult to follow. Could we split validation and processing into separate functions?
第二种表达更加聚焦于问题,也更容易推动改进。
2. Actionable Feedback
评论应该尽量可执行。
例如:
不理想:
Don't use globals.
更好的方式:
Could we replace these global variables with a configuration object to make testing easier?
3. 区分必须修改和个人建议
并不是所有 Review Comment 都应该阻止 PR 合并。
例如:
Blocking Issue:
一个身份验证函数允许未授权用户访问数据。
这是需要优先解决的正确性和安全问题。
Suggestion:
某个变量名可以更清楚。
这是可以讨论的改进建议,不一定需要阻止合并。
4. 不要无限追求细节
代码审查需要关注重要问题。
如果审查者不断纠结无关紧要的排版细节,会消耗开发者和维护者的时间。
这也是前面为什么需要 Formatter:机器可以自动处理的问题,就没有必要让人工 Review 占用大量时间。
七、Education:如何提出好的技术问题?
教材引用了 Julia Evans 关于如何提问的建议。
其中最重要的技巧是:
先说明你的理解,再指出你不理解的具体部分。
例如:
不理想:
I don't understand Docker.
更好的问题:
My understanding is that Docker containers share the host kernel, while virtual machines have their own guest OS. Does that mean Docker containers cannot run a different kernel from the host?
第二个问题更加具体。
回答者能够直接看出你已经理解了什么,以及需要澄清什么。
同样,提问时还应该尽量说明自己尝试了什么,以及遇到了怎样的结果。
这不仅适用于人与人之间的沟通,也适用于向 AI 提问。
八、AI Etiquette:AI 使用规范
教材最后讨论了 AI 在软件工程中的使用规范。
它强调:
当 AI 对你的工作有实质性贡献时,应该按照项目或组织的规则披露 AI 的使用情况。
例如:
- AI 只帮助润色了 README。
- AI 实现了某个函数,但你编写并验证了测试。
- 整个应用主要由 AI 生成。
这几种情况并不相同。
教材还提醒,如果公司或团队对数据隐私、合规、代码生成以及 AI 使用有特殊要求,开发者应该遵守相关政策。
在考试、技术面试等考核情境中,也需要遵守明确的工具使用规则。
AI 可以帮助你提高效率,但不能代替你对代码正确性和工程责任的判断。
第四部分:如何将三节课结合起来?
我们用一个具体项目,把三份课程材料连接起来。
假设你准备开发一个 Python 命令行工具:
CSV Cleaner:自动删除 CSV 文件中的重复记录。
第一阶段:利用 Coding Agent 开发
你向 Coding Agent 提出任务:
Create a Python CLI that reads a CSV file, removes duplicate rows, and writes the cleaned data to a new file. Use argparse for command-line arguments, include type annotations, and add unit tests. Do not overwrite the input file.
Agent 可以阅读文件、创建程序、编写测试,并尝试执行检查。
第二阶段:通过 Code Quality 验证
代码生成后,你运行:
ruff format .
ruff check .
mypy .
pytest
分别检查代码格式、潜在问题、类型一致性和功能正确性。
接下来通过 Pre-commit Hook 和 GitHub Actions 自动运行这些检查。
这样,即使 AI 修改了代码,也必须经过相同的质量验证流程。
第三阶段:通过 Communication & Collaboration 交付
你创建清晰的提交记录:
git add .
git commit -m "Add CSV deduplication CLI"
编写 README,说明工具用途、使用方法和安装方法。
然后创建 Pull Request,向审查者说明:
项目解决了什么问题、采用了什么实现方式、如何测试,以及是否存在已知限制。
完整的软件工程工作流
需求与验收标准
明确什么才算完成
Coding Agent + Developer
生成、理解、修改代码
Formatter + Linter + Type Checker + Tests
自动验证代码质量
Git Commit + Pull Request
记录修改目的和技术决策
CI + Human Code Review
自动检查与人工判断,共同决定能否合并
这三个部分共同构成了现代软件工程实践的核心。
第五部分:课程练习与学习路线
三份教材包含了很多练习。如果你刚开始学习,我建议先完成下面这些具有代表性的任务。
综合学习任务
0/8 已完成
用 Coding Agent 完成一个小功能
让 AI 修改一个 Python 程序,并观察它如何读取文件、运行命令和修复错误。
创建 Agent 项目指导文件
编写 AGENTS.md,规定代码风格、测试命令和禁止修改的文件,测试 Agent 是否遵守。
配置 Ruff 和 pytest
让一个 Python 项目具备代码格式化、静态分析和单元测试。
练习覆盖率分析
执行带覆盖率的测试,找出未覆盖代码,并为关键情况补充测试。
配置 GitHub Actions
在 Push 和 Pull Request 时自动运行格式检查、Lint 和测试。
练习正则表达式
使用捕获组提取日期月份,比较贪婪与非贪婪匹配,再用 JSON 解析器处理结构化数据。
编写 README 和 Commit Message
说明项目用途、使用方法,并在提交说明中解释 Problem、Solution 和 Implications。
进行一次 Code Review
审查一份真实或练习用的 PR,区分 Blocking Issues 与 Suggestions,并提出可执行的反馈。
三节课最重要的知识总结
| 课程 | 最值得记住的原则 |
|---|---|
| Coding Agents | AI 可以执行编程任务,但必须有明确目标、合适上下文和结果验证 |
| Code Quality | Formatter、Linter、Type Checker、Tests 和 CI 解决不同层次的代码质量问题 |
| Communication & Collaboration | 优秀的技术沟通应该解释 Why,让其他人理解和长期维护你的工作 |
最终需要建立的思维方式是:
不要只是追求把代码写出来,而是要确保代码符合需求、经过验证、容易理解,并能够由其他工程师继续维护。
这三节课真正教给你的,就是如何从“会写程序的人”逐渐成长为“能够可靠交付软件的工程师”。
我虽然行过死荫的幽谷,也不怕遭害,因为你与我同在。
诗篇 23:4
现代软件工程实践:AI 编程智能体、代码质量与工程协作
基于 MIT The Missing Semester 三个主题的系统化教程
- 第一部分:Agentic Coding(AI 编程智能体)
- 第二部分:Beyond the Code(软件工程沟通与协作)
- 第三部分:Code Quality(代码质量与自动化工程实践)
导言:从编写代码到交付可靠的软件
传统计算机课程通常比较关注算法、数据结构、编程语言和操作系统。但是,在真实的软件开发工作中,仅仅掌握这些知识是不够的。
一个软件工程师还需要解决以下问题:
- 如何快速理解一个陌生的软件项目?
- 如何借助 AI 完成代码开发,同时保证代码正确?
- 如何发现代码中的潜在错误?
- 如何自动执行代码检查和测试?
- 如何让其他开发者理解自己的代码?
- 如何与团队成员协作开发?
- 如何提交高质量的代码修改?
- 如何确保软件能够长期维护?
这三个主题共同回答了这些问题。
1. 软件工程的三个层次
我们可以将现代软件工程划分为三个层次。
第一层:Software Development(软件开发)
核心问题:如何把需求转化为代码?
涉及:
- 需求理解
- 代码阅读
- 功能实现
- AI 辅助编程
- 代码重构
对应第一部分 Coding Agents。
第二层:Software Quality Assurance(软件质量保证)
核心问题:如何确保代码正确、稳定和可靠?
涉及:
- 代码格式化
- 静态分析
- 类型检查
- 自动化测试
- 持续集成
对应第三部分 Code Quality。
第三层:Software Engineering Collaboration(软件工程协作)
核心问题:如何让不同的人共同理解、开发和维护软件?
涉及:
- 技术文档
- 代码注释
- Git 提交记录
- Bug Report
- Pull Request
- Code Review
- 开源社区协作
对应第二部分 Beyond the Code。
三个层次不是独立的。
例如,当 AI 修改了一段代码后:
- Coding Agent 负责实现修改。
- Code Quality 工具负责验证修改。
- Code Review 负责判断修改是否值得合并。
- 技术文档负责记录修改背后的设计决策。
2. 本教程的贯穿案例
为了避免知识点彼此孤立,我们将围绕一个小型 Python 项目进行学习。
项目名称:Markdown Link Extractor
目标:开发一个命令行工具,从 Markdown 文件或网络资源中提取链接。
最终项目结构:
markdown-link-extractor/
│
├── AGENTS.md
├── README.md
├── pyproject.toml
├── uv.lock
├── .gitignore
├── .pre-commit-config.yaml
│
├── src/
│ └── link_extractor/
│ ├── __init__.py
│ └── cli.py
│
├── tests/
│ ├── test_extract.py
│ └── test_cli.py
│
└── .github/
└── workflows/
└── ci.yml
我们将逐步完成:
- 使用 AI 编程智能体开发程序。
- 为程序添加类型注解和自动化测试。
- 配置 Formatter 和 Linter。
- 配置 Git Pre-commit Hooks。
- 配置 GitHub Actions。
- 编写 README 和技术文档。
- 创建 Git Commit 和 Pull Request。
- 通过 Code Review 完成软件交付。
第一部分:Agentic Coding(AI 编程智能体)
第 1 章 理解 AI 编程智能体
1.1 从传统编程到 AI 辅助编程
传统软件开发通常遵循以下过程:
需求
↓
程序员分析问题
↓
编写代码
↓
运行程序
↓
发现错误
↓
修改代码
↓
重新测试
随着大语言模型的发展,程序员可以将部分开发任务交给 AI。
例如:
- 解释一个陌生项目。
- 根据需求生成函数。
- 修改已有代码。
- 编写单元测试。
- 修复类型检查错误。
- 重构大型模块。
- 分析 Git 修改记录。
但需要区分三种 AI 编程方式。
1.1.1 AI Autocomplete(智能代码补全)
这是最基础的 AI 编程辅助方式。
例如:
def calculate_average(numbers):
AI 根据上下文预测后续代码:
return sum(numbers) / len(numbers)
特点:
- 以局部代码补全为主。
- 通常不需要复杂的任务规划。
- 适合减少重复编码。
- 程序员仍然主导整个开发过程。
1.1.2 Inline Chat(代码编辑器中的 AI 对话)
程序员可以选中一段代码,让 AI:
- 解释代码。
- 优化代码。
- 增加注释。
- 修改函数。
- 转换编程语言。
例如:
将这个函数重构为三个独立函数,
分别负责数据验证、数据处理和结果输出。
AI 根据要求生成修改建议或直接修改选中的代码。
1.1.3 Coding Agent(AI 编程智能体)
Coding Agent 的能力更进一步。
它不仅生成代码,还可以操作开发环境。
例如:
请分析这个项目。
找到处理用户登录的代码,
增加密码长度验证,
补充相应的单元测试,
运行测试和类型检查,
直到所有检查通过。
Agent 可以自主执行一系列操作:
- 查找项目文件。
- 阅读相关代码。
- 确定需要修改的位置。
- 修改代码。
- 创建测试。
- 执行测试。
- 根据错误继续修改。
- 总结最终结果。
这就是 Agentic Coding。
1.2 Coding Agent 的核心架构
要理解 Coding Agent,首先需要理解它不是一个单独的语言模型,而是由多个部分构成的系统。
1.2.1 LLM:大语言模型
LLM(Large Language Model)是 Coding Agent 的核心推理和文本生成组件。
从课程采用的高层模型来看:
给定输入字符串,模型根据训练得到的概率分布预测后续 Token。
可以写成:
[
P(y\mid x)
]
其中:
- (x):输入 Prompt。
- (y):模型生成的输出。
- (P(y\mid x)):给定输入时生成某个输出的概率。
在编程任务中:
输入可以是:
- 用户的需求。
- 项目源代码。
- 终端输出。
- 历史对话。
- 外部文档。
输出可以是:
- 解释。
- Python 代码。
- 修改建议。
- 工具调用请求。
需要注意,LLM 的输出是概率生成结果,并不意味着它已经通过程序运行验证。
1.2.2 Agent Harness:智能体运行框架
Agent Harness 是连接语言模型与外部工具的程序。
它负责:
- 将上下文发送给模型。
- 识别模型生成的工具调用请求。
- 执行对应工具。
- 将执行结果反馈给模型。
- 管理对话和工具调用过程。
- 根据权限策略决定是否需要用户确认。
例如,模型可能生成一个执行测试的工具调用请求:
Tool: Terminal
Command:
pytest tests/
Harness 负责执行命令,并将结果返回模型。
模型随后根据结果决定下一步。
1.2.3 Tools:外部工具
常见工具包括:
| 工具类别 | 典型功能 |
|---|---|
| File Tools | 读取、创建和修改文件 |
| Shell | 执行终端命令 |
| Search | 搜索项目和外部文档 |
| Git | 查看版本历史和代码差异 |
| Testing | 执行自动化测试 |
| Type Checking | 执行类型检查 |
| Browser | 浏览网页和在线文档 |
1.2.4 Agent Loop:智能体循环
Coding Agent 最重要的工作机制是反复执行:
Observe → Think → Act → Observe
即:
观察 → 分析 → 行动 → 再次观察。
例如:
用户提出需求
↓
Agent 阅读项目
↓
Agent 分析代码结构
↓
Agent 修改代码
↓
Agent 运行测试
↓
测试失败?
↙ ↘
是 否
↓ ↓
分析错误 汇报结果
↓
重新修改
↓
重新测试
这就是为什么允许 Agent 运行测试工具特别重要。
如果 Agent 只能生成代码,却不能获得测试反馈,它就很难建立可靠的自动修正循环。
第 2 章 如何正确使用 Coding Agent
2.1 从模糊需求到明确任务
一个常见错误是只告诉 Agent:
帮我优化这个项目。
这个需求过于模糊。
Agent 不知道:
- 哪些地方需要优化?
- 是性能还是可维护性?
- 是否允许改变 API?
- 是否可以修改项目结构?
- 如何判断优化成功?
更好的方法是把需求拆分为五个部分。
任务描述模板
Task:
需要完成什么任务?
Context:
当前项目是什么情况?
涉及哪些文件和功能?
Constraints:
有哪些不能违反的限制?
Acceptance Criteria:
什么情况下认为任务完成?
Verification:
应该运行哪些检查?
例如:
Task:
将现有 Markdown 链接提取脚本改造成命令行程序。
Context:
当前程序通过 urllib 下载 Markdown 文件,
使用正则表达式提取链接。
Constraints:
1. 使用 Python 标准库。
2. 保留现有提取逻辑。
3. 不引入不必要的第三方依赖。
4. 使用类型注解。
Acceptance Criteria:
1. 支持通过命令行传入 URL。
2. 支持将结果输出到终端。
3. 支持将结果保存到文件。
4. 网络请求失败时给出清晰错误信息。
Verification:
运行 pytest。
运行 mypy。
运行 ruff。
这种任务描述比简单的自然语言要求更容易得到可靠结果。
2.2 实践:让 Agent 改造一个 Python 程序
原课程给出了一个示例。
首先有一个简单程序:
from urllib.request import urlopen
import re
def download_contents(url: str) -> str:
with urlopen(url) as response:
return response.read().decode("utf-8")
def extract(content: str) -> list[str]:
pattern = r"\[.*?\]\((.*?)\)"
return re.findall(pattern, content)
print(
extract(
download_contents(
"https://example.com/document.md"
)
)
)
这个程序有几个问题:
- URL 被写死在代码中。
- 无法通过命令行传入不同 URL。
- 缺少完善的错误处理。
- 没有定义清晰的程序入口。
- 还没有建立自动化测试。
现在让 Coding Agent 将其改造成命令行程序。
目标结构:
import argparse
import re
from urllib.request import urlopen
def download_contents(url: str) -> str:
with urlopen(url, timeout=10) as response:
return response.read().decode("utf-8")
def extract(content: str) -> list[str]:
pattern = r"\[.*?\]\((.*?)\)"
return re.findall(pattern, content)
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument(
"url",
help="Markdown document URL"
)
args = parser.parse_args()
content = download_contents(args.url)
links = extract(content)
for link in links:
print(link)
if __name__ == "__main__":
main()
运行:
python main.py https://example.com/document.md
这里使用了 Python 的 argparse 模块。
它负责解析命令行参数。
例如:
python main.py --help
可以显示程序使用说明。
进一步改进
可以继续要求 Agent:
请进一步改进这个程序:
1. 增加输出文件参数 --output。
2. 网络请求失败时返回非零退出状态。
3. 对输入 URL 进行基本检查。
4. 为 extract 编写单元测试。
5. 使用 mypy 检查类型。
6. 使用 Ruff 检查代码质量。
这里需要特别注意:
课程中的正则表达式是一个简单的 Markdown 链接提取示例,不是完整的 Markdown 解析器。对于包含嵌套括号、转义字符等复杂情况的 Markdown,需要更完善的解析方案。
2.3 让 Agent 形成闭环
最重要的不是让 AI 生成更多代码,而是让它能够验证自己的修改。
推荐工作方式:
明确需求
↓
阅读代码
↓
制定修改方案
↓
执行修改
↓
运行测试
↓
检查失败信息
↓
修改
↓
再次验证
可以使用如下 Prompt:
请不要只根据代码阅读结果判断任务完成。
你需要:
1. 修改相关文件。
2. 运行项目已有的测试。
3. 运行类型检查。
4. 运行 Linter。
5. 检查失败原因。
6. 修复你造成的问题。
7. 再次执行全部检查。
最后列出:
- 修改了哪些文件
- 解决了什么问题
- 执行了哪些检查
- 哪些检查通过
- 还有哪些未解决的问题
注意:Agent 声称检查通过,不等于检查确实通过。需要查看实际终端结果。
第 3 章 Coding Agent 的主要应用场景
3.1 Feature Implementation:新功能开发
任务:为一个已有程序增加功能。
例如:
为现有用户管理系统增加 CSV 导出功能。
要求:
- 支持选择导出字段。
- 支持 UTF-8 编码。
- 不修改原有查询 API。
- 为正常情况和异常情况增加测试。
适用场景:
- 增加功能。
- 编写重复代码。
- 扩展 API。
- 增加命令行参数。
- 实现数据处理逻辑。
3.2 Error Fixing:错误修复
当编译器、Linter、Type Checker 或测试出现错误时,可以将错误信息交给 Agent。
例如:
运行 mypy 后出现以下错误:
Incompatible return value type
请分析原因并修复。
不要通过简单关闭类型检查规则来消除错误。
为什么要避免只关闭规则?
因为这可能只是隐藏了问题,并没有真正修复问题。
3.3 Refactoring:代码重构
重构是改善代码内部结构,而尽量不改变外部行为。
例如:
原本有一个过长的函数:
def process_user(data):
# 数据验证
# 数据清洗
# 数据保存
# 日志记录
pass
可以拆分为:
def validate_user(data):
pass
def clean_user(data):
pass
def save_user(data):
pass
def process_user(data):
validate_user(data)
clean_user(data)
save_user(data)
重构之后,应该使用测试验证原有功能没有被破坏。
3.4 Code Review:AI 辅助审查
可以让 Agent 检查最近一次 Commit 的修改:
Review all changes since the last commit.
Focus on:
1. Correctness bugs.
2. Security vulnerabilities.
3. Missing error handling.
4. Performance issues.
5. Missing tests.
For each finding, provide:
- File and line number.
- Explanation.
- Potential impact.
- Suggested fix.
Do not report purely stylistic preferences.
AI 的审查结果应作为候选问题,而不是最终结论。
3.5 Code Understanding:理解陌生项目
当你接触一个大型代码库时,可以让 Agent:
- 识别项目入口。
- 解释模块关系。
- 找出关键数据结构。
- 追踪函数调用关系。
- 解释一个功能的执行路径。
建议使用逐步追问,而不是一次要求它解释整个仓库。
例如:
第一步:
请识别项目的主要入口文件。
第二步:
请追踪用户登录请求的调用路径。
第三步:
请解释认证中间件的工作机制。
第四步:
请指出这一实现可能存在的安全风险。
3.6 Shell as an Interface:自然语言驱动终端
Agent 还可以将自然语言转换为 Shell 命令。
例如:
找到项目中所有超过 500 行的 Python 文件。
Agent 可能使用:
find . -name "*.py" -exec wc -l {} \;
但是,Shell 命令具有很强的操作能力。
涉及删除文件、修改权限、覆盖数据、访问网络和读取秘密信息的命令,必须谨慎审查。
3.7 Vibe Coding:自然语言驱动应用开发
Vibe Coding 适合:
- 小型原型。
- 概念验证。
- 个人工具。
- 快速试验。
- 交互界面草稿。
不适合直接将未经理解和验证的生成结果投入关键生产系统。
第 4 章 进阶智能体工程
4.1 Reusable Prompts:可复用提示词
如果你经常要求 Agent 做代码审查,可以把任务要求保存为模板。
例如:
你是一个 Python Code Review Agent。
请重点检查:
1. 逻辑正确性。
2. 异常处理。
3. 类型安全。
4. 资源管理。
5. 安全问题。
6. 测试覆盖。
请优先报告高风险问题。
不要仅仅为了个人风格提出修改建议。
这样可以减少重复编写 Prompt 的时间。
4.2 Parallel Agents:并行智能体
多个 Agent 可以同时完成不同任务。
例如:
| Agent | 工作 |
|---|---|
| Agent A | 实现 API |
| Agent B | 编写测试 |
| Agent C | 阅读技术文档 |
| Agent D | 审查安全风险 |
但要避免多个 Agent 同时修改相同文件。
课程推荐使用 Git Worktree 隔离工作目录。
例如:
git worktree add ../project-api feature-api
git worktree add ../project-tests feature-tests
这样不同工作目录可以对应不同分支。
4.3 MCP:Model Context Protocol
MCP 是连接 AI 应用与外部工具、数据源的开放协议。
例如,一个支持 MCP 的 Coding Agent 可以通过相应服务:
- 读取项目文档。
- 查询数据库。
- 访问 Notion。
- 与其他工作系统交互。
MCP 解决的是模型如何通过标准化接口访问外部工具和上下文的问题。
它不是一个新的语言模型,也不是编程语言。
4.4 Context Management:上下文管理
课程重点介绍四种方式。
Clear Context
对于完全不同的任务,开始新的对话。
避免无关历史干扰当前任务。
Rewind
当 Agent 沿着错误方向执行时,回退到之前的状态。
比不断叠加修正 Prompt 更有效。
Compaction
将较长的历史压缩为摘要。
保留重要信息,减少上下文消耗。
llms.txt
教材介绍了 /llms.txt 这一提议标准。
它的目标是提供适合语言模型阅读的文档入口。
例如,一个工具的文档可能包含:
https://example.com/llms.txt
它可以帮助 Agent 更高效地获取相关文档,但具体是否提供以及内容质量取决于项目。
4.5 AGENTS.md
AGENTS.md 是项目级指导文件。
例如:
# AGENTS.md
## Project
Python 3.12 project.
## Coding Standards
- Use type annotations.
- Use Ruff for formatting.
- Use Ruff for linting.
## Testing
Run pytest before completing a task.
## Restrictions
- Do not modify generated files.
- Do not commit secrets.
- Preserve public APIs unless explicitly requested.
它可以帮助 Agent 在多个任务和会话中遵循统一项目规范。
4.6 Skills 与 Subagents
Skill 是专门工作流程的封装。
Subagent 是负责特定任务的子智能体。
例如:
Main Agent
|
+-- Research Subagent
|
+-- Testing Subagent
|
+-- Security Review Subagent
它们可以让主 Agent 将工作委派给专门的子流程。
但不同产品对 Skills 和 Subagents 的支持方式不完全相同。
第 5 章 AI 编程的局限与风险
5.1 概率生成不等于程序正确
AI 可能生成:
- 不存在的 API。
- 错误的依赖用法。
- 不符合需求的逻辑。
- 不安全的代码。
- 看似正确但实际失败的测试。
5.2 Debugging Spiral:调试循环
有时 Agent 会:
- 修改代码。
- 运行测试。
- 发现错误。
- 修改另一个位置。
- 引入新的错误。
- 再次尝试。
这种过程可能不断扩大修改范围。
解决方法:
- 要求先定位根因。
- 控制修改范围。
- 查看 Git Diff。
- 每次修改后运行相关测试。
- 必要时回退。
- 不要让 Agent 无限制地重复尝试。
5.3 Privacy:隐私风险
课程提醒,许多 AI 编程工具默认使用云端模型。
可能被发送到云端的信息包括:
- 源代码。
- 项目文件。
- 提示词。
- 终端输出。
- 交互历史。
因此,使用前应该检查工具的数据处理方式、企业政策和隐私要求。
5.4 Overreliance:过度依赖
如果程序员只负责提出需求,完全不理解生成的代码,就很难承担长期维护责任。
尤其在学习阶段,完全让 AI 代替自己思考,可能使你获得了一个程序,却没有获得相应的编程能力。
最终原则:AI 可以承担执行任务的工作,但程序员必须承担理解、验证和决策的责任。
第二部分:Beyond the Code(软件工程沟通与协作)
第 6 章 软件工程为什么不仅仅是编程?
软件工程师的工作不仅包括编写程序,还包括与其他工程师进行持续交流。
真实项目中,你需要:
- 解释系统如何工作。
- 了解其他团队的需求。
- 阅读他人的代码。
- 参与技术决策。
- 报告错误。
- 审查代码。
- 维护已有系统。
课程将工程沟通大致分成两类:
- One-way Communication:单向沟通。
- Two-way Communication:双向沟通。
单向沟通主要通过文档、注释和提交信息完成。
双向沟通主要通过讨论、Issue、PR、Review 和技术交流完成。
第 7 章 One-way Communication:单向技术沟通
7.1 为什么需要单向沟通?
假设你今天完成了一个复杂的功能。
六个月以后,另一位程序员接手这个项目。
他可能不知道:
- 为什么使用当前算法?
- 为什么选择这个数据结构?
- 为什么某个参数是 1492?
- 为什么没有采用另一种实现?
- 哪些部分存在尚未解决的限制?
如果没有记录,这些知识就会随着开发者离开项目而消失。
因此,技术文档的一个核心任务是保存决策背后的原因。
7.2 Code Comments:代码注释
课程强调:
好注释解释 Why,而不是重复 What。
错误示例:
# Add one to counter
counter += 1
正确性或设计理由示例:
# Preserve insertion order because the downstream
# serializer depends on deterministic field ordering.
后者记录了代码中不容易直接看出的约束。
值得记录的七类注释
1. TODO
标记尚未完成的工作。
不好的写法:
# TODO: optimize
更好的写法:
# TODO: Current lookup is O(n^2), which is acceptable
# for fewer than 100 records. Introduce an index if
# the dataset grows beyond this size.
2. References
记录算法、论文或外部规范的来源。
例如:
# Implements the retry policy described in:
# https://example.com/specification
正式项目中最好引用稳定的文档版本或永久链接。
3. Correctness Arguments
解释复杂代码为什么正确。
# The loop invariant guarantees that all elements
# before i have already been processed.
4. Hard-learned Lessons
记录不容易发现的问题。
例如:
# Flush the buffer before closing the stream.
# Otherwise the final partial batch may be lost.
5. Rationale for Constants
解释魔法数字。
# Maximum batch size is 128 to stay within the
# downstream service's request limit.
MAX_BATCH_SIZE = 128
6. Load-bearing Choices
解释为什么某个实现细节不能随便改变。
# Keep this collection ordered because output
# generation depends on deterministic iteration.
7. Why Not
解释为什么没有采用看起来更简单的方案。
# Avoid global mutable state because it prevents
# independent tests from running safely in parallel.
7.3 README:项目说明文档
README 是项目的入口。
推荐结构:
# Project Name
## What does this project do?
## Why should you use it?
## Usage
## Installation
## Configuration
## Development
## Testing
## License
核心顺序:
What → Why → Usage → Installation
先让读者理解项目价值,再让读者学习使用和安装。
README 的质量标准
一个好的 README 应当:
- 能快速说明项目目标。
- 有可以直接理解的使用示例。
- 给出清楚的安装步骤。
- 说明必要的配置。
- 指明项目限制。
- 给出开发和测试方法。
- 不用大量无关文字淹没核心信息。
7.4 Commit Messages:提交说明
Git Commit 不只是保存代码变化,也是记录项目演进历史。
不好的提交:
fix bug
update files
change code
这些内容无法帮助未来的开发者理解修改原因。
课程建议使用:
Problem → Solution → Implications
示例
Prevent duplicate payment processing
Problem:
Network retries may submit the same payment
request multiple times.
Solution:
Use an idempotency key to identify repeated
requests and reuse the existing result.
Implications:
Callers must now provide a stable idempotency key.
如何组织复杂 Commit?
一个 Commit 应当尽量代表一个逻辑完整的修改。
例如:
Commit 1: Add authentication validation
Commit 2: Add authentication unit tests
Commit 3: Update authentication documentation
不要把无关重构、格式化和功能开发混在一个提交中。
使用:
git add -p
可以交互式选择文件中的不同修改块。
这有利于:
- 审查。
- 回滚。
- 使用 git bisect 定位问题。
- 理解项目历史。
使用 LLM 生成 Commit Message
课程指出一个重要问题:
如果只给 LLM 一个 Git Diff,它通常只能知道 What,不知道 Why。
因此可以让模型:
根据当前 Diff 编写 Commit Message。
重点解释:
1. 修改所解决的问题。
2. 选择该方案的原因。
3. 考虑过哪些替代方案。
4. 修改带来的影响。
如果缺少必要背景,请先向我提问。
如果 Agent 本身参与了开发,它通常能够访问此前的需求和设计讨论,因此更容易生成准确的提交说明。
第 8 章 Collaboration:软件工程协作
8.1 软件开发中的参与者
开源项目中通常存在三类群体:
- Users:使用软件的人。
- Contributors:参与贡献的人。
- Maintainers:负责项目长期维护的人。
课程指出,通常用户数量远大于贡献者数量,而贡献者数量又远大于维护者数量。
因此,维护者的时间是一种稀缺资源。
一个贡献是否值得被接受,不仅取决于它能否解决问题,还取决于它是否值得维护者投入时间。
这就是 High Signal-to-Noise Ratio(高信噪比)的重要性。
8.2 Bug Report:高质量错误报告
一个好的 Bug Report 应当包括:
| 内容 | 说明 |
|---|---|
| Environment | 操作系统、版本、配置 |
| Expected Behavior | 预期行为 |
| Actual Behavior | 实际行为 |
| Reproduction Steps | 复现步骤 |
| Previous Attempts | 已经尝试的排查方法 |
示例:
# Bug: CSV export fails for empty input
## Environment
Python 3.12
Ubuntu 24.04
Application v1.2.0
## Expected Behavior
An empty CSV should produce a valid output file
containing the header row.
## Actual Behavior
The program raises IndexError.
## Steps to Reproduce
1. Create an empty input.csv.
2. Run the export command.
3. Observe the exception.
## What I Tried
The issue does not occur with non-empty CSV files.
8.3 Search Existing Issues
提交 Issue 前,先检查:
- 是否已经有人报告相同问题?
- 是否存在正在讨论的解决方案?
- 是否有相关的 Pull Request?
- 是否有项目文档解释该行为?
重复创建 Issue 会增加维护成本。
如果已经存在相关讨论,通常更好的方式是补充有价值的信息。
8.4 Minimal Reproducible Example
最小可复现示例是帮助维护者定位问题的重要工具。
例如,原本有一个大型应用:
10000 lines of code
你逐步删除不相关模块,最后得到:
def parse_value(value):
return value[0]
然后发现输入空字符串时会引发问题。
这比提交整个大型项目更有助于分析根因。
8.5 Issue 的沟通礼仪
课程特别提醒:
- 不要频繁催促维护者。
- 不要反复发布没有新增信息的“me too”。
- 不要只复制终端输出。
- 应该先搜索已有问题。
- 如果等待一段时间没有回复,可以礼貌跟进。
- 如果能够复现问题,尽量提供最小示例。
维护者可能是志愿者,不能假设他们随时有时间响应。
8.6 CONTRIBUTING.md
参与开源项目之前,应阅读:
CONTRIBUTING.md
它通常规定:
- 如何创建开发分支。
- 如何运行测试。
- 代码风格要求。
- PR 格式。
- 提交信息要求。
- 项目开发流程。
还需要注意项目许可证。
例如,某些 Copyleft 许可证会对衍生作品的发布方式产生要求。贡献之前应该了解许可证对代码使用和分发的影响。
如果发现安全漏洞,不应直接公开发布详细利用信息。应该优先通过项目的安全披露渠道联系维护者,给予合理修复时间。
8.7 Pull Request:提交代码贡献
一个好的 PR 应当满足:
- 修改范围明确。
- 尽量避免无关变化。
- 解释为什么需要修改。
- 解释为什么采用当前实现。
- 提供测试方式。
- 指出需要审查的重点。
如果一个功能确实需要很多相关文件的修改,可以使用较大的 PR,但应该保持 Commit 组织清晰,使维护者能够逐步审查。
AI 生成的 PR
课程特别提醒:
AI 可以快速生成代码,但不能因此降低贡献者的责任。
如果提交者自己无法解释代码,维护者就需要承担额外的理解、审查和维护成本。
因此,提交 AI 生成的代码之前,必须:
- 理解主要实现。
- 检查功能正确性。
- 检查安全性。
- 验证测试。
- 删除无关修改。
- 补充必要文档。
维护者最终接受的是长期维护责任,而不是一次性的代码输出。
第 9 章 Code Review:代码审查
9.1 为什么需要 Code Review?
Code Review 不仅仅是寻找错误。
它还有以下价值:
- 提高代码质量。
- 发现设计缺陷。
- 传播项目知识。
- 统一开发规范。
- 帮助初级工程师学习。
- 减少错误进入生产环境的概率。
即使你不是高级工程师,也可以参与 Review。
新成员有时反而更容易发现项目中的隐含假设。
9.2 Code Review 的六项原则
原则一:Review the Code, Not the Person
不要针对个人。
不好的评论:
You wrote confusing code.
更好的评论:
This function is difficult to follow.
Could we separate validation and processing?
原则二:Prefer Actionable Comments
评论应该让作者知道下一步怎么做。
不好的评论:
Don't use globals.
更好的评论:
Could you replace these global variables
with a configuration dataclass?
This would allow tests to run independently
and safely in parallel.
原则三:Ask Questions Rather Than Making Demands
例如:
What happens if this value is null?
比直接命令:
Handle null values.
更容易展开有效讨论。
原则四:Explain the Why
建议:
Consider using a named constant here
so the timeout can be adjusted by environment.
而不是仅仅说:
Use a constant.
原则五:Distinguish Blocking Issues and Suggestions
可以在评论中区分:
- Blocking:必须修改的问题。
- Suggestion:改进建议。
- Nit:非阻塞的小问题。
原则六:Acknowledge Good Work
不要只指出问题。
如果某个实现很好,也应该告诉作者原因。
代码审查应该帮助团队建立更好的工程习惯,而不是让作者感到所有修改都在被否定。
9.3 Know When to Stop
不要把每个细节都变成必须修改的要求。
优先处理:
- 正确性。
- 安全性。
- 兼容性。
- 可维护性。
- 重要的性能问题。
对于不影响整体质量的小型风格问题,应合理控制评论数量。
第 10 章 Education:如何提出有效的技术问题
程序员需要持续学习,也需要向其他人提问。
课程介绍了五个重要方法。
1. State Your Understanding First
先说明自己已经理解的内容。
例如:
我的理解是 LEFT JOIN 会保留左表的所有行,
即使右表没有匹配记录也会保留。
这个理解正确吗?
2. Ask Yes/No Questions
适当提出可以用是或否回答的问题。
这样更容易确认某个具体理解是否正确,避免无关讨论。
3. Be Specific
不要问:
How does SQL work?
可以问:
LEFT JOIN 是否会保留右表中没有匹配记录的左表行?
4. Admit When You Don't Understand
如果不理解一个概念,应直接说明。
我理解了前面的执行流程,
但不清楚这里为什么需要使用事务隔离级别。
这比假装理解更有助于学习。
5. Don't Accept Incomplete Answers
不要因为别人给出一个看起来专业的答案就停止思考。
应该继续追问,直到真正理解。
当然,也要尊重对方时间。
这些原则同样适用于与 LLM 对话。
第 11 章 AI Etiquette:AI 使用规范
AI 已经成为软件开发的重要工具,但使用方式仍然需要遵守工程伦理和团队规则。
11.1 Disclose AI Usage
当 AI 对工作产生实质性贡献时,应该按照团队和项目要求披露。
例如:
AI Assistance:
- AI helped generate the initial unit tests.
- The implementation was manually reviewed.
- All tests were executed by the contributor.
比简单写一句“Used AI”更有价值。
11.2 Follow Team Policies
不同团队可能对以下问题有不同要求:
- 哪些代码可以发送到云端模型?
- 是否允许使用第三方 AI 服务?
- 是否可以让 AI 生成核心业务代码?
- 是否需要记录 AI 参与情况?
- 是否涉及数据驻留和合规要求?
应该遵守组织的规则。
11.3 Interviews and Assessments
面试和考试通常是为了评估个人能力。
如果不确定是否允许使用 AI,应该事先询问。
不能假设所有考核都允许外部 AI 工具。
第三部分:Code Quality(代码质量)
第 12 章 代码质量体系概述
代码能够运行,并不代表代码质量足够好。
例如:
def process(data):
result = []
for x in data:
if x > 0:
if x < 100:
result.append(x)
return result
它可能运行正常,但仍然存在:
- 风格不统一。
- 条件嵌套不必要。
- 缺少类型注解。
- 缺少测试。
- 没有错误处理。
- 没有说明输入约束。
代码质量需要多种工具共同保障。
本课程介绍六项主要内容:
- Formatting
- Linting
- Testing
- Pre-commit Hooks
- Continuous Integration
- Command Runners
另外还介绍 Regular Expressions。
第 13 章 Formatting:自动格式化
13.1 什么是代码格式化?
Formatter 会自动调整代码的表面形式。
例如:
原始代码:
def calculate(a,b):
return a+b
格式化后:
def calculate(a, b):
return a + b
主要处理:
- 缩进。
- 空格。
- 引号。
- 换行。
- 行长度。
- 代码布局。
13.2 为什么需要统一格式?
如果每个人都按照自己的习惯编写代码,团队会在大量无关紧要的风格问题上浪费时间。
这类争论在软件工程中称为 Bike-shedding。
例如:
- 使用单引号还是双引号?
- 缩进使用两个空格还是四个空格?
- 一行代码最长多少字符?
如果 Formatter 能够自动统一这些选择,开发者就能将注意力放到逻辑和设计上。
13.3 常见工具
| 工具 | 语言 |
|---|---|
| Ruff Formatter | Python |
| Black | Python |
| Prettier | JavaScript、TypeScript 等 |
| gofmt | Go |
Python 项目可以执行:
ruff format .
如果只想检查、不修改:
ruff format --check .
13.4 Formatter 配置
一些工具支持较多配置。
建议将项目格式配置提交到版本控制中。
此外,还可以使用 EditorConfig。
例如:
root = true
<li>charset = utf-8</li>
end_of_line = lf
insert_final_newline = true
[*.py]
indent_style = space
indent_size = 4
它能够帮助不同 IDE 使用一致的编辑器设置。
第 14 章 Linting:静态代码分析
14.1 什么是 Linter?
Linter 在不运行程序的情况下分析代码,寻找可疑模式和潜在问题。
这叫 Static Analysis(静态分析)。
例如:
import os
def calculate():
x = 10
return 42
这里存在:
- 未使用的 import。
- 未使用的变量。
Linter 可以发现这些问题。
14.2 Formatter 与 Linter 的区别
| 对比 | Formatter | Linter |
|---|---|---|
| 目标 | 统一外观 | 发现问题 |
| 关注 | 表面语法 | 代码模式和潜在错误 |
| 是否修改代码 | 通常可以 | 部分规则可以 |
| 示例 | Ruff Format | Ruff Check |
14.3 使用 Ruff
安装:
pip install ruff
检查:
ruff check .
自动修复可修复的问题:
ruff check --fix .
检查格式:
ruff format --check .
14.4 Linter Rules
Linter 通常包含大量规则。
某些规则可能:
- 检查未使用变量。
- 检查未使用导入。
- 检查不合理的条件嵌套。
- 检查危险函数调用。
- 检查不合理的代码复杂度。
不同规则可以根据项目情况配置。
14.5 False Positives:误报
Linter 并不完美。
有些情况下,它可能认为某种写法有问题,但开发者实际上有合理理由。
因此可以通过配置或局部注释关闭某些规则。
例如:
# noqa: S602
这类抑制应该有明确理由,不应该把它当成解决真实安全问题的默认方式。
14.6 Semgrep:基于语法结构的代码搜索
普通 grep 主要根据字符进行搜索。
Semgrep 可以基于程序语法结构匹配代码模式。
例如,课程展示了对以下危险模式的检查:
subprocess.Popen(
command,
shell=True
)
命令:
semgrep -l python \
-e "subprocess.Popen(..., shell=True, ...)"
这种方式能够比简单的文本匹配更准确地查找符合语法结构的代码。
适用场景:
- 安全规则。
- 自定义代码规范。
- 大型代码库检查。
- 危险 API 搜索。
第 15 章 Testing:自动化测试
15.1 为什么需要测试?
测试的基本思想:
编写程序之后,再编写一组程序来验证它的行为是否符合预期。
例如:
def add(a, b):
return a + b
测试:
def test_add():
assert add(2, 3) == 5
测试通过,说明在这个输入条件下,程序符合预期。
但一个测试无法证明函数在所有输入下都正确。
15.2 Unit Testing:单元测试
单元测试检查一个较小的功能单元。
Python 常用 pytest。
安装:
pip install pytest
项目结构:
project/
├── calculator.py
└── tests/
└── test_calculator.py
calculator.py:
def add(a: int, b: int) -> int:
return a + b
def divide(a: float, b: float) -> float:
if b == 0:
raise ValueError("b cannot be zero")
return a / b
test_calculator.py:
import pytest
from calculator import add, divide
def test_add():
assert add(2, 3) == 5
def test_divide():
assert divide(10, 2) == 5
def test_divide_by_zero():
with pytest.raises(ValueError):
divide(10, 0)
执行:
pytest
15.3 Integration Testing:集成测试
集成测试验证多个模块之间能否正确协作。
例如:
API
↓
Service
↓
Database
需要验证:
- API 是否正确调用 Service。
- Service 是否正确访问数据库。
- 数据是否能够正确保存和读取。
15.4 Functional Testing:功能测试
功能测试主要从用户功能或完整业务流程出发。
例如:
用户注册
↓
用户登录
↓
创建订单
↓
支付订单
↓
查询订单
测试重点是整个流程是否满足预期需求。
15.5 Test-Driven Development:测试驱动开发
TDD 的基本循环:
Red → Green → Refactor
Red
先编写测试,让它因为功能尚未实现而失败。
Green
编写最少的代码,让测试通过。
Refactor
在测试保护下改进代码结构。
这种方法能够让开发者在编写实现之前就明确预期行为。
15.6 Regression Testing:回归测试
当程序发生 Bug 时,不仅应该修复 Bug,还应该增加相应测试。
例如:
某个函数曾经在空输入下崩溃。
修复后增加:
def test_empty_input():
assert process([]) == []
以后每次修改程序时,都可以自动检查这个问题是否再次出现。
15.7 Mocking:模拟外部依赖
如果程序依赖第三方 API 或数据库,测试时不一定需要真的访问外部服务。
可以使用 Mock 替代真实依赖。
例如:
from unittest.mock import Mock
def fetch_user(client, user_id):
return client.get_user(user_id)
def test_fetch_user():
client = Mock()
client.get_user.return_value = {
"id": 1,
"name": "Alice"
}
result = fetch_user(client, 1)
assert result["name"] == "Alice"
这样可以:
- 避免真实网络请求。
- 避免依赖外部服务是否可用。
- 提高测试速度。
- 控制测试输入。
但 Mock 不应过度使用,否则测试可能只验证模拟对象,而没有验证真实系统的集成行为。
15.8 Property-Based Testing:基于性质的测试
普通单元测试通常使用手工指定的输入。
例如:
assert add(2, 3) == 5
Property-Based Testing 则通过描述程序应当满足的性质,让工具自动生成大量输入。
教材介绍了 Hypothesis。
安装:
pip install hypothesis
例如,假设我们有一个字符串填充函数:
def pad(text: str, length: int) -> str:
return text.ljust(length)
我们希望满足:
[
|\operatorname{pad}(s,n)| \geq n
]
对于任意合法的字符串 (s) 和非负整数 (n),填充后的长度不应该小于 (n)。
测试可以写成:
from hypothesis import given
from hypothesis import strategies as st
@given(
text=st.text(max_size=20),
length=st.integers(min_value=0, max_value=50)
)
def test_pad_length(text, length):
result = pad(text, length)
assert len(result) >= length
Hypothesis 会生成许多输入并检查性质。
但它不是数学证明。
它只是通过大量生成的测试样本寻找违反性质的情况。
测试数据的范围和性质仍然需要开发者合理设计。
第 16 章 Code Coverage:代码覆盖率
16.1 什么是覆盖率?
Code Coverage 衡量运行测试时有多少代码被执行。
例如:
def classify(x):
if x > 0:
return "positive"
elif x < 0:
return "negative"
return "zero"
如果只测试:
classify(10)
那么负数和零的分支就没有被执行。
覆盖率报告可以帮助开发者发现未测试区域。
16.2 使用 pytest-cov
安装:
pip install pytest-cov
执行:
pytest --cov=src --cov-report=html
生成 HTML 报告。
查看:
htmlcov/index.html
可以看到:
- 哪些文件被测试。
- 哪些代码行被执行。
- 哪些分支没有覆盖。
16.3 为什么覆盖率不等于质量?
假设:
def divide(a, b):
return a / b
测试:
def test_divide():
divide(10, 2)
这个测试可能执行了函数的全部代码行。
但它没有验证结果是否正确。
所以:
高覆盖率不一定意味着测试有效。
课程建议:
关注高质量测试,而不是单纯追求覆盖率数字。
覆盖率的真正价值是帮助发现测试盲区。
第 17 章 Pre-commit Hooks:提交前自动检查
17.1 Git Hook 的概念
Git Hook 是 Git 提供的事件触发机制。
例如,在提交前执行脚本:
git commit
↓
pre-commit hook
↓
执行检查
↓
允许或阻止提交
17.2 Pre-commit Framework
教材介绍了 Pre-commit 工具。
首先安装:
pip install pre-commit
在项目根目录创建:
.pre-commit-config.yaml
示例:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.9.0
hooks:
- id: ruff
- id: ruff-format
注意:示例版本仅用于说明配置结构,真实项目应根据当前工具版本选择经过验证的版本。
安装 Hook:
pre-commit install
现在执行:
git commit -m "Add new feature"
Git 会在提交前执行配置的检查。
如果检查不通过,提交可能被阻止。
开发者修复问题后重新提交即可。
第 18 章 Continuous Integration:持续集成
18.1 什么是 CI?
CI 是 Continuous Integration 的缩写。
它指将代码集成到共享代码库时,自动运行构建和质量检查。
例如:
开发者修改代码
↓
git push
↓
GitHub Actions
↓
自动运行检查
↓
返回检查状态
CI 能够在代码进入主分支之前发现问题。
18.2 GitHub Actions
在项目中创建:
.github/workflows/ci.yml
示例:
name: Python CI
on:
push:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: |
python -m pip install pytest ruff
- name: Check formatting
run: ruff format --check .
- name: Lint
run: ruff check .
- name: Test
run: pytest
当代码 Push 或 PR 时,GitHub Actions 会自动运行。
18.3 为什么 CI 使用 Check-only Mode?
在开发者本地:
ruff format .
可以自动修改代码。
但是在 CI 中,通常使用:
ruff format --check .
原因是 CI 的任务应该是验证提交的代码,而不是悄悄修改代码。
如果格式不正确,就返回失败,让开发者修复后重新提交。
18.4 Matrix Testing:矩阵测试
有些项目需要同时支持:
- Linux
- macOS
- Windows
还可能支持:
- Python 3.11
- Python 3.12
- Python 3.13
可以通过 CI Matrix 让多个环境自动执行相同测试。
这样能发现本地开发环境中不容易暴露的兼容性问题。
18.5 Scheduled CI:定期检查
CI 不一定只在代码变化时运行。
也可以设置定时任务。
例如,每周重新检查依赖。
为什么?
因为即使你的代码没有变化,外部依赖也可能发布新版本,或者出现兼容性和安全问题。
定期 CI 可以帮助发现这些问题。
18.6 CI Status Badge
GitHub README 中经常出现:
Build: Passing
这类状态标记可以告诉使用者最近的自动化检查是否通过。
但它只能表示指定工作流的状态,不代表软件在所有环境中都没有问题。
第 19 章 Continuous Deployment:持续部署
CI 主要验证代码。
CD 则进一步将通过验证的修改部署到目标环境。
例如:
git push
↓
CI 检查
↓
构建网站
↓
部署到 GitHub Pages
教材以 Missing Semester 网站为例,说明代码更新后可以自动构建并发布网站。
CD 也可以用于:
- 发布 Docker 镜像。
- 部署 Web 服务。
- 发布二进制文件。
- 更新文档网站。
CI 和 CD 可以结合在同一个自动化流水线中。
第 20 章 Command Runners:项目命令管理
随着项目规模扩大,命令可能越来越复杂。
例如:
uv run ruff check .
uv run ruff format .
uv run pytest
uv run mypy .
Command Runner 可以将这些命令封装为简短入口。
课程介绍了 just。
例如创建:
lint:
uv run ruff check .
format:
uv run ruff format .
test:
uv run pytest
typecheck:
uv run mypy .
然后执行:
just lint
just format
just test
just typecheck
这样新加入项目的开发者不需要记住所有工具的完整参数。
类似能力也可以由:
- npm scripts
- Hatch scripts
- Make
- 其他项目管理工具
提供。
第 21 章 Regular Expressions:正则表达式
21.1 什么是正则表达式?
Regular Expression(Regex)是一种描述字符串集合和匹配模式的语言。
例如:
abc
匹配字符串中的 abc。
\d+
匹配一个或多个数字。
missing|semester
匹配 missing 或 semester。
正则表达式常用于:
- 文本搜索。
- 日志分析。
- 数据验证。
- 批量替换。
- 命令行工具。
- IDE 搜索。
- 测试筛选。
21.2 基础语法
| 表达式 | 含义 |
|---|---|
abc |
字面匹配 abc |
. |
任意单个字符(默认不匹配换行符) |
[abc] |
a、b、c 中的一个 |
[^abc] |
不属于 a、b、c 的字符 |
[a-f] |
a 到 f 范围中的字符 |
a|b |
匹配 a 或 b |
\d |
数字字符 |
\w |
单词字符 |
\b |
单词边界 |
(...) |
分组 |
? |
前面的模式出现零次或一次 |
* |
前面的模式出现零次或多次 |
+ |
前面的模式出现一次或多次 |
{N} |
前面的模式重复 N 次 |
\. |
匹配真正的点号 |
\\ |
匹配真正的反斜杠 |
^ |
行或字符串开头 |
$ |
行或字符串结尾 |
21.3 日期匹配
\d{4}-\d{2}-\d{2}
匹配:
2026-01-14
但不验证日期是否合法。
例如:
2026-01-99
仍然能够匹配。
21.4 邮箱匹配
教材给出了简单示例:
.+@.+
它只表示:
- @ 前面至少有一个字符。
- @ 后面至少有一个字符。
这不是可靠的邮箱验证规则。
完整邮箱地址验证涉及更多语法和规范,通常应使用成熟的验证方案,而不是自己编写一个极其复杂的正则表达式。
21.5 Capture Groups:捕获组
例如:
import re
text = "2026-01-14"
match = re.match(
r"\d{4}-(\d{2})-\d{2}",
text
)
print(match.group(1))
输出:
01
括号:
(\d{2})
表示捕获一组两位数字。
21.6 Greedy and Non-greedy:贪婪与非贪婪
例如:
".*"
* 默认采用贪婪匹配,尽可能匹配更多内容。
而:
".*?"
使用非贪婪匹配,尽可能匹配较短内容。
这在文本提取中非常重要。
21.7 Regex 的局限性
正则表达式非常适合简单文本模式。
但不适合所有结构化数据解析。
例如:
- HTML。
- 嵌套 JSON。
- 复杂编程语言语法。
- 有任意层级嵌套结构的数据。
对于 JSON,应该使用 JSON Parser:
import json
data = '''
{
"name": "Alyssa P. Hacker",
"college": "MIT"
}
'''
obj = json.loads(data)
print(obj["name"])
对于复杂语法结构,应使用专门的 Parser。
选择工具的原则:简单文本模式使用 Regex,结构化数据使用对应解析器。
第四部分:综合实践项目
第 22 章 建立完整的软件工程工作流
现在将三个主题整合起来。
项目:Markdown Link Extractor。
22.1 项目需求
实现一个 Python 命令行程序:
link-extractor input.md
提取 Markdown 文件中的链接。
支持:
link-extractor input.md --output links.txt
将提取结果写入文件。
要求:
- 使用类型注解。
- 使用 argparse。
- 有合理的异常处理。
- 编写单元测试。
- 使用 Ruff。
- 使用 mypy。
- 配置 Pre-commit。
- 配置 GitHub Actions。
- 编写 README。
- 通过 Pull Request 完成代码审查。
22.2 创建项目
mkdir markdown-link-extractor
cd markdown-link-extractor
git init -b main
uv init --package
创建虚拟环境并安装开发工具:
uv add --dev pytest ruff mypy pre-commit
22.3 让 Coding Agent 实现功能
给 Agent 提供:
请在当前项目中实现 Markdown Link Extractor。
功能:
1. 接收 Markdown 文件路径。
2. 提取普通 Markdown 链接。
3. 支持 --output 指定输出文件。
4. 输入文件不存在时返回清晰错误。
5. 使用类型注解。
6. 为主要功能编写单元测试。
限制:
1. 不修改输入文件。
2. 优先使用 Python 标准库。
3. 不要为了通过测试而删除测试。
4. 不要关闭 Linter 规则来隐藏问题。
完成后:
运行 pytest、ruff 和 mypy,
并报告实际结果。
22.4 编写测试
至少测试:
- 有一个链接。
- 有多个链接。
- 没有链接。
- 空文件。
- 非法文件路径。
- 输出到指定文件。
例如:
def test_extract_single_link():
text = "[Google](https://google.com)"
assert extract(text) == [
"https://google.com"
]
def test_extract_no_link():
text = "No links here"
assert extract(text) == []
这里的测试针对简单 Markdown 链接格式,不代表完整覆盖所有 Markdown 语法。
22.5 配置质量检查
执行:
uv run ruff format .
uv run ruff check .
uv run mypy src/
uv run pytest
确保所有检查通过。
22.6 配置 Pre-commit
添加 .pre-commit-config.yaml,让提交前自动执行格式和 Lint 检查。
安装:
uv run pre-commit install
22.7 配置 CI
创建:
.github/workflows/ci.yml
在每次 Push 和 PR 时运行:
- Ruff Format Check。
- Ruff Lint。
- Mypy。
- Pytest。
22.8 编写 README
README 至少应该包括:
# Markdown Link Extractor
## What
A command-line tool for extracting links
from Markdown documents.
## Why
It helps inspect and process links
without manually reading the document.
## Usage
link-extractor input.md
## Installation
...
## Development
...
## Testing
...
22.9 提交 Git Commit
git add -p
git commit -m "Add Markdown link extraction CLI"
复杂修改应该拆分为多个逻辑独立的 Commit。
22.10 创建 Pull Request
PR 应当说明:
- 解决了什么问题。
- 实现了什么功能。
- 为什么使用当前方案。
- 如何验证。
- 有哪些限制。
最后让其他开发者进行 Code Review。
第五部分:课程练习与考核
第 23 章 分阶段实践任务
建议不要一次完成所有练习,而是分阶段训练。
阶段一:Coding Agent
| 任务 | 验收标准 |
|---|---|
| 使用 Agent 阅读陌生代码库 | 能解释项目主要模块 |
| 使用 Agent 开发小功能 | 修改符合需求 |
| 使用 Agent 修复测试失败 | 有真实测试结果 |
| 创建 AGENTS.md | Agent 能遵守项目规则 |
| 创建 Skill | 能复用专门工作流程 |
| 创建 Subagent | 能完成独立检查任务 |
| 体验并行 Agent | 能避免工作目录冲突 |
阶段二:Beyond the Code
| 任务 | 验收标准 |
|---|---|
| 阅读大型项目 README | 能评价其信息组织 |
| 编写代码注释 | 解释关键设计原因 |
| 编写 Commit Message | 包含问题与解决动机 |
| 创建 Bug Report | 具有明确复现步骤 |
| 制作最小复现示例 | 能独立重现错误 |
| 创建 Pull Request | 修改范围清晰 |
| 进行 Code Review | 评论具体、可执行 |
阶段三:Code Quality
| 任务 | 验收标准 |
|---|---|
| 配置 Formatter | 代码风格统一 |
| 配置 Linter | 能识别常见问题 |
| 配置 Type Checker | 类型错误能够被发现 |
| 编写 Unit Tests | 核心函数得到测试 |
| 使用 Coverage | 能发现未覆盖代码 |
| 配置 Pre-commit | 提交前自动检查 |
| 配置 CI | Push 和 PR 自动测试 |
| 学习 Regex | 能完成常见文本提取任务 |
第 24 章 最终知识框架
三部分内容可以归纳为以下知识体系。
| 领域 | 核心概念 | 核心工具或方法 |
|---|---|---|
| AI Development | LLM、Agent Loop、Harness | Coding Agents |
| Agent Context | Context Window、Compaction | AGENTS.md、Skills |
| Agent Architecture | Tool Calling、Subagents | MCP、Git Worktree |
| Technical Writing | Comments、README | 技术文档 |
| Version History | Atomic Commits | Git Commit Message |
| Collaboration | Issue、PR | GitHub |
| Code Review | Blocking、Suggestions | Review Practices |
| Formatting | 代码风格 | Ruff、Black、Prettier |
| Static Analysis | 静态检查 | Ruff、Semgrep |
| Type Safety | 类型一致性 | mypy |
| Testing | Unit、Integration、Functional | pytest |
| Property Testing | 性质验证 | Hypothesis |
| Coverage | 执行覆盖情况 | pytest-cov |
| Automation | Pre-commit | Git Hooks |
| Integration | CI | GitHub Actions |
| Deployment | CD | 自动化部署 |
| Command Management | 命令封装 | just |
| Text Processing | 正则表达式 | Python re、grep |
结语:真正的软件工程能力是什么?
这三份课程材料传达了一个共同的思想:
软件工程不仅仅是写代码。
一个成熟的工程师需要同时具备以下能力:
- 开发能力:能够将需求转化为可运行的程序。
- 工具能力:能够利用 AI、Shell、Git 和自动化工具提高效率。
- 验证能力:能够使用测试、静态分析和 CI 验证程序质量。
- 表达能力:能够通过注释、文档和提交记录解释设计决策。
- 协作能力:能够参与 Issue、PR 和 Code Review。
- 责任能力:能够对代码的正确性、安全性和长期维护负责。
尤其在 AI 编程时代,生成代码的成本正在降低,但理解代码、验证代码、判断设计取舍以及维护系统的能力依然重要。
最终的学习目标不是成为一个只会向 AI 提问的人,而是成为一个能够利用 AI、工程工具和团队协作机制,持续交付可靠软件的工程师。
我虽然行过死荫的幽谷,也不怕遭害,因为你与我同在。
诗篇 23:4