敏捷开发
软件开发中的理想与现实(五)——知己知彼,百战不殆
冬天改不了凶残的本性,唯一能躲过它的淫威的地方就只有实验室,所以,嗯,2月18日早早的从实验室开始了。
今天是培训的最后一天,终于我也和大家一起投入真正的开发之中,不过我更多的还是以一个引导者的身份出现。昨天大家已经各自认领了一些人任务,也做了一些事情,不过如何知道互相之间进展呢?先从每日会议开始吧!
一听到这个名字可能就会让人产生反感,嗯,每日会议,真是文山会海啊!其实不然。每日会议只有项目开发人员参加,一般情况下只会持续10分钟(当然,根据项目人数不同,时间也有多有少),而且为了让这个会议尽量简洁,所有与会者采用站立的方式开会,所以每日会议有一个别名:站立会议。每日会议的议题十分确定,只用回答以下五个问题就行:
自从昨天工作之后,你都做了些什么?
从现在开始到今天工作结束,你将做什么?
是什么阻碍了迭代目标的实现?
有没有任务添加到待办事宜中?
相对其他的团队成员,你是否学到了一些东西,或者做出了一些新的决定?(技术方面、需求方面……)
这些问题的答案应该是尽量全面而且简练的,不要将回答问题变成了技术讨论,如果要讨论技术问题,或者要详细介绍前些天的所学所得,那么可以在会后请感兴趣的同学留下来慢慢交流。回答问题的顺序一般是根据站的位置确定的,大家围一个半圆形的圈,白板作为直径,从最左边的同学开始按顺序回答(抢位子很重要,嗯,不过万一没有抢到好位置,临时要求反序发言也是不错的^_^)。回答问题和一般讨论式的开会不同,大家各自发言,只有很少情况下会引发一些简短的讨论,简单的说,嗯,大家玩过“杀人游戏”没,每日会议可能和“杀人游戏”中的自辩很像,不过气氛不用那么沉闷……
通过这样的交流,项目的引导者(就是我啦)就会知道现在的项目情况到底如何,是否遇到了困难,大家也可以明确一天的工作重点,把自己遇到的问题、学到的知识用最方便的方式进行交流,让信息在项目组之内快速传播到每个人。
10分钟,花的还是很值得的!
软件开发中的理想与现实(四)——兴致勃勃做计划
今天实在是很漫长,到重构练习完成,我们还有时间做下一个活动的演练:计划游戏。
作为一个最不专业的解释,计划游戏就是在现场客户、开发人员、相关负责人员的参与之下,分解、分配和估计任务的活动。之所以可以称之为游戏,因为这个活动充满了游戏性:由客户编制一些“故事卡片”,并初步标明一些优先级,用于描述自己的需求,然后开发人员估计它们;客户可以选择自己最想要实现的“故事”是哪些,并和开发人员一起商定能够实现的时间,开发人员通过不断的优化对故事的任务分解和协调每个人具体的任务来尽可能满足客户的要求。每个开发人员都有选择任务的权利,实际上,任务在最开始都应该是自主选择的,而不是强制指定的,就算选择任务的人并不是“最适合”的人。不过为了满足客户的一些急切的愿望,引导者应该能够兼顾兴趣和效率之间的关系。
根据比较官方的说法,计划是持续的、循序渐进的,每2周,开发人员就为下2周估算候选特性的成本,而客户则根据成本和商务价值来选择要实现的特性。不过实际操作中,到底要计划到什么程度倒可以由引导者来指定,例如,发现任务提前快完成了,当然应该就可以准备下一步要做的事情了。
在演练计划游戏中,我是作为现场客户存在的,我今天说的故事比较简单,就是我们项目中的一些前期分析的工作(嗯,其实准确来说,我不是在说故事,而是直接开始指定任务并要求分解了,但作为演练应该还是够了)。大家分解这个任务,并把小的任务写在白板上,每个人每选一个任务就对它进行一次估计,一会就分解完成。XP里面推荐的估计方法是通过“点数”来算的,但这个似乎比较难操作,所以我们是通过“理想工作小时数”来估计的。每个人每天有4个小时的理想小时,然后想想自己如果专心致志的做这个任务需要多少个小时。这个演练要持续一天,所以每个人能够做的就只有4个小时的事情,当选完4个小时之后也就是用完自己所有可用的时间,这样也就不能再多选了。另外,还需要为任务分级,这是从开发的角度上进行的估计,分为任务的架构意义、风险和重要性三项,每项有1到3的权值,3为最高。当所有任务分解完毕,而大家手上还有空闲时间,那么我们就可以请求客户增加任务(嗯,这种情况比较少);当所有时间分配完毕,而还有任务没人认领,那么我们就要考虑重新调整任务的认领人来获得更多空余时间,或需要和客户交流适当把一些低优先级的任务删减掉,保证总时间点在忍受范围内。
由于这只是一个演练,所以事情很简单也很顺利,我们恰好分解完所有任务,然后兴致勃勃准备投入到明天的工作中去。(噢,原来今天结束了啊!)
软件开发中的理想与现实(三)——用重构来清扫战场
2月17日的早晨非常寒冷,就算躲在被子里也可以清楚地感觉到,不过到实验室就不会觉得冷了(嗯,有空调就是好啊),所以,我很早就来了。
重新检查大家的代码,我有种想重写的冲动(呵呵),不过这正合我意,因为今天的工作就是清扫战场,做清扫的人当然是大家。
首先我把需要修改的内容列一下:
在算Prime的时候没有采用最优化的算法。我们最开始采用的方法是开一个bool类型的大数组,数组下标对应正数,然后不断用乘积的方法标出所有不是素数的数,最后再遍历一遍数组收集所有是素数的数。这种方法不是最好,还有优化的余地。
没有把算过的Prime缓存起来。其实Prime这个东西很乖,不管怎么算,一个数是Prime的那么它的性质永远都不会变,因此,适当的缓存一些数,可以提高不少效率。
采用vector存储得到的Prime。出于众所周知的原因,当vector大小改变时代价是很大的,换一个其他东西作存储或许会更好。
好,准备好问题当然就可以开工了。
作为一种优化性质的修改,最关键的就是保证原来已实现的功能不能消失、接口不能够改变。这在原来真的挺难做到(嗯,我很讨厌那种一波未平、一波又起的解决问题方式,那是一种折磨),不过现在有了单元测试用例做保证,那就没有任何疑虑了——只要通过了如此充分的单元测试,原有的功能当然没问题啦。OK,麻烦一个个被解决,无论是简单问题还是复杂问题,我们始终可以很放心的对原来的代码动刀子。
不过这种优化只是最初级的重构(哦,这就算是重构啊),比较需要勇气的还在后面——嗯,我这个合格的客户又要发挥作用了。
新的需求:CPrime类要能够处理负数的情况。
约定,如果一个数的绝对值是质数则这个数就是质数。
传入CreatePool的max是质数的绝对值范围,也就是找出所有大于等于-|max|并小于等于|max|的所有质数。例如,CreatePool(-5)则会把-5、-3、-2、2、3、5都放到Pool里面去。
传入GetPrime的index如果大于等于0,则是从最小的质数开始按从小到大的顺序获得质数,0代表最小的质数,如果小于0,则意味着从最大的质数开始按从大到小的顺序获得质数,-1代表最大的质数。例如,CreatePool(-5)之后再GetPrime(2),拿到的是-3,GetPrime(-3),拿到的是3。
不要问我为什么会有这种需求,反正就是要做了。嗯,其实问题也不大,大家其实一看就很明白,毕竟我们通过刚才的练习已经很明确如何做功能上的重构,只不过这次我们需要把已有的测试用例先一个一个按照新的需求进行改变(比较繁琐,需要勇气来做这件事情)。嗯,一次全改完还是分批改呢?我觉得可以根据测试用例的类型来分批改变,这样我们依然可以一小步一小步的可靠的开发。
虽然通过这些练习并没有了解多少重构的理论,但是我们已经有信心在未来的开发中运用重构的方法来清扫以前留下的麻烦。
参考文献:
[1] Robert C. Martin,敏捷软件开发:模式、原则和实践,中国电力出版社,2004
软件开发中的理想与现实(二)——让测试赶着我们的步伐
2月16日上午,第一天的培训开始。
首先当然应该说说单元测试的必要性,我很欣赏JUnit In Action这本书里面列的几条理由:
带来更大的测试范围。单元测试能够更精确地发现问题,能覆盖更广泛的情况,当然,使得项目更可靠。
带来团队协作的可能。单元测试能够让我们写一点测一点,保证每次提交的质量,而且,团队协作时要是出了问题,找起责任人来也要方便得多。
防止衰退,减少调试。好的单元测试可以带来自信,也给予我们重构的勇气,同时作为一个副作用,一组好的测试用例能够精确的定位问题,我们或许就省去很多调试的时间。
使得重构可行。没有单元测试,谁知道我们是在做重构还是在拆房子呢。
改进实现设计。这个很意外!为了方便测试,开发者会开发出更容易测试的代码,往往这就意味着这些代码更容易维护和更改,而且使得被测函数变得小巧灵活,易于调用。嗯,如果我们真的认真使用一下自己做的东西之后应该就能体会“愚蠢的客户”到底是怎么回事了。
当作开发者文档来用。测试用例就是这些函数的API!这真是一件奇妙的事情,我们早该想到这点的。
非常有趣。是的,非常有趣,试试就知道了!
今天的重点是测试驱动开发的体验,为了方便实施自动单元测试,一个成熟易用的测试框架当然必不可少。我们项目采用C++开发,CppUnit当然成了最佳选择。
CppUnit非常好用,特别是它的帮助文档中还有贴心的“CppUnit Cookbook”和“Money, a step by step example”,使得我们很容易入手,不过真正麻烦的是:何时做测试、如何选择测试用例和如何选择测试的粒度。
既然是测试“驱动”开发,那当然是测试先于开发。具体而言,如果我们有一个类,要实现一个Add方法(功能和它的名字一样),第一步我们会这么做:
class CAddImpl
{
public:
// Add two numbers
int Add(int first, int second)
{
return 0;
}
};
然后呢,当然就开始测试(嗯,这么简单的函数居然都不一步写完……是的,这只是举一个例子):
class CTestAdd : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE(CTestAdd);
CPPUNIT_TEST(TestAddNormalCase);
CPPUNIT_TEST_SUITE_END();
// Test add, normal case
// 1 + 2 == 3
void TestAddNormalCase()
{
CAddImpl add;
CPPUNIT_ASSERT_EQUAL(3, [...]
软件开发中的理想与现实(一)——先把观念引进门
首先要说明的是,我在软件开发中还只是一个小小的菜鸟,只不过因为有幸逮到一个进入大公司实习的机会,亲身体验了CMM4中一些“规范”的流程、“正确”的做法,自己也出于好奇的看了一些相关的书,所以开始渐渐有些自己的想法。现在,回到自己应该呆的小团队,总觉得可以把自己的些许想法变成点什么实际的东西,最好还能在这团队里留下些什么,也算是不枉费我这段经历。所以,当我回到这个团队时我就开始策划如何实践自己所想的东西、什么东西可能适合这里。
为了获得更多可利用的资源,我提前和刘老师(就是团队负责人了)谈了自己的想法,她非常的赞同我做这样的实验,并承诺提供一切的方便,我当然非常感动,而且也很有责任感。
说来也巧,正好团队里面有个项目缺人却不缺技术,也就是说,里面存在大量的重复性劳动,如果用心分析,完全可以写一个自动生成代码的程序。我觉得这是我做实验的好机会,因为在这样的项目里面,我可以找到现场客户,并可以和他们随时交流,而且,刘老师还分给我一间独立的房间,让我带领一个三人小组随意使用这个房间的资源。白板当然必不可少,随处可贴的便条纸当然必不可少,无阻碍的工作环境当然必不可少,由于刘老师的支持,很轻易的,我获得了所能想到的所有东西。接下来,自然是要实现我的一些想法了。
2月15日晚,项目正式开张。
大家都还不了解我的想法,当然无法按我的想象开始工作,为此我准备了为期三天的培训,为大家介绍我所要引进的东西。它们列在下面:
测试驱动开发
迭代开发
每日会议
在这三点中,我最看重的是“测试驱动开发”,我觉得这一条最难以贯彻而且最能够改变人的观点。如果接受了这个思想,其实也就为后续“有序”的开发打下极好的基础。所以今天晚上,我将把我所了解的测试驱动开发说给大家听,作为以后的一个铺垫。
为了阐明测试驱动开发与传统软件开发方法的不同和优点,我给大家提了一个有趣的问题:
“如果我现在是你们的客户,需要你们画出我心中的刘老师的样子,你们会怎么做?”
大家各有想法,最容易得到认同的是:先画出刘老师的样子,然后给我检查,并在我的指导之下进行返工。
这确实是一个好办法,但我认为并不是最好的。因为把图先画完再交给客户,有可能造成前期投入太多无用成本,况且,我们的开发人员其实并不是素描专家,只是刚刚能完成工作而已,而且还有一个更关键的问题,其实我这个客户自己可能都不清楚心中的刘老师应该是什么样子(我真是一个合格的客户啊),我仅仅是有这个需求而已!如何让客户一开始就知道正在开发的东西是不是自己要的东西呢?我的回答是:
“可以考虑创建一些‘标准’的五官,并在客户面前进行‘组合’的工作,这样就可以降低开发难度并让客户及早的参与进来;当确定了一些五官之后,我们就可以在已经确定的框架上进行细化工作,即使客户发现五官和细节配合的不好,由于五官之间耦合很小,我们可以轻易的拿出另外的标准五官。”
我认为,针对现实的情况,这是更好的做法。其实我一说完大家就懂了,这不就是刑警们干的事情么?
对!他们作为这个项目的开发人员再好不过了,虽然他们确实只是刚刚能够完成工作而已,但是他们往往能够成功的完成工作。这是方法的威力。
从某种意义上来说,这就是测试驱动开发所能带来的好处,它可以让每一步更为坚实、让客户更加满意、让工作难度降低、让模块耦合下降、让整体框架相对稳定、让额外投入变少……不止这些表层的东西,它更能改变一个人的编程思维,让我们从“分析式”的开发思维转变成“归纳式”的开发思维,这样,我们的想法就更加贴近客户了……
看大家似懂非懂的样子(嗯,其实我也不能算是懂得这些道理),我知道,我只有通过真正的编程体验才能够让大家了解什么是测试驱动开发,如何开发。是的,我已经想好了,明天就会开始这些演练。
参考文献:
[1] Kent Beck,测试驱动开发,中国电力出版社,2004.3
[2] Alistair Cockburn,敏捷软件开发,人民邮电出版社,2003.11
[3] http://www.extremeprogramming.org/
软件开发中的理想与现实(引子)
软件开发实在不应该是一个令人厌恶的工作,而更应该像一种艺术家的创作,充满新意和乐趣。可是,我看过不少软件开发者却一直在写另自己都厌恶的代码,做连自己都不敢正视的测试,最后在项目完成时长叹一口气,将自己的成果束之高阁、不敢再碰。
造成这种窘境的根源在哪里?是谁让开发人员做出连自己都感到厌恶的东西?
答案是多样的,这不完全是开发人员的错,但开发人员自己应该反思,自己真的有想过在开发中主动避免这些窘境出现么?或者说,开发者知道怎么做会更好一些么?
这些答案往往是否定的,至少在我身边的开发人员都是这样:在迷茫中开发、在迷茫中迷失,有时看到一些救命的稻草就死命的去抓,但是最后只能适得其反。他们还不懂的如何选择合适的方法。
其实我也不懂,不过至少我有理想(不知道它是否也只是稻草而已),我想用一些足够客观的实验来验证书上所说以及我所理解的是否可行,是的,我想在自己所在的小团队中看看软件开发中的理想与现实。
这,也就是我所希望在这里记录的东西。
现在, 我的实验项目还在继续,所以我只能在这里不断聊聊自己在不同阶段的不同感受。我一定会犯错,其实我已经犯过一些错误了,但是无所谓,我正希望通过这些好的坏的结果来修正我的观念,说不定最后,我真的能懂得如何为自己所在的小团队量身定做一个合适的开发方式。这也是我的理想之一。
附:实验项目情况简介
开发人员:4人
开发时限:总时间,1年。
开发环境:Windows平台,Visual Studio.Net 2003,使用C++的STL和boost库编写基于命令行的程序
测试环境:CppUnit作为单元测试平台,自行设计的 IntegratedTestTool 作为系统测试平台
主要运用到的实践:测试先行、迭代开发、每日会议、自动构建、重构
其它情况:经过4次迭代,现在已经完成一期的工作,并通过验收。
