记录一次代码体检:290 行的 Streamlit(2026-05-28)

自己写的东西,自己最不容易看出问题

那天没写新功能,做了两件更基础的事:给一套自己写的 Streamlit 做代码体检,再把另一个小项目的架构摊开看一遍。都是"回头看"的活,比往前冲更难受,但也更有用。


第一件:给效率分析系统做个体检

被体检的是一个 290 行的 Streamlit 应用——效率分析管理系统。结构其实挺清楚,一共七个区块:

  1. 页面配置(7-13 行)
  2. 样式注入(15-35 行)—— 自定义 CSS,用 unsafe_allow_html
  3. 常量定义(37-52 行)—— 硬编码了 D:\work 路径和三个 Excel 文件路径,三组列名
  4. Session State 初始化(54-69 行)—— 把三个表读进 session_state
  5. 工具函数(71-98 行)—— DataFrame 转 Excel bytes、校验列名后导入
  6. 主界面三个 Tab(106-282 行)—— 每个 Tab 都是"导入/导出/在线编辑"
  7. 页脚(284-289 行)

功能上没毛病:三表管理、Excel 导入带列名校验、导出、data_editor 在线编辑、样式美化,该有的都有。

问题在于**这些功能全都是"看起来能用"**。往下一挖,九个坑排着队:

严重的三条:

  1. 数据无持久化。 所有数据只活在 session_state 里,用户刷新页面、关掉浏览器,改的东西全没。导入的新数据也没有回写本地 xlsx。
  2. 导出触发逻辑有问题。 点"导出"后先设一个 trigger = True,等下一次 rerun 才显示下载按钮——但 trigger 立刻又被置回 False。结果就是下载按钮会闪一下、甚至点不到。正确做法是把 st.download_button 直接放在按钮旁边。
  3. 数据覆盖风险。 导入时直接整个 DataFrame 替换,没有"追加"或"合并"选项,用户辛苦编辑的本地数据会被一把覆盖。

中等的三条:

  1. 硬编码路径 D:\work。 跨平台差,而且路径不存在时函数静默返回空表,一声不吭。
  2. 字段校验太死。 要求列名和顺序完全一致,列顺序不同但内容相同也拒绝,还不给自动排序。
  3. 数值列全读成 str。 dtype=str 一路到底,data_editor 的数字格式化不生效,算的时候还得再转类型。

轻微的三条:

  1. 样式注入带 XSS 隐患(这次是静态 CSS,但是坏习惯)
  2. 异常被静默吞掉,用户不知道文件为什么加载失败
  3. 三个 Tab 的代码几乎完全重复,该抽成函数

最后给出了六条改进建议:加持久化(回写 xlsx 或者上 sqlite)、导出改用 st.download_button 去掉中间态、导入加"替换/追加"选项、路径配置化、抽公共渲染函数、数值列先 pd.to_numeric 再入 state。

说句实话,第 9 条最扎心。三个 Tab 各写一遍,意味着以后改一处要改三处,漏一处就出不一致——这种债不会当场爆,但会一直收利息。

第二件:把 UI_agnet_ev 摊开看

另一个被翻出来的是 UI_agnet_ev——一个多智能体对话可视化监控,定位是 Codex ↔ Hermes 的实时对话展示。整个项目只有三个文件:

1
2
3
4
UI_agnet_ev/
├── app.py # Streamlit 监控面板(558 行,端口 8508)
├── bridge.py # FastAPI 桥接服务(69 行,端口 8643)
└── run.bat # 一键启动

结构是"事件收集 + 实时展示":Bridge 收 Codex / Hermes 发来的消息(POST /api/chat),Streamlit 轮询(GET /api/conversation、GET /api/logs)把对话和日志画出来。前端四张指标卡 + 两个 Tab(对话记录 / 活动日志),每 0.1 秒 rerun 一次。

优点是真的很简单——三个文件,前后端分离清楚,靠轮询把实时性做出来了。

局限也同样明显:

  • 消息存内存,重启即丢,没有数据库
  • 完全没有认证,CORS 全开
  • 端口硬编码,没有配置文件

那天给它开的方子也是四条:持久化(文件或 SQLite)、把轮询换成 WebSocket、配置外置到 config.toml、补一个 requirements.txt。

顺带认识了一下搭档

那天还花了点时间搞清楚"我到底在跟什么打交道"——当前会话跑的模型是 mimo-v2.5,但它本质上是一个 Agent 框架(Hermes Agent),底层模型是可以换的。

这件事值得单独记一笔,因为它解释了很多后面的行为:环境是长期存在的,模型是可以替换的,而跨会话记住的偏好和环境信息,才是真正沉淀下来的东西。


最后

那天学到的最大一件事是:**"能跑"和"扛得住"是两回事。**

290 行那个应用,功能全都能演示一遍,看起来很完整。但它的每一个 Tab 都建立在"用户不会刷新页面""用户不会传错列顺序""用户只在一台机器上跑"这三个脆弱前提上。任何一个前提松动,它就开始丢数据或者沉默地失败。

代码体检的价值就在这儿——它不增加功能,但它把"什么时候会坏"提前指出来了。而提前知道,就有机会在它真的坏掉之前修掉。

至于那两个当天的答案(RESTful 到底是什么、微服务该怎么拆),后来我把它们单独记成了知识条目。知识本身是通用的,反而是"这套 290 行的东西该怎么改"才是一次性的——通用的东西存成知识,一次性的东西就当场改掉,这也算是那天顺手定下来的分工。