☰
t3code 编码工作流:用 Think-Type-Test 三步法重塑开发习惯
2026/10/27 23:05:46 网站建设 项目流程

我最近把自己写代码的流程彻底推倒重来了一遍,沉淀出一套叫t3code的个人工作流体系。简单说,它把一次编码任务从接到需求到提交代码的全过程,拆成 Think、Type、Test 三个阶段,配合一套终端环境和编辑器配置,让每一步都有明确输出物,不靠脑子硬记。这套东西不是某个框架,也不是插件市场里能搜到的工具,而是一套把“怎么想、怎么写、怎么验”固化成肌肉记忆的方法论,配上了对应的命令行脚本。今天这篇就完整拆开讲讲,从为什么需要它,到落地每一步怎么操作,再到我踩过的坑,一次说清。

先打个预防针:t3code 不是什么黑科技,没有一行代码是别人没写过的。它真正的价值在于把散落在我各种习惯里的“好做法”收拢成一个可以反复执行的流程。适合谁用?如果你是那种每天要开很多分支、频繁在多个任务之间切换的开发者,或者你总觉得自己写代码太随性、改起 bug 来没有章法,那这套思路应该对你有用。如果你刚入行,我更建议先把基础语法和调试能力练扎实,再来折腾流程,否则容易变成形式主义。

1. 整体设计思路:为什么是“Think–Type–Test”三步

1.1 t3code 的 T3 到底指什么

命名很直白,就是三个 T 打头的英文单词。

第一个 T 是Think,对应编码前的思考阶段。这个阶段要做的是:搞清楚需求边界、设计大概率的实现路径、预估风险点,并且把这些东西写下来。不是在大脑里过一遍就完事,是真的落到一个 md 文件或者代码注释里。第二个 T 是Type,就是正式写代码的阶段。第三个 T 是Test,指验证阶段,不是考试那个 test,而是运行起来验证行为是否符合预期。

三个阶段对应三个产物:Think 阶段产出_plan.md,Type 阶段产出 diff,Test 阶段产出一份验证记录。这个设计逻辑很简单——如果中途被打断,或者写完一周后回来看,我都能通过这几个产物快速重建当时的上下文。以我自己的体验来说,“写下来”这个动作,价值最大的反而不是给未来的人看,而是在写的当下逼着我把模糊的想法理清楚。

1.2 为什么我会需要一套“编码仪式”

大概在三年多前,我注意到自己的工作状态有一个典型曲线:接到一个有挑战性的任务时,头半小时总是特别容易焦虑。因为没有现成思路,代码写三行删两行,然后打开浏览器查资料,查完又觉得自己思路不对,再删。一天下来,产出很少,但人非常累。这种状态在专注做一个小功能时还好,一旦同时被线上问题、评审、IM 消息穿插,整个节奏就崩了。

我试过用番茄钟、用 todo list、用 TDD 严格要求自己,但都缺了点东西。番茄钟只管时间颗粒度,管不了“单位时间内我在干什么”;todo list 能列出要做的项,但列完怎么下手,它不关心;TDD 本身非常优秀,但它在红绿重构的循环之前,还缺一个“先想清楚整个模块边界”的步骤,而这个步骤恰恰是我最容易偷懒的地方。

后来我意识到,问题的根源不是自控力,而是认知负荷过高。我试图把所有决策都放在“写”的那一刻做,一边写一边想边界条件、变量命名、失败处理,还要兼顾代码风格,大脑当然会过载。t3code 做的事情,就是把这些决策点拆开,分散到三个阶段里,让每个阶段只加载一部分认知任务。

1.3 与 TDD、防御式编程的关系:不是替代,是收编

有一点必须说清楚:t3code 不是 TDD 的替代品。我把 TDD 的红绿重构循环,原封不动地收编到了 Test 阶段里。区别在于,TDD 强调的是“先写测试再写实现”,而 t3code 的 Think 阶段要求我先确定验收场景。这个验收场景比测试用例更粗,可能是一句话描述,比如“用户传一个非法路径时,程序应该给出中文错误提示而不是崩掉”。有了这个粗粒度验收清单,我再到 Test 阶段去补充具体的测试用例,或者手动验证这些场景。

同样地,防御式编程(对输入做各种检查)也没被丢掉,Type 阶段会有意提醒自己在关键入口加断言。但这一切都放在编码流程里,而不是事先把所有情况都预演一遍——那样既做不到,也不必要。

2. 落地环境准备:把流程固化成工具

2.1 终端工作区:tmux + shell 别名

