打开网易新闻 查看精彩图片

去年我统计过自己处理cron故障的时间:平均每次"小问题"消耗2.3小时,全年47小时。这个数字让我愣了一下——相当于整整6个工作日,全花在给过去的自己擦屁股上。

程序员圈子里有个默契:临时文件没删、日志爆掉、脚本换个目录就罢工——这些都不算真bug。就像家里抽屉里缠成一团的充电线,你明知道它在那,但总有更急的事。

直到我开始记录时间成本,才发现这个账算反了。

两个实验组:拖延派 vs 立即修

两个实验组:拖延派 vs 立即修

我把自己过去两年的cron维护拆成两类场景。第一类是"先放着":发现脚本有瑕疵,但功能正常,标记为"下次重构时一起处理"。第二类是"当场修":哪怕只改三行,也立即动手。

拖延派的前三个月很舒服。我节省了17次"打断当前工作"的上下文切换,每次平均省8分钟。但第4个月开始,账单来了。

一个备份脚本因为没加目录检查,在cron环境下跑了半年,输出文件全写进了根目录。我发现时,/var/log已经膨胀到需要专门清理。更麻烦的是,部分备份文件被后续脚本覆盖,数据完整性成了谜。

另一个监控任务失败时不会报错,只是静默跳过。我直到查看季度报表时才发现,过去11周里有3周的数据是空的。补数据花了6小时,解释为什么"系统没报警"又花了2小时。

这些都不是设计缺陷。全是"下次再修"清单上的条目。

立即修的那组数据完全不同。前三个月看起来"效率更低"——我花了23次碎片时间处理小问题,累计4.7小时。但后续维护时间趋近于零。

最直观的对比:两组脚本在一年后,平均故障恢复时间相差11倍。

为什么"小毛病"会复利增长

为什么"小毛病"会复利增长

cron作业有个特点:它不说话。你手动跑脚本时,当前目录、环境变量、输出重定向都是透明的。但cron是个黑盒,它用最小环境执行命令,然后把结果塞进邮件或日志。

打开网易新闻 查看精彩图片

这个特性放大了所有"小瑕疵"。

相对路径是最典型的陷阱。我在本地测试时站在脚本所在目录,./config.ini能正常读取。cron从/etc/cron.d出发,同样的路径指向的是另一个文件——或者空。

更隐蔽的是错误处理。shell脚本默认"失败继续",除非显式声明。一个管道命令中间出错,最终状态码可能是0,因为最后一个命令成功了。你的备份脚本"完成"了,但中间某一步已经丢了数据。

日志问题则是慢性毒药。我有过一个任务每天追加写入同一个文件,没做轮转。一年后这个文件达到14GB,grep需要90秒才能返回结果。排查问题时,我在等grep的过程里刷了27分钟手机。

这些设计在初期都是"可以接受的"。它们不会立即崩溃,只是偶尔行为异常、偶尔需要手动干预、偶尔让你怀疑自己的记忆。

但cron不会给你反馈。它不会说"这周我失败了两次",也不会说"你的日志让我很难堪"。它只是执行,然后沉默。

沉默让问题隐形,隐形让问题堆积。

我现在的"无聊清单"

我现在的"无聊清单"

停止等待大规模重构后,我建立了一套最小干预规则。不追求优雅,只追求诚实。

第一条是shell脚本的头部三件套:

set -euo pipefail

e表示命令失败立即退出,u表示未定义变量报错,pipefail让管道中任一命令失败都能被捕获。这三行不会让你的脚本更聪明,但会让它停止撒谎。

第二条是清理的绑定机制。不再相信"脚本最后会删临时文件",而是用trap把清理挂到退出信号:

打开网易新闻 查看精彩图片

TMP_DIR="$(mktemp -d)" cleanup() { rm -rf "$TMP_DIR"; } trap cleanup EXIT

即使脚本中途崩溃,EXIT信号仍会触发清理。这个模式我复制了23次,零额外故障。

第三条是路径的绝对化。所有脚本开头定义BASE_DIR,所有文件操作基于它展开。相对路径只在交互式场景使用,自动化任务里禁绝。

第四条是日志的克制。每条记录包含时间戳、任务名、状态、关键指标,仅此而已。拒绝把日志当调试器用,拒绝printf式开发。

这些改动没有任何一项值得发技术博客。它们太基础、太乏味、太像"正确的废话"。

但它们的共同点是:一次性修复,永久性收益。不需要记忆,不需要上下文,不需要"等我有空"。

时间账本的真相

时间账本的真相

我重新核算了那47小时的去向。真正用于"修复"的时间只占31%,剩下69%花在:定位问题(哪个脚本?哪次运行?)、恢复数据(从哪备份?覆盖范围?)、解释现状(为什么监控没报警?)。

换句话说,拖延节省的是"写代码的时间",透支的是"当侦探的时间"。前者单价低,后者单价高,且不可预测。

有个细节让我印象深刻。那个14GB日志文件的排查,我花了4小时。而添加logrotate配置只需要6分钟。比例是40:1。

这不是技术债的比喻,这是 literal 的债务利息。

现在我对待cron作业的态度变了。不再评估"这个问题有多严重",而是问"修复它需要多久"。如果答案小于15分钟,立即执行。这个阈值基于一个简单的观察:任何需要"记下来以后处理"的问题,实际消耗的时间至少是预估的3倍——因为你要重新进入上下文。

同事问我为什么不把这些经验写成工具库。我想过,但拒绝了。这些不是可以抽象的模式,它们是纪律。工具可以生成set -euo pipefail,但没法生成"看到就改"的习惯。

那个全年47小时的数字,我现在更新到了8小时。降幅83%,但剩下的8小时仍然让我不舒服——它们属于遗留系统,属于我无法立即替换的第三方依赖。

上周有个新同事问我,怎么判断一个cron作业是"健康"的。我说了个粗糙的标准:如果你三个月没碰过它,还能在30秒内说出它的输入、输出、失败时的行为,那它就是健康的。否则,它只是在假装工作。