
字节笔记本
2026年10月7日 · 约 7 分钟读完
一个口算练习页的五轮迭代:AI 结对开发中的工程决策
给孩子做一个能用的练习工具,大概是最容易被低估的小项目之一:需求方注意力短、使用设备是手机、验收标准却很硬——题目不能超纲,得分不能算错。最近完整复盘了一次 AI 结对开发:目标是一个三年级口算练习网页,随机出十来道题,运算覆盖加、减、乘,计算结果限定在 100 以内。前后共五轮迭代,全程没有引入构建工具,最终落在一个单文件 HTML 页上。整个过程很像是真实项目的缩影:没有一步到位,每一版都由上一版暴露的问题推着走。这篇文章按时间线拆开,讲每一轮改了什么、为什么改,以及哪些决策可以搬到你的下一个小工具上。

起点:一道「合格题」是怎么生成的
第一版需求很朴素:随机出十来道题,一次性铺在页面上,设计上讲究留白和呼吸感。真正值得看的是出题函数——它不是简单地随机两个数,而是一次「约束采样」:先随机挑运算符(加、减、乘),再在 do-while 循环里反复生成操作数,直到结果落在 0 到 100 之间才放行。
do {
a = Math.floor(Math.random() * 20) + 1;
b = Math.floor(Math.random() * 20) + 1;
// 按运算符计算 result
} while (result > 100 || result < 0);减法还有一个细节:生成后把两个操作数按大小交换,保证大数减小数,永远不会出负数;乘法的结果上限靠 do-while 重试兜底,而操作数被限制在 20 以内,正是为了让重试次数保持在可接受的范围。这个「采样、校验、重试」的三步模式非常通用——凡是需要随机生成但必须满足业务约束的场景,比如抽题、造测试数据、生成优惠码,都可以直接套用,比先随机一大堆再过滤省事得多。
当然,重试式采样也有代价:约束越紧,单次采样失败的概率越高,极端情况下循环要空转很多圈。更讲究的做法是按运算符直接推导操作数的取值区间,让第一次采样就合法。对这个量级的小工具来说,重试循环已经足够好——简单比聪明重要。
第一处分水岭:一页十题改成一页一题
列表版很快被否掉了:它本质上是一张电子卷子,孩子看不到进度,没有作答动作,也没有结果反馈。第二轮改成单题模式后,页面结构变成了一个三态小状态机——开始、答题中、已结束,外加三个关键字段:题目数组、当前题号、答对数。进度提示「题目 3 / 10」,加上结束页的得分和正确题数,构成了一个完整的练习闭环。
这次改动的启发不在代码量,而在产品判断:对注意力有限的孩子来说,每屏只该有一个焦点,进度和分数则提供即时的目标感——做多少了、做得好不好,一眼可见。「一次只做一件事」比「信息一次给全」更对,列表模式适合成人刷题,单题模式才像游戏。而落到实现上,这一切不过是一个数组下标加一个计数器。

触屏输入:只读输入框加自制数字键盘
第三轮来自真实使用场景:孩子用手机答题,系统输入法一弹出来就遮住半个屏幕,还要手动切到数字键盘。方案是把输入框设为 readonly,彻底不触发系统键盘,然后在页面下方铺一块 3 列网格的自制数字键盘:1 到 9,一个横跨两列的 0,右下角一个删除键。因为所有答案都在 100 以内,输入长度直接限死 3 位。
这套组合里最值得记的是 readonly:它把「输入」从依赖系统输入法变成纯按钮事件,孩子点的是 56 本身,而不是先唤起键盘再找数字。这个模式同样适用于验证码输入、支付密码框等一切「只要数字、不要输入法」的场景;在桌面上它也不吃亏,鼠标点键盘反而更符合这个页面的使用姿态。顺带一提,输入框类型用 text 而不是 number——在 readonly 生效后这已不影响键盘行为,但能让桌面浏览器彻底告别数字输入框自带的上下调节小箭头。
一个真实的坑:得分为什么不对
第四轮的用户反馈只有一句话:得分不正确。回头看判分入口的代码:
const userAnswer = parseInt(input.value);
if (userAnswer === questions[currentQuestion].answer) {
correctAnswers++;
}输入框为空时,parseInt('') 返回 NaN,而 NaN 与任何值比较都是 false,于是空提交被静默判成答错;答题节奏被打断,孩子和家长看到的分数自然「不对劲」。修复分两层:判分前先做空值守卫,空输入要么拦截提交,要么给出明确的「未作答」提示;更进一步,把得分从散落在函数里的变量,收敛成由答对数和总数派生的计算属性,让分数只有一个真相来源。
这一轮的教训比代码重要:AI 生成的代码,主路径往往是对的,错都埋在边界上——空输入、连点提交、回车与按钮双入口重复判分,这些都应该列进一张短短的测试清单,一次性过掉。验收标准要由人带着真实用例去跑,「应该是正确的」这句话在工程上等于没有验证。
用 UnoCSS 重构:原子类与声明式状态机
最后一轮是样式重构:内联 CSS 换成 UnoCSS,逻辑层顺势迁到 Vue 3,两者都走 CDN 引入,依然免构建。UnoCSS 是原子化 CSS 引擎,按需生成工具类,类名风格与 Tailwind 同源:渐变背景、space-y-6 拉开纵向间距、max-w-md 限制内容宽度、rounded-xl 补圆角、hover:scale-105 和 focus:ring-2 补交互反馈。「呼吸感」在这里不再是形容词,而是一组可以逐条核对的类名。
迁移到 Vue 的收益比换样式更大:旧版靠 innerHTML 整段重绘页面、每次手动重绑事件;现在开始、答题、结果三个状态对应三块 v-if 模板,输入、判分、翻题都成了声明式代码。对单文件小项目来说,CDN 版 UnoCSS 加 Vue 3 是一套几乎零成本的技术栈组合,很适合快速做内部工具和教学 demo。唯一要留意的取舍:UnoCSS 的 CDN 运行时是在浏览器里现场生成样式的,弱网下首帧可能闪一下无样式内容;正式项目更稳妥的姿势是构建期接入,让样式在打包阶段就确定下来。
收尾:五轮迭代沉淀下来的节奏
回头看这五轮:出题算法、单题闭环、自制键盘、边界修复、样式重构——每一轮只解决一类问题,而且每一轮都能让使用者在几分钟内给出明确反馈。这大概就是 AI 结对开发小工具的合理粒度:需求一次说清验收标准(结果范围、题量、要进度和得分),实现后立刻在真机上跑,反馈用一句话讲清现象。
再往上加功能,路也是现成的:错题本可以存进 localStorage,答得好可以升级乘除法和两位数运算,进度条可以换成星星。工具虽小,但约束采样、状态机、触屏输入这三个模式,放进任何稍大的项目里都同样成立。



