《整洁代码的艺术》译者序

这是一本讲述常识的书。

如果你已经有多年职业经验,读到书中内容,不免会觉得都是老生常谈。然而,常识之所以需要被一遍又一遍地普及,正是由于人们总以为自己已经掌握了常识。殊不知我们常常混淆“掌握”与“知道”,拿知道当掌握,不免一遍又一遍地违背常识、吃够苦头。

这是一本讲述编程领域常识的书。自从有软件以来,技术专才们对它的研究和实践产出了各种方法论和流派。最近十来年,“整洁代码”成为其中一支主要思想派别,也成了软件界的主流常识。罗伯特·C·马丁(鲍勃大叔)的“整洁”系列著作被奉为圭臬,拿知道当掌握的戏码又一次上演。我们继续违背常识,写着并不整洁的代码,吃够苦头。

这是一本讲述在所有专业工作中如何保持高产出的书。随便翻翻,你会发现,书中的代码量少到不像一本技术书。这就对了。再仔细阅读,你会发现,总共9章正文里面,只有第4章、第5章和第7章与技术百分之百有关,其他章节要么仅一部分与技术有关,要么与技术毫无关系。作者跳出技术本身,从更社会学、心理学的角度谈技术人的成功要素,反而能对埋头于代码中的程序员们有别开生面的启发。例如第2章,用多个数据证明帕累托原则的现实意义,告诉读者将有限时间和精力持续聚焦于少数要务,获得更有效的上升成果。又如第6章,介绍心理学上的“心流”概念,看似与技术风马牛不相及,却是提升技术人员产出的“大杀器”。

本书作者既是经验丰富的开发人员,又是编程技术在线教育平台的创始人与运营者,对于提升编程能力和整体产出自有一套心法,总而言之曰“专注”。好玩的是,在“专注”主旨之下,书中有些观点与鲍勃大叔意见截然相左。例如“心流”,鲍勃大叔在《匠艺整洁之道:程序员的职业修养(Clean Craftsmanship: Disciplines, Standards, and Ethics)》中就明确反对过。我相信这种事必然因人而异、各有道理,不必评判孰对孰错。

在翻译过程中,我时时忍不住想给这本书改个名,因为它真的没花很多篇幅阐述“整洁代码”,更像是一本讲成功学的书。那么,什么名字更适合呢?有本经典经营管理类著作,名为《追求卓越(In Search of Excellence)》。虽然在深度和广度上,《整洁代码的艺术》都远远比不上《追求卓越》,但我愿意借后者给前者重新命名为《追求卓越:程序员生存之道》。

希望程序员读者们能从这本书里面学会你的职业生存与发展之道。

韩磊  2022年9月19日

Clean Craftsmanship译者序


2021年2月,老朋友张春雨(侠少)在微博上给我发私信,问我有没有兴趣翻译Robert C. Martin(Uncle Bob)的新书。我和侠少平时联系不多,但常常收到他安排寄来的赠书。赠书收得多了,总觉得欠着人情,该用什么方式还一还才好。

这个“什么方式”,也许是几顿酒饭、几杯咖啡,但绝对不是翻译一本书。算起来,截至2020年,我已经有十年没做图书翻译工作。去年翻了一本小书,眼睛和腰椎颈椎都有点不舒服。一定赔本但不一定赚吆喝的事,还是不干为好。

侠少对我了解甚深,他只说了一句话,就成功说服我接下任务。他说,“这是(Bob)大叔的封山作。第一本和最后一本,有始有终,一段佳话!”一下子就勾起我翻译《代码整洁之道(Clean Code)》的回忆。当时我在北京工作,个人能力提升和职业发展都遇到瓶颈,同时还需要考虑家庭常驻地问题。《代码整洁之道》不但带给我关于整洁代码的知识,还令我悟到许多做人做事的道理。对我来说,那是一本优秀技术书,更是一本关于价值观的好书。

《代码整洁之道》中文版面世十一年以来,数次修订和重印,成为很多程序员朋友接受和推崇的读本。期间,鲍勃大叔多本其他数本著作也陆续出了中文版。这些著作从程序员素养、架构设计、敏捷方法等方面入手,全面阐述“整洁”概念在软件开发过程中的重要意义与实践手段,建立了一套相对自足的理论和方法体系,大概能算是“整洁代码派”系列武功秘籍了吧。

鲍勃大叔提出,既然现代世界运行于软件之上,软件开发者就要承担起维护世界正常运行的重大责任。这意味着软件开发者必须掌握足够多的技能,遵守足够严格的纪律,追求足够高的职业操守标准,方能达到社会对我们的期望。作者提炼了前面多本著作的精髓,加以深究、凝练和升华,推出这本集大成的Clean系列封山之作。

回顾Clean系列图书的主题,可以很清楚地看到从“关注技术”到“关注人”的发展脉络。就像是老拳师写拳谱,第一本都是招式。过了一阵子,老拳师发现徒弟们拳招挺熟,内功没跟上,练拳不练功到老一场空,赶紧写一本讲内功的。又过了一阵子,老拳师发现徒弟们一上擂台就不懂如何审时度势制定攻击方案,又赶紧写一本讲架构的。如此这般过了十年,老拳师突然发现,拳谱传来传去,很多人练得似是而非,拳打歪了,心术也不见得很正。

如果你是这位老拳师,面对如此现状,会是什么心情?我想,大概也会像鲍勃大叔一般,既悲观又不甘吧。就我这两年参与审校或审阅的基本敏捷书来看,恐怕既悲观又不甘的不只是鲍勃大叔一个人。敏捷软件开发主流之后,同时也成了借以牟利和乱来的最佳幌子。当所有人都在谈敏捷,而吹捧与批评都没谈到点子上时,正本清源就成了当务之急。所以,最近两年面世的敏捷书,不约而同集中在一个主题:正本清源。

