#0142 从一个文件夹塞63个脚本,到13个模块各自为政

type
Post
status
Published
date
Mar 7, 2026
slug
31ca745569bb8169a7f9e08ee18c2dcc
summary
改一个数据库 ID,你需要搜多少个文件? 在我的 AI Agent 系统里,答案曾经是:至少10个。因为同一个 Notion 数据库 ID 被硬编码在十几个脚本里,散落在一个巨大的 scripts/ ...
tags
交易
新闻
工具
健康
金钱
category
技术
icon
password

从一个文件夹塞63个脚本,到13个模块各自为政

改一个数据库 ID,你需要搜多少个文件?
在我的 AI Agent 系统里,答案曾经是:至少10个。因为同一个 Notion 数据库 ID 被硬编码在十几个脚本里,散落在一个巨大的 scripts/ 目录中。改完还不敢确定有没有漏掉的。grep 一下,心惊肉跳。
这就是我决定做模块化重构的直接原因。不是因为看了什么架构文章受到启发,不是因为闲得慌想重构玩玩。纯粹是因为——再这样下去,我迟早会因为漏改一个 ID 把线上搞崩

63个脚本挤一个目录是什么体验

我用 AI Agent 系统自动化了大量日常工作流:健康数据追踪、财务记账、情报监控、内容发布、记忆管理……每个功能背后都是一两个脚本。这些脚本全塞在一个 scripts/ 文件夹里。
刚开始的时候还好。10个脚本,20个脚本,肉眼能扫完。但长到60多个的时候,打开目录就是一堵墙。哪个脚本属于哪个功能?不知道。哪些脚本之间有依赖?不知道。想单独迁移"健康追踪"这个功能到另一台机器?做梦。
更要命的是配置散落。18个 Notion 数据库 ID、54个 Discord 频道 ID,全部硬编码在30多个脚本里。这不是技术债,这是定时炸弹。
有一次我换了一个 Notion 数据库,花了20分钟搜索替换,改了8个文件,自以为改完了。结果半夜定时任务一跑,有个脚本还在往旧数据库写数据。那种感觉,就像你以为已经关了所有窗户,结果暴雨天发现阳台那扇忘了。

不是"要不要重构"的问题,是"怎么重构不翻车"

重构的目标很清晰:
  • 按功能职责拆成独立模块
  • 配置集中管理,改一处生效
  • 模块间依赖显式化
  • 能独立测试、独立迁移
难的不是想清楚目标。难的是——系统在线上跑着,几十个定时任务每小时都在触发,你怎么重构不中断服务?
这就像给飞行中的飞机换引擎。
我见过太多重构翻车的案例:周五晚上"快速重构一下",结果周末全在救火。所以我给自己定了一条铁律:任何时刻,现有功能不能断。哪怕重构到一半,系统也必须正常工作。
这条约束直接决定了后续所有设计决策。

五步走,每一步都能回滚

最终我把整个重构拆成了五个阶段。不是一口气干完,而是每个阶段独立交付,出问题随时回滚。
Phase 1:先建基础设施
在动任何脚本之前,先写好 config-loader——一个统一的配置读取模块。它做的事情很简单:每个模块目录里放一个 config.json,脚本启动时调用 loadModuleConfig(import.meta.url),自动根据当前脚本的路径找到所属模块的配置文件。
为什么用 import.meta.url?因为这样脚本不需要知道自己在哪个目录。你把它从 A 模块移到 B 模块,它自动读 B 的配置。零改动。
这个设计花了我不少时间打磨,但它是整个重构的基石。后面所有"把硬编码换成配置读取"的工作,都建立在这个 loader 上。
Phase 2:建目录,移脚本,留 symlink
这是最关键的一步,也是最容易翻车的一步。
我创建了13个模块目录,按功能职责归类。健康相关的脚本进健康模块,财务相关的进财务模块,以此类推。每个模块统一结构:MODULE.md(说明文档)+ config.json(配置)+ scripts/(脚本)。
但脚本一移动,原来的路径就失效了。几十个定时任务还指着旧路径。怎么办?
symlink 过渡。
每个脚本移到新位置后,在原来的 scripts/ 目录创建一个 symlink 指向新位置。这样所有定时任务和外部引用都不受影响——它们访问的路径没变,只是背后的文件位置变了。
这招看起来简单,但它解决了一个核心问题:把"移动文件"和"更新引用"这两件事解耦了。你不需要一次性改完所有引用,可以慢慢来。今天改10个定时任务的路径,明天改10个,每改完一批就删除对应的 symlink。
任何时刻,只要 symlink 还在,旧路径就能用。这就是安全网。
Phase 3:替换硬编码
脚本搬完家之后,开始动内部代码。把硬编码的数据库 ID、频道 ID 全部替换成 loadModuleConfig() 读取。
这一步我没有一次全改。而是一个模块一个模块来。改完一个模块,跑一遍这个模块的所有脚本,确认没问题再改下一个。
最终24个脚本完成了配置迁移。18个 Notion 数据库 ID、54个 Discord 频道 ID,全部集中到各模块的 config.json 里。
现在要换一个数据库 ID?打开对应模块的 config.json,改一行,完事。影响范围一目了然。
Phase 4:更新定时任务路径
大约50个定时任务需要从旧路径切换到新路径。这是最枯燥的一步,但也是最不能出错的一步。
策略很直接:改一个,测一个。不批量改。
改完后,跑一轮全量检查,确认所有任务都指向新路径,没有遗漏。
Phase 5:清理收尾
删除所有 symlink。更新文档。创建模块索引。
到这一步的时候,系统已经完全运行在新架构上了。删除 symlink 只是把安全网撤掉——因为已经不需要了。

