• 2013-12-31

    回望2013

    2013年就这么又过去了,站在又一年的门槛上,想想这过去的一年,如果选择一个主题词,我想是“坚持”。

    Moco,我在2012年无意间开启的一个开源项目,居然让我坚持住了。我经常会兴之所至,开始写一些小项目,但如果不是有像Github之类的站点,我都不记得我曾经这么想过。但Moco,我就这么一直做下来了。回想起来,Moco从一开始就是一个相对完整的小项目,从一开始就有用户,有反馈,我也就乐呵呵地一直做着。每天为Moco写点代码已经成了我日常生活的一部分,所以,我有了超过300天连续提交。

    坚持的回报是,Moco在2013年拿了两个大奖,一个是ThoughtWorks中国区技术大赛最具技术创新奖,另一个则是更具外部影响力,2013年Oracle的Duke选择奖,这个奖让更多人认识了Moco,也让Moco有了更多的用户,更多的反馈,也促使我不断地,美滋滋地继续为Moco写代码。在可见的未来,Moco还有不少要实现的东西。

    2013年11月,我用了大半年翻译的《The Joy of Clojure》终于正式出版了,也算是为之前的辛苦划上一个句号。正是因为我花了很多时间和精力在上面,所以,我觉得,翻书这活这不好干:不好好干,对不起别人;好好干,对不起自己。于是,我暗下决心,洗手不干了。就在我决定洗手不干了之后,我又组织翻译了《ThoughtWorks文集2》,虽然我没有翻译初稿,但后续的审校花费的时间和精力不比任何一个译者少,所以,最终写译者序的人,居然是我。

    2013年,我还偶尔出去讲了几次东西。在Open Party成都,我讲过Moco,讲过《Java as a Platform》。因为Duke选择奖的原因,我又得到了一个机会,在QCon上分享Moco的故事:《不可思议的Moco》。运气很不错,除了免费差旅,我还拿到了优秀讲师的称号,奖品是一个Kindle Paperwhite 2,据说InfoQ中文站的老大Kevin肉身翻墙背回来的。去年年底,因为出差澳洲的原因,错过了敏捷之旅成都站,今年组织者竟然还记得有我这个人存在,于是,年底又在敏捷之旅讲了一个《你应该更新的开发知识》,还被放到了主题演讲的位置。

    从2013年5月份底开始,ThoughtWorks中国区这边开始有了一个小栏目,ThoughtWorker好声音。我们每周会选择几篇ThoughtWorker写的技术方面的文章组织起来,分享给更多的人看。从一开始的成都办公室内部分享,到后来中国区的内部分享,再到后来,我们搭建了自己的网站,通过ThoughtWorks的微博、微信对外分享。这个小栏目已经逐渐受到越来越多人的关注,它已经成为ThoughtWorker对外展示的舞台。在内部发行版里,我们甚至会把它翻译成英文,让我们更多的外国同事了解到中国区这边的技术发展情况。之所以我把公司的小栏目放到自己的年终总结里面,因为我是这个活动背后的发起者,好声音最初的几期完全是由我一手打造的。现在,我已经不再是这个栏目的瓶颈了,背后已经有了更多的人。我很欣慰,因为我们坚持下来了。

    2013年初,我给自己制定了一个读书目标,一年读30本书。最终的结果是,一年读了52本书。原来制定目标,只是为了让自己多读书,且不会压力太大,结果养成了读书的习惯,每天早晚都会拿起书读一会。虽然我一直号称自己喜欢读书,但还真没抓紧时间读过,有不少以前“想”读的书,这次是真读了。感谢Kindle,让读书变得比以往更容易了。

    2013年,也是我写blog的第十个年头,也许因为时间关系,现在已经没有从前写得那么频繁了,但脚步没有停下。用它总结“坚持”的一年是很恰当的。在年中做的一篇CSDN访谈中,我说过:“找到一个适合自己的平台,坚持写程序,坚持思考,十年后,便会与众不同。”现在,我越发相信坚持的力量了。

    2013年就这么结束了,从结果上来看,这是我值得骄傲的一年,做出了以往不曾做过的一些事。2014年,我还希望自己能够做一些不同以往的事。

  • 2013-12-11

    十年

    刚开始工作的时候,我很愿意了解关于计算机发展的各种各样故事,俗称八卦,因为我特别想知道,我所从事的职业是如何走到今天的。如今,我偶尔会梳理一下经历过的事情,讲给其他人听,他们的神情和我当年了解各种八卦时,别无二致。原来,我前面经历过的时代业已成为历史的一部分。

    十年前,初出茅庐不久的我,选择了一种表达方式,blog,那时候绝对算是个新东西,知道这个名字的人都算是站在时代的风口浪尖,更别说自己写了。不记得在blog上说过多少次了,我能坚持下来的东西不多,编程是一个,写blog居然成了另外一个。

    是的,我写blog,整整十年了。

    十年过去了,blog已经成了落寞贵族,连Google都不玩Reader,微博这个后来才兴起的东西,现在也快被微信挤掉了。人们喜欢表达,只是表达方式越来越简洁,越来越便利。不过,当我觉得自己需要整理一下自己的思路时,blog依然是我的首选,在我看来,安安静静写字时,会有更多的思考。

    曾经有许多人问过我,为什么不自己搭建一个独立blog,或者是在Github上弄个至少有代码样式的blog。懒,是一个很重要的原因。另外一个原因是,习惯。十年了,我甚至连blog的模板都没有换过,所以,今天看我的blog,样子上和十年前如出一辙,我还是从前的那个我。也许未来我会换,但我不知道那一天什么时候到来。

    十年前,我选择了blogbus作为blog的存放地,如今看来是件很幸运的事。但很多人不知道或者不记得当年的BSP之争,十年后,回过头来看,当年的BSP留到现在的几乎没有几家了,而我的blog十年了,依然还在,只有运气可以形容。

    还记得第一年写了100篇,之后,几乎就是一个稳步减产的过程。不管怎样,十年下来,我还是写了500多篇blog。偶尔翻看从前的blog,那是一个追忆似水年华的过程,我会感叹,年轻就是生猛,或者我还有这么傻的时候。感谢这些blog,让我有了一些可以追溯的往事,让我不曾遗忘曾经发生过的事情。

    我想我还会写下去,我现在格外相信坚持的力量,我的一些坚持让我变得和从前不一样,不再只是纯粹的三分钟热血。总的说来,我喜欢这种改变,并没有滑向“成为自己讨厌的人”的深渊。

    希望十年之后,我还可以说,我又坚持了十年。

  •  

     

     

     

     

    去年六月份,我正在与DSL这本书做斗争,痛苦不堪,我甚至已经暗下决心,再也不接手翻译这种费力不讨好的事情了。张凯峰突然发了封邮件问我要不要翻译书,我当时有一种杀了他的心。除非是《The Joy Of Clojure》,因为我实在是想了解一下Clojure,而且关于这本书,很多人都说它是可以改变人们对于编程认识,我很好奇。天杀的的张凯峰真的找来了《The Joy Of Clojure》,我知道,又有一部分业余时间没了。

    这次的合作是和陈冀康(@childchen),人邮信息分社。按照出版社的要求,他们肯定是希望这本书越早完成越好,感谢陈冀康对我宽容,答应我半年的翻译期限。我的算法很简单,每天两页,三百多页的书,大概一除,半年左右。另外,鉴于翻译DSL的痛苦经历,我暂且不想与任何人合作翻译,要知道,团队协作,每个人的责任感就会大幅度下降,这是导致DSL晚出版近一年的主要原因。

    之后就是漫长的翻译过程,我不得不承认,这是一本非常好的书,但却是不是一本给初学者的书。作为一个没有Clojure经验的开发者,为了能翻译得更地道,只好又额外地学了很多东西。幸运的是,在翻译过程中,我找到了一个国内很少有的真正的Clojure开发者,庄晓丹(Dennis Zhuang),他愿意作为审校加入到这个过程中来。事实上,对我来说,他不仅仅是一个审校者,更是我的一个老师,教会了我许多关于Clojure的东西。

    我从前翻译的时候,大多不加译者注,因为我会把读者同自己的能力等同起来,我觉得只要我能理解,读者理解起来是没有什么问题的,我不想把读者假设得太小白,但这本书里,我写了很多译者注,因为很多东西我理解起来都不容易,所以,这是我加注最多的一本书。

    相比于之前翻译过的一些书,这本书在英文上也绝对是个大挑战,别的不说,就是目录标题都让我头疼了好一阵子。Dipping our toes in the pool,用脚趾头蘸一下游泳池,如果这样的文字出现在最终的书里面,肯定有人要骂娘的。书里面还有很多很地道的英文用法,换句话说,站在第二语言的角度,真的很难理解。幸好,我们的办公室里有许多外国同事,就为了这些翻译,我也不知道多少次骚扰了人家。

    这本书其实还有许多审校者,真正大篇幅坚持下来的不多,其中,我的原同事胡振波是一个。他也参与过一些文章,一些书,文字功底还不错,帮我改了很多文字上的细节。还有一个审校者,虽然只看了序和前言,但也不得不提一下,我们家领导,作为一个对编程一窍不通的英语老师,帮我到这种程度,也是尽心尽力了。原本打算如果有写译者序的机会,一并感谢了,但人邮居然没给我这个机会。不管怎样,在此谢过了。

    翻译是在是个劳心费力的事情,我不打算再做,我又一次暗下了决心,除非《The Joy Of Clojure》出第二版。好吧,你或许已经知道了,第二版在第一版的中文版还没有出来之前,就已经开始写了。

    不管怎样,书出版了,心里总是高兴的。希望有更多的人通过这本书,开始体会编程的乐趣,即便你不是一个Clojure程序员。

  • 当我在Moco的代码库里删除了关闭链接的代码时,我惊奇地发现Moco的一个测试失败了。怎么会失败呢?难道测试里面还有人需要强制关闭链接。

    带着种种疑问我又踏上了新一轮的代码跟踪之旅,这次的所有代码都在我自己的代码库里,一个简单的单元测试,无非是里面用到了Apache的HTTP Client。又是无数次的重试,我站到了一个InputStream的面前,这是在处理应答体时,要从HTTP应答流中读内容,然后,挂掉了。显然,当我强制关闭链接,读取就会返回,所以,测试可以通过。

    为什么会是这样?

    我不再简单粗暴地关闭链接,而是根据情况,如果客户端希望保持链接,我就保持,否则才会关闭。结果,测试依然是挂的。原因是这个测试请求保持链接,所以,服务器端不会关闭它的链接,所以,还会遇到相同的问题,它会挂住。

    到底什么样的行为才是正确的呢?我找到一本厚厚的《HTTP权威指南》,仔细读了一下保持链接的章节。我对HTTP如何处理链接有了一个相对完整的认识,但对于这个问题,我依然没有答案。

    其它测试也会有保持链接的需求,为什么就可以顺利通过,而只有这个测试会挂住。

    坐在电脑面前解决不了的问题,往往需要在路上解决。我突然意识到,通过者和不通过者似乎是有些许差别的,赶紧回到电脑前,打开日志,这个不通过的测试与其它测试最大的差别是,它只有应答头,没有应答体。不奇怪,因为这个测试就是测试能够返回正确的HTTP头。

    我似乎明白了,当客户端要求保持链接时,它还是需要从链接中读取数据的,但应该读取多少,谁能告诉它这个答案呢?如果没有额外的提示,似乎就只能挂住。答案就在Content-Length上,有了这个HTTP头,客户端就知道该读多少了。

    想通这一点,代码就好改了。在保持链接的情况下,如果没有Content-Length,就设置相应的Content-Length。

    果然测试一遍通过,又把之前同事的测试用例拿了过来,重新跑了一遍,一切正常。放到他们的工程里面测试,也是顺利通过。

    从发现一个不稳定的问题,到最终比较完整地修复了它,前前后后两个星期。如果没有我那个执着的同事,可能我也不会严肃地对待这个问题;没有仔细地解决这个问题,也不会对HTTP协议处理有个新的认识。非常感谢我这个执着的同事,他叫崔鹏飞,下一次Moco发布公告中,我可要着力感谢一下他。

    一个同事跑到我面前,我在使用Moco时发现一个问题,它有时会挂住。你用的是不是CXF,我问他。是啊!他回答。我笑了,因为我刚刚修好了这个问题。

  • 去参加QCon的路上,一个同事给我打了个电话,他在使用Moco的过程中,遇到了一个奇怪的问题:测试会时不时的挂住。在不明就里的情况下,我给出了一些常规性的建议,换个新版本试试,要不,能不能把情况简化到最简单的情形,发给我试试。

    几天后,我从QCon回来,那个同事愁眉苦脸地找到我:换了新版本,问题依然,更奇怪的是,当试图用最简单的情形重现时,现象居然就消失了。用抓包工具试试吧,看看底层发送的消息是不是对的。我又想到了一个常规的解决方案。他同意了,尝试了,又失败了,它在中间加了个代理,结果现象又消失了。两边发送的消息从HTTP报文的内容上,看不出什么端倪。

    我这个同事在办公室里是出了名的执着,几天之后,他又跑了过来,我已经写了个测试,已经能够稳定的重现这个现象了。我们一起坐下来,用单步跟踪的方式追着代码。经过无数次的运行,我们惊奇地发现,挂住的地方根本没有走到Moco里面,当第一次访问Moco服务器成功之后,离开Moco代码,在Netty框架里,试图select下一个要处理的请求时,就挂住了。而且很有趣的现象是,如果两次访问Moco服务器时间间隔较长,比如5秒,万事大吉,如果时间很短,基本上就会挂住。我们甚至开始怀疑是不是Netty本身出了问题。

    第二天,这个同事又取得了新的进展,他已经把这个测试简化到完全不依赖于他们的工程。这样,我们就可以在别的机器上进行调试,而且简化到最简单的情形之后,我们只关注最重要的东西。这次现象是相同的,挂住,但跟进去的结果却截然不同,这回出问题的地方是客户端,当发送了第一个请求之后,试图进行第二次发送时,请求就挂住了。

    我能怎么办呢?我又仔细品味了阅读了一下Moco的代码,突然注意到里面有这样一句:

      future.addListener(ChannelFutureListener.CLOSE);

    这句话的意思是,当请求处理完成之后,关闭链接。似乎灵光闪现,我查看了一下客户端发起的请求,在HTTP头里面有这样一句:

      Connection: keep-alive

    这显然是HTTP协议里面,用来请求服务器端保持链接的一种方式。如果你不知道的话,在一些HTTP协议的实现中,有时为了节省链接建立的资源,会复用之前已经建立好的链接,当然,前提条件是,服务器和客户端都支持。

    显然,Moco是不支持的。但客户端又有这种期望,所以,挂住了。简单地删除了负责关闭的代码,代码顺畅地通过了测试。这里面有两个问题,Moco服务器端支持是一方面,客户端实现是另一方面,换句话,当服务器端不支持长链接时,客户端应该做相应的处理,至少这个客户端没有做到,顺便说一下,这个库是CXF。

    这不是故事的结尾。