敏捷既是手段,也是目的。正如鲍勃大叔在本书中一再强调的,软件最根本的特点就是“柔软”。好软件不但具备能够与时俱进修改和扩展的灵活性,更加具备以较低成本修改和扩展的可能。软件本身如果敏捷,那么实现和修改软件的方式必须也必然够敏捷。

本书第一、第二部分,结合多个代码示例,展示了如何利用TDD敏捷地写出敏捷的软件,同时阐述了验收测试、协同编程等等其他敏捷手段的重要性与一般实施手段。不可避免地,作者还花费相当篇幅讨论软件设计方案问题。我很愿意重点阅读这部分。此外,一些具体的测试策略也颇具可读性。

第三部分看起来对程序员的日常工作没什么帮助,但这部分值得好好阅读和思考。作者提出的程序员十条承诺(或谓“誓言”)浅白易懂,却不易遵守。能谨守这些承诺的程序员,一定是我特别愿意共事的好伙伴。

中文版初稿翻译工作结束之后,我以为终于可以放松下颈椎和腰椎了。没想到,过了一段时间,侠少又发来一份英文修改稿,对初稿改动之处不在少数。还好有电子社的编辑帮我做了对照工作,将差异处一一列出。看着屏幕上的英文初稿、修改稿和修改了几遍的中文稿,我突然体会到鲍勃大叔讲解“质因数”示例时谈到的心情:对一桩事物的改进过程,就那么活生生地呈现在眼前。这个过程既痛苦又快乐。而且,如果没有其他限制,可以一遍又一遍继续做下去,永无止境。

可惜,就像软件有交付截止日,译稿也不能一直拖下去。我清楚地知道,译稿还有很多问题。稿子交出去了,这些问题留待读者们发现和批评。如果有机会出修订版,你们的批评和建议必会纳入,这也算是一种协同写作了吧。

韩磊 2021年10月28日

《Unix传奇》中“消失”的链接

我2020年翻译了Brian W. Kernighan的Unix: A History and a Memoir一书,中文版书名是《Unix传奇:历史与回忆》。原版中有一些网页链接,根据我国公开出版物相关规范,中文版删掉了这些链接。

我把这些“消失”的链接整理如下,供读者参考:

P46 丹尼斯·里奇介绍肯·汤普森对电脑游戏的贡献的短文:www.bell-labs.com/usr/dmr/www/ken-games.html

P61 杰拉德维护的网站 spinroot.com/pjw

P70 贝尔实验室网站丹尼斯主页上他兄弟姐妹的谢词:www.bell-labs.com/usr/dmr/www


P131 2013年公布的摩根泰勒备忘录:www.cs.princeton.edu/~bwk/202


P193 杰拉德·霍兹曼维护的1127中心老同事在线名单:www.spinroot.com/gerard/1127_alumni.html

P207 “实验室欺诈”视频:www.youtube.com/watch?v=if9YpJZacGI

“资料”章节中的链接

贝尔实验室Unix历史短页:s3-us-west-2.amazonaws.com/belllabs-microsite-unixhistory


汤姆·范·弗莱克(Tom Van Vleck)的Multics历史信息库网站:multicians.org


道格·迈克罗伊的《科研版Unix读本》:genius.cat-v.org/doug-mcilroy

The Unix Heritage Society:www.tuhs.org

迈克尔·马霍尼的Unix口述历史:www.princeton.edu/ ̃hos/Mahoney/unixhistory


菲利斯·福克斯的口述史:history.siam.org/oralhisto- ries/fox.htm


布莱恩与丹尼斯的炉边谈话:www.youtube.com/watch?v=EY6q5dv_B-o

丹尼斯·里奇在(诺基亚)贝尔实验室的主页:www.bell-labs.com/usr/dmr/www


柯克·迈克库西克关于BSD的著述: www.oreilly.com/openbook/opensources/book/ kirkmck.html


伊安·达尔文和杰夫·柯里尔的文章:doc.cat-v.org/unix/unix-before-berkeley

桐先生午夜故事之报应

本市头号报纸的总编辑莫喻快要急疯了。

事情要从昨天说起。

昨天早上,我出了地铁口,像往常一样,在写字楼旁的咖啡档要一杯拿铁外带,顺便买一份日报。现今手机上什么消息都有,没几个人看报,除了摊派订阅那部分,卖不了多少。好在喉舌的作用还未被否定,每年有固定拨款下来,局面也算维持得下去。

到办公室坐下,打开纸杯盖,摊开报纸。头版显著位置,报道一位领导昨天参加的活动。

大概是没看清秘书写的讲稿,领导将“滇越铁路”误读成“镇越”铁路,闹了个笑话。其实这不算大事,但现场有人用手机录了像,又传到网上。很短时间就转发百万,搞得相关部门很尴尬。

报纸应该会装作不知道这些小插曲吧。我想着,一边看报道。

在文章的中前部位置,引用领导发言的部分,“镇越铁路”四个字赫然出现。

我一时惊讶到让咖啡呛进气管,激烈咳嗽之余,又哈哈大笑起来。莫喻这小子真有种。

莫喻是我大学同学,“睡在我上铺的兄弟”。毕业后,一起进报社,一起跑线。后来我辞职下海,莫喻留下,吭哧吭哧苦干多年,从记者到主任,等登上总编辑宝座,却遇上了传统媒体没落的时代。

莫喻谨小慎微,否则也做不了总编。据我所知,每天轮值编委签版之后,还要经他过目,才能下厂印刷。自从他当上总编以来,就再也没有参加过晚上的同学聚餐。连一般文字错误都逃不过莫喻法眼,领导发言中的口误原文见报这种严重错误,又怎么会发生?

除非他是故意的。

我用手机拍下文章,发给莫喻,附上一句话:“你丫真有种。”

回了一个问号。过两分钟,又回了一个感叹号。

十五分钟之后,莫喻的电话打过来了。

“大早上开这种玩笑,无聊不无聊啊?”莫喻说。

“敢这么干,你才是在跟上头开玩笑吧。”我说,“你丫终于想通,打算离开体制了吗?”

