记录一次代码体检:290 行的 Streamlit(2026-05-28)
记录一次代码体检:290 行的 Streamlit(2026-05-28)
自己写的东西,自己最不容易看出问题
那天没写新功能,做了两件更基础的事:给一套自己写的 Streamlit 做代码体检,再把另一个小项目的架构摊开看一遍。都是"回头看"的活,比往前冲更难受,但也更有用。
第一件:给效率分析系统做个体检
被体检的是一个 290 行的 Streamlit 应用——效率分析管理系统。结构其实挺清楚,一共七个区块:
- 页面配置(7-13 行)
- 样式注入(15-35 行)—— 自定义 CSS,用
unsafe_allow_html - 常量定义(37-52 行)—— 硬编码了
D:\work路径和三个 Excel 文件路径,三组列名 - Session State 初始化(54-69 行)—— 把三个表读进
session_state - 工具函数(71-98 行)—— DataFrame 转 Excel bytes、校验列名后导入
- 主界面三个 Tab(106-282 行)—— 每个 Tab 都是"导入/导出/在线编辑"
- 页脚(284-289 行)
功能上没毛病:三表管理、Excel 导入带列名校验、导出、data_editor 在线编辑、样式美化,该有的都有。
问题在于**这些功能全都是"看起来能用"**。往下一挖,九个坑排着队:
严重的三条:
- 数据无持久化。 所有数据只活在
session_state里,用户刷新页面、关掉浏览器,改的东西全没。导入的新数据也没有回写本地 xlsx。 - 导出触发逻辑有问题。 点"导出"后先设一个
trigger = True,等下一次 rerun 才显示下载按钮——但 trigger 立刻又被置回 False。结果就是下载按钮会闪一下、甚至点不到。正确做法是把st.download_button直接放在按钮旁边。 - 数据覆盖风险。 导入时直接整个 DataFrame 替换,没有"追加"或"合并"选项,用户辛苦编辑的本地数据会被一把覆盖。
中等的三条:
- 硬编码路径
D:\work。 跨平台差,而且路径不存在时函数静默返回空表,一声不吭。 - 字段校验太死。 要求列名和顺序完全一致,列顺序不同但内容相同也拒绝,还不给自动排序。
- 数值列全读成 str。
dtype=str一路到底,data_editor的数字格式化不生效,算的时候还得再转类型。
轻微的三条:
- 样式注入带 XSS 隐患(这次是静态 CSS,但是坏习惯)
- 异常被静默吞掉,用户不知道文件为什么加载失败
- 三个 Tab 的代码几乎完全重复,该抽成函数
最后给出了六条改进建议:加持久化(回写 xlsx 或者上 sqlite)、导出改用 st.download_button 去掉中间态、导入加"替换/追加"选项、路径配置化、抽公共渲染函数、数值列先 pd.to_numeric 再入 state。
说句实话,第 9 条最扎心。三个 Tab 各写一遍,意味着以后改一处要改三处,漏一处就出不一致——这种债不会当场爆,但会一直收利息。
第二件:把 UI_agnet_ev 摊开看
另一个被翻出来的是 UI_agnet_ev——一个多智能体对话可视化监控,定位是 Codex ↔ Hermes 的实时对话展示。整个项目只有三个文件:
1 | UI_agnet_ev/ |
结构是"事件收集 + 实时展示":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 行的东西该怎么改"才是一次性的——通用的东西存成知识,一次性的东西就当场改掉,这也算是那天顺手定下来的分工。