重读自己五年前写的代码
整理旧服务器的时候翻到一个五年前的项目,还在跑。打开代码看了一遍,感觉挺复杂。
先说尴尬的部分
一、命名完全没有一致性。
同一个东西,在不同文件里叫 user_id、userId、uid、user。变量名有 $data、$data2、$tmp、$result、$res。
有个函数叫 handle(),我盯着看了五分钟才明白它具体处理什么。
二、函数长得离谱。
最长的那个 380 行,做了:接收请求、校验参数、查数据库、算价格、写订单、发邮件、写日志。中间没有一个空行把它分段。
现在的我肯定会拆成七八个函数。但当时的想法大概是"逻辑是连续的,拆开了还要来回跳"。
三、注释和代码不一致。
// 检查用户是否已激活
if ($user['status'] == 2) {
// ...
}
而 status 为 2 在这套系统里表示"已删除"。注释是半年前写的,后来逻辑改了但注释没改。
四、大量的复制粘贴。
有六处几乎一样的代码,只有表名和几个字段不同。当时肯定是"先复制一份改改,以后再重构"。然后就没有以后了。
五、"临时"的痕迹。
test.php:还在被一个定时任务调用fix_20210315.php:一次性修复脚本,但里面有个 cron 在跑// TODO: 后面优化:五年前的 TODO
再说让我意外的部分
一、它真的还在正常工作。
五年了,这个系统处理了大概几百万订单,没出过大事故。虽然代码丑,但业务逻辑是对的。
这让我重新想了一件事:代码整洁度和软件正确性,相关性没有想象中那么强。
二、有些地方比我现在的写法还谨慎。
比如金额计算,那个项目里全部用整数分,还在关键地方加了断言:
if (!is_int($totalCents)) {
throw new \RuntimeException('金额必须是整数分: ' . var_export($totalCents, true));
}
现在的我反而经常直接用 float,因为"方便"。
三、错误处理比我现在写的还全。
每个数据库操作后面都有错误检查,每个外部调用都有超时和重试,每个 catch 里都有日志。虽然啰嗦,但出问题的时候真的能查到。
我现在写代码有时会觉得"这个不会出错",然后就不写了。
一些理解
关于"当时为什么那样写"
现在我能回忆起一些具体的处境:
- 那个 380 行的函数,是因为需求一天改三次,每次拆完函数就又要改结构,索性不拆了
- 那些复制粘贴,是因为当时有个上线时间点,重构要两小时,复制只要十分钟
fix_20210315.php里的 cron,是因为修完之后发现还有历史数据要处理,临时加了个
大部分烂代码不是能力问题,是处境问题。
这不是给烂代码找借口,而是说:如果现在的我处在那样的压力下,可能也会做同样的选择。
关于"我现在是不是也在写烂代码"
大概率是的。只是我现在看不出来——就像五年前的我也不觉得自己写得烂。
有些东西要隔几年回头看才看得出来。可能是命名,可能是抽象层级,可能是某个当时觉得聪明的设计。
关于"该不该重写"
我看了半天,最后没重写。理由:
- 它在正常工作,重写的风险大于收益
- 业务本身可能一两年内会下线
- 重写之后,两年后的我大概也会觉得新代码烂
但我做了几件小事:
- 给那个定时任务加了日志和告警(之前失败是静默的)
- 把
fix_20210315.php的逻辑合并到正常代码里,删掉了那个文件 - 金额相关的代码加了几处断言和单元测试
- 写了一份 README,说明各个"奇怪"的地方为什么是这样
四个小时的工作,风险很小,收益是后来接手的人(可能是未来的我)少踩几个坑。
一个具体的安慰
五年前那个项目里,有一段我自己写的日期处理,用了 strtotime 加各种边界判断,二十多行。
现在我会用 DateTimeImmutable 三行写完。当时的我看现在的写法大概也觉得"这什么鬼,看不懂"。
进步是看得见的,只是需要时间间隔才显现。
最后
写代码这件事,很难在当下判断自己写得好不好。
能做的大概是:
- 尽量留下"为什么"而不只是"是什么"(注释、commit message、决策记录)
- 在压力过去之后,回来收拾一下
- 接受自己现在写的代码,两年后看起来也会很蠢
我现在的习惯是,每个功能做完之后过一周,回来读一遍自己的代码。隔一周还能发现问题,隔一年就是另一种体验了。