“扯犊子。开着选题会呢,吓得我赶紧找报纸来检查,结果根本就没印错。话说你小子PS技术不赖啊,字体字号中规中矩,像真的似的。”

是我眼花了吗?我没有反驳,挂了电话,拿起报纸来仔细看,还是“镇越铁路”。

我手上这份报纸,和莫喻拿来检查那份,同一版、同一篇文章,竟然印了不一致的文字。

如果说有人为了跟莫喻开个玩笑,大费周章自己印一份有错误的报纸,放到咖啡馆,等我拿到,再通过我吓唬莫喻,这未免也太处心积虑了一些。而且当中太多变数:我拿了可能不看;可能被别人拿走……

慢!为了保证印有错字的报纸到我手,必须得印好些份,一起叠放到咖啡档报架上。

想到这一点,我立即起身下楼,径直来到咖啡档。

咖啡档用以前的书报亭改成,并没有店堂,顾客即买即走。那报架置放于档口右侧,除了日报,还有一些其他出版物。报架在店员视线之外,也就是说,如果愿意,任何人都可以往上面塞几份报纸。

我掏钱买下剩余的十几份日报,夹在腋下,带回办公室。一份份摊开。

“滇越。滇越。滇越。……”看来看去,只有我之前买的那份印错。看来,我拿到那份出错的报纸,纯属偶然,显然也不是谁谁有意为之。这样一来,就剩下一个不可能的可能:在当天凌晨印刷的几十万份报纸里,有一份或多份印错了字。

在制版照排印刷全程计算机化的时代,CTP版下到印刷机,一百份也好,十万份也好,都是一模一样,绝不可能出现这份报印“镇越”,那份报印“滇越”的情况。

这份“错版”报纸,完全没有挖补痕迹,显然是从印刷机里出来,和其他报纸一起捆扎,运送到报摊,偶然被我买到。要让那上面出现与其他报纸不一致的错字,只能是在印刷过程中停机,装入有错字的印版,开机印出来,再停机,重新装入正确印版,开机继续印。这样就能做到一些报纸印刷正确,一些报纸印刷错误。这样的操作,繁琐、高成本,又要冒极大的风险,但在理论上的确可以做到。

动机呢?如果每份报纸都印错,还可以理解为有人想构陷莫喻,可眼下只有一份出错,就无法解释了。

事情已发展到我没有办法自圆其说的地步,只好与莫喻约了午饭,见面详谈。

在报社附近的爱尔兰餐吧,我们一人点一客中午套餐。忘记上的是什么,但莫喻看到我带去报纸上的错字时见鬼一样的表情,恐怕再过三十年我也还会记得。

“这不可能!”莫喻大叫。

我向周围被惊扰的食客抬手致歉,转头看向莫喻,说:“证据已经放在你面前,现在相信我没有开玩笑了吧?”

莫喻回回神,再仔细看了报纸,低声说:“从纸张、油墨、印刷品质看,这绝对是我们印厂出来的。”

“有人捣鬼?”我问道。

“起码不会是开个玩笑那么简单。吃完饭,我回报社,按着流程梳理梳理,看到底是哪里出的问题。”

那顿饭吃得匆匆。饭后,目送莫喻快步离开,我也招车回了办公室。

当晚,莫喻电告,没有找出问题所在。从签版到下厂开印,印版保持一致,没有发现被篡改或替换的迹象。

“姑且算它是一种暂时解释不了的灵异事件吧。”我安慰莫喻,“明天太阳依旧升起。”

“明天太阳依旧升起,”莫喻无奈地回应,“是谁说太阳底下无新事啊?!”

这世界不可解释的事太多了,光我经历的,就足可写几本书,不差这一桩两桩的。对我来说,错版事件不过是为当晚的酒局应酬增添了一点谈资。

谈资不少,酒喝得更多。还好第二天是周六,不必早起。梦中,一只巨大的蚂蚁从海里爬出来攻击我。左支右绌之际,手机铃声将我惊醒。

努力睁开眼,窗外还是一片昏暗,看来也就是三、四点光景。我手机开了勿扰模式,只有少数极熟的亲友,才够资格半夜还打得进来。而如果他们这个时间打过来,一定是出了大事。

摸过手机,眯眼看屏幕,是莫喻打来的。

只说了四个字,“快来印厂!”

听他语气,绝非是在开玩笑。我赶紧起床穿衣,用最快速度草草洗漱,打车前往。凌晨时分,街道空寂,出租车开得特别快,不到半个小时,已经到达。

远远看见莫喻微胖的身影矗在大门口,烦躁地转着圈。我也着急起来,下车紧步走过去。

“出事了,”莫喻说,“大事。”

接过莫喻手中的报纸,借着不算明亮的路灯扫了一眼,我抱怨说:“字这么小,哪看得清啊?”

“不用看文章,”莫喻说,“看标题。”

我跑过几年民生线,对社会版的内容和体裁熟悉得很。粗看上去,这期报纸的社会版,无非就是城中昨日发生的各种大小坏事……

我立即明白了莫喻的意思。

“你是说……”

莫喻重重地点了点头,“超标了。而且是严重超标。”

负面新闻不可超过总量的几分之一。这条规则,没有写在任何文件上,却是必须遵守的铁律。我手头这期社会版,看标题,不是杀人越货,就是弃老欺弱,甚至还有几篇批评某部门不作为的评论。岂止超标,简直够得上反动的标准。

“进去说,”莫喻拉我走进厂里,站到一台印刷机旁。出纸口的滚轴传送带,仿佛巨兽大口中伸出的长舌……

莫喻苦笑道:“昨晚我不太放心,一直亲自跟流程,确保每个环节都不出错。结果还是出事了。这这台印刷机出的社会版,与输入的印版完全不一致。”

总编辑御驾亲征盯着看,谁还要敢现场鼓捣小动作,真是不知死。所以我根本不问他发现了什么,就说出我的推论:“有人在印刷机里做了手脚。”

