程序员寓言集
借条
村口有一座桥,是老王在赶工期时搭的。他没时间打地基,就找了几根圆木架上去,桥面铺得平整,踩上去甚至有点弹性,像是特意设计的缓冲。验收那天,甲方走过桥,很满意,说这桥比隔壁村的还稳。
老王在心里给自己记了一笔账:地基,欠着。
后来老王升了职,调去了别的村子修别的桥。桥还在,车马照常经过,只是每年汛期,村里都要派两个人去桥墩边守着,盯梢似的听水声。没人知道具体在防什么,只是老守夜人说,这桥"有点意思,得看着"。
第十一年,一辆运货的马车照常经过,桥没塌在那天,塌在第二天凌晨,压根没人在场。村里翻出旧账本,想找当年是谁修的桥,翻到最后一页,只写着一行小字:赶工期,地基后补。
后补的那部分,从来没有人补过。利息滚了十一年,本金早就记不清是谁欠的了,但账,村里所有人一起还了。
大坝
工程师老周在河边造了一座水坝,坝身厚得像座山,能扛住百年一遇的洪水。设计图纸上写着最大流量的三倍冗余,钢筋用量是隔壁镇同类工程的两倍,工期因此拖了八个月。
镇长来看进度,问他为什么这么厚。老周说,万一发洪水呢。
镇长又问,历史记录里这条河发过洪水吗。老周想了想,说,没有,但万一呢。
大坝造好那年,河水一如既往地温顺,绕着坝基打了个转,从旁边的旧河道流走了——旧河道比坝址低两米,洪水从来就没打算走这条路。老周没算错任何一个数字,他只是从没问过,水会不会来,往哪儿去,什么时候。
镇上后来把这座坝当作景点,导游词是这么讲的:"这是我们镇最坚固的建筑,修给一场从未到来的洪水。"
孩子们喜欢在坝顶上跑来跑去,觉得很好玩,没人告诉他们,坝下面本来是要过河的路,现在得绕八里地。
帐篷
工地上支了顶帐篷,工头说,先凑合两天,等正式宿舍盖好就拆。帐篷是蓝色的,塑料布的那种,钉子钉得很随意,四个角高低不平,风一吹哗啦哗啦响。
两天过去,正式宿舍的图纸还没批下来。工头往帐篷里加了张桌子,说再凑合几天。
三个月后,帐篷里通了电,架了空调外机,工头往帐篷四周砌了圈砖,说好歹挡挡风。半年后,帐篷升级成了"临时办公区",钉在告示牌上,字体是正式的,配了编号。
三年后,新来的工人问,那个蓝色的帐篷是干什么用的。老工人说,那是临时的,等正式宿舍盖好就拆。
新工人又问,正式宿舍什么时候盖。老工人说,你问的这个问题,我十年前也问过。
蓝帐篷至今立在那里,四个角依然高低不平,只是砖已经砌到了齐腰高,谁也不再提"拆"这个字,它现在有自己的门牌号,物业费也是按正式建筑收的。
推倒重来的人
镇上有个木匠,叫老赵,见谁的家具都要摇头。张家的柜子,他说,这榫卯结构不对,早晚要散架,得整个拆了重做。李家的桌子,他说,这木料选得不行,将就用是能用,可惜了,要是我来做,肯定比这好。
老赵手艺是真的好,这点没人否认。但凡他真动手做的东西,确实漂亮,严丝合缝,摸上去都是舒服的。
问题是老赵从没做完过一件"重做"的家具。张家的柜子他拆到一半,说这年头的木料跟他想的不一样,得先去山里选新料,一去就是半年。李家的桌子他量了尺寸、画了图纸、订了新工具,然后镇上来了个更有意思的活,他就搁下了。
这些年,村里被老赵判了"死刑"又晾在半路的家具,堆了半间仓库:缺一条腿的柜子,只有图纸没有实物的桌子,量好尺寸从没裁过的木料。张家最后自己找了个普通木匠,把柜子榫卯重新紧了紧,用到现在,将就,但一直在用。
老赵路过时还是会摇头,说这修得不彻底,要是他来,肯定推倒重来。张家现在学乖了,笑笑,不接话。
九个妇人
工期紧,包工头急了,把原本三个月的活儿,喊来九个人,说一个月就能干完。逻辑很简单:三个人干三个月,那九个人干一个月,人多力量大。
活是砌一堵需要严丝合缝、一气呵成的墙,因为砂浆有固化时间,前一段没干透,后一段就接不上茬。九个人挤在同一段墙基前,反而互相绊脚,你等我的浆干,我等你的活干完,工头在旁边急得直转圈,又找了三个人来帮忙。
墙没有提前一个月砌完,反而比原计划的三个月还多花了两周——十二个人分派任务、对接进度、开会协调所花的时间,比三个人踏踏实实干活还长。
工头后来跟人念叨这事,说人多了怎么反而慢了。旁边一个老瓦匠抽着烟说:一个女人怀胎十月生一个孩子,你找九个女人来,一个月能生出来吗?
工头没说话,墙修好了,只是没人再提当初那句"人多力量大"。
打地鼠
系统上线那晚,出了个 bug,用户下单后收不到确认短信。值班的小林两点被叫醒,查了半小时,发现是短信队列的一个字段被写错了,改了一行代码,发布,短信恢复正常,小林松了口气,回去睡觉。
四点,报警又响了。这次是订单状态没同步,一查,是小林改的那行代码影响了另一个消费者,那个消费者原来依赖那个字段的旧值做判断。小林改回来,同时加了个特判,绕开短信那边的问题,再发布。
六点,第三个报警:库存扣减重复了两次。追根溯源,是那个"特判"里少考虑了一种并发场景,恰好被凌晨的补偿任务触发。
小林已经不困了,处于一种诡异的清醒状态。他盯着监控大盘,忽然觉得自己不是在修 bug,是在一个游乐场里打地鼠——锤子落下去,这只鼠进洞了,另一只从旁边的洞口冒了出来,甚至怀疑是同一只,绕到了背后。
八点,天亮,第四个 bug 冒头的时候,产品经理发来消息:"问一下,加个新功能大概什么时候能排上?"
小林看着这条消息,第一次认真地思考了退出这个行业的可能性,然后关掉聊天窗口,继续打地鼠。
打磨匠
雕匠陈师傅接了个活,给庙里刻一尊佛像,工期两个月。他每天雕一点,反复端详,觉得眉心的弧度不够慈悲,重刻;衣纹的褶皱不够自然,重刻;佛像的手指比例,他觉得差了那么一丝,又重刻。
两个月到了,佛像雕了三分之二,陈师傅说,快了,再给我半个月,一定精益求精。
半个月又半个月,庙里换了三次主持,都来看过陈师傅的活,都点头说手艺是真好,然后婉转地问一句,什么时候能完工。陈师傅说,快了,这个耳垂的弧度还差一点。
庙会那天要开光,佛像还差最后一道打磨没做完,主持没办法,只好先把这尊未完工的佛像请上台座,用红布把有瑕疵的那个角落遮住。开光仪式照常举行,香客络绎不绝,没有一个人发现那处被遮住的瑕疵,也没有一个人问起。
陈师傅站在人群后面,看着自己耗时四个月、仍未完工的作品被无数人跪拜、上香、许愿,忽然明白了一件事:他一直以为自己在追求完美,其实他只是害怕那个作品被交出去以后,就再也不属于他了。
红布后面的那道瑕疵,后来再没人去补。
图纸上的墙
一家建筑公司要盖一栋写字楼,负责设计的建筑师从没来过工地,图纸都是通过邮件传给三个不同的施工队:地基队、结构队、水电队。三个队伍分处三个不同的分包公司,平时只在每周一次的视频会议上打个照面。
楼盖起来之后,大家发现一件怪事:整栋楼里,凡是需要地基队和结构队配合的地方,衔接处都特别粗糙,缝隙用水泥草草糊住;凡是结构队和水电队交界的地方,管线绕了很大的圈子,明明直线三米能到的地方,绕了十几米。
后来有个从外地来考察的工程师看了图纸,又看了实物,说了句大实话:这栋楼的结构,长得跟你们三个施工队之间的关系一模一样——凡是两拨人碰头开会才能解决的地方,就是整栋楼最丑、最绕、最经不起推敲的地方。
公司高层开会讨论要不要调整施工队的分工,讨论了三周,因为负责讨论"要不要调整分工"的,又是三个不同部门的人,谁也说服不了谁,最后不了了之。
那栋楼现在还在,粗糙的接缝处后来贴了瓷砖,看不出来了,但电梯还是要绕那十几米的弯,每天准时把所有人绕进去,再绕出来。
起名字
新来的实习生要给一个变量起名字,那个变量存的是"用户上次登录之后、到现在为止、排除掉被删除的、且已经通过审核的、非草稿状态的消息数量"。
他盯着这个变量看了十分钟,敲下的第一版名字是 msgCount。改完之后,他自己都不信,第二天团队里另一个人问这个变量是干嘛的,他解释了一大段,对方说,那这名字取得也太不讲究了。
他改成了 unreadValidMsgCountSinceLastLogin,写完自己念了一遍,感觉像在念一份判决书。有人路过看了一眼,说这也太长了吧,看着都喘不过气。
他又改成 pendingMsgNum,觉得简洁多了,直到三周后,另一个同事在这个变量旁边写了一个新函数,管它叫 pendingMsg,两个名字看起来像双胞胎,实际上一个指的是"待审核",一个指的是"未读",谁都不知道谁是谁。
实习期结束那天,他在交接文档里认真写下一句话:"以后起名字,宁可长一点、丑一点,也别让人猜。"
他自己也知道,这句话是留给下一个要起名字的人的,而下一个人,大概率也不会看这份文档。
在我机器上是好的
测试环境挂了,开发老孙的电脑却运行得好好的。他把自己的屏幕分享出来,功能演示得行云流水,登录、下单、支付,一气呵成,全程零报错。
"你看,"他说,"在我机器上是好的。"
运维小声问了一句,你的机器上,数据库版本是多少。老孙说,不知道,反正一直没换过,从我入职那天到现在。
测试环境的数据库,三个月前升级过一个小版本。老孙的本地环境,因为他嫌升级麻烦,一直用着两年前装的那个。这两年里,操作系统打了十七个补丁,Node 版本停在他觉得"用着顺手"的那个小版本,还有一个环境变量,是他三年前为了debug另一个问题临时加的,加完就忘了,一直留在他的 .bashrc 里,从没告诉过任何人。
后来这事上了升级会的复盘,老孙认真地说,以后我一定注意环境一致性。会开完,他打开自己的电脑,看着那个跑了两年、如今已成为团队里唯一一台"活化石"机器,认真思考了一下要不要重装,最后决定,先不动,等这个迭代忙完再说。
他心里知道,"等这个迭代忙完",是一句从来不会兑现的承诺,就像他机器上那个已经没人记得起因的环境变量一样,会一直留在那里,直到下一次没人能解释的报错发生。