-
2008-05-12
客户来了
敏捷软件开发中,与客户的沟通是一项必不可少的内容,因为只有客户才知道他们自己想要的是什么,只有他们才真正了解自己的业务流程。于是,在ThoughtWorks,客户来访是很常见的,这不,我们的客户今天就来了。
一大清早,客户就到了办公室,他们给我们做了一个关于项目的介绍,让我们有机会聆听一下真正使用这个系统的人究竟如何看待这个系统,他们为什么会有这样的业务流程,在他们看来,哪些东西才是最有价值的部分以及他们希望这个系统未来会在他们的工作中起到怎样的作用。虽然在这个项目也工作了一段时间,但我也只是知道有哪些东西,对于为什么是这样,完全没有概念。今天客户的到来算是给我们补上了欠缺的一课。
对于开发者而言,客户到来的作用很直接的体现在某些细节问题的确定上。通常,我们有了问题会问我们的BA(业务分析师),但BA不能给出答案的部分,就只能等待客户的回答了。原本因为时差的关系,一旦涉及到客户,这个过程至少需要一天。而如今客户就在身边,一旦我们有了问题,几乎瞬间就可以得到解决,开发周期大大的缩短。当然,客户不会一直在这里,所以,趁着客户在的这几天,我们要充分发掘他们的作用。
以前也曾经历过客户到来,但这次给我留下的印象却特别深刻。因为,客户让我们近距离的接触到他们的业务。
我们的项目是一个关于宾馆审计的项目,简而言之,有人制定标准,有人按照标准进行检查。虽然听起来很简单,但是系统里面的概念却离我们很远,很难想象究竟是什么样子的。今天,客户就带着我们亲身体验了一下做审计的感觉。
我们去的是客户旗下的一家五星级宾馆,宾馆的总经理带着我们在宾馆内部进行参观,过程之中,还为我们解释了很多真正的检查过程中会出现的问题,在这个过程中,一个个曾经因为写程序而听说过的名词不断的出现在耳边,那个曾经遥不可及的概念,一下子变得异常清晰。上午的讲解和晚上的参观让这个系统变得更加真实了。以前只是住过宾馆,还从来没有从其他角度想过问题。今天的参观,明显是抱着“找茬”的心理。顺便说一下,我们的“审计”还真起到了一定作用,有人在宾馆的游泳池边上的宣传牌上发现了错别字。
客户到来,还有一个很直接的好处,那就是,给了我们一次team building的机会。即便单从这个角度说,我们也总是欢迎客户来访的。^_^ -
2008-03-28
半路出家
不知道算是幸运,还是不幸,我参与的多数项目,如果不是从头开始,我也是最开始加入的几个人之一,所以,我通常对项目的来龙去脉都比较清楚。这次,我有机会尝试一下在一头雾水的情况下开始一个项目。
这个项目已经进行了超过一个月,因为客户需要在赶在最近的一次发布之前,增加一些功能。老板按照目前的进度估算了一下,如果不加人,这个任务很难完成,于是客户很合作的同意增加两个人。就这样,我成了临时工。事实上,在这个阶段进入,项目早就过了最初的阶段,远远望去,没有几百也有几十的程序文件屹立在那里。虽然有最熟悉项目的人为我们介绍了项目的背景和架构,但这些宏观层面上的东西,对于编写代码这样“微观”的操作而言,几乎是没有任何直接的帮助。作为一个新人加入到项目,除了发呆,我还能做什么呢?
回想在以前公司的情形,大多数新进项目的人多半是得到一大堆文档,然后,有人语重心长的说,先把文档看了吧!且不说这些文档这种东西几乎写出来就过时,单单读完这些文档就需要花费很多时间。运气好的话,还有代码可以对应,但千万不要指望你能够一下子读懂这些代码。也许,你会想找别人来问,如果不出意外,大多数人都会不明就里的非常忙,忙到有时间上网聊天却没有时间给你讲这些东西。事实上,很多人即便自己做了很多东西,也不一定能够清晰的描述出来,所以,即便找到一个“闲”人,成功从他那弄明白这些东西的概率也很低。其结果是,我们经常会看到一些人在那对着电脑上的文档发呆。
在ThoughtWorks,我也恨不得找到一堆这样的无聊文档来打发时间,以此来享受偷懒的时光。遗憾的是,完全没有这种机会。因为那些“文档”会被视为浪费,这在以消除浪费为己任的敏捷来看,是无论如何不能接受的。于是,我在找不到任何借口的情况下,进入项目的第一天,就开始写程序了。不了解项目,怎么写程序?这是个问题。
敏捷实践中,有一个叫做Pair Programming的,从字面上来看,就是两个人一起开发。对于ThoughtWorker们来说,Pair是一种常态。所以,我在开发时也会有一个Pair,虽然我对项目一头雾水,但我的Pair已经在这个项目上工作了很长一段时间,所以,他很清楚这个项目的一切,差不多一切,因为代码是集体所有,所以,他在开发过程中会接触到各个部分。
拿到我们要做的Story,我的Pair会结合这个Story给我介绍上下文,并结合代码大致描述一下我们要做的事情。虽然在这个项目上我是新手,但我并不是对编程一无所知。有了这些基本的信息,我至少对我们要做什么,以及如何来做,在心里已经形成了一个大致的印象。刚开始时,基本上是我的Pair在主导开发,一边做一边告诉我,我们已经走到了哪里。渐渐的,我已经对我们在做的代码有了一些认识,开发也开始由一个人主导转向两个人讨论。随着开发的深入,我也发现了现有做法的一些不足,于是,我提出对代码进行重构,并给出了自己的分析和建议。我的Pair在听了之后,认为这是一个可行的建议,于是,我们毫不留情的将那段大家看着不舒服的代码改掉,这段代码从此清净了。
这就是在加入项目前几天所做的事情,虽然我目前还不能对整个项目有个很好的把握,但是,我相信,我已经开始在这个项目中起作用了。我想,Pair Programming是主要原因。正如前面所说,虽然我对项目很无知,但我的Pair很好的弥补了我的不足。正是两个人的协调工作,让我可以在对项目没有完整认识的情况下,可以很快入门,以最快的速度融入到开发之中。曾几何时,我对Pair Programming的认识还停留在大家一起写程序和知识分享上,原来它对半路出家的人帮助也很大。虽然《人月神话》教导我们说,加人起不了很大的作用,但Pair的方式至少可以在相当大的程度上发挥新人的价值,削弱加人带来的负面影响。
-
2007-09-17
闲聊C++单元测试框架
今天下午,和Darwin聊了一下C++单元测试框架,主要参考对象是CppUnit和CxxTest。
表现形式
因为C++不支持reflection,所以,必须要做一些额外的工作,让框架知道相关内容的存在。CppUnit的做法是用宏进行注册。这种做法要求我们每添加一个测试,就要考虑用相应的宏进行注册,这种做法很繁琐,最大的问题在于由于疏忽而遗漏,这种靠人工保证的东西不可靠。在这点上,CxxTest做得要好一些,有一个专门的脚本做这件事。通过这个脚本扫描这个自己编写的文件,生成一些新的文件,完成这个工作。从代码的表现力和可靠度来说,要好得多。唯一的问题是引入了一个脚本,而且这个脚本一般是由某些动态语言写成的(目前的CxxTest有Perl和Python的脚本),从而引入了对这种语言的依赖。不过,由于C++语言本身的限制,从接口的角度来看,这种做法已经很不错了。
语法
有一种C++的单元测试框架叫TUT,Template Unit Test的缩写。顾名思义,它是用模板完成的(其实,CppUnit和CxxTest都有模板的部分)。随着C++编译器的进步,在大多数情况下,模板都是可以顺利通过编译的。但是,不要忘了,还有一种环境叫嵌入式,那里的编译器基本上还是很原始的,模板并不见得能够顺利的通过编译。
此外,模板还会带来另外一个问题,编译时间的增长,相信有过模板编程经验的人都会对此深有体会。编译时间增长意味着什么?我们接下来讨论。
编译时间
有一种敏捷实践叫做测试驱动开发(Test Driven Development)。测试驱动开发的基础是单元测试。测试驱动开发希望达成的一个目标是快速反馈,所以,站在C++语言的角度,如果执行时间受限于代码本身无法缩短,那么我们希望编译时间尽可能短,这样,才不会把生命都浪费在等待代码编译上。
除了刚才提到的模板问题之外,CppUnit会把所有测试编译生成一个可执行文件,这意味着什么?几乎修改任何一个文件都会造成这个文件的重新生成。随着目标文件的增加,这个过程时间就会增长。相对于修改范围(可能只是某一个文件),还是显得有些长了。为什么Java语言不会存在这种现象?因为Java是动态连接的,所以,Java生成.class就结束了。对应到C++上,这只是完成了目标文件的生成,而在C++我们不得不再进一步生成可执行文件。从道理上,CxxTest可以为不同的测试文件生成不同的可执行文件,不过这么做又少了总体的过程,统计起来又显得心有余力不足了,而且通常不会这么做。
个人而言,对这几个单元测试框架都不是非常了解,如果前面的讨论存在谬误,欢迎有识之士指出。 -
2007-07-04
消除浪费的敏捷
敏捷的核心:消除浪费,走向精益
谈到敏捷,你会想起什么呢?测试驱动开发?结对编程?我刚到ThoughtWorks不久,gigix就和我提到过,关于敏捷消除浪费的观点,说实话,我一直不太理解这个观点。不过,随着在ThoughtWorks体会的点滴逐渐增多,我慢慢的体会到这句话的含义。
举个简单的例子,为什么要做测试驱动开发呢?从字面上看,将“测试”放在前面,提高了测试的重要性,告诉人们应该注意这一重要但经常被忽略的事实,这一点对于保证软件质量来说,确实是应该的,再有单元测试的存在让自动化回归测试变得容易了许多,还有写测试的价值会让人从不同的角度考虑设计,也许会让设计的耦合性大大降低。人们通常想不通的一点是,为什么要先写测试。如果只是单纯的交换编码和测试的位置,实际上并不会带来太多的好处。那该从怎样的角度理解测试驱动开发呢?测试代表着什么呢?需求。软件开发的目的就是为了满足需求,如果能够把需求确定了,编写代码满足它就相对容易多了。从需求出发,按照需求编写满足需求的最简单代码,而不去做许多额外的工作,这就是测试驱动开发不同于传统编写方式的地方。按照传统的方式先写代码,后写测试的话,我们的测试代码需要做的工作尽可能覆盖代码的路径,而为了覆盖面尽可能大,我们就需要花费许多额外的努力,这就是浪费。
敏捷的思想不仅仅体现在测试上,在这两个月左右的时间里,我对设计的理解也开始发生了一些转变。曾经我对设计的理解是设计一个能够应对未来变化的美好结构,但事实上,这种竭尽全力设计出来的“好”设计总是轻而易举的被现实问题打败。为什么呢?因为这个设计是想出来的,并不是基于实际的需求逐步演化出来的。在ThoughtWorks的招聘流程中,会有一个编写程序的过程。在这个过程中,我们总会听到应聘者说他的设计是为了应对将来的变化。但事实上,这样的变化是否存在呢?不一定,多半是没有的。所以,许多看起来比较华丽的设计完全经不起推敲。其中的一个例子是,为了面向接口编程,设计一个接口,却只有一个对应的实现类,本应枝繁叶茂的继承树显得十分单薄。当然,这里并不是说面向接口编程就不好,但当只有一种情况时,就做这种抽象显然是一种过度设计,过度设计就意味着浪费,在这个例子里面,至少需要多付出编写一个接口的代价。也许你会说,未来如果真的出现第二种或者更多的情况,那该怎么办。很简单,等真正出现了再做也来得及。
当然,问题并不绝对。一个例子是在数据库的设计通常我们会提前做出。基于前面的讨论,也许有人会认为这么做不太敏捷,恰恰相反,因为随着项目的进行,数据库修改的成本会越来越大,所以,我们宁肯在项目的前期多做些功课,以保证后面尽可能不去或少去修改它,以便减少浪费,所以,这种选择也是敏捷指导下的选择。
也许,这种讨论会给人一种人嘴两张皮的印象,其实,这和做许多事一样,就是一个度的把握。所以,很多人都看了一些敏捷的书,但用到实际中,却总觉得不是那么回事。如果开头的那篇文章是写给各种各样的管理者的,那我这篇算是写给程序员的吧!