t3code 的第一步是环境准备。我把以前花里胡哨的终端配置重新梳理了一遍,砍掉所有“好看但没用”的东西,保留了一套极简但高效的组合:zsh(bash 也行)加 tmux。

tmux 的用途不是分屏炫技,而是给三个阶段分别准备独立的会话。我的习惯是,接到任务后先开一个三窗格的 tmux 窗口:左边窗格开编辑器(Neovim 或 VS Code 的终端集成),右上是运行测试的命令窗格,右下放一个用于临时操作的 shell。这样切来切去不用鼠标,也不会把验证输出淹没在历史记录里。

Shell 别名这块,我加了几个实打实有用的。

# 新建 t3 工作区,参数为任务名 alias t3new='bash ~/.t3code/scripts/t3new.sh' # 在 Think 阶段完成后,把 _plan.md 转成 todo 清单 alias t3plan='bash ~/.t3code/scripts/t3plan.sh' # Type 阶段结束,跑一遍测试并生成验证记录 alias t3test='bash ~/.t3code/scripts/t3test.sh'

别小看这几个别名。它们的核心作用是制造仪式感。当你敲下t3new的时候,相当于给大脑发了一个信号:现在进入编码模式,非编码相关的杂念暂时挡在外面。这比单纯在心里默念“我要专注”可靠得多。

2.2 编辑器的最小必要配置

编辑器这块,我用的是 Neovim,但以下思路对 VS Code 同样成立。t3code 对编辑器的要求只有一个:能快速跳转、快速折叠、快速在文件间切换。为此我删掉了大量花哨的补全插件和主题,保留了一套配置:

  • 左键 Alt+1/2/3 切换三个 buffer(plan、src、test)
  • 折叠代码用空格键,默认折叠所有函数体
  • 打开文件时自动进入 insert 模式并定位到上一次光标位置,减少无谓的移动
  • 关闭所有“保存时自动格式化”之外的“保存后自动做某件事”的功能,避免操作被打断

这样做的好处是,编辑器退化为纯粹的文本操作工具,而不是一个总想给你推送信息的“集成开发环境”。在 Think 阶段,我需要大量书写和重写;在 Type 阶段,我需要的只是码字和调整结构。一切干扰因素都是敌人。

2.3 把流程物化:t3new.sh 脚本做了什么

t3new.sh做的事非常简单,但非常重要:它在当前目录下建立一个以任务名命名的子目录,然后生成两个空文件。

#!/bin/bash # t3new.sh 脚本本体 TASK_NAME=$1 mkdir -p "$TASK_NAME" touch "$TASK_NAME/_plan.md" touch "$TASK_NAME/README.md" echo "# $TASK_NAME" >> "$TASK_NAME/_plan.md" echo "## Goal" >> "$TASK_NAME/_plan.md" echo "## Risks (placeholder)" >> "$TASK_NAME/_plan.md" echo "## TODO (placeholder)" >> "$TASK_NAME/_plan.md" cd "$TASK_NAME"

看起来没什么技术含量对吧?但它强制我面对一个问题:这个任务叫什么名字。如果连一个准确的任务名都起不出来,说明我对任务本身还不够了解。这比某些项目管理工具里填一大段描述有效得多,因为填表是可以敷衍的,而给任务起名是没法敷衍的。

3. 实操全流程:一个完整任务的走读

3.1 Think 阶段:写 _plan.md,而不是马上写代码

现在从头走一遍流程。假设我突然接到一个需求:写一个工具,把某个目录下的图片按拍摄日期归档到子目录里,同时生成一个 CSV 索引文件。

以前的我会怎么做?打开 IDE,建个 main.py,开始写os.listdir()、PIL读取 EXIF,写到一半发现不对:“CSV 索引应该包含哪些字段来着?归档的时候如果同年同月同日有很多张,命名冲突怎么办?”然后停下来翻需求文档,发现文档里没写,于是去问人,问完回来继续写,思路已经断了。

t3code 下,我不会碰代码。打开_plan.md,开始写:

# image-archiver ## Goal 扫描输入目录中的所有图片, 按拍摄日期(YYYY/MM/DD)移动到归档目录, 并生成 index.csv,包含: - 原文件名 - 新路径 - 拍摄时间 - 文件大小 ## Constraints - 支持 jpg/png,忽略其他格式 - 如果目标文件已存在,自动加后缀 _1 _2…… - 不修改原文件内容 ## Risks - EXIF 缺失的图片没有日期,需要单独处理,统一放在 "未分类/" - 大量文件时移动操作是否太慢(是否需要先复制再删除?) - 路径含空格/中文要小心 ## TODO (placeholder) [ ] 扫描文件列表 [ ] 读取 EXIF 日期 [ ] 分类逻辑 [ ] CSV 索引生成 [ ] 写测试夹具

