一个不会前端的人,用 AI Agent 从零写了一个基金管理系统
一个不会前端的人,用 AI Agent 从零写了一个基金管理系统
技术栈是 React + TypeScript + Vite + Dexie,最终出来了一个本地运行的个人基金/ETF管理工具,有仪表盘、持仓管理、收益分析、定投计划、家庭账本、AI投资助理这些模块。代码量超过一万五千行,没写过前端,全程靠 Codex Agent 做开发。
听起来好像"AI 帮你把活干了",实际上远没有那么顺。过程中有些坑是 Agent 自己引入的,有些是我对技术理解不够导致的指令不清,还有些是做到一半才发现方向不对。
下面是几件印象比较深的事情,都是从实际开发过程中摘出来的。
一、第一天就白屏了
2026-03-28 下午项目初始化,同一天第一个功能版本跑起来。技术栈选的是 React + Dexie(浏览器端 IndexedDB 封装)+ Vite,没有后端。选 Dexie 是因为"不需要服务端,打开浏览器就能用"——当时我对本地存储方案没有任何认知,Agent 推荐的,我觉得合理就用了。
问题出在初始化演示数据的逻辑上。Agent 把一个写操作 seedDatabaseIfEmpty() 放进了 Dexie 的 useLiveQuery 里。useLiveQuery 是 Dexie 提供的响应式查询 hook,用来实时监听数据库变化。在这个 hook 里同时执行写入,首屏初始化就中断了——React 根组件根本没挂上,页面只有背景色,什么内容都没有。
排查用了一段时间。白屏不报红色错误,控制台里只有一条 DexieError,很容易忽略。最后的修法是把初始化写操作从 useLiveQuery 里拿出来,单独放到 useEffect 中。
出问题的写法大致是这样:
// ❌ 在 useLiveQuery 里做写操作
const data = useLiveQuery(async () => {
await seedDatabaseIfEmpty(); // 写操作
return db.assets.toArray(); // 读操作
});
改成:
// ✅ 写操作放到 useEffect
useEffect(() => { seedDatabaseIfEmpty(); }, []);
const data = useLiveQuery(() => db.assets.toArray());
这是一个典型的 Agent 对框架约束不够敏感的例子。useLiveQuery 文档里虽然没有明确禁止写操作,但在响应式监听的 callback 里做写入,会触发循环依赖。Agent 没有意识到这一点,我自己也不懂这个 hook 的工作机制,所以都没有在第一时间拦住。
二、数据存在浏览器里,清缓存就没了
用了几天之后我才意识到一个问题:数据存在浏览器的 IndexedDB 里。如果清浏览器缓存、换浏览器、或者无痕模式访问,数据就不见了。对一个打算长期用的投资管理工具来说,这不可接受。
我让 Agent 加了一层 SQLite 持久化,Vite 本身支持自定义中间件,可以在开发服务器或本地 Node 服务里挂一个 API,每次前端写 IndexedDB 时同步写一份 SQLite。到这一步看起来解决了问题。
但后来出了一次数据事故。
某天我让 Agent 做一个功能:从最近的收益快照里恢复丢失的目标仓位。Agent 把快照里的整包数据(bundle)回写到了 SQLite——这里面不只有目标仓位,还包含了两天前的持仓现价。结果主库里所有持仓的现价被回滚了,连带着总资产显示、仓位占比、偏差计算全部偏了。
更糟的是,Agent 第一次恢复操作只恢复了目标仓位,漏掉了现金持仓。用户端看到"偏差 Top 回来了,但 5 万多现金不见了"。又补了一次才把现金也恢复。
这件事暴露的问题不是 Agent 写错了代码,而是 Agent 对"恢复操作"的影响范围估计不足。整包回写意味着所有字段都被覆盖,没有字段级保护。我后来让 Agent 加了持久化操作日志(~/assetAllocation/logs/persistence/operations.jsonl),至少出问题后能回溯。
三、收益怎么算的,做了三版
第一个版本的收益计算,Agent 直接从行情接口取了整月涨跌幅,当作我的月度收益。问题是:我月底才买的日经225ETF,只持了几天,不能用这个 ETF 整月的涨幅来算我的收益。这件事是我自己发现的,Agent 没有主动提出来。
第二个版本改成了按持仓成本计算:(现价 - 买入均价) × 份额。更合理了,但每次和券商账单核对都对不上。原因是:券商的累计收益口径包含了分红再投资、部分减仓后的成本摊薄、手续费等,而系统里这些东西没有建模完整。
最后的方案是引入了"手工快照":用户每月手动录入从券商看到的总资产、当日盈亏、累计盈亏。系统算的只是估算值,手工快照才是真实数据。显示的时候优先用手工值。
这个演进过程说明一个问题:AI Agent 能帮你实现"你说的东西",但不会帮你发现"你没说的东西"。收益计算的口径问题,本质上是业务理解的问题,不是编码问题。Agent 按我的指令实现了,但指令本身就是错的。
四、仪表盘抖了一个晚上
加了实时行情刷新之后,仪表盘上的盈利折线图开始抽搐。一进页面就看到图表反复闪烁,数据一会儿有一会儿没有。
原因排查下来是一个循环:实时行情写回持仓 → Dexie 检测到有变化 → 触发 useLiveQuery 重算 → 重算结果写回数据库 → 再次触发 → 循环。Agent 把行情更新和收益计算放在了同一条 Dexie 响应链上,没有做防重入。
行情接口返回新价格
→ 写入 holdings 表
→ useLiveQuery 检测到 holdings 变化
→ 重算收益并写回 holdings
→ useLiveQuery 再次检测到变化
→ 循环
修法是把自动刷新改成只按定时器走,不再因为数据库更新自身而反复唤醒。图表也改成首次加载才显示 loading 态,后续刷新时只在后台更新数据,不重置图表。
这个问题在有后端开发经验的人看来可能很直觉——"不要在 onChange 里做 set"。但我没有这个背景,Agent 也没有主动做这个判断。
五、App.tsx 涨到 15000 行的时候
整个项目的 UI、状态管理、弹窗、业务逻辑、数据库查询,全部写在一个 src/App.tsx 文件里。到最后一次提交(2026-04-16),这个文件有 15716 行,23 个 activeTab === 'xxx' 分支,每个分支就是一整页的 JSX。
我不知道什么时候应该拆,Agent 也没有主动提出拆分。每次加新功能,Agent 都在已有的文件里继续写,因为"上下文都在这里,不用导入别的文件"。这对 Agent 来说效率最高——减少工具调用、减少文件切换——但对项目来说是技术债的持续累积。
后来做代码评审时,第一条风险就是这个文件。评审的原话是:"App.tsx 已经到 13,831 行,页面、状态、事件、弹窗都堆在一起。真正危险的不是本地数据库不能用,而是单个 App.tsx 承载太多业务决策,后面每加一个模块,回归成本都会上升。"
之后我开始正式拆分,按"执行卡片"的方式把重构分成多步:先抽导航配置、再抽共享数据模型、再逐页面拆分。这个过程本身也是 Agent 来做的,但我得先给出一个比较完整的重构规范,包括每一步的边界、禁止做的事、验收标准。否则 Agent 会一口气把所有东西都拆了,引入更多问题。
几个回头看的感受
用 AI Agent 从零建一个中等复杂度的系统是可以做到的。16 次提交、不到 20 天,系统基本成形,大部分功能可用。但这不意味着过程是平滑的。
Agent 擅长的事情很明确:技术选型给建议、快速出代码、批量改 UI、写测试、做格式变换。这些事情确实比人快很多。
Agent 不擅长的事情也很明确:它不会质疑你的指令。如果你说"用整月涨幅算我的月收益",它会实现得很好,不会问一句"你是不是月底才买的"。它的代码在局部上通常是对的,但在系统层面可能引入你还没意识到的连锁反应——比如把数据整包回写,比如在响应式 hook 里做写操作。
另外,Agent 对"什么时候该停下来重构"几乎没有判断。它会一直在当前文件上追加功能,因为这样效率最高、上下文最完整。文件涨到一万行它也不会说一句"是不是该拆了"。这个判断只能靠你自己。
所以实际的分工是:Agent 是开发者,你是产品经理 + 架构师 + 测试员。你得知道要什么、边界在哪里、什么时候该踩刹车。如果你只是把需求扔进去不管,出来的东西大概率能用,但早晚你会回来收拾碎片。

浙公网安备 33010602011771号