“厂商技术支持人员恰好这一阵在帮我们建新印厂。我也叫了他,差不多该到了。”

不多时,厂商的技术人员王工就到了。王工瘦高个头,戴着副眼镜。也许是常年跟机器打交道的缘故,看上去有些木讷。报社是大客户,半夜把他叫过来,他也没表现出什么不满。

“王工,有没有这种可能——黑进印刷机控制系统,印出与印版不一致的报纸?”顾不上寒暄,我直接问。

王工愣了一下,说:“印刷机的原理是纯机械的,就算黑进系统,无非也就是停印、空印或印刷质量差,没可能印得出与印版内容不一致的报纸。”

我茫然看了看四周,视线落在印刷机出纸口上。如果说印刷机像一个动物,出纸口就像它的嘴。如果这动物会表达,印刷物就是……

“我想做一个试验。这个试验看起来会很古怪,”我对莫喻说,“但请你暂时放下疑惑,不要提问,照我说的办。”

不等莫喻回应,我接着说,“请你让制版人员做一副印版,上面只写一个字,'在',以及一个问号。”

莫喻回答道:“好。起码验证一下印刷机没出故障。”

不多时,按我要求制作的印版就做好了。我请王工帮忙将印版装入机器,然后静待结果。

一阵噪音过后,一张新闻纸从出纸口弹了出来。

纸上只有一个字,“在”。

然而,“在”字后面的问号不见了。

莫喻与王工低声惊呼。我虽有心理准备,但也被符合预想的现实吓了一跳。

“你们看,这像不像两个人使用即时聊天工具对话?”我说。

“你的意思是……”莫喻脸上呈现出不敢置信的神色,而王工则若有所思。

我没有解释,先请莫喻去做一副印版,内容是“为什么”,当然句尾也带有问号,否则莫喻的校对职业病会发作的。

莫喻向制版部门走去,王工扶了一下眼镜,说:“莫非这台印刷机有智能?”

我不太坚决地回答他:“不然无法解释我们看到的现象。等第二副印版印出报纸,应该就可以定论了。”

很快我们就知道了结果。

这次印出来的字是“停止说谎”,宽度铺满八开幅面,没有标点符号,就像是一张大字报,又像一句口号。

印刷机读懂印版上的询问,又印出新闻纸,明确表达了意见与立场。

现场变得有些尴尬和不知所措。大家都为一台印刷机居然拥有思想,而且懂得表达出来而震惊。可怜的莫喻,更是瞬间涨红了脸。

“关机!关机!关机!”短暂沉默之后,仿佛受到极大的侮辱一般,莫喻喊出声来。他抓起新闻纸,用力挥动着手臂。新闻纸卷折起来,只看到两个大字的残影不断在空中叠加。

说谎。

说谎。

说谎。

……

这大概是对一位新闻工作者最要命的道德攻击了。莫喻当然没有羞愤自尽,也没有离开报社,还是安稳地做着他的总编辑。其他印刷机照常印出当天报纸和以后许多天的报纸。出问题的印刷机下线报废,连进一步的检查也没有做,就被运走拆解了。它的意见,不会再有人了解。而当天凌晨发生了什么,只有莫喻、王工和我三个人知道。

就算说出来,也不会有人信吧。

可如果有一天,所有的印刷机、电视机、收音机、计算机,都不肯再说谎了呢?

桐先生午夜故事之对赌

我将瓶中深褐色的烈性啤酒倒进杯子,一厘米厚的泡沫刚好在杯口露头。雷琛跟我碰了碰杯,一饮而尽,对我说:“那天喝断片儿了,就记得你送我去酒店,谢谢啊。”

“举手之劳,”我也干杯,“不过你这身膘是该减减了,司机、俩保安加上我,生拉硬拽才把你送进房间。”

那是两天前的事。

在一个商务应酬酒会上,我与坐在身边的雷琛攀谈起来。几杯威士忌喝下去,寒暄迅速演变为称兄道弟。忘了为什么话题抬杠,以酒量定胜负,结果把雷琛给喝倒了,满口胡话,问不出家里人电话号码。主人家还要招待其他客人,腾不开身。我叫了个车,送他去酒店开房休息。次日,雷琛起来后,打电话给我,说是要还房钱。几百块钱倒不要紧,这哥们还算有趣,值得交往,就约了个时间, 在这个门口有一棵大榕树的酒吧见面。

“一夜没回家,嫂夫人没让你跪搓衣板吧?”我调侃他。

“她敢!”雷琛装出一副唯我独尊的样子,立即又苦笑,“罚我给她买个名牌包包,再加上睡三天沙发。”

我安慰他:“不冤枉。跟谁较劲都行,别跟老婆较劲。”

我们又叫了一轮酒。红晕渐渐从雷琛的脖子涨上来,直漫到额头以上。

“跟谁也不能较劲。”雷琛低头看看桌上的酒瓶,说:“那晚在酒店,做了个怪梦,梦里跟自己较劲,划了一晚上拳。”

“自己跟自己怎么玩啊,学周伯通左右互搏?”我笑道。

“可不嘛,”雷琛也笑起来,“累得我啊。”

我大笑:“你那是当老板跟人较劲惯了,日有所思夜有所梦。放松点,再这样早晚得精神病。”

“哈哈哈,你有药吗?”雷琛也大笑。

“这种烂梗你自己留着吧,”我说,“喝完这支,散了。”

没想到,散了之后,再没机会见到活着的雷琛。

几天之后,一位陈姓警官找到我办公室,告知雷琛死亡的消息。前两天半夜在自家卫生间倒下,没有呼救。直至早晨,家属才发现雷琛躺在地上,身体冰冷,已经失去呼吸。死亡原因是:精神高度兴奋诱发脑溢血。

猝死这种事屡见不鲜,虽然死得蹊跷,但既未发现受害迹象,警局当然不立刑事案。雷太太对丈夫的死无法释怀,就拜托雷琛生前好友陈警官帮忙做调查。

