2.8 Fetchmail 长大了
那时我手头有一套精巧新颖的设计,一套我每天使用、运行稳定的代码,Beta 测试用户群体还在持续扩张。我慢慢意识到,我做的早已不再是那种碰巧能给极少数人派上用场、无足轻重的个人项目。我手里的程序是每个有 Unix 机器和 SLIP/PPP 邮件链路的黑客都真正需要的。
有了 SMTP 转发功能,它就大幅领先一众竞品,有望成为一款“品类杀手”。这种经典的程序能完美契合自身的细分定位,好用到同类替代方案不仅会被弃用,甚至几乎被人遗忘。
我认为你没法单纯以这样的结果作为目标、提前规划出来。你只会被强有力的设计理念牵引,事后回望,这份成果看起来理所应当、浑然天成,甚至像是早已注定。想要追求这类设计思路只有两种方式:积累丰富的创意;具备工程判断力,能把别人的优秀构想拓展到原创者预想之外的高度。
安德鲁・塔能鲍姆(Andy Tanenbaum)最初的想法是为 IBM PC 构建一套简易原生 Unix,用作教学工具(他把它命名为 Minix)。林纳斯・托瓦兹把 Minix 的理念推进到安德鲁或许不曾预想的地步,最终发展成了不起的成果。同理(尽管规模更小),我吸收了卡尔・哈里斯、哈里・霍赫海瑟的部分思路并大力拓展。我们二人都算不上大众认知里那种理想化的原创性天才形象,但说到底,大部分科学研究、工程实践与软件开发都并非依靠原创性天才完成,广为流传的黑客迷思恰恰与此相反。
尽管如此,结果依然令人兴奋不已——实际上,这正是每一位黑客毕生追求的成功!想要让 fetchmail 达到如今我所能预见的完善程度,那我就不能只为自己的需求写代码,还要兼容、支持圈子外其他用户所需的各类功能,同时维持程序简洁、稳定。
意识到这一点后,我新增的第一个、也是最关键的功能,就是多投递支持(multidrop support):这个功能可以从存放多名用户全部邮件的公共邮箱抓取邮件,再将每一封邮件分发到对应的收件人。
我决定新增多投递支持,一部分原因是不少用户反复提出需求,但更主要的是,我认为这套功能会倒逼我完整处理各类地址场景,从而暴露单投递代码里潜藏的缺陷。事实也的确如此。把 RFC 822(http://info.internet.isi.edu:80/in-notes/rfc/files/rfc822.txt)a地址解析逻辑做完善耗费了我大量时间,并非其中某一块逻辑难以实现,而是整套规则牵扯大量相互关联、繁琐细碎的细节。
但多投递地址处理事后证明也是一项绝佳的设计选择。理由如下:
14.任何工具都能完成预设场景下的工作,而一款真正出色的工具,还能适配各类你从未预想过的使用场景。
开启多投递模式的 fetchmail 有一个意料之外的用法:可以在网络连接的客户端侧管理邮件列表,列表管理与别名解析全都在本地客户端完成。也就是说,使用者通过 ISP 账号运营个人主机时,不必持续调取服务商的别名文件,就能自主管理邮件列表。
测试用户提出的另一项重要的改动需求,是增加 8 位 MIME(多用途互联网邮件扩展)兼容支持。实现这项功能相当简单,因为我一直有意识保证代码 8 位洁净——也就是不占用 ASCII 闲置的第 8 个比特位承载程序内部信息。我当初这么做并不是因为预料到用户会需要该功能,而是恪守另一条准则:
15.开发任何网关类软件时,务必尽量减少对原始数据流的改动——除非接收端硬性要求,否则绝不丢弃任何数据!
如果我当初没有遵循这条准则,实现 8 位 MIME 兼容将会十分棘手,还很容易产生漏洞。实际情况却是,我只需研读 MIME 规范文档 RFC 1652(http://info.internet.isi.edu:80/in-notes/rfc/files/rfc1652.txt)b,再新增一小段生成头部的逻辑即可。
不少欧洲用户反复要求我增加参数选项,限制单次会话拉取的邮件总量,以此控制拨号网络产生的高昂通信费用。这件事我一直都有些抵触,到现在我也不完全认同这种设计。可是如果你的软件面向全球的用户,就必须倾听使用者的需求——哪怕他们没有付费,这条准则也不会失效。