Superpowers Day 2:我学会了 TDD 和 Git Worktrees,这是我的实践记录
4 周学习 Superpowers 系列文章 - Day 2
🎯 写在前面
在 Day 1 中,我用 subagent-driven-development 模式完成了第一个项目(极简计算器)。虽然是 13 个任务全部自动完成,但我其实没有真正理解 TDD 的每个步骤,也没看到 Git Worktrees 是如何创建的。
今天,我决定用 手动分步执行模式 重做一个项目,深入观察 TDD 的 RED-GREEN-REFACTOR 循环,以及 Git Worktrees 的创建和管理过程。
📚 项目:Python 字符串处理工具库
技术选型
- 语言:Python 3.x
- 测试框架:pytest
- 代码规范:PEP 8
- 类型注解:Optional, Dict, bool, str
功能需求
- reverse_string:反转字符串
- count_chars:统计字符出现次数
- is_palindrome:判断回文
- remove_duplicates:去除重复字符
- convert_case:大小写转换
边界情况
- 空字符串处理
- None 输入处理
- 特殊字符支持
- Unicode 支持
🎬 执行流程:手动模式 vs 自动模式
Day 1 vs Day 2 对比
| 维度 | Day 1(自动模式) | Day 2(手动模式) |
|---|---|---|
| 执行方式 | subagent-driven | 手动分步执行 |
| 控制程度 | 🟢 低 | 🔴 高 |
| TDD 观察 | ❌ 看不到 RED-GREEN-REFACTOR | ✅ 完整看到每个步骤 |
| Git Worktrees | ✅ 自动创建,看不见 | ✅ 看到创建过程 |
| 学习效果 | 📚 中等 | 📚 优秀 |
| 开发时间 | 42 分钟 | 30 分钟 |
| 参与感 | 🟢 弱 | 🔴 强 |
🚀 阶段 1:Brainstorming(需求澄清)
Claude 问我什么?
好的!在开始之前,我需要了解一些细节:
1. 这个工具库的用途是什么?个人工具 / 开源项目 / 商业项目?
2. 每个函数的接口设计?
3. 需要处理哪些边界情况?
4. 需要类型注解吗?
5. 需要文档字符串(docstring)吗?
我的回答
1. 个人学习项目
2. 接口设计:
- reverse_string(s: str) -> str
- count_chars(s: str) -> Dict[str, int]
- is_palindrome(s: str) -> bool
- remove_duplicates(s: str) -> str
- convert_case(s: str, mode: str) -> str
3. 边界情况:空字符串、None 输入、特殊字符、Unicode
4. 需要类型注解
5. 需要文档字符串
自动生成设计文档
文件: docs/superpowers/specs/2026-03-21-python-string-tools-design.md
# Python 字符串处理工具库设计文档
**日期**: 2026-03-21
**状态**: 已批准
## 1. 概述
构建一个 Python 字符串处理工具库,提供常用的字符串操作函数。
## 2. 功能需求
### 2.1 核心功能
- reverse_string(s) - 反转字符串
- count_chars(s) - 统计字符出现次数
- is_palindrome(s) - 判断回文
- remove_duplicates(s) - 去除重复字符
- convert_case(s, mode) - 大小写转换
### 2.2 接口设计
```python
def reverse_string(s: str) -> Optional[str]
def count_chars(s: str) -> Optional[Dict[str, int]]
def is_palindrome(s: str) -> bool
def remove_duplicates(s: str) -> Optional[str]
def convert_case(s: str, mode: str) -> Optional[str]
3. 边界情况
3.1 None 输入
所有函数返回 None
3.2 空字符串
reverse_string("") → "" is_palindrome("") → True
3.3 特殊字符
正确处理 Unicode 字符
4. 验收标准
4.1 功能验收
- 所有函数正常工作
- 有完整的测试覆盖
- 有类型注解
- 有文档字符串
4.2 具体测试用例
| 用例 | 输入 | 预期结果 |
|---|---|---|
| 反转字符串 | "hello" | "olleh" |
| 统计字符 | "hello" | {"h": 1, "e": 1, "l": 2, "o": 1} |
| 判断回文 | "racecar" | True |
| 去除重复 | "hello" | "helo" |
| 大小写转换 | ("hello", "upper") | "HELLO" |
| None 输入 | None | None |
**自动生成设计文档的优势:**
- ✅ 需求更清晰
- ✅ 作为后续参考
- ✅ 零成本生成
---
## 🔍 阶段 2:TDD 实战(重点观察)
### Task 2:实现字符串反转
#### 【RED】编写失败的测试
**我看到的:**
【TDD - RED】编写失败的测试...
创建文件:tests/test_reverse.py
添加测试用例: ✓ test_reverse_normal_string() - "hello" → "olleh" ✓ test_reverse_empty_string() - "" → "" ✓ test_reverse_none_input() - None → None
测试代码:
import pytest
from string_tools import reverse_string
def test_reverse_normal_string():
assert reverse_string("hello") == "olleh"
def test_reverse_empty_string():
assert reverse_string("") == ""
def test_reverse_none_input():
assert reverse_string(None) is None
运行测试... ❌ 失败(预期!)
**我的感受:**
- 测试清晰地描述了功能需求
- 写测试时,我更理解了边界情况
- 测试失败是正常的,这是 TDD 的第一步
---
#### 【GREEN】实现最小代码
**我看到的:**
【TDD - GREEN】实现最小代码...
创建文件:src/string_tools.py
实现函数:
from typing import Optional
def reverse_string(s: str) -> Optional[str]:
"""反转字符串"""
if s is None:
return None
return s[::-1]
运行测试... ✅ 通过!
实现特点: ✓ 最小代码,只解决问题(YAGNI) ✓ 使用 Python 切片,简洁高效 ✓ 处理 None 边界情况
**我的感受:**
- 只写能通过测试的最小代码
- 不过度设计(YAGNI 原则)
- 快速验证思路
---
#### 【REFACTOR】优化代码
**我看到的:**
【TDD - REFACTOR】优化代码...
代码审查检查: ✓ 符合规格要求 ✓ 有类型注解 ✓ 有文档字符串 ✓ 代码清晰易读 ✓ 遵循 PEP 8
优化内容: ✓ 无需优化(代码已经很好)
【代码审查】通过!✓
**我的感受:**
- 在测试保护下,重构更安全
- 代码质量有保障
---
### Task 3:实现统计字符
#### 【RED】编写失败的测试
【TDD - RED】编写失败的测试...
添加测试用例: ✓ test_count_chars_normal() - "hello" → {"h": 1, "e": 1, "l": 2, "o": 1} ✓ test_count_chars_empty() - "" → {} ✓ test_count_chars_none() - None → None
测试代码:
def test_count_chars_normal():
assert count_chars("hello") == {"h": 1, "e": 1, "l": 2, "o": 1}
def test_count_chars_empty():
assert count_chars("") == {}
def test_count_chars_none():
assert count_chars(None) is None
运行测试... ❌ 失败(预期!)
---
#### 【GREEN】实现最小代码
【TDD - GREEN】实现最小代码...
实现函数:
from typing import Optional, Dict
def count_chars(s: str) -> Optional[Dict[str, int]]:
"""统计字符出现次数"""
if s is None:
return None
result = {}
for char in s:
result[char] = result.get(char, 0) + 1
return result
运行测试... ✅ 通过!
实现特点: ✓ 使用字典统计 ✓ 处理 None 边界情况
---
#### 【REFACTOR】优化代码
【TDD - REFACTOR】优化代码...
优化建议:可以使用字典推导式
优化后的代码:
from collections import Counter
def count_chars(s: str) -> Optional[Dict[str, int]]:
"""统计字符出现次数"""
if s is None:
return None
return dict(Counter(s))
运行测试... ✅ 仍然通过!
【代码审查】通过!✓
**我的感受:**
- 在测试保护下重构,非常安全
- 重构后代码更简洁
---
## 🌳 阶段 3:Git Worktrees 实战
### 创建过程(手动模式)
**我看到的:**
【Step 1】创建新的 Git Worktree...
正在执行:git checkout -b feature/02-reverse ✓ 分支已创建:feature/02-reverse
正在执行:git worktree add ../worktree-02 feature/02-reverse ✓ Worktree 已创建:/path/to/worktree-02
【Step 2】进入 worktree 工作区... 当前工作区:/path/to/worktree-02
**目录结构:**
```day2-python-string-tools/
├── .git/ # 共享的 Git 数据库
├── README.md
├── requirements.txt
├── src/
│ └── string_tools.py # 主分支(初始为空)
├── tests/
│ └── test_string_tools.py # 主分支(初始为空)
├── worktree-01/ # Task 1 工作区
├── worktree-02/ # Task 2 工作区(独立!)
│ ├── src/
│ │ └── string_tools.py # 独立版本
│ └── tests/
│ └── test_reverse.py # 独立测试
└── ...
关键理解:
- ✅ 每个任务在独立的 worktree 中工作
- ✅ 互不干扰,可以并行开发
- ✅ 容易回滚和修改
- ✅ 测试隔离
Git Worktrees 命令
查看所有 worktrees:
$ git worktree list
/path/to/day2-python-string-tools 8ea71da [master]
/path/to/worktree-01 abc1234 [feature/01-init]
/path/to/worktree-02 def5678 [feature/02-reverse]
/path/to/worktree-03 fedc098 [feature/03-count]
...
创建新的 worktree:
# 创建新分支
git checkout -b feature/03-count
# 创建 worktree
git worktree add ../worktree-03 feature/03-count
# 进入 worktree
cd ../worktree-03
# 工作...
git add .
git commit -m "feat: add count_chars function"
删除 worktree(任务完成后):
# 删除 worktree
git worktree remove ../worktree-02
# 删除分支
git branch -d feature/02-reverse
Git Worktrees 的优势
1. 上下文隔离
worktree-02/ 只有 reverse_string 的代码
worktree-03/ 只有 count_chars 的代码
worktree-04/ 只有 is_palindrome 的代码
→ 每个任务只看到自己需要的东西
→ 不会被其他代码干扰
→ 容易理解和审查
2. 并行开发
子代理 1 在 worktree-02/ 做任务 2
子代理 2 在 worktree-03/ 做任务 3
子代理 3 在 worktree-04/ 做任务 4
→ 可以同时进行多个任务
→ 互不干扰
→ 提高效率
3. 独立测试
worktree-02/
pytest test_reverse.py # 只运行任务 2 的测试
worktree-03/
pytest test_count.py # 只运行任务 3 的测试
→ 测试隔离,不会互相影响
→ 容易定位问题
4. 轻松回滚
如果任务 3 做错了:
❌ 传统方式:
整个项目都被污染
回滚很麻烦
✅ Git Worktrees:
只删除 worktree-03/
重新创建一个 worktree-03/
重新做任务 3
→ 干净、简单、快速
🎯 阶段 4:所有任务完成
项目结构(合并前)
day2-python-string-tools/
├── .git/
├── README.md
├── requirements.txt
├── src/
│ └── string_tools.py # 主分支(初始为空)
├── tests/
│ └── test_string_tools.py # 主分支(初始为空)
├── worktree-01/ # Task 1:项目初始化
├── worktree-02/ # Task 2:reverse_string
├── worktree-03/ # Task 3:count_chars
├── worktree-04/ # Task 4:is_palindrome
├── worktree-05/ # Task 5:remove_duplicates
├── worktree-06/ # Task 6:convert_case
└── worktree-07/ # Task 7:集成测试
触发 finishing-a-development-branch
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🎉 所有任务完成!
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
项目统计:
- 7 个任务完成
- 所有测试通过
- 耗时约 30 分钟
- 零 Bug
下一步操作,请选择:
【1】Merge - 合并所有分支到主分支
【2】Pull Request - 创建 PR 供审查
【3】Keep - 保留所有分支供后续工作
【4】Discard - 丢弃所有分支
你想怎么做?
我选择:Merge
【Step 1】切换回主分支
✓ git checkout master
【Step 2】合并所有分支
✓ git merge feature/01-init
✓ git merge feature/02-reverse
✓ git merge feature/03-count
✓ git merge feature/04-palindrome
✓ git merge feature/05-dedup
✓ git merge feature/06-convert
✓ git merge feature/07-integration
【Step 3】清理 worktree
✓ 删除 worktree-01 到 worktree-07
【Step 4】删除临时分支
✓ 删除 feature/01-init 到 feature/07-integration
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ 合并完成!
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
最终项目结构
day2-python-string-tools/
├── .git/
├── README.md ✅ 完整文档
├── requirements.txt ✅ pytest
├── src/
│ ├── __init__.py ✅
│ └── string_tools.py ✅ 5 个函数
└── tests/
├── __init__.py ✅
└── test_string_tools.py ✅ 15+ 测试用例
💡 核心收获
1. TDD 的真正威力
RED 阶段:测试即需求
# 写测试时,我更理解了需求
def test_reverse_normal_string():
assert reverse_string("hello") == "olleh"
# 这个测试明确告诉我:
# - 函数名是 reverse_string
# - 输入是 "hello"
# - 输出应该是 "olleh"
# - 返回类型是 str
我的感受:
- ✅ 测试就是需求的精确描述
- ✅ 写测试时更理解功能
- ✅ 测试可以作为文档使用
GREEN 阶段:YAGNI 原则
# 只写最小能通过的代码
def reverse_string(s: str) -> Optional[str]:
if s is None:
return None
return s[::-1]
# 没有过度设计:
# ❌ 没有添加不必要的功能
# ❌ 没有过度抽象
# ❌ 没有过度优化
我的感受:
- ✅ 只解决当前问题
- ✅ 代码更简洁
- ✅ 开发更快
REFACTOR 阶段:在测试保护下优化
# 原始代码
def count_chars(s: str) -> Optional[Dict[str, int]]:
if s is None:
return None
result = {}
for char in s:
result[char] = result.get(char, 0) + 1
return result
# 重构后
def count_chars(s: str) -> Optional[Dict[str, int]]:
if s is None:
return None
return dict(Counter(s))
# 有测试保护,重构很安全!
我的感受:
- ✅ 重构更安全
- ✅ 代码质量更高
- ✅ 不破坏已有功能
2. Git Worktrees 的价值
对比传统 Git
传统 Git:
day2-python-string-tools/
├── src/
│ └── string_tools.py # 所有代码混在一起
└── tests/
└── test_string_tools.py # 所有测试混在一起
# 修改时:
git checkout feature/02-reverse
[做任务 2...]
# 突然需要做任务 3
git stash # 保存进度
git checkout feature/03-count
[做任务 3...]
git stash pop # 恢复进度
问题:
- ❌ 频繁切换分支,容易混乱
- ❌ 需要使用 stash
- ❌ 容易忘记保存进度
Git Worktrees:
day2-python-string-tools/
├── worktree-01/ # 任务 1 独立
├── worktree-02/ # 任务 2 独立
├── worktree-03/ # 任务 3 独立
# 同时工作:
# 终端 1
cd worktree-02/
[做任务 2...]
# 终端 2
cd worktree-03/
[做任务 3...]
# 互不干扰!
优势:
- ✅ 不需要切换分支
- ✅ 不需要 stash
- ✅ 可以同时工作
3. 手动模式 vs 自动模式
Day 1(自动模式)
体验:
你说:"go"
Claude:[自动执行所有任务]
你:[等待 42 分钟]
Claude:🎉 完成!
你看不到:
- ❌ TDD 的 RED-GREEN-REFACTOR
- ❌ Git Worktrees 的创建
- ❌ 每个步骤的细节
适合:
- 大型项目
- 有清晰规格
- 信任 AI
- 想快速完成
Day 2(手动模式)
体验:
你说:"开始 Task 2"
Claude:[一步步展示 TDD]
【RED】写测试...
【GREEN】写代码...
【REFACTOR】优化...
你:[每步都看到]
Claude:[展示 Git Worktree 创建]
git worktree add ../worktree-02 feature/02-reverse
你:[完全理解过程]
适合:
- 学习理解
- 需要控制
- 小型项目
- 想边看边学
我的选择:
- Day 1:自动模式(快速完成)
- Day 2:手动模式(深入理解)
- 以后会根据需求灵活选择
📊 项目成果
最终代码
src/string_tools.py:
"""字符串处理工具库"""
from typing import Optional, Dict
from collections import Counter
def reverse_string(s: str) -> Optional[str]:
"""反转字符串
Args:
s: 要反转的字符串
Returns:
反转后的字符串,如果输入为 None 则返回 None
"""
if s is None:
return None
return s[::-1]
def count_chars(s: str) -> Optional[Dict[str, int]]:
"""统计字符出现次数
Args:
s: 要统计的字符串
Returns:
字符出现次数的字典,如果输入为 None 则返回 None
"""
if s is None:
return None
return dict(Counter(s))
def is_palindrome(s: str) -> bool:
"""判断是否为回文
Args:
s: 要判断的字符串
Returns:
如果是回文返回 True,否则返回 False
空字符串视为回文
"""
if not s:
return True
return s == s[::-1]
def remove_duplicates(s: str) -> Optional[str]:
"""去除重复字符
Args:
s: 要处理的字符串
Returns:
去除重复字符后的字符串,如果输入为 None 则返回 None
"""
if s is None:
return None
return "".join(dict.fromkeys(s))
def convert_case(s: str, mode: str) -> Optional[str]:
"""大小写转换
Args:
s: 要转换的字符串
mode: 转换模式(upper/lower/title)
Returns:
转换后的字符串,如果输入为 None 则返回 None
Raises:
ValueError: 如果 mode 不是 upper/lower/title
"""
if s is None:
return None
if mode == "upper":
return s.upper()
elif mode == "lower":
return s.lower()
elif mode == "title":
return s.title()
else:
raise ValueError(f"Invalid mode: {mode}")
tests/test_string_tools.py:
"""字符串处理工具库测试"""
import pytest
from string_tools import (
reverse_string,
count_chars,
is_palindrome,
remove_duplicates,
convert_case,
)
def test_reverse_normal_string():
assert reverse_string("hello") == "olleh"
def test_reverse_empty_string():
assert reverse_string("") == ""
def test_reverse_none_input():
assert reverse_string(None) is None
def test_count_chars_normal():
assert count_chars("hello") == {"h": 1, "e": 1, "l": 2, "o": 1}
def test_count_chars_empty():
assert count_chars("") == {}
def test_count_chars_none():
assert count_chars(None) is None
def test_is_palindrome_true():
assert is_palindrome("racecar") is True
def test_is_palindrome_false():
assert is_palindrome("hello") is False
def test_is_palindrome_empty():
assert is_palindrome("") is True
def test_remove_duplicates_normal():
assert remove_duplicates("hello") == "helo"
def test_remove_duplicates_all_same():
assert remove_duplicates("aaaa") == "a"
def test_remove_duplicates_none():
assert remove_duplicates(None) is None
def test_convert_case_upper():
assert convert_case("hello", "upper") == "HELLO"
def test_convert_case_lower():
assert convert_case("HELLO", "lower") == "hello"
def test_convert_case_title():
assert convert_case("hello world", "title") == "Hello World"
def test_convert_case_none():
assert convert_case(None, "upper") is None
def test_convert_case_invalid_mode():
with pytest.raises(ValueError):
convert_case("hello", "invalid")
项目统计
- 代码行数:约 150 行(含测试)
- 函数数量:5 个
- 测试用例:15+ 个
- Git 提交:7 次(每个 Task 一次)
- 开发时间:约 30 分钟
- Bug 数量:0
- 测试覆盖率:100%
🎯 Day 2 总结
✅ 完成的目标
-
✅ 深入理解 TDD(RED-GREEN-REFACTOR)
- 看到了每个步骤的细节
- 理解了为什么要先写测试
- 体验了 YAGNI 原则
-
✅ 理解 Git Worktrees
- 看到了创建过程
- 理解了上下文隔离
- 体验了并行开发
-
✅ 完成一个小项目(手动模式)
- Python 字符串处理工具库
- 5 个函数,15+ 测试用例
- 100% 测试覆盖率
-
✅ 对比两种模式
- 自动模式:快速,但看不到细节
- 手动模式:稍慢,但学习效果好
💡 关键洞察
-
测试不是负担,是需求
- 测试就是需求的精确描述
- 写测试时更理解功能
- 测试作为文档使用
-
Git Worktrees 很强大
- 上下文隔离,互不干扰
- 可以并行开发
- 容易回滚和修改
-
手动模式适合学习
- 每步都清晰可见
- 完全掌控节奏
- 学习效果优秀
-
根据需求选择模式
- 大型项目:自动模式
- 学习理解:手动模式
🚀 准备进入 Day 3
明天学习重点:
-
系统化调试(systematic-debugging)
- 4 阶段根因分析
- 识别问题 → 定位原因 → 验证假设 → 修复验证
-
代码审查流程
- 请求代码审查(requesting-code-review)
- 回应审查意见(receiving-code-review)
-
完成一个带 Bug 的项目
- 学习如何系统化调试
- 练习代码审查
📚 延伸阅读
💬 交流讨论
如果你也在学习 Superpowers,或者有任何疑问,欢迎在评论区交流!
也可以加入 Discord 社区 和其他开发者一起讨论。
📅 系列文章预告
- Day 1: ✅ Superpowers 入门与第一个项目
- Day 2: ✅ TDD 深度实践与 Git Worktrees(本文)
- Day 3: 🐛 系统化调试与代码审查(敬请期待)
- Day 4: 🚀 自定义技能与高级技巧(敬请期待)
如果你觉得这篇文章对你有帮助,欢迎点赞、收藏、转发!
我是竹哥,4 周时间,系统学习 Superpowers,带你从入门到精通。
发布日期:2026-03-22 系列:Superpowers 4 周学习计划 作者:竹哥 项目:Python 字符串处理工具库 技术栈:Python 3.x + pytest