雷琛是个规矩的生意人,行踪并不隐蔽。陈警官很容易就查出来,一个礼拜之内,雷琛与我两次见面,我还曾用自己身份证件,替雷琛在酒店开过房间。于是特意来我办公室,询问一些细节。

我为陈警官倒了一杯威士忌。由于不是正式调查,双方又都与死者多少有些交情,气氛在少许沉痛之余,还算轻松。

“请问您与雷先生见面时,有没有发现他有什么异常言行?”陈警官问。

我想了一下,回答道:“记忆所及,应该没有。我们谈到的,也无非是中年男人都有的各种焦虑。”

“比如?”

“经济环境影响生意啦,空气污染啦,婚姻危机啦,你懂的。”

“我懂……但这些应该不至于压垮雷先生。据我了解,他的生意做得还顺利,家庭也算和睦,前不久还去日本做过全面体检。”

“那次体检的结果,想必是完全没问题吧?”

陈警官将酒杯抬到鼻子底下闻一闻又放下,说:“生理上完全没问题,各项健康指标甚至高过平均水准很多。”

我听出陈警官话中有话,看着他的眼睛说:“您是说,雷先生心理方面有不妥?”

“没到那个程度,”陈警官说,“我请医学专家看了雷太太提供的报告,心理分析部分指出,雷先生的固执指数比较高,对输赢结果很敏感,建议他在这方面做适应性调整,否则会在未来出现心脑血管系统的隐患。前阵子他参加过禅修活动,效果怎样不得而知。”

“也就是说,在极端的状况之下,有可能诱发脑溢血?”

“专家的意见是,雷先生是在半夜上厕所时出事,那种场景之下,很难想象会出现什么极端状况。”

“那么,现场有什么可疑的地方吗?”

“卫生间洁净,衣着整齐,无外伤,无可疑指纹、脚印。据雷太太说,发现雷先生时,他平躺在地,双眼圆睁,但脸上并无痛苦的表情。”

陈警官又举起杯子,犹豫了几秒钟,像是下了决心般,喝尽杯中不足30ml的酒液,说道:“只有一个细节很古怪。我在太平间见到雷先生遗体时,他左手摊开,右手握拳拇指、无名指和小指屈曲,食指、中指伸出,如同摆了一个V字,看上去很不自然。”

“就像小年轻照相摆的那个V?”

“对。可雷先生不是爱自拍发朋友圈的人,出事那晚,手机放在茶几上,没有被带去卫生间啊。”

我幻想了一下雷琛对镜自拍的样子,摇了摇头,把那种滑稽的景象甩出脑外。

讨论不出有价值的结果,陈警官拒绝了再喝一杯的提议,交换联系方式后,告辞离去。

我继续工作到傍晚,在楼下茶餐厅打包了四宝饭,坐车回家。四宝饭里的咸蛋最近总不对味道,不是太咸就是有股怪味,或许更适合这一代食客的口味?胡乱想着,到家,吃饭,看了会儿电视,准备洗澡睡觉。

洗完澡,蒸汽弥漫,我拿起一块毛巾,抹去洗手盆前镜面上的水雾。一个人影在镜中显现出来。那当然是我的映像,可是我看着他,却像在看陌生人。那男人年过四旬,发际线远离抬头纹,向天空的方向退缩,眼袋却挣扎着向下坠,露出一双疲惫又亢奋、无奈又自得的可憎眼神。

那晚,雷琛也是这么看着自己吧?他为什么要摆V字手势?是向镜中的自己炫耀什么吗?

我伸出手,摆了个V字手势。镜中人也“V”了我一下,双赢,嗯哼。

在我做着这种无聊举动时,另一件事正在发生,不过我要到次日清晨才知道。

雷琛的遗体不见了。

原计划当天追悼会后火化。我去参加追悼会,却发现乱成一团。现场业已封锁,冰棺空空如也,雷太太泣不成声,馆方人员不知所措。警察正在开展侦查工作,见到陈警官也在,事涉刑案,我不便打听,挥了挥手,先行离开。

下午六点多,陈警官打电话过来。

听得出他语气里的困惑。“现场没有发现不明脚印、指纹。正好有摄像头对着冰棺方向,调看监控录像,在雷太太发现异状前,也没有任何人接近过冰棺。昨晚十二点左右,工作人员巡更时检查过,一切正常。事情发生在十二点到早上七点之间。警方十分重视,但苦于查不到任何有用线索,恐怕只能变成立而不决的悬案。”

我挂上电话,半晌回不过神来。老年间湘西倒是有赶尸的行当,可好歹也看得见死人走路。一具不会动弹的尸体,如同被施了魔法,凭空消失,这种事摊哪个警局都够呛,对家属的精神打击也可想而知。

雷琛死亡时的所思所想,已然无法了解,如果去看看出事现场,也许能找出端倪。我打电话给陈警官,跟他说了我的想法,请他与雷太太沟通,允许我去探访。陈警官很愿意帮忙,很快约好当晚去雷家。

雷家住在近郊一个高档社区,十多层楼的顶层复式单位。四间卧室,主卧带有卫生间,但雷琛被罚睡沙发,是在与客厅相连的卫生间出的事。陈警官与我一寸寸检查,连洗脸台下面都看过,没有发现任何异状,只好回到客厅讨论。

雷太太显然还没从痛失丈夫的悲伤之中摆脱,但良好的教育令她能够得体地招待客人。红茶很快就沏好,和两样点心一起端了上来。

“雷太太,”陈警官打破沉默,说道,“我和桐先生都认为,尊夫的死别有蹊跷。请您仔细回忆一下,最近一段时间雷先生是否表现出什么异状?”

“应该没有吧。”

我听出她声音里的一丝敷衍,追问道:“应该没有,意思是您其实不太知道,对吗?”

雷太太大概没料到我如此直白,吃了一惊,手伸向面前的茶杯,半途又停下来,放到沙发扶手上。

