1. 从一句指令到完整应用:积木智能体到底做了什么
第一次看到“让智能体从零开发一个 Todo 应用”这个说法,我脑子里冒出来的第一个念头是:又是一个演示级别的玩具吧。毕竟这些年各种“AI 写代码”的演示看得太多了,大多是给一个函数签名让它补全,或者生成一段孤立的算法,真正让它从零搭出一个能跑、能用、结构完整的小应用,并且过程中还主动反问需求,这种体验确实少见。
我花了几个晚上把整个流程完整跑了一遍,从最初的模糊需求到最终拿到一个可运行的 Todo 应用,中间经历了需求澄清、技术选型、目录结构设计、核心逻辑实现、状态管理、界面渲染、边界情况处理等环节。整个过程下来,最大的感受不是“它写代码有多快”,而是它在动手之前先问了我两个问题——这个细节才是真正值得聊的地方。
这篇文章适合几类人看:一是对智能体开发流程好奇、想知道它和普通代码补全有什么区别的开发者;二是想自己搭一套类似工作流、把智能体接入日常开发的人;三是单纯想搞清楚“让 AI 从零做应用”这件事到底靠不靠谱、坑在哪里的技术爱好者。我会把整个项目的设计思路、核心环节、实操步骤、踩过的坑全部摊开讲,尽量做到你看完能自己复现一遍。
先说结论:这个 Todo 应用本身不复杂,复杂的是智能体在动手前的那套“先问再做”的机制。理解了这套机制,你就能理解为什么有些智能体做出来的东西能用,有些做出来的一堆代码根本跑不起来。
2. 项目整体设计与思路拆解
2.1 为什么一个 Todo 应用值得认真对待
很多人会觉得 Todo 应用太简单了,增删改查而已,有什么好讲的。但恰恰是这种“简单”应用,最能暴露一个智能体开发流程的成熟度。原因很简单:Todo 应用虽然功能少,但它是一个完整闭环——有数据输入、有状态存储、有界面渲染、有交互反馈、有边界处理。任何一个环节缺失,应用就是残的。
我见过太多智能体生成的 Todo 应用,要么只有一堆函数没有界面,要么界面能看但数据一刷新就没了,要么增删改查逻辑写了一半漏了一半。问题不在于智能体不会写代码,而在于它没有把“一个完整应用”当成一个整体来对待,而是把它当成了一堆代码片段的拼接。
所以这个项目的核心价值,不在于 Todo 应用本身,而在于验证一件事:智能体能不能像一个真正的开发者那样,先理解需求、再规划结构、最后动手实现。而“先问两个问题”这个行为,正是这套流程的入口。
2.2 智能体“先问再做”背后的逻辑
普通代码补全工具的工作模式是:你给它上下文,它给你下一段代码。它不关心你要做什么,只关心你当前这行要写什么。这种模式在写单个函数时很好用,但一旦涉及多文件、多模块的完整应用,就会立刻暴露问题——它不知道你的数据存在哪、不知道你的组件怎么组织、不知道你的状态怎么流转。
积木智能体的做法不一样。它在接收到“开发一个 Todo 应用”这个指令后,没有立刻开始写代码,而是先抛出了两个问题。这两个问题我后来反复琢磨,发现它们精准地卡在了需求歧义最大的两个点上:
- 第一个问题问的是数据持久化方式:数据是存在内存里、浏览器本地存储里,还是需要接后端接口?这个问题决定了整个应用的数据层架构。
- 第二个问题问的是功能范围:是只做最基础的增删改查,还是需要包含分类、优先级、截止日期、搜索过滤等扩展功能?这个问题决定了整个应用的复杂度和工作量。
你看,这两个问题问完之后,整个应用的轮廓就清晰了。如果它不问,直接按默认假设去写,那写出来的东西大概率不是我想要的——可能我想要本地存储它给我写了内存存储,可能我只想要基础功能它给我堆了一堆用不上的扩展。
这里有个关键点:智能体问问题的能力,比它写代码的能力更重要。因为代码写错了可以改,但方向错了,改的成本是指数级上升的。
2.3 技术选型的取舍与考量
基于我后来给它的回答(本地存储 + 基础功能 + 少量扩展),它给出的技术方案是这样的:
| 层级 | 选型 | 选择理由 |
|---|---|---|
| 界面层 | 原生 HTML + CSS + JavaScript | 无需构建工具,打开即用,降低环境依赖 |
| 数据层 | 浏览器本地存储 | 无需后端,刷新不丢数据,符合轻量定位 |
| 状态管理 | 单一数据源 + 手动渲染 | 应用规模小,引入框架反而增加复杂度 |
| 交互层 | 事件委托 + 局部更新 | 避免频繁重绘,提升操作流畅度 |
这套选型看起来“土”,但非常务实。我特意问过它为什么不选某个流行框架,它的回答逻辑是:Todo 应用的状态复杂度极低,引入框架带来的收益(组件化、响应式)远小于成本(构建配置、依赖体积、学习门槛)。这个判断我认同,很多开发者做小项目时容易过度设计,上来就搭一套完整工程化体系,结果光配置就花了两小时,真正写业务逻辑只花了二十分钟。
当然,这个选型也有代价。原生 JavaScript 手动管理 DOM 更新,在功能扩展时会越来越吃力。如果后续要加分类、标签、拖拽排序,代码会迅速膨胀。所以这个方案适合“快速验证 + 轻量使用”的场景,不适合长期迭代的产品。
2.4 目录结构与模块划分
它给出的目录结构很干净,没有多余的层级:
todo-app/ ├── index.html # 页面骨架 ├── styles.css # 样式定义 ├── app.js # 应用入口与初始化 ├── store.js # 数据存储与状态管理 ├── render.js # 界面渲染逻辑 └── utils.js # 工具函数这个划分方式值得说一下。很多智能体生成的项目会把所有代码塞进一个文件,或者按“技术类型”划分(所有函数一个文件、所有事件一个文件),前者难以维护,后者逻辑割裂。而这里按职责划分:store 管数据、render 管展示、app 管调度、utils 管通用工具。每个文件职责单一,互相之间的依赖关系清晰。
我后来在扩展功能时验证过这个结构的好处:加一个“过滤已完成/未完成”的功能,只需要改 store(加过滤状态)和 render(根据状态渲染),app 和 utils 完全不用动。这种改动局部化的能力,就是好结构的价值。
3. 核心细节解析与实操要点
3.1 数据存储层的设计细节
数据层是整个应用的地基,也是最容易出问题的地方。它给出的方案是:单一数据源 + 本地存储同步。具体来说,所有 Todo 数据存在一个数组里,每次数据变更后,把整个数组序列化写入本地存储。
// store.js 核心逻辑示意 const STORAGE_KEY = 'todo_items'; let todos = []; function loadFromStorage() { const raw = localStorage.getItem(STORAGE_KEY); todos = raw ? JSON.parse(raw) : []; } function saveToStorage() { localStorage.setItem(STORAGE_KEY, JSON.stringify(todos)); } function addTodo(text) { todos.push({ id: Date.now(), text: text, done: false, createdAt: new Date().toISOString() }); saveToStorage(); }这里有几个细节值得注意。第一,id 用时间戳生成,简单够用,但如果同一毫秒内连续添加两条会冲突。更稳妥的做法是用递增计数器或者随机字符串。第二,每次变更全量写入,在数据量小的时候没问题,但如果 Todo 数量上千,每次写入都会有性能损耗。第三,没有做数据校验,如果本地存储里的数据被手动改坏了,JSON.parse会直接抛错导致应用崩溃。
实操心得:本地存储读取一定要包一层 try-catch,并且对解析结果做类型校验。我踩过一次坑,手动改了本地存储里的数据格式,结果应用白屏,排查了半天才发现是解析失败。
3.2 状态变更与界面更新的联动机制
这是整个应用最容易写乱的地方。原生 JavaScript 没有响应式系统,数据变了界面不会自动更新,必须手动触发渲染。它采用的策略是:所有数据变更操作统一走一个入口,变更完成后统一调用渲染函数。
// app.js 中的调度逻辑示意 function handleAddTodo(text) { addTodo(text); // 更新数据 renderTodoList(); // 重新渲染 updateCounter(); // 更新计数 }这种“手动联动”的方式在应用规模小时很清晰,但规模一大就容易漏。比如你加了一个新功能,改了数据但忘了调渲染,界面就不会更新。更优雅的做法是引入一个简单的发布订阅机制:数据变更后自动通知所有订阅者。
// 简易发布订阅示意 const listeners = []; function subscribe(fn) { listeners.push(fn); } function notify() { listeners.forEach(fn => fn()); } function addTodo(text) { todos.push({ /* ... */ }); saveToStorage(); notify(); // 自动通知,不用手动调渲染 }这个改动不大,但能显著降低后续扩展时“忘记更新界面”的概率。我在自己的项目里后来就换成了这种模式,省心很多。
3.3 界面渲染的性能考量
渲染逻辑它用的是全量重绘:每次数据变更,清空列表容器,重新生成所有 DOM 节点。这种方式在 Todo 数量少时完全没问题,但数量一多就会卡顿,因为每次操作都要重建所有节点。
优化的方向有两个。一是局部更新:只更新变化的那一条,而不是全部重绘。二是虚拟列表:只渲染可视区域内的节点。对于 Todo 应用来说,局部更新就够了,虚拟列表属于过度设计。
局部更新的实现思路是:给每个 Todo 节点绑定唯一的 id,数据变更时找到对应的 DOM 节点,只修改它的内容或样式,而不是重建整个列表。这样即使有几百条 Todo,操作起来也很流畅。
注意:局部更新虽然性能好,但代码复杂度会上升。如果 Todo 数量不超过几十条,全量重绘完全够用,没必要为了性能牺牲代码可读性。这是一个典型的“看场景做取舍”的问题。
3.4 边界情况的处理清单
一个应用能不能用,很大程度上取决于边界情况处理得好不好。它生成的代码里,我梳理出了几个关键处理点:
- 空输入拦截:添加 Todo 时如果输入框为空或只有空格,不执行添加操作,并给出提示。
- 超长文本处理:Todo 文本过长时,界面上做截断显示,避免撑破布局。
- 删除确认:删除操作不可逆,加一层确认机制,避免误删。
- 空列表状态:列表为空时显示引导文案,而不是一片空白。
- 本地存储异常:读取失败时降级为空数组,保证应用能正常启动。
这几个点看起来琐碎,但正是它们决定了一个应用是“能用”还是“好用”。很多智能体生成的代码只覆盖了正常流程,一遇到边界情况就崩,问题就出在这里。
4. 实操过程与核心环节实现
4.1 从零开始的完整搭建流程
我把整个搭建过程拆成了六个阶段,每个阶段都有明确的产出物和验证标准。你可以按这个顺序一步步来。
第一阶段:需求澄清。这一步就是智能体问问题、我给回答的过程。核心是把模糊需求变成明确需求。我的回答是:本地存储、基础功能、加一个“清除已完成”的扩展。这三个信息定下来,后面的工作就有了边界。
第二阶段:结构设计。确定目录结构和模块划分。这一步不需要写代码,只需要把文件建好、把每个文件的职责写清楚。我习惯在每个文件顶部写一行注释说明职责,这样后面写代码时不会跑偏。
第三阶段:数据层实现。先写 store.js,把数据的增删改查和本地存储同步做完。这一步做完后,可以在控制台手动调用函数验证数据是否正确读写。
第四阶段:渲染层实现。写 render.js,把数据渲染成界面。这一步做完后,可以手动改数据然后调渲染函数,看界面是否正确更新。
第五阶段:交互层实现。写 app.js,把用户操作和数据处理、界面更新串起来。这一步做完后,应用基本就能用了。
第六阶段:边界处理与优化。补上前面说的那些边界情况,然后做一轮完整测试。
这个顺序的核心逻辑是:先做数据、再做展示、最后做交互。因为数据是地基,展示依赖数据,交互依赖展示。如果顺序反了,先做界面再做数据,很容易出现界面写好了但数据接不上的尴尬。
4.2 关键代码段的实现与注释
数据层的核心是增删改查四个操作,我把关键实现贴出来并加上注释:
// 添加:生成唯一 id,记录创建时间 function addTodo(text) { const trimmed = text.trim(); if (!trimmed) return false; // 空输入拦截 todos.push({ id: generateId(), text: trimmed, done: false, createdAt: Date.now() }); saveToStorage(); return true; } // 删除:按 id 过滤 function removeTodo(id) { const before = todos.length; todos = todos.filter(item => item.id !== id); if (todos.length !== before) { saveToStorage(); return true; } return false; } // 切换完成状态 function toggleTodo(id) { const target = todos.find(item => item.id === id); if (target) { target.done = !target.done; saveToStorage(); return true; } return false; } // 清除已完成 function clearCompleted() { todos = todos.filter(item => !item.done); saveToStorage(); }这里有个细节:每个操作都返回布尔值表示是否成功。这个设计看起来多余,但在交互层很有用——可以根据返回值决定是否给出用户提示。比如添加失败时提示“请输入内容”,删除失败时提示“该事项不存在”。
4.3 参数选择与配置说明
虽然这个项目没有复杂的配置参数,但有几个数值选择值得说明:
| 参数 | 取值 | 选择理由 |
|---|---|---|
| 本地存储键名 | todo_items | 加前缀避免与其他应用冲突 |
| id 生成方式 | 时间戳 + 随机数 | 兼顾唯一性和简单性 |
| 文本最大长度 | 200 字符 | 超过则截断显示,存储仍保留完整 |
| 渲染节流间隔 | 16ms | 约等于一帧,避免高频操作卡顿 |
文本最大长度这个参数我特意调过。最初没限制,结果有人粘贴了一整篇文章进去,界面直接撑爆。后来加了 200 字符的显示截断,鼠标悬停时显示完整内容,体验就好多了。
渲染节流是针对快速连续操作的优化。比如你快速点击多次“添加”,每次都触发全量重绘会卡。加一个 16ms 的节流,把多次重绘合并成一次,流畅度明显提升。
4.4 完整测试与验证过程
写完代码只是开始,验证才是关键。我按这个清单做了一轮完整测试:
- 基础功能测试:添加、删除、切换完成状态、清除已完成,逐个验证。
- 持久化测试:添加几条数据后刷新页面,确认数据还在。
- 边界测试:空输入、超长文本、特殊字符、连续快速操作。
- 异常测试:手动改坏本地存储数据,确认应用能正常启动。
- 兼容测试:在不同浏览器里各跑一遍,确认表现一致。
测试过程中发现了一个问题:连续快速添加时,由于全量重绘,输入框会失去焦点。解决办法是在重绘后重新聚焦输入框。这个细节不测试根本发现不了,但实际使用时非常影响体验。
实操心得:测试一定要模拟真实使用场景,而不是只点一遍功能。真实用户会快速操作、会输入奇怪内容、会在各种时机刷新页面,这些才是暴露问题的地方。
5. 常见问题与排查技巧实录
5.1 数据不持久化的排查思路
这是最常见的问题:明明写了本地存储,刷新后数据还是没了。排查顺序是这样的:
- 先确认
saveToStorage有没有被调用。在函数里打一行 console.log,看操作时有没有输出。 - 再确认存储键名有没有写错。本地存储的键名是大小写敏感的,
todo_items和Todo_Items是两个不同的键。 - 然后确认存储的数据格式。在浏览器开发者工具里直接看本地存储的内容,确认是合法的 JSON。
- 最后确认读取时机。
loadFromStorage必须在应用初始化时调用,如果调用晚了,数据会被空数组覆盖。
我遇到过一次诡异的情况:数据能存进去,但读出来是空的。排查半天发现是loadFromStorage在saveToStorage之后才执行,把刚存的数据又覆盖成空了。这种时序问题最隐蔽,一定要按顺序排查。
5.2 界面不更新的几种原因
数据变了但界面没变,原因通常有这几类:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 完全不更新 | 渲染函数没被调用 | 在渲染函数里打日志 |
| 部分更新 | 渲染逻辑有遗漏 | 检查渲染函数覆盖了哪些数据 |
| 更新后错乱 | DOM 节点复用冲突 | 检查节点 id 是否唯一 |
| 更新后闪烁 | 全量重绘过于频繁 | 加节流或改局部更新 |
最常见的是第一类:改了数据忘了调渲染。解决办法就是前面说的发布订阅机制,让数据变更自动触发渲染,从根上避免遗漏。
5.3 性能问题的定位与优化
Todo 应用一般不会有性能问题,但如果数量上去了或者操作频繁了,还是会有卡顿。定位性能问题的步骤:
- 打开浏览器开发者工具的性能面板,录制一段操作过程。
- 看火焰图里哪个函数占用时间最长。
- 如果是渲染函数,考虑局部更新或节流。
- 如果是存储函数,考虑防抖或增量写入。
我实测下来,全量重绘在 100 条以内基本无感,超过 300 条开始有可感知的延迟。所以如果你的应用预期数据量不大,不用过早优化。
5.4 智能体协作中的沟通技巧
这个项目最大的收获其实不是代码,而是和智能体协作的方式。我总结了几个技巧:
- 需求要具体:不要说“做个好看的界面”,要说“用卡片式布局,每条 Todo 有圆角边框和阴影”。模糊的描述只会得到模糊的结果。
- 及时纠偏:发现方向不对立刻说,不要等它写完再改。改一个方向比改一堆代码便宜得多。
- 让它解释:对它的某个选择有疑问时,直接问“为什么这么选”。它的解释往往能帮你发现自己没想到的点。
- 分阶段验证:不要等它全部写完再验证,每个阶段做完就验证一次,问题早发现早解决。
注意:智能体不是万能的,它也会有知识盲区和判断失误。把它当成一个需要你引导的协作者,而不是一个全知全能的工具,协作效率会高很多。
6. 从这个小项目延伸出的几点思考
做完这个项目后,我对“智能体开发”这件事有了几个新的认识,分享出来供参考。
第一个认识是:智能体的价值不在于写代码的速度,而在于流程的完整性。它写代码不一定比我快,但它不会忘记做需求澄清、不会跳过结构设计、不会漏掉边界处理。这些“不会忘”的能力,才是它真正区别于普通代码补全的地方。
第二个认识是:“先问再做”这个机制值得所有开发者借鉴。我自己写代码时也经常犯一个毛病:拿到需求就开干,写到一半发现方向错了,推倒重来。如果每次动手前都先问自己两个问题——数据怎么存、范围有多大——能省下大量返工时间。
第三个认识是:小项目是验证工作流的最佳载体。Todo 应用足够小,小到你能完整掌控每个细节;又足够完整,完整到能暴露工作流的所有问题。用它来验证一套新的开发流程,成本低、收益高。
最后分享一个我在这个项目里用到的小技巧:把智能体问你的问题记下来。它问的每一个问题,都代表一个需求歧义点。这些问题积累多了,就形成了一份“需求澄清清单”。下次你自己做项目时,照着清单过一遍,能提前发现很多潜在问题。这个习惯我坚持了一段时间,效果很好,推荐你也试试。