写作过程中,我发现两个问题值得记录:一是 EXIF 缺失的处理方式,二是目标路径冲突的命名规则。这些都是在没写代码前就能暴露的设计缺口——这正是 Think 阶段的价值。

写完后,我运行t3plan,这个脚本会把 TODO 里的每一行变成一个独立的待办项,方便我在 Type 阶段逐项消费。严格来说,这只是把 markdown checkbox 变成个.todos.json文件,并没有太多黑科技,但每次看到那个文件,就提醒我“现在处于哪个阶段”。

3.2 Type 阶段:把每个 TODO 当成一个小型实现单元

进入 Type 阶段后,我按照 TODO 列表一项接一项地做,每完成一项,就把[ ]改成[x]。这个阶段的关键原则是:一个 TODO 项只对应一次“写代码”动作,写完就运行一次或验证一次,绝不积压。

以“读取 EXIF 日期”这个项为例,我不会写一个完整函数然后等所有代码就绪后再测试。我会这样操作:

写的方法是先搭一个最简的骨架,让它在失败时能给我明确的信息。

def extract_date(filepath: str) -> str | None: try: with Image.open(filepath) as img: exif = img._getexif() if not exif: return None date_tag = 0x0132 # DateTimeOriginal dt_str = exif.get(date_tag) return dt_str or None except Exception as e: # 暂时把异常细节先透传出来 return f"__ERROR__:{e}"

写到这一步,我会停下来问自己:这段代码在真实场景里可能怎么挂?能想到的至少有:图片损坏抛异常、exif 结构不是字典而是列表、标签键不是 int 而是 str。这些疑虑先记录到_plan.md的 Risks 区,等 Test 阶段用不同的图片试。

我在 Type 阶段一般会准备三组数据:

  • 一组正常的带 EXIF 的 jpg/png
  • 一组故意损坏的图片文件(比如把 txt 改成 .jpg)
  • 一组无 EXIF 的图片

三类数据都备好后,再继续写下一个 TODO“分类逻辑”。这里有个我踩过多次的坑:分类逻辑看着简单,但“判断是否已存在同名文件”和时间格式化——两件事如果放在同一个函数里,很容易因为职责不纯粹而写出难以测试的代码。

所以我把“生成目标路径”拆成了一个纯函数:

def destination_path(base_dir: str, file_name: str, existing_names: set[str]) -> str: target = file_name counter = 1 while target.lower() in existing_names: stem, suffix = file_name.rsplit('.', 1) target = f"{stem}_{counter}.{suffix}" counter += 1 return os.path.join(base_dir, target)

这函数不碰磁盘上的文件,只接收一个已有文件名的集合,逻辑非常干净,后面测试友好。Type 阶段的独立性就在这里:先写纯逻辑,再写 IO 操作。不是所有情况都能这么拆分,但在可达成的场景里,这是一个物超所值的习惯。

3.3 Test 阶段:先跑通场景,再补异常用例

所有 TODO 都打勾后,进入 Test 阶段。我做的第一件事不是写测试代码,而是手动跑一遍整个流程,用一个只有 5 张图片的临时目录,执行 main.py,看输出是否符合预期。这一步前置很关键——如果主流程都没走通,写测试基本是自娱自乐。

跑通示例场景后,才开始写 pytest 用例。t3code 对测试的要求不是“覆盖率 100%”,而是“每个 Risks 都有对应的用例”。回看 _plan.md 里记录的三个风险,我会写三条用例:

def test_archive_moves_file(): # 正常流程:jpg 且带 EXIF ... def test_no_exif_goes_to_unsorted(): # 无 EXIF 文件进入"未分类/" ... def test_name_conflict_appends_suffix(): # 同一天同一分钟拍摄的照片,冲突时加 _1 ...

写测试的过程中,往往会发现代码里的一些隐藏问题。比如我第一次实现时,destination_path的existing_names是从实际目录里收集的,在测试环境里这个目录永远为空,导致冲突逻辑根本没被触发。于是我把函数签名改成“先收集集合,再传入判断”,让它成为上面那种纯函数。这正符合我在 Type 阶段保留的独立设计。

测试全绿之后,我还留了一个“验证记录”的步骤:在 README.md 里追加一段记录,写上验证了哪几个场景、数据是什么样的、结果如何。这个记录对外人可能没用,对我自己则是事后回溯时的路标。