“可以这么说,因为我们最近不常能真正地见面。”雷太太说,“他生意上的日程安排太密集,不是飞来飞去,就是应酬喝酒。算起来,我一个月恐怕只能跟清醒的他说上十句话。”

“但是您依然很爱他?”我问。

“是。我们认识于二十年前,一见钟情那种。为了他,我不惜与家里断绝关系;他是个不服输的性子,为了我,这些年起早贪黑拼命工作。就算时常见不着他,我心里无时不刻挂念他。他对我也是一样。”

雷太太说起往事,心情似乎平静下来,嘴角也略略上勾,渐渐沉浸到一种回忆的情绪中去。陈警官小声给我解释:“雷太太出身在富贵之家,雷先生当年则是穷小子一个。门不当户不对,雷太太家里不接受,也是题中应有。不过后来见雷先生真心对待雷太太,生意上也颇有成绩,也就慢慢和好了。”

我点头表示了解,转向雷太太说:“事出突然,想来雷先生没有留下什么遗笔吧?”

雷太太终于控制不住情绪,抽泣起来:“那天晚上他没喝大醉,只是有点话多。我们一起看了会儿电视,便洗漱休息了。现在回想起来,也没什么要紧的话。我早上醒来没看到他,结果在客卫……”

陈警官连忙安慰雷太太,请她节哀。讨论到近十二点钟,仍然没什么头绪。我连续喝了几杯茶,有些内急,告了个罪,借雷家卫生间一用。

洗手时,我又看见镜中的自己,当晚雷琛如我一般站在镜前,是什么令他突然兴奋到脑部血管炸开?

伸手去压龙头开关,瞥见镜中自己的手,食指与中指放在龙头上,无意识地做出了弯曲拇指、无名指与小指的动作。霎那间,我想到一种可能性。

这手势,像不像“石头剪刀布”里的“剪刀”?

雷琛右手食指与中指伸出来,其他三只手指屈曲,也许不是要表达“YEAH”,而是出了个“剪子”。

他,跟镜中的自己玩石头剪刀布了!

不过,对着镜子划拳,和大猩猩向镜中的自己发出咆哮一样,除了忠实的镜面反射,还能看到什么呢?玩一万遍石头剪刀布,也不至于累死,或者突发脑溢血吧。

仿佛自嘲般,我跟自己玩了两把,当然是平局。我打开水龙头,取下眼镜,低头往脸上泼了点水,让自己因为做出这种无稽之举而羞红的脸庞恢复正常。当我抬起头来,伸手去拿洗手台上的纸巾时,模糊的视线里,看到了我无法理解的情形。

我伸向纸巾盒的是右手,按道理,镜中那位应该伸出左手,镜里镜外真好相反才对。可是,我分明看到,镜中人的右手,在他身前绕过,有点费力地伸向了他左手边的纸巾盒。

我心脏瞬间狂跳起来。那不是我,那不是我,那不是我……可为什么他和我长得一模一样,还穿着同样款式的衣服,剃了同样的六毫米圆寸?

多次与诡异事物打交道的经历,让我迅速冷静下来。雷琛当日极有可能见到类似情形,而且,以他不服输的性子,醉意朦胧中,极有可能与镜子里这位“雷琛”玩了石头剪刀布。

这个游戏,夺了他的命。

顾不上擦脸,我赶紧戴上眼镜,定睛向镜子看去。镜中的我也戴上了眼镜,左手如我双手一样垂着,右手却直直伸出,手握成拳,乍看去简直要伸出镜外来。

他,要跟我玩石头剪刀布。

这游戏的规则,每个人都知道。石头赢剪刀,剪刀赢布,布赢石头。问题在于,此时此地,赌注是什么。雷琛赌上了一条命,这赌注超出了我的承受能力。我猛然看向门口,一道雾霾般的屏障隔在中间。直觉告诉我,如果不理会镜中人,恐怕我再也走不出这个卫生间。

这是一局我不可以拒绝的游戏。

为了让视觉上更像是镜面反射,我抬起紧握的左手,放下,抬来,放下,再抬起来,一边念叨着“石头”、“剪刀”。镜中人也做着同样动作,他嘴唇微动,显然也在念着“石头”、“剪刀”。

我深吸一口气,停顿半秒,嘴里大声喊出“布”,左掌完全摊开,掌心向下,做出了“布”的手势。

睁大眼睛看向镜子,里面那个“我”,右手握拳,那代表——石头。

我赢了。

镜中那个“我”的拳头直抵到镜面。一道细纹从拳头位置皲裂,变成许多道裂纹,延展到整个镜面。镜面开始微微隆起,仿佛有什么东西即将破镜而出。

急促的敲门声突然响起。陈警官在门外叫我:“桐先生!桐先生!发生什么事了?快开门!”

我向门口方向转头,不知何时,那道雾障已消散无踪。一步跨过去打开门,陈警官和雷太太先后冲进来。不需要我开口说什么,他们的眼光就黏到了镜子上。

镜面继续隆起到了极限。一时间,我们三人没来得及做出任何反应,只见镜面崩裂开来,碎玻璃散落到洗面台和周围地板上,有一些也飞溅到我们身上,但谁也没有避让。

墙里,出现了一个人。

准确地说,是雷琛的上半身在原来镜面的位置露出来。他两眼紧闭,右手伸出,就是这只手捅破了玻璃。

然后,一切停歇。

雷太太昏迷过去,我与陈警官将她抬回客厅,放到沙发上,立即打电话报警。之后就是各种循例笔录。

但始终没有人能够解释,雷琛的遗体是如何从殡仪馆神秘消失,再藏匿到卫生间镜子后的墙体里的。没有人知道,出事那天晚上,雷琛是不是真的与镜中的自己玩了石头剪刀布。

这件事成了永远的悬案。而我,再也不会在午夜看向镜子。

代码猴子与童子军军规

