<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>SpecDD on 老朱的赛博工位</title><link>https://blog.zjkun.top/tags/specdd/</link><description>Recent content in SpecDD on 老朱的赛博工位</description><generator>Hugo</generator><language>zh-CN</language><lastBuildDate>Fri, 16 Oct 2026 12:30:00 +0800</lastBuildDate><atom:link href="https://blog.zjkun.top/tags/specdd/index.xml" rel="self" type="application/rss+xml"/><item><title>AI 写代码跑偏？5 步驯服 Coding Agent</title><link>https://blog.zjkun.top/posts/tame-coding-agent-5-steps/</link><pubDate>Fri, 16 Oct 2026 12:30:00 +0800</pubDate><guid>https://blog.zjkun.top/posts/tame-coding-agent-5-steps/</guid><description>一句话生成 App 看着爽，真实项目里全靠模型猜。SpecDD 定义驱动 + TDD 测试驱动的 5 步工作流：画图纸、盯脑补、红绿重构、两种验证。附我做的 5 道代码题真实片段。</description><content:encoded><![CDATA[<p>让 AI 写代码最爽的一刻是 prompt 下去、代码出来的那 30 秒；最崩的一刻是打开页面发现它「做完了」但根本不是你要的。</p>
<p>我上周把 Vibe Coding 第一课（莫欣）的讲义啃完，加了 5 道代码题实操，把 SpecDD + TDD 这套流程整理成 5 步。核心就一句话：<strong>AI 只能「应试」，不能「自证」</strong>——它写没写完、写对没写对，必须有一个先于代码存在的客观检查说了算。</p>
<h2 id="先把两个词掰开">先把两个词掰开</h2>
<p><strong>SpecDD（Spec-Driven Development，定义驱动开发）</strong>：动手之前，先和 AI 把「什么算做对了」写成一份规格文件——场景、输入输出、关键行为、异常处理、验收方式，再加一条「本次不做什么」。检验标准很朴素：<strong>照着这份 Spec 干活，还有没有要猜的地方。</strong> 有，就还没到开工的时候。</p>
<p><strong>TDD（Test-Driven Development，测试驱动开发）</strong>：代码还没写，先写一个小测试表达 Spec 里的预期行为。这时功能未实现，测试必然失败（红）→ 写最小代码让它通过（绿）→ 整理代码不改行为（重构）。AI 场景里，这个测试就是防止 Agent「自称做完」的验收单。</p>
<p>关系一句话说清：SpecDD 定义验收标准，TDD 把标准变成先于代码存在的检查。AI 只能「应试」不能「自证」。</p>
<h2 id="第-1-步先画图纸再开工">第 1 步：先画图纸再开工</h2>
<p>让 AI 写码前，先写个文件讲清五件事：<strong>做什么给谁用｜输入什么输出什么｜正常该怎么表现｜出了问题怎么办｜最后怎么验收</strong>。再补一句「本次不做什么」：不自动回复、不管理后台。</p>
<p>边界清单比愿望清单好控得多。「做到什么程度算过」没有定义时，AI 给你做词频图也算「实现了需求」——但词频图、自动回复、问答页面解决的是三个不同的问题。</p>
<p>我做题时最深的一个体会是「口径必须写进 Spec」。题目要求按时间范围过滤消息，<code>sent_at</code>（发送时间）和 <code>collected_at</code>（采集时间）两种口径结果不同，Spec 确认后必须显式传入，不许默认；没有发送时间的消息「保留未知」，不许偷换成采集时间：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">filter_by_range</span>(messages: list[dict], start, end, field: str) <span style="color:#f92672">-&gt;</span> list[dict]:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> field <span style="color:#f92672">not</span> <span style="color:#f92672">in</span> (<span style="color:#e6db74">&#34;sent_at&#34;</span>, <span style="color:#e6db74">&#34;collected_at&#34;</span>):
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">raise</span> <span style="color:#a6e22e">ValueError</span>(<span style="color:#e6db74">&#34;时间口径必须由 Spec 明确指定&#34;</span>)
</span></span><span style="display:flex;"><span>    out <span style="color:#f92672">=</span> []
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> m <span style="color:#f92672">in</span> messages:
</span></span><span style="display:flex;"><span>        t <span style="color:#f92672">=</span> m<span style="color:#f92672">.</span>get(field)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> t <span style="color:#f92672">is</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">continue</span>                 <span style="color:#75715e"># 未知就是未知，不偷换口径</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> start <span style="color:#f92672">&lt;=</span> t <span style="color:#f92672">&lt;=</span> end:
</span></span><span style="display:flex;"><span>            out<span style="color:#f92672">.</span>append(m)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> out
</span></span></code></pre></div><h2 id="第-2-步ai-给你规划后只盯一样东西它自己脑补的">第 2 步：AI 给你规划后，只盯一样东西：它自己脑补的</h2>
<p>规划输出里那些「写得很肯定、但你从没提过」的规则——比如我遇到的「只分析带问号的评论」——是 AI 替你做的决定，不是需求。第一动作不是采纳也不是删掉，是<strong>追问：这条规则来自用户需求，还是你自己的建议？</strong></p>
<p>审查规划可以按四类拆：已确认需求 / Agent 假设 / 实现方案 / 开发任务。重点盯第二类，跑得越顺的脑补规则越危险。</p>
<h2 id="第-3-步验收清单写在开工前">第 3 步：验收清单写在开工前</h2>
<p>TDD 循环：红→绿→重构。两个坑：</p>
<ol>
<li><strong>红灯要坏得对</strong>。缺依赖、模块导不进来这类报错是「考场坏了」，不算验收——先修运行条件，这时候还不能说明业务规则得到了检查。</li>
<li><strong>测试第一次就绿，别造假红灯</strong>。要么目标行为早已实现（保留即可），要么断言漏了关键结果——是断言的问题，不是改坏代码表演一次的事。</li>
</ol>
<h2 id="第-4-步ai-应用最核心验证分两种别混">第 4 步：AI 应用最核心——验证分两种，别混</h2>
<p><strong>查逻辑</strong>（去重、筛选、引用不越界）→ 固定假数据 + 模型替身，逐字段断言，像数学题有标准答案。数数这种活是程序干的，不归模型：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">FakeModel</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">__init__</span>(self, answer): self<span style="color:#f92672">.</span>answer <span style="color:#f92672">=</span> answer; self<span style="color:#f92672">.</span>call_count <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">complete</span>(self, msgs, q):
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>call_count <span style="color:#f92672">+=</span> <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> self<span style="color:#f92672">.</span>answer
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">start_analysis</span>(snapshot: list[dict], question: str, model) <span style="color:#f92672">-&gt;</span> dict:
</span></span><span style="display:flex;"><span>    msgs <span style="color:#f92672">=</span> tuple(snapshot)           <span style="color:#75715e"># 快照语义：本批输入就此冻结</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> len(msgs) <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> {<span style="color:#e6db74">&#34;status&#34;</span>: <span style="color:#e6db74">&#34;empty_range&#34;</span>}
</span></span><span style="display:flex;"><span>    ans <span style="color:#f92672">=</span> model<span style="color:#f92672">.</span>complete(msgs, question)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> {<span style="color:#e6db74">&#34;status&#34;</span>: <span style="color:#e6db74">&#34;ok&#34;</span>, <span style="color:#e6db74">&#34;answer&#34;</span>: ans,
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;count&#34;</span>: len(msgs),      <span style="color:#75715e"># 程序统计，不是模型统计</span>
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;input_ids&#34;</span>: [m[<span style="color:#e6db74">&#34;msg_id&#34;</span>] <span style="color:#66d9ef">for</span> m <span style="color:#f92672">in</span> msgs]}
</span></span></code></pre></div><p>快照为空时断言 <code>call_count == 0</code>——模型一次都不能被调用；引用了范围外的 msg_id 断言被拒。这类用替身测，稳定、免费、可重复。</p>
<p><strong>看效果</strong>（摘要准不准、有没有编）→ 真实调用、多样本反复跑。程序只能校验「引用没越界」，校验不了「引用真能支撑回答」——后者是语义判断，标准是「措辞可以变，意思必须留住」。一次通过≠稳定。</p>
<h2 id="第-5-步别信测试全部通过这句话">第 5 步：别信「测试全部通过」这句话</h2>
<p>AI 报告全部通过时，看四项汇报里有没有：<strong>真实模型调用过吗？页面实际操作过吗？</strong> 程序逻辑测试 100% 通过，只说明确定性逻辑没问题；模型效果和用户流程仍然是待验证状态。这是两个轨道，不能互相顶替。</p>
<h2 id="速查表">速查表</h2>
<table>
	<thead>
			<tr>
					<th>模块</th>
					<th>核心</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Spec</td>
					<td>场景+输入输出+关键行为+异常+验收；「哪些还要靠猜」</td>
			</tr>
			<tr>
					<td>审规划</td>
					<td>已确认需求／Agent假设／实现方案／开发任务；盯「很肯定但没讨论过」</td>
			</tr>
			<tr>
					<td>用例</td>
					<td>前置+输入操作+预期+<strong>不应发生</strong>+检查方法</td>
			</tr>
			<tr>
					<td>TDD</td>
					<td>红灯(因目标行为)→最小绿灯→重构；不为通过删断言</td>
			</tr>
			<tr>
					<td>双轨验证</td>
					<td>程序逻辑用替身精确断言；模型效果真实调用+语义标准</td>
			</tr>
	</tbody>
</table>
<p>一句话总结：<strong>一句话生成是表演，说清什么叫「做对了」才是工程。</strong></p>
<blockquote>
<p>本篇最早于 2026 年 10 月 16 日发布于小红书，此为博客完整版。</p>
</blockquote>
]]></content:encoded></item></channel></rss>