聊聊结对编程 (By Jun Guo)

关于项目

      先讲讲我们的项目本身。这一次和我组队的是和我来自同一个学院的华平。

      这个项目来源于实际需要。大家很容易想到这么个场景,在公司里,有若干路电梯,而用户发出上下楼请求后,调度器就需要调度至少一架电梯来响应用户的请求。但是,如何调度电梯就成了比较麻烦的问题。

      简单的调度方法就是我们常见的做法——随机挑选一架电梯到达用户所在的位置,然后把用户运到目的地,中途用户可以进出电梯(如果电梯运行方向合适的话)。这种做法应付普通时段的请求效率不错,但对于上下班、吃饭高峰期就不大适合了。比如说,上班时期,绝大多数人都是从楼底乘电梯上行,只有极少数的人会在中间楼层发出上下楼请求。此时,我们就需要一个专门优化的算法来提高电梯的运行效率,从而缩短用户的平均等待时间。

 

      为了减少我们入手这个项目的时间,一开始秋丰老师就给了个框架。这个框架能很好地再现现实生活中的电梯调度系统,但其设计比较复杂。因此,一开始我和华平也花了较多的时间在看懂这个框架上。

      框架有个很奇怪的设计。它定义了电梯、乘客和调度器的接口,并采用工厂模式等方式来生成具体的对象,这看起来是很合理。但事实上,在读入测试数据时,框架直接采用具体类的Loader将测试数据反序列化为实际对象,这导致接口完全失去了意义——为了读入测试数据,你不得不使用特定的具体类,根本不能改动它们,更不能使用继承自接口的自定义具体类来进行操作(Loader产生的类型已经写死了)。

      唯一可以换成自定义具体类的只有调度器。但事实上,为了达到更好的调度效果,仅仅依靠调度器是不行的,我们还需要电梯能够提供更多的辅助功能(比如电梯当前正在响应的请求、接下去还要执行的请求等等),从而为调度器的调度算法提供更好的决策基础。也许你会argue说,这些东西完全可以记录在调度器里。但事实上,当一个电梯需要同时响应多个请求时,它可以根据这些请求的目的地,计划一条较好甚至最好的路线。如果把各个电梯所负责的请求都记录在调度器里,电梯计划路线时就需要从调度器取数据。此外,当电梯完成请求后,又需要将请求从调度器的请求队列中移除。这会让类与类之间的耦合性变得非常强。因此,提供一个“增强版”电梯势在必行。但正如前文所述,继承电梯接口来实现自己的电梯类是行不通的,能够做的只有继承给定的具体电梯类,或者用装饰器模式把它包起来,从而实现更多功能。经过漫长的argue,到最后我和华平决定用装饰器解决之。

 

      回到算法上。其实最近我和华平都比较忙,因此能够构思算法的时间也不多。往年有很多人在博客上提出了各种复杂算法,从动态规划+模拟退火到模式识别都有,看得我们两人心惊胆战的。By the way,到这一步结对编程就暂停了,因为我们认为如果两人都没成型想法就开始讨论,只会越搞越乱,更好的做法应该是两人都独立想一些完整算法,之后再做讨论和整合。华平使用了个贪心算法——综合考虑剩余容量和最近电梯之后使用贪心策略挑选电梯,这个算法在对付绝大多数随机数据时能有非常不错的效率提高。而我作为极简主义者,算法更加简单——每次开门后的5个Tick里,只对在第一个Tick进入电梯的用户所发出的请求进行响应,对在之后4个Tick进入电梯的用户所发出的请求全部暂缓。事实上,我采用这个算法的初衷是为了方便编程——只要加一个标记变量就可以完成算法;但后来发现,这个算法的效果还是挺不错的。对付上班高峰期的数据大概只要400 ticks(相对于原本的1200 ticks),对付下班高峰期的数据大概只要600 ticks(相对于原本的1400 ticks),可以说改进还是比较明显。其实仔细想一下,也可以大概理解这种做法简单但却能达到较好效果的原因——在第一个Tick进入电梯的用户往往已经在外面等待很久,而且人数比较多;而在后续Tick才进入电梯的用户是没有经过等待的,并且人数比较零星,所以我们应该优先响应在第一个Tick进入电梯的用户所发出的请求。

      最后,我们两人一起分析各自的算法,互相交流改进,并进行大量测试,把我们各自提出的算法相融合,得到最终算法。虽然不知道对比其他人的效果如何,但自我感觉调度效率还是挺高的。

       

      总的来说,由于这次项目代码量很少,需要敲代码的时间远远少于理解框架和设计算法的时间,代码行数也极少(调度算法少于100行),因此结对编程更多地体现为二人讨论,并没有显示出它能够提高代码质量这一优势。

 

 

结对编程吐槽

      注意,以下内容可能让结对编程崇拜者感到不适。如果您是结对编程崇拜者,建议现在就关闭此网页。

 

      在软件工程开发的各种实践中,有一种非常特别的开发方式:你有一个Parter,你们两人坐在一起,面对同一台显示器,敲打同一个键盘,挥舞同一个鼠标;你们一起思考,一起分析,一起编程。对,这就是Kent Beck在《Extreme Programming Explained》一书中介绍的结对编程(Pair Programming, Driver Observer Pattern)。

      这也许是各种实践中最具争议的开发方式了。如果有人问你,你喜欢Scrum吗,你多半会回答,“不怎么清楚,要试试看”;但如果有人问你,你喜欢结对编程吗,我猜你马上会给予明确的回复。喜欢它的人会觉得好处多多而且成本不高,不喜欢它的人会觉得讨厌得难以想象。喜欢与不喜欢都可以形成强大的阵营,两边都不乏重量级的高手。

 

      这一次的项目,按要求我们使用的就是结对编程。很不幸,在大一课程里被强制使用结对编程后,我对结对编程好感度就急剧下降。因此虽然这个项目用了结对编程来开发,但我依旧会吐槽结对编程。下面的吐槽与本次项目无关,仅仅是个人感受。

      结对编程里经常提到,两个人一起工作,使用电脑的人就不会去聊QQ,不会去刷微博,因此效率会变得更高。但事实上,两个人一起工作,也可能在一起交谈一些与工作无关的事情,比如谈下Surface可能的售价,聊聊Lumia 920的无线充电多牛逼等等,这反而分散了注意力,导致效率更为低下。

      虽然结对编程总是宣称两个人一起检查代码能够增强代码和产品质量,并有效的减少BUG,但也有很多人觉得,与其两个人一起检查代码,还不如一人写代码一人写测试。写测试的人可以完全不管另一个人的代码实现,从而避免两个人一起陷入思维误区,这可能让代码质量更高。也许有人会argue说两个人一起陷入思维误区的概率有多高,但其实你只要去看下ACM比赛的数据,看看每道题用同一个错误算法的人有多少你就知道了。

      另外,结对编程总是假定两个人能够和谐工作。但事实上,很多程序猿写代码时非常不愿意别人在他身后指指点点——无论这个人是大牛还是小白;很多程序猿们也不怎么愿意一边写代码一边和别人说明自己的想法,因为他觉得这降低了自己的编码效率。我就是这么一例。总的来说,人是一种非常复杂的动物,人的缺点和内心的阴暗面可能会比我们想像的要糟糕得多,而这些东西是可以让一切事物失败的。所以,正如《人件》所说,人才是软件开发中最核心,也是最需要花时间去关注的事情。而结对编程,更倾向于把两个人看成两台具有相同先验的和谐机器。

posted @ 2012-09-14 16:15  smart-code  阅读(557)  评论(31编辑  收藏  举报