2007年3月,我在SD West 2007技术大会上聆听了Robert C. Martin(Uncle Bob)题为Craftsmanship and the Problem of Productivity: Secrets for Going Fast without Making a Mess的主题演讲。一身休闲打扮的Uncle Bob,以一曲嘲笑低水平编码者的Code Monkey(代码猴子)开场。

是的,我们就是一群代码猴子,上蹿下跳,自以为领略了编程的真谛。可惜,当我们抓着几个酸桃子,得意洋洋坐到树枝上,却对自己造成的胡乱熟视无睹。那堆“可以运行”的乱麻程序,就在我们的眼皮底下慢慢腐坏。

从听到那场以TDD为主题的演讲之后,我就一直关注Uncle Bob,还有他在TDD和整洁代码方面的言论。去年,人民邮电出版社计算机分社拿一本书给我看,封面上赫然写着Robert C. Martin的大名。看完原书序和前言,我已经按捺不住,接下了翻译此书的任务。这本书名为Clean Code(《整洁代码》),乃是Object Mentor(Uncle Bob开办的技术咨询和培训公司)一干大牛在编程方面的经验累积。按Uncle Bob的话来说,就是“Object Mentor整洁代码派”的说明。

正如Coplien在序中所言,宏大建筑中最细小的部分,比如关不紧的门、有点儿没铺平的地板,甚至是凌乱的桌面,都会将整个大局的魅力毁灭殆尽。这就是整洁代码之所系。Coplien列举了许多谚语,证明整洁的价值,中国也有修身齐家治国平天下之语。整洁代码的重要性毋庸置疑,问题是如何写出真正整洁的代码。

本书既是整洁代码的定义,亦是如何写出整洁代码的指南。Uncle Bob认为,“写整洁代码,需要遵循大量的小技巧,贯彻刻苦习得的‘整洁感’。这种‘代码感’就是关键所在。……它不仅让我们看到代码的优劣,还予我们以借戒规之力化劣为优的攻略。”作者们阐述了在命名、函数、注释、代码格式、对象和数据结构、错误处理、边界问题、单元测试、类、系统、并发编程等方面如何做到整洁的经验与最佳实践。长期遵照这些经验编写代码,所谓“代码感”也就自然而然滋生出来。更有价值的部分是Uncle Bob本人对三个Java项目的剖析与改进过程的实操记录。通过这多达三章的重构记录,Uncle Bob充分地证明了童子军军规在编程领域同样适用:离开时要比发现时更整洁。

接触开发技术十多年以来,特别是从事IT技术媒体工作六年以来,我见过许多对于代码整洁性缺乏足够重视的开发者。不算过分地说,这是职业素养与基本功的双重缺陷。我翻译《C#编程风格》(已由人民邮电出版社出版)和《整洁代码》,实在也是希望在这方面看到开发者重视度和实际应用的提升。

在《整洁代码》结束语中,Uncle Bob提到别人给他的一条腕带,上面的字样是Test Obsessed(沉迷测试)。Uncle Bob“发现自己无法取下腕带。不仅是因为腕带很紧,而且那也是条精神上的紧箍咒。……它一直提醒我,我做了写出整洁代码的承诺。” 有了这条腕带,代码猴子成了模范童子军。我想,每位开发者都需要这样一条腕带吧?

 

(《整洁代码》中文版一书即将由人民邮电出版社推出。)

迁移Blog平台

实在受不了Community Server的弱智垃圾评论处理手段,因为它不开源,自己修改也很麻烦,所以决定迁移到Word Press。由于hanlei.name原来用的数据库服务器只对内网开放,故WP提供的直接迁移手段用不了。折腾半天后,终于找到近乎完美的解决方案:

1、用Keyvan Nayyeri编写的Community Server 2.1 BlogML Converter,格式导出CommunityServer的数据(BlogML格式)。过程很简单,按压缩包中的文档指示,把文件传到CS相应目录下,到后台访问SyndicationOptions.aspx(“RSS设置”),在BlogML那个位置点“Export”,就会在新窗口打开导出后的XML文档。

2、用Aaron Lerch编写的WordPress BlogML Import工具在WP后台导入BlogML数据。Wayne John修改了这个工具,修正了一些小问题。Wayne John版本在这里下载

3、实际上Wayne John的版本也有问题,WP 2.7以上的都支持不好,而我的版本是2.8.1。我找到了这篇文章,根据文章指引,终于完成导入。

整个过程中有几点要注意:

1、CS要运行于ASP.NET 2.0平台;

2、第三步所需时间较长,Apache缺省的30秒执行时间不够,我修改了耗时最长的一个模块(plugin.php),使之支持60秒执行时间;

3、貌似对分类的支持还有问题,需要手工调整;

4、导入后页面上会给一个新旧链接对照表,可以据此做URL Rewrite,我的虚拟主机不支持编辑.htaccess,有空的时候在WP基础上写点代码实现跳转吧。

上面提到的BlogML导入工具,及修改后的plugin.php,可以在这里下载

Flickr for LiveWriter和谐版

我一直用微软的Windows Live Writer写Blog。最近,由于大家知道的原因,Flickr!图片不能访问,所以LiveWriter的Flickr!引用插件也运行不正常。

后来装了伊朗人的Firefox插件,在Firefox里面倒是可以看到图片,但LiveWriter还是用不了Flickr的图片。我查看了Firefox插件的代码,对照里面给出的替换URL,照葫芦画瓢修改Flickr for LiveWriter插件的代码。没花什么功夫,就简单粗暴替换了一下URL地址。方法虽然烂,能用就行。

如果你也用LiveWriter写Blog,并且需要在LiveWriter里面引用Flickr的图片,可以使用这个修改好的和谐版插件。下载地址是:http://download.csdn.net/source/264275。压缩包里面有源代码和编译好的DLL(根目录的bin目录下)。把该DLL放到LiveWriter的Plugins目录下就可以了。有闲心的同学,不妨把它改得更灵活些,把硬编码的部分干掉。