3.4 完整示例:把一切串起来

把上面跑完全程,成果目录结构大致如此:

image-archiver/ ├── _plan.md ├── README.md ├── archiver/ │ ├── __init__.py │ ├── cli.py │ └── core.py └── tests/ ├── fixtures/ │ ├── with_exif/ │ ├── without_exif/ └── test_core.py

最终验证时,我用一个包含中文目录名、空格、不同大小写的混合目录实测,跑完打开归档目录确认文件位置符合预期,CSV 字段也完整,流程结束。

4. 常见问题与排查技巧实录

4.1 Think 阶段脑袋空空,根本写不出 _plan.md 怎么办

遇到这种情况,第一步是检查自己是不是还没看懂需求。我的做法是:打开需求原文,把里面所有“动词”和“名词”都画出来,然后逐个问“这个名词从哪来”“这个动作怎么执行”。比如“生成 CSV 索引文件”这个需求,就至少有三个隐藏问题要澄清:字段从哪来、文件放哪个目录、文件名怎么定。

如果需求文档本身也不清晰,那别写代码了,先去问。问完之后再把答复写进 _plan.md 的 Goal 区。说到底,Think 阶段如果没有产出,责任不在流程,而在输入端,这时候补需求澄清比硬撑流程更重要。

4.2 测试总是写不好,或者测试全绿但功能明显是坏的

这个问题的根源,大概率是测试用例和真实场景脱节。要治这个病,唯一有效的办法是让测试数据从真实环境里“长出来”。我在做归档工具时,先把几张真实的 jpg/png 图片复制成一个小小的 fixtures 目录,测试里用的就是这些真实文件。不要拿临时生成的假数据——因为假数据往往只验证了你想验证的情况,漏掉的恰恰是真实世界里的缺失 EXIF、文件损坏之类的问题。

还有一个技巧:刻意保留几个“故意坏的”文件,比如扩展名是 .jpg 但内容不是图片。很多人在测试时会下意识过滤掉这些“不友好的输入”,但跑一次就能发现很多防御性代码其实根本没用上。

4.3 一套流程用了一周就想放弃,觉得太繁琐

这是所有方法论最大的坑。我的亲身体会:强制自己做三步(Think/Type/Test)的初期,效率一定下降,因为以前写代码是单线程思考,现在要频繁切换模式,天然要多花时间。一般最快要两周后才能抵消前半程的时间损耗。

我的建议是,不要把所有环节全部铺开。最开始只做“成对压缩”:Think 阶段只写 Goal 和 TODO 两行;Test 阶段只做手动跑一遍场景,不写完整测试用例。等这两步变成肌肉记忆后,再把 Risks 和 pytest 加进来,这是渐进式采纳,而不是革命式切换。

4.4 团队协作时,t3code 会不会被别人嫌弃

会让别人感觉突兀的,不是流程本身,而是你突然不按旧的协作方式来。我建议这部分保持低调:自己用,自己的分支上保留_plan.md和测试逻辑。等某个任务因为你提前写清楚风险和 TODO 而显著少返工时,再顺势分享那一次的成功经验,比一开始就挥舞方法论大旗有用。

5. 扩展思路与个人最终体会

5.1 把三阶段思维迁移到读代码和写文档上

t3code 的三段式并不只适用于写新代码。我后来把同样的框架移植到了“读老代码”场景:Think 阶段先写出这个模块究竟是干嘛的、输入输出是什么;Type 阶段逐块阅读代码并注释;Test 阶段用实际的奇怪输入去运行模块,验证注释是否正确。这一套下来,比埋头逐行读代码省一半时间。

写技术方案文档也一样。先 Think 把目标写清楚,然后 Type 填充方案,最后 Test 用评审反馈来验证。以前我写文档总是想一次性写完,现在三段分步走,反而写得又快又不容易漏。

5.2 最后的一点实在话

t3code 的所有环节都不是我原创,每个模块都来自具体的实践反馈。比如目标路径冲突加后缀这个规则,就是我在正式需求里吃过一次亏——重复文件覆盖了原始文件,损失了不可恢复的数据。所以我才在 Type 阶段专门为它设计了一个独立函数,并把它写进 Risk 清单。

如果你自己也想尝试这套流程,我建议第一次使用时就选一个真正需要动脑的中等复杂度任务,不要选 hello world 级别的小练习——流程在太小的任务上会显得多余,而在太大的任务上又会暴露不够灵活的一面。中等复杂度的任务,刚好能让你体会到三个阶段各自解决的是什么问题,也更能检验它是否适合你长期使用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询