-
2008-01-20
实践测试驱动开发
作为一个有理想、有追求的程序员,你成天被各种名词包围着,你对其中一个叫做敏捷的东西特别感兴趣,因为它特别强调人的作用,这听着都让做程序员的你感到舒服。为了让自己早日敏捷起来,你从众多的敏捷实践中选择了一个叫做测试驱动开发(Test Driven Development,TDD)的作为你的起始点。因为它对你周遭的环境要求是最低的:它不像结对那样,要求其他人和你一起合作;也不像采用Story那样改变你所在团队的做事方式……你所需要做的,只是在你编写业务代码之前,把测试先写好。这完全是一种润物细无声的做法,根本无需告诉你之外的任何人。就在别人忙碌的找bug时,你便开始享受敏捷带给你的快乐了。顺便带来的好处是,下次在那里和别人争论敏捷的时候,你可以以一个实践者的姿态出现,而不是在那里信口开河。
你不会打无准备之仗,于是,你通读了Kent Beck的那本薄册子。通读之下,你对TDD更是充满了信心。因为“红——绿——重构”的步骤实在是简单得令人发指。好吧!总而言之,你已经信心十足的准备开始TDD,步入敏捷的康庄大道了。
理想很美好,现实很残酷。
当你着手在实际项目中体验TDD的时候,一切变得并不像最初看起来的那样美好。虽然你努力的坚持着TDD的原则,但你经常就会发现某些东西不好测,比如你遇到了数据库,比如你遇到了GUI,比如你遇到了计时器(Timer)。敏捷并非教条,当某些事不可为的时候,你完全可以不那么坚持。于是,你告诉自己,不好测的东西可以不测,这样,至少从心理上来说,你觉得舒服多了。随着工作的继续,你发现,你不能测的东西越来越多,单元测试的覆盖率随着开发的进行正在逐渐降低,一丝恐惧涌上心头。回过头来,再去看Kent Beck的书,你突然觉得,你似乎被骗了,因为Kent Beck的例子貌似全都是逻辑,如果只是逻辑,当然好测了,但现实从来就不是这样。
难道TDD只是看上去很美?
显然,你不愿意就这样放弃,放弃你苦心学来的软件开发秘籍,那些传说中的高手极力推崇的TDD必然有一定道理,TDD确实能够让你感觉很好:能测试的那部分代码确实极大的增强了你对软件质量的信心,而且出错了也确实好找,每次修改代码之后运行测试出现的绿条也确实让你身心愉悦。
那问题到底出在哪呢?你陷入了沉思。
信马由缰,你翻开了自己写过的代码。看着自己写的这些代码,你忽然意识到一个问题,自己遇到的问题并不属于TDD,而是属于单元测试。正如你之前所想到的那样,TDD做法本身的结果是让你感到快乐的。对,一定是单元测试本身出了问题。那单元测试出了什么问题,很显然,一大堆不能测试的部分让单元测试变得很难写,降低了单元测试的覆盖度。那是不是这会是一个无解的问题呢?你显然不愿意就此放弃,所以,顺着这个思路继续向前。
TDD之所以让你安心,主要是每次编写代码之后,运行测试会出现一个绿条,告诉你测试通过。这样,你可以放心大胆的向前继续,因为你的代码并没有破坏任何东西。究竟是什么让你感到不安,显然是那些测试没有覆盖到的代码。你又仔细翻看了一下那些没有测试覆盖的代码,你的思路一下子清晰起来。之所以这部分让你不安,因为里面除了那些确实不好测试的部分之外,里面还有一些逻辑。如果只是那些真正不好测试的部分没有被测试覆盖到,你会觉得心里还有一些安慰。你确定了,真正使你不安的就是与不好测试的代码共存亡的这些逻辑部分。
如果测试可以覆盖到这些逻辑的部分,至少从感情上来说,就可以接受了。那怎么才能让这些部分被测试覆盖到呢?你仔细观察着那些没有测试的代码,如果这样做,这个部分就可以测试了,如果那样做,那个部分也可以测试了,一来二去,这些貌似不可测试的代码可以分解出许多可以测试的部分。
你的心情一下子好了许多,因为这么做终于可以让测试的覆盖度达到让你心理上可以接受的范围。不过,新的问题也随之而来。我在做什么?拆来分去,这不就是设计吗?怎么走到这里来了。我不是在分析单元测试的问题吗?对了,我最初的问题是TDD,怎么一路跑到设计上来了?
TDD?设计?
你突然发觉自己对TDD的理解有一些偏差。TDD,并不代表不需要设计。读过很多书的你突然想起了Robert Martin那本著名的《敏捷软件开发》,上面有一个关于数据库访问的例子。那个例子里面,前后两个版本的差异正好就是考虑设计的结果。通常,在设计中考虑测试,会很容易找到设计中僵硬的部分,让程序更加灵活。再进一步,如果在开始动手之前,稍微进行一些设计,这些问题还是可能注意得到的。你突然觉得,正是因为TDD本身过于强调测试的价值所在,让你忽略软件开发中很重要的部分:设计。
思路一下子清楚起来,TDD其实不只是“红——绿——重构”,它还是与设计相关的:在动手之前,还是要有一定的设计,而且,在设计中要考虑测试的问题。终于解开了心中的困惑,现在的你,对于TDD有了一个新的认识,虽然这个认识不见得是什么终极真理,但至少是通过自己的思考得来的,这让你更加相信实践出真知的道理。理清思路后,你更加坚信TDD本身的价值所在,也坚定了在日后开发中继续使用TDD的念头,当然,目光远大的你已经盯上了其它的敏捷实践。
-
2008-01-07
Hello, Lucene
Lucene是什么?下面是官方回答。
Apache Lucene is a high-performance, full-featured text search engine library written entirely in Java.
简而言之,它是用来做搜索的库。提及搜索,我们的思绪就会情不自禁飞到串匹配上。没错,串匹配确实是一种搜索,但对于不同的应用,搜索的方法不一样,对于在一篇文档中进行搜索这种小规模应用而言,串匹配足够了,而Lucene为我们向大规模搜索铺上了一条大道。大规模?是不是想到了搜索引擎,事实上,Lucene就是被很多人用来构建搜索引擎。
关于搜索引擎的实现,很多人或多或少的听说过一些,比如网络爬虫,比如分布式的架构,比如PageRank。抛开其它其它复杂的部分,最关键的步骤便是建立索引,然后进行搜索。不妨让我们Lucene是如何实现这最关键的部分。
import java.io.File;
import java.io.FileReader;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexWriter;
public class Indexer {
public static void main(String[] args) throws Exception {
File indexDir = new File("index");
File dataDir = new File("data");
IndexWriter indexWriter = null;
try {
indexWriter = new IndexWriter(indexDir, new StandardAnalyzer(), true);
for (File file : dataDir.listFiles()) {
if (file.isFile() && file.getName().endsWith(".txt")) {
Document document = new Document();
Field pathField = new Field("path", file.getCanonicalPath(),
Field.Store.YES, Field.Index.TOKENIZED);
document.add(pathField);
Field contentField = new Field("contents", new FileReader(file));
document.add(contentField);
indexWriter.addDocument(document);
}
}
indexWriter.optimize();
} finally {
if (indexWriter != null) {
indexWriter.close();
}
}
}
}
这段代码很容易理解,遍历数据目录下的文本文件,为每个文件生成索引。
这里有一个Document的概念,它在Lucene表示的是索引和搜索的单位,也就是说,建立索引,是以Document为单位的,搜索也是以Document为单位的。Document中有一堆的Field,我们可以把它们理解为Document中一个一个小节。有了Field,我们可以为Document添加一些属性,比如这里,我们就添加了路径(path)和内容(content)两个属性。这样,搜索之后,我们可以利用这些属性提供更多的信息,比如,告诉别人搜索的词出现在哪个文档中。
上面的代码中,我们可以清楚看到,建立Document,并向其中插入Field的过程。有了Document,我们就可以把它借助IndexWriter将它们写入索引中,至于最后的optimize,显然是为了让搜索更有效率而存在的。
有了索引,那就该进行下一步的工作,搜索。
import org.apache.lucene.document.Document;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
public class Searcher {
public static void main(String[] args) throws Exception {
String type = "contents";
String key = "game";
String path = "index";
IndexSearcher searcher = new IndexSearcher(path);
Term t = new Term(type, key);
Query query = new TermQuery(t);
Hits hits = searcher.search(query);
for(int i = 0; i < hits.length(); i++){
Document document = hits.doc(i);
System.out.println("File: " + document.get("path"));
}
}
}
IndexSearcher是用来在索引中进行搜索主要帮手,前提是我们要告诉它到索引在哪。Term表示文本中的一个词,它说明了我们要在哪个Field(type)中找什么(key)。然后,我们用Term做成一个Query,表示我们要进行搜索了。做好准备,接下来,就是搜索了。搜索的结果叫做Hits。遍历这个Hits,便可以将搜索结果一一展示出来。如前面所说,这里利用路径这个属性报告搜索的结果。
有了Lucene做基础,能做的事就很多了,比如搭建一个搜索引擎。事实上,已经有了这样的开源项目,比如与Lucene同出一门的Nutch,比如比Nutch年纪更大的Compass。 -
2008-01-03
与高手共事
大多数人都愿意与高手共事,因为他们指望着从高手身上可以学到很多东西。在ThoughtWorks,因为要pair,所以,我们可以有更多的机会与高手近距离一起工作。
如果真的有机会和高手一起工作,有时,你会发现,从具体做的事来说,这些高手做的事并不像想象的那么高深,甚至可以说很简单,简单到换了谁都能做。于是,心中的高手形象逐渐开始动摇,难道令N多人景仰的高手就是这个样子。
不知道你有没有想过这样一个问题,同样的事,如果没有高手的参与,换你来做,结果会怎样呢?
老大给我讲了一个他当年和Ward Cunningham在一起工作的故事。每天做的工作就是日常的测试驱动开发,写测试、写代码,所有的一切都是异常简单,下午从不会耽误喝咖啡的时间,到点也就正常下班。一个月后,要做的事情做完了,没有觉得有什么特别之处。不过,回想了一个月前对于这个项目的看法,老大突然发现,这一个月里原来做了许多事情:一个月前,他还觉得这是一项不可能完成的工作。
我最近的一个项目里,和我一起工作的是有我们中国区的CTO。这个项目的前期是一个类似于可行性论证的工作,项目最初,他为整个项目的结构订下了一个基调,让整个项目的结构显得特别清晰,准确的说,应该是很简单,简单到让人觉得理所当然。单从工作的具体内容来看,他并没有在这个项目里面做太多的事情,但从另外一个层面来说,正是他做的前期所做的工作,让后面的工作变得容易了许多。
这么一说,是不是有一种高手形象顿失的感觉。其实,高手通常不会觉得自己是高手。多年积累下的,只是良好的工作习惯而已。他们知道,自己是普通人,自己不能应付过于复杂的东西,于是,把自己要做的事分解成一些非常简单的小事。只要把这些微不足道的小事做好了,所谓的大事便也做成了。
我很喜欢读的书中,有几本书出自贝尔实验室,比如《程序设计实践》、《Unix编程环境》、《C程序设计语言》等等,每一本都是那么轻薄。这些书里面的内容读起来都是那么轻松,每一步做的事都让人觉得太过简单,但回过头来,可能你才发现,原来一些貌似很复杂的工作已经完成了。
曾有一段时间,我一直觉得自己掌握的东西不够复杂,为此,我总是惴惴不安。后来发现,但凡我学过的东西本质上都很简单,于是我想,到底怎么才能让自己复杂起来。读过那几本书之后,我释然了:做事本就该是做简单的事。如果你觉得复杂,多半是走错了路。
与高手共事,技术之外的东西,也许更值得学习。 -
2007-12-25
圣诞聊敏捷
在ThoughtWorks待了大半年,听的见的经历的最多的当然是敏捷。圣诞之夜,不妨整理一下现时心目中的敏捷。
提及敏捷,主要是两种反映,济世良药或是洪水猛兽,当然,也有人置身事外。其实,敏捷只不过是一种软件开发方法而已,与传统软件开发方法开发方法并无本质区别:敏捷也需要知道到底软件要干什么,所以,分析需求不可或缺;敏捷最终也是把软件提交给客户,所以,也要一行行把代码写出来;敏捷出的软件也会有错误,所以,测试阶段是不可缺少的;敏捷出的软件业务也会变化,所以,代码也需要维护;敏捷的情况下,为了在给客户展示,所以,偶尔也要加班……
如此说来,敏捷是不是和传统软件没有分别了呢?显然不是,任何事物一旦拉高到本质的高度,恐怕都没有区别了,就像“人”可以涵盖人这个物种,但事实上,人还有男人、女人、好人、坏人……等等众多区别手段。
相信大多数人有这样的感觉,念书的时候,总有那么一些怪物,看起来,玩得不比别人少,书读得不比别人多,但考试成绩总比别人。这样的怪物是怎样炼成的呢?说起来,很简单,他们懂得了一些学习的方法,所以,他们可以在同样的时间内,取得比别人高许多的结果。
做事,是有方法的。
好的做事方法绝对是大幅度的提高效率的。相对来说,敏捷就是一种好的工作方法。
敏捷重视人在软件开发中的价值,所以,在敏捷团队中,开发者在团队中地位很高,这让我们在心理上得到了极大的满足,于是乎,心情很是愉悦。在很多强调传统软件工程的团队中,开发者不过是一颗螺丝钉,一个可有可无的角色,受重视程度自然很一般,除了自力更生,心情上与敏捷团队不可同日而语。两种团队,我都经历过,差异极大。其实,无论在什么样的团队中,我们都要完成类似的工作——编写代码,但是,想必人人都清楚,心情对于一个人工作的影响。
除了完成工作,我们需要的还有发展。专业技能上的成长,除了自己摸索,更重要的还有吸收来自别人的营养。在传统的方法团队中,除了读书,只有偶尔大家的交流才能让人得到向别人学习的机会,大多数情况下,我们所能做的就是单枪匹马的孤军奋战。幸运的话,也许能够走上的正确的道路,大多数人必然会绕很大的圈子,更不幸者,也许就此失去了对软件开发的兴趣,或默默忍受编程带给自己的痛苦,或干脆离开这个是非圈。在敏捷团队中,过程本身采用的一些方法就是前人经验的总结,比如测试驱动开发。结对编程可以让人有机会观察别人是如何工作的,从中吸取养分,这些细节是几乎不可能从书本或是课堂学到的,比如,在ThoughtWorks工作之前,我从未意识到快捷键对工作的帮助如此之大,当看到其他人按键如飞后,我决定在今后的开发中,尽可能多的使用快捷键。在两个人共同开发的过程中,我们有机会看到自己同伴对于一个问题思考的过程,这对于我们的提高绝对是有益的,特别是和一些高水平的人合作。良性循环和恶性循环显然不是一回事。
软件是为人服务的。软件开发是为了将软件创造出来,而不是为了为难开发团队。如果你曾经在传统开发团队工作过,提到这一点,脑子中多半会浮现出“文档”这两个字。敏捷团队也会写文档,但我们写的是那些对客户有价值的文档,而不是把时间浪费在那些为了过级而编写而且完成之后便会束之高阁的文档。不给自己找麻烦是很必要的。敏捷和某些传统软件开发过程可以说是殊途同归,比如,CMM到了最高的级别,会向自适应的方向发展,而敏捷本身也是在过程中不断的进行自我调整,做到兵来将挡。当然,走到一起之前,二者的差别显得还是很大。
以上这些都是站在我——一个开发者——的角度体会的敏捷,显然,并不全面。某些人提到敏捷,还会有更加伟大的角度,比如,业务之类的,那不是我所擅长的。
既然敏捷如此之好,为什么敏捷团队还是如此有限?回到最初那个读书的比喻上,优等生的方法不见得是每个人都知道的。即便知道了,相信、学习到掌握还要有一个过程。敏捷是不是终极解决方案,我想不会,天外总有天,如果哪一天,我知道更好的方法,我愿意接受。
写下这些文字的时候,Ruby 1.9.0发布了,感谢Ruby开发团队带给大家的圣诞礼物! -
2007-12-17
2007 Away Day
第一次参加ThoughtWorks的Away Day。Away Day是公司给大家提供一个在一起交流放松的机会,一般会利用周末的两天,第一天有人各种各样的session,用以交流,第二天就是玩,用以放松。
Away Day,字面的意思是,一个离开办公室的日子,鉴于今年刚刚搬进新办公室,出于对新办公室的喜爱,今年Away Day第一天的活动就放在了办公室里。
Away Day,公司一下子变得很热闹。原来驻扎西安的大部队来到的北京办公室,原本分散在北京和西安两地的中国区员工终于团聚在一起。ThoughtWorks Studio的两个团队——CCE和Mingle也在北京扎根。此外,还有不少从其他办公室来参加Away Day的同事和已经签约明年准备加入公司的毕业生。当所有人坐到一起的时候,我才第一次觉得ThoughtWorks在中国已经有很多人了。依稀记得我当初面试时,二三十个人围坐在桌边的情景。不到一年,我们壮大了许多。老大在讲话中回顾了当年和黄亮谈offer时的情景,颇有些忆往昔的感觉。
第一天的session内容很丰富,技术、招聘、公司策略、创新等等,应有尽有,甚至有人还组织了一个世界发展的话题。虽然是一次内部活动,但内容一点都不含糊,而且,讲者与听众通常很熟悉,所以,常常会有一些精彩的问答。据说,Jon Tirsen在介绍Erlang的session上,问到讲演者不得不说自己还不是太熟悉。我也亲见了一个session里面,一个“why not”让讲演者沉默了半天。
我也同样做了一个session,话题是关于招聘中的code review。因为在ThoughtWorks工作这段时间,除了写代码,干得最多的就是招聘了,而code review做得尤其多,几百个总是有的,所以,略有心得。原本我考虑讲一个技术的话题,但后来一想,Away Day是一个轻松的日子,讲生涩的技术会让大多数头疼的。事实证明,在Away Day中,技术讲座的听众相对来说会少一些,比如,徐X那个关于用Ruby做interpreter的话题,只有为数不多的人在场。:)
对我而言,做session并不是一个最好的选择。因为当天我还是花了不少时间在准备上,所以,我只听了很少的内容,有些遗憾。我的session是当天的最后一个,因为前面人的超时,我不得不用绕口令的速度进行快速讲解,否则,耽误了大家的晚饭,后果会很严重。
结束了session,就彻底进入到一个交流的环节。无论是当天的晚饭,还是第二天的出游,感觉自己几乎一直在和不同的人说话。在ThoughtWorks,我们是鼓励和不熟悉的人进行交流的,因为指不定哪天大家可能就会在一起工作,即便是简短的交流,也会为未来奠定一个良好的基础。
虽然是第一次参加Away Day,感觉不错!







