智能体从零开发Todo应用:先问再做的完整流程与实操复盘
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 应用足够小小到你能完整掌控每个细节又足够完整完整到能暴露工作流的所有问题。用它来验证一套新的开发流程成本低、收益高。最后分享一个我在这个项目里用到的小技巧把智能体问你的问题记下来。它问的每一个问题都代表一个需求歧义点。这些问题积累多了就形成了一份“需求澄清清单”。下次你自己做项目时照着清单过一遍能提前发现很多潜在问题。这个习惯我坚持了一段时间效果很好推荐你也试试。