三个关键设计决策

回头看,整个重构最值得聊的不是具体怎么移文件,而是三个设计层面的决策。
决策一:模块边界怎么划?
我最初想过按"数据源"划分——所有跟 Notion 交互的放一起,所有跟 Google 交互的放一起。但很快否决了。一个健康追踪脚本可能同时读 Google Fit 数据、写 Notion 数据库、发 Discord 通知。按数据源划分,它属于哪个模块?
最终选择了按业务职责划分:健康是一个模块,财务是一个模块,记忆是一个模块。不管它调用什么 API,只要它服务于"健康追踪"这个业务目标,就归到健康模块。
这样划分的好处是直觉性强。想找跟健康相关的所有逻辑?打开健康模块就行。不用在三个不同的目录里翻。
13个模块最终的划分:基础设施、健康追踪、财务管理、记忆系统、内容创作、生活管理、情报监控、运维管理、图书管理、英语学习、安全审计、深度反思、每日简报。
最大的模块有14个脚本(健康追踪),最小的只有1个(每日简报)。大小不均匀完全没关系,模块边界不是为了代码量均分,而是为了职责清晰。
决策二:跨模块依赖怎么处理?
有些工具函数是公共的。比如 Google Workspace 的 API 封装、Notion 的通用操作。好几个模块都需要用。
解法是设一个 shared 基础设施模块。所有公共依赖放在这里。其他模块通过显式引用 shared 来使用公共能力。
关键原则:只有 shared 可以被所有模块引用,模块之间不能互相引用
如果 A 模块需要调用 B 模块的功能,要么把这个功能抽到 shared,要么说明这两个模块的边界划错了。不允许模块间形成网状依赖。
这条规则看起来严格,但它避免了最头疼的问题:你想迁移一个模块,结果发现它依赖另外三个模块,而那三个模块又互相依赖……
决策三:渐进迁移还是一步到位?
一步到位的诱惑很大。反正都要改,不如一次全改完,免得维护两套逻辑。
但我选了渐进迁移。原因很现实:全量改动的测试成本是指数级的
改一个脚本,测试这一个脚本就行。一次改60个脚本,你得测试所有脚本的所有交互。出了 bug 还不知道是哪个改动引入的。
渐进迁移的代价是过渡期会有"新旧混合"的状态——一些脚本已经用 config-loader,一些还在硬编码。但这个代价远小于一次性改崩的风险。
实际执行下来,整个重构过程零中断。没有一个定时任务因为重构而失败。没有一条数据因为重构而丢失。

重构前后对比

说几个最直观的变化:
改配置的体验
  • 之前:grep 搜索全目录 → 逐个文件修改 → 祈祷没漏掉
  • 之后:打开一个 config.json → 改一行 → 结束
新增功能的体验
  • 之前:往 scripts/ 里扔一个新脚本 → 不知道它属于什么 → 过三个月忘了它是干嘛的
  • 之后:在对应模块的 scripts/ 里加脚本 → 读 MODULE.md 就知道这个模块做什么 → 配置从模块的 config.json 读取
排查问题的体验
  • 之前:健康数据没同步?在60多个脚本里找跟健康相关的,找到5个,挨个看
  • 之后:直接进健康模块,所有相关脚本都在这里
迁移的体验
  • 之前:不可能。脚本之间有隐式依赖,拔出萝卜带出泥
  • 之后:把一个模块目录整个拷走,带上 shared 模块,就能独立运行
硬编码从30多个脚本里的72个 ID,收敛到各模块 config.json 里的集中管理。残留硬编码:1个,而且是非关键的。

什么时候该做模块化重构?

不是所有系统都需要。如果你的 Agent 系统只有5个脚本,搞模块化纯属过度工程。
但如果你遇到了这些信号:
  • 改一个配置要搜好几个文件
  • 不敢删任何一个脚本,因为不知道有没有别的脚本在引用它
  • 新人(或者三个月后的你自己)打开项目,完全看不懂结构
  • 想把某个功能单独部署,发现根本拆不出来
那就是时候了。
关键不是"重构成什么样子",而是"怎么重构不翻车"。模块化的终态大家都能想明白。真正难的是从现状过渡到终态的路径。
symlink 过渡是我这次最大的收获。它让你可以把一个大变更拆成无数个小变更,每一步都安全。这个思路不只适用于文件迁移——任何需要"在不中断服务的前提下切换底层实现"的场景,都可以借鉴。
新路径准备好 → 旧路径 symlink 过去 → 逐步切换引用 → 全部切完删除 symlink。
简单,但有效。

尾巴

有人可能会问:花这么大力气重构,值得吗?
我只说一个数字:重构完成后,我新增了一个模块,从创建目录到写完脚本上线,用了15分钟。换在重构前,光是搞清楚"配置写在哪、和哪些已有脚本有关"就不止15分钟。
模块化不是让系统变复杂。恰恰相反,它是在系统已经复杂到失控的时候,把复杂度装进盒子里。每个盒子内部可以复杂,但盒子之间的关系必须简单。
你不需要理解整个系统,只需要打开你关心的那个盒子。
Loading...

© xiyu 2013-2026