那天要做的事情,表面上很简单:把一套自动化技能带到另一台电脑,或者交给另一个人使用。最初的想法很容易理解——找到技能所在的目录,完整复制过去,看到目标位置出现了文件,就算同步完成。
但真正开始处理时,我先停了一下。技能目录里不只有运行所需的脚本,还可能混着缓存、日志、构建结果、临时素材、当前机器生成的中间文件,以及只对原环境有意义的私有状态。它们在源电脑上共处,不代表它们都属于技能本身。
这次同步先做了一轮过滤试算。原本约 25G 的内容,过滤后约 654M,文件数约 4.2 万个,然后才开始正式拷贝。这个变化说明,问题从来不只是“能不能复制”,而是“哪些东西值得被复制”。
文件在一起,不等于依赖在一起
我一开始也倾向于把目录当成一个整体。目录既然能在当前电脑上工作,就把它原样带走,似乎最省判断。后来发现,这种省事只是把判断推迟了,而且通常会推迟到最难排查的时候。
一个技能能运行,至少涉及入口、执行脚本、配置约定和它实际依赖的资源。除此之外,目录里还可能存在“曾经运行过”的痕迹。缓存可以让当前电脑启动得更快,日志可以帮助回看一次故障,生成物可以让某一步看起来已经完成;但这些东西不一定是另一个环境再次运行时的必要条件。
技能同步的对象不是一个目录,而是一组能在目标环境重新建立运行链条的必要依赖。
这句话听起来只是换了说法,实际会改变导出动作。复制前不能只问“这个文件在不在目录里”,还要问“删除它以后,目标机是否仍能从入口走到结果”。如果答案是否定的,它属于必需项;如果只是方便调试或保留历史,它就不该自动进入交付包。
先把文件分成 4 类
我会先建立 4 类清单,而不是直接执行复制。
第 1 类是必需项。它包括技能的入口、运行脚本、静态配置、必要的模板,以及脚本明确读取的资源。判断标准不是文件名听起来重要,而是缺少它会不会让最小调用无法开始或无法完成。
第 2 类是可选项。它们可能用于示例、调试、说明或扩展场景。可选不等于没价值,而是它们不应该阻塞核心能力的迁移。把这类文件单独标出来,之后需要补充时可以有依据地加回,而不是一开始把所有东西都打包。
第 3 类是生成物。缓存、临时输出、构建目录、历史日志和一次运行产生的中间结果都属于这一类。它们在源环境中有用,甚至可能很大,但目标机通常应该重新生成。把生成物带过去,除了增加体积,还可能把旧环境的路径、版本或错误状态一起带过去。
第 4 类是私有状态。它可能包含本机偏好、登录状态、用户数据或不适合交付的本地信息。即使某个文件看起来能让技能“更快恢复”,也不能因为方便就把它放进同步包。可运行不等于可交付,能帮助当前电脑恢复的状态,未必有资格进入共享文件。
这 4 类不是为了做一份漂亮的目录说明,而是为了让每个保留动作都有理由。分类之后,导出规则应该尽量只表达“带哪些必需项、明确排除哪些生成物和私有状态”。规则越清楚,换一台电脑时越少依赖记忆。
过滤清单还应该能被别人读懂,而不是只对写规则的人有效。每个排除项至少要有一个简单理由:可重新生成、只服务调试,或包含不应共享的状态。这样后续有人维护时,才不会因为害怕遗漏而把整个目录重新塞回去。
过滤前先做一次试算
过滤规则写好后,我不会立刻开始长时间拷贝,而是先试算体积和文件数量。试算的价值不在于得到一个好看的数字,而在于提前发现规则有没有失控。
如果过滤前后几乎没有变化,可能是排除条件没有命中,也可能是目录里确实大部分都是必要内容;如果体积从约 25G 降到约 654M,说明原目录中有大量内容不是迁移核心。这个差异会促使我回头检查:是不是误删了必要资源,或者只是把大量缓存和中间文件排除了。
我会重点看 3 件事。第 1,保留下来的文件是否包含入口和入口会调用的依赖。第 2,被排除的大文件主要是什么,是否能在目标机重新生成。第 3,文件数量是否仍然合理,是否出现某个本应被排除的目录整体被带入。
这里最容易误判的,是把“体积变小”当成“过滤正确”。体积只是结果,不是证据。一个包可以很小,却缺少唯一的入口;也可以很大,却只是携带了无数不影响运行的历史文件。
最容易误判的是导出成功
当正式拷贝完成,命令返回成功,目标位置也确实出现了文件,人很容易松一口气。这是最容易走的错误捷径:把“文件搬过去了”直接等同于“技能可以用了”。
导出命令成功,只能证明一次文件传输完成,不能证明目标环境具备运行条件。
如果不处理这个区别,后面会出现一种很麻烦的故障:目标机上的目录看起来完整,调用时却找不到入口、缺少运行依赖,或者引用了源电脑才存在的路径。此时再回头比较两台电脑,往往会面对大量文件,难以判断究竟是复制漏了,还是环境本来就不同。
我选择把验证放在目标机,是因为技能的可用性只在那里成立。源电脑能运行,只能说明源电脑已经满足条件;它不能替目标机回答依赖是否存在。验证也不需要一开始就跑完整业务,只要先从入口级检查开始,确认入口能被发现、依赖能被加载、配置不会指向私有位置。
用最小调用验证真实可用
入口级自检通过后,我会再做一次无副作用的最小调用。它不修改业务数据,不发送外部消息,也不触发不可逆操作,只验证技能能否沿着最短路径完成一次基本动作。
这样做有一个重要好处:失败时,问题范围比较小。如果入口级检查就失败,通常应先看文件分类、路径和基础依赖;如果入口检查通过、最小调用失败,再去看运行时依赖、配置参数或目标机权限。每一层都对应一类缺口,不需要继续盲目复制整棵目录。
边界情况也要记录。比如,目标机缺少某个可安装依赖,不能马上得出“技能不可迁移”的结论;更准确的表述是“当前导出包缺少这项依赖”或“目标环境尚未满足运行条件”。这种记录方式很重要,因为它把失败转化成可处理的依赖问题,而不是让人继续扩大复制范围。
我更愿意把这看成一次小型的环境契约检查:导出包承诺提供哪些东西,目标机已经具备哪些东西,剩下的缺口是什么。只有这 3 件事都说得清楚,后续维护才不会靠猜。
下次我会怎么做
下次遇到技能迁移,我会先写 4 类清单,再写过滤规则。必需项进入导出范围,可选项单独保存,生成物默认排除,私有状态明确禁止带走。规则写完后先试算体积和文件数,检查结果是否符合预期,再开始实际拷贝。
拷贝完成后,我不会只看返回成功,而会在目标机做 2 层验证:先检查入口和基础依赖,再执行一次无副作用的最小调用。每层都留下明确结果,失败时记录缺少什么、在哪一步缺少,以及这项缺口应该补文件、补依赖,还是调整目标机配置。
这件事让我重新意识到,自动化能力真正需要迁移的不是一堆文件,而是一条别人能够重新走通的运行路径。一个流程成熟的标志,不是它在当前电脑上跑通过,而是换到新的环境后,仍然知道从哪里开始、需要什么,以及失败以后该往哪里查。
真正可靠的同步,不以文件存在为终点,而以目标机完成最小可用验证为终点。