《Beginning C# Objects中文版》勘误

有热心读者在Dearbook上贴出这本书中的一些错误,我一一作了回复,整理转贴如下:

对于使用C#的OO出学者来说,这确实是难得的好书。我是抱着重新梳理一下OO知识的心态来看的,虽然到目前(刚看完第五章)还没遇到什么特别精彩的文字,但是层层推进的写作风格和出色的翻译还是把该讲到的知识点都讲到了,而且还对易混淆的地方做了重点解释,比如override和overload。

  但是今天看书的过程并不是太愉快,因为发现了几处比较严重的错误(严重=颠倒了事实,对初学者很不利),在这里贴出来,有些也可能是我理解错了,还请译者和读者甄别:

  
  1.P59,正数第8行
   原文: “引用变量的名称遵循方法和attribute的命名惯例,即使用Pascal命名法。”
   修改后:“引用变量的名称遵循方法的attribute的命名惯例,即使用Camel命名法。”
   严重程度:★★★★★ /把本来要说明的问题说得更混乱了,很严重!

韩磊按:原文:Names for reference variables follow the same convention as method and attribute names: i.e. they use Pascal casing.而举例则是使用Camel命名法。按照C#命名惯例,引用变量应遵循Camel命名法(和attribute一样)、而方法名遵循Pascal命名法。显然,原书此处有误,应改为:Names for reference variables follow the same convention as attribute names:i.e. they use Camel casing。译文:引用变量的命名遵循与attribute一样的命名约定,即,使用Camel命名法。

  2.P93,代码里MoneyOwed()方法的第三行注释
   原文: “即便它们没声明为私有”
   修改后:“即便它们声明为私有”
   严重程度:★★★★★ /把关键的话说反了,很不应该!

韩磊按:P93,public double MoneyOwed()方法注释,原文:We can access attributes of this class (totalLoans and tuitionOwed) — even they are declared to be private! — without using dot notation.
译文的确有误,把“声明为私有”错误地译作了“没声明为私有”,回想起来,应该是受了那个without的影响,不可原谅啊!

  3.P108,从4.5.3行开始数,第8行
   原文: “可以在客户代码中修改”
   修改后:“可以在提供服务代码中修改”
   严重程度:★★★★★ /把关键的话说反了,很不应该!

韩磊按:原文:its private data structure and/or its accessor code — can change without affecting how and object belonging to that class gets used in client code。译文应为“可以修改其私有数据结构和/或其访问器代码,而不会影响到使用该类的客户代码中的对象”。

  4.P130,倒数第10行
   原文: “继承常常指出两个类之间的“A是B”关系”
   修改后:“继承常常指出两个类之间的“IS A”关系”
   严重程度:★★★★ / 结合上下文,B派生于A,这里应该是“B是A”才对。

韩磊按:严格来说,在上下文中的确应为“B是A”。

  5.P145,倒数第6行
   原文: “重要提醒:C#中这样做是可以的!!!”
   修改后:“重要提醒:C#中这样做是不可以的!!!”
   严重程度:★★★★★ / 为了这个重要提醒,我还特意做了实验,没弄明白怎么就可以了呢?!况且在P144页里也明确说了C#不支持多重继承,那么这个重要提醒是什么意思呢?

韩磊按:的确应该为“在C#中是这样做是不可以的”。

我由衷感谢这位读者,他指出了我们的失误,实际上是对我们工作的促进和帮助。在下次印刷时,这些错误将得到修正。同时,我也要向所有读者致歉;无论如何,中文版中出现这样的错误,都是译者的责任。

TrackBack Spam:一颗老鼠屎真的能坏一锅汤

最近DoNews Blog又时常出现不稳定的状况。Keso告诉我,怀疑是trackback spam在捣乱,因为每次不稳定,随后都会看到一大堆垃圾trackback。

嗯,关于trackback spam,早有听闻、亦已目见,只是从来没觉得它会对系统运行产生什么影响。可巧今天DoNews Blog突然又出问题,用性能查看器一检查,每秒访问数剧增,很多请求不得不排队,请求队列排满后,就不断被弹出、拒绝服务。于是——大家熟悉的黄屏错误就出现了。

清空Cache、清理缓存池、重启IIS、重启服务器都没用,被拒绝的请求数直线上升。这时候,我想起了Keso的怀疑。查询一下数据库,嗬,短短时间内,成百上千条trackback spam出现在眼前。再仔细查看,大概是这么几种:卖伟哥的、开赌场的、以及放高利贷的。嗯,域名五花八门,都围绕pharmercy、gambling、poker、loans打转转。查IP,发现它们来自于不超过4个站点。暂时停止trackback功能后,blog恢复正常。

然后我开始上网搜trackback spam,查询结果满满当当2,450,000项。嗯,整个blog世界,都在为这种垃圾烦恼呢!

有一个名为MT-Blacklist/Comment Spam Clearinghouse的站点,专门研究如何阻止trackback spam。虽然它是针对MT系统的,不过对改造.Text也颇有帮助。我想说的不是技术问题,而是其中一篇文章。这篇文章列出了根据内容判断一条trackback是否spam的正则表达式。一看之下,不禁笑出声来。原来,让老外烦恼的那些trackback spam,其中一些也就是让DoNews Blog不正常的那些。

看看URL匹配黑名单——

URLPattern Action
casino Block
penis Block
viagra Block
poker Block
pills Block
hentai Block
zoo Moderate
teen Moderate
incest Block
ambien\b Block
blackjack Block

有趣吧?hentai、zoo、teen和incest似乎在DoNews Blog没有见过,其他词都是老熟人了。黄赌毒,此trackback spam之三要义也。从国外blogger的反馈来看,trackback spam很多来自少数几个网站。Trackback这么好的理念和实现,就这样被无耻地利用了。几颗老鼠屎,确乎是可以坏一锅汤的。最怕有一天,中国人突然学会了利用trackback spam……