首页 > 代码库 > 结对编程总结 1175 1176
结对编程总结 1175 1176
一、Pair Programing的优点和缺点:
1、 优点:首先,在独立编程的过程中,每人在各自独立设计、实现软件的过程中不免要犯这样那样的错误。而在结对编程中,因为有随时的复审和交流,程序各方面的质量取决于一对程序员中各方面水平较高的那一位(水平较低的那个有时也能发现一些关键性错误,毕竟每个人分析问题的角度是不一样的)。这样,程序中的错误就会少得多,程序的初始质量也会高很多,为以后省下修改、测试的时间。借用官方的总结如下:
(1)在开发层次,结对编程能提供更好的设计质量和代码质量,两人合作能有更强的解决问题的能力。
(2)对开发人员自身来说,结对工作能带来更多的信心,高质量的产出能带来更高的满足感。
(3)在心理上, 当有另一个人在你身边和你紧密配合, 做同样一件事情的时候, 你不好意思开小差, 也不好意思糊弄。
(4)在企业管理层次上,结对能更有效地交流,相互学习和传递经验,能更好地处理人员流动。因为一个人的知识已经被其他人共享。
2、 缺点:首先,高效的pair programing并不是一两天就能做到的,每个人都有自己的编程习惯,初期合作并不一定能实现结对编程的效率高过个人编程效率;其次,个人认为结对编程让滥竽充数的人也能获得同等的成果,对于他的pair来说是不公平的。从另一个角度来说,水平较差的那个人得不到有效的锻炼,依赖性增强。
二、此次电梯Pair Programing的个人优缺点分析
12061175:他是这次结对编程的核心,对算法实现的大部分工作都是他完成的,优点较多,以下就简单列举几点:1、编程能力强 2、对整个项目的理解与把握到位,全局掌控能力强3、热情耐心,对于队友十分耐心且又热情4表达能力强,自己的想法都能形象地表述清楚且易被理解。缺点:非要说缺点,那就是有点强迫症,追求完美。
12061176:优点:1、比较细心2、态度认真3、虚心学习,对自己不会的都虚心学习。缺点:比较懒散,依赖性较强,过分依赖队友。
三,由编程看待 Information Hiding, interface design, loose coupling 这些好方法
通过此次Pair Programing的实践,让我们对以上方法印象深刻,也收获不少,下面是我们的一些收获:
1、 信息隐藏(Information Hiding):对于面向对象的程序设计而言,信息隐藏是一种重要的软件开发手段,它与对象的封装(encapsulation)与模块化(modularity)密切相关。在我看来,信息隐藏使得一个类把复杂的、敏感的、一旦被外界捕获可能会引起不良后果的内容封装在自身内部,这个类以外的代码得不到此类信息(通过反射等手段可能对得到),以提高程序的安全性与健壮性。首先,在类中,定义的变量和方法可以再前面加上一个下划线"_"来标识,这是一个好的命名规范,可以避免无意中对私有成员进行赋值。类与类之间交换信息时,要交流私有变量时,要用事先设计好的方法来访问,这样如果我们在其它类里面调用另外一个类的私有变量,那么我们必须定义一个获得该类私有变量的方法;要在另一个类里面改变另外一个类里面的变量时,我们也要定义一个改变该类私有变量的方法。在C#里特别方便的一点就是有set和get,我们可以很方便的定义访问一个类私有变量的方法。
2、 接口设计(interface design)和松耦合(loose coupling):在我看来,这两个概念是相辅相成的,后者是前者的目的,前者是后者的实现手段。从街口设计来说,一个好的接口能够提供给后面的程序设计一个良好的框架,在这次电梯调度项目里,接口IElevator、IPassenger、IScheduler、IRequest,我们通过接口能很快的知道电梯、乘客、调度方案、请求都有哪些属性,要实现哪些方法,而不用关心具体的实现细节;这样我们的软件测试也变得更简单了。而从松耦合角度来看,在代码设计时,不用担心会破坏其它地方的代码。这种类与类之间依赖性低的设计方法,使一个类与另外一个类仿佛隔开了,它们之间只是通过消息来联系的,所以设计一类时,可以不用担心破坏另外一个类。当代码有改动时,可以不用大规模的改动我们的代码,我们只用定位于一个出问题的模块,然后对其进行更改就好了,而且能做到不改变其它模块的服务。这在MVC模式中体现得淋漓尽致。
四、DBC
刚刚看到契约式设计的时候我就想到了大二下期同本课程一样杀伤力巨大的课程OO,有段时间(包括最后考试)我们在写每一个类、每一个方法以前,都要先用注释写非常多的requires,在类里面要定义抽象函数、不变式等等,在方法中要对参数有requires,要注明该方法需要改变的非临时变量以及检测该方法运行前后是否满足类的不变式等等,这是我第一次接触到契约式设计(或者说契约编程)。
还是简单地梳理一下契约式设计吧,它有六大原则:
l 原则1 区分命令和查询。查询返回一个结果,但不改变对象的可见性质。命令改变对象的状态,但不返回结果。
l 原则2 将基本查询同派生查询区分开。派生查询可以用基本查询来定义。
l 原则3 针对每个派生查询,设定一个后验条件,使用一个或多个基本查询的结果来定义它。这样一来,只要我们知道基本查询的值,也就能知道派生查询的值。
l 原则4 对于每个命令都撰写一个后验条件,以规定每个基本查询的值。结合“用基本查询定义派生查询”的原则(原则3),我们现在已经能够知道每个命令的全部可视效果。
l 原则5 对于没个查询和命令,采用一个合适的先验条件。先验条件限定了客户调用查询和命令的时机。
l 原则6 撰写不变式来定义对象的恒定特性。类是某种抽象的体现,应当将注意力集中在最重要的属性上,以建立关于类抽象的正确概念模型。
另外,还有六大准则:
l 准则1 在适当的地方添加物理限制。尤其是那些需要限制变量不应该为void的地方。
l 准则2 先验条件中尽可能使用高效的查询。如果有必要,可以增加高效的派生查询,并在其后验条件中确保其与较低效的基本查询之间的等价关系。
l 准则3 用不变式限定属性。如果一个派生查询被实现为一个属性,则应通过类中不变式的断言保证它与其他查询保持一致。在一个类的开发过程中,通常应该先把断言检测的级别设为最高级,使先验条件、后验条件和不变式都得到检测;一旦通过了检测和测试,一般就可以降低断言检测的级别,只检测先验条件,以提过代码的运行效率。
l 准则4 为了支持特性的重定义,用相应的先验条件确保每个后验条件。这样就允许在子类开发过程中进行各种不可预见的重定义。
l 准则5 将预期发生的变化和框定规则这两种不同的限制分别放置在不同的类中。这使开发者在扩展已有类时享有更多的自由。
l 准则6 若有保密性要求,则违背保密性的查询只能被设为私有熟悉在契约中使用。这里的“设为私有”,是对“在契约中使用了该查询的类”之下的层次而言,至于类之上的层次,这个查询当然是可以使用的。
实际上,在本次电梯调度的编程中,很多地方我是运用了契约设计的思想,虽没有严格按照DBC的要求,但有类似的想法,举一个例子来讲,很多方法需要读电梯当前的方向,自然应该讲status.currentdireciton作为参数传入这些方法,问题来了:对调度电梯的方法来讲,通常只对电梯是UP或者DOWN进行调度(因为涉及楼层优先级的判断,因此必然要有明确的UP,DOWN),那电梯在开门时direction是NO怎么处理?因此我在将direction传入之前,增加了对No的判断,如果是No,就取电梯的historydirection,还涉及到刚开始时电梯连historydirection也是No,这也要做特殊处理,不再赘述。在无意中实际上我是做了dbc中的这样一个事情:调用者首先保证自己满足调用条件,才能得到正确的结果。
五、Unit Test测试(以我们的sumreq()方法为例)
首先说明一下:Sumreq()方法是我们用于实现统计:从电梯当前楼层到外部请求指令发出的目标楼层之间所需要停靠楼层的次数。用于计算某乘客从发出请求到电梯达到所需要等待的时间 。
Test1:
第一种情况是:电梯刚起步,我们设定的是这种情况返回0,测试结果如下:
Test2:
电梯顺路,且请求在电梯运行前方,
电梯顺路,从20层到达9层,中途会停下5次,与实际运行相符。
Test3:
电梯同向,但请求楼层在电梯后方,
Test4:前面的测试都是模块测试,所以没有考虑代码覆盖率,这下面的测试是对整个函数方法的测试,但也只考虑单方向的测试(即方向向下),因为方向向上的别什么区别,所以就稍微偷懒了一下,没有进行那部分代码的覆盖,所有覆盖率只有35%左右,考虑方向,覆盖率应在80%左右,差不多整个代码就覆盖了。测试覆盖的一个例子如下:
六、结对编程合作合照
最后附上结对编程合作照片一张,以示纪念!
七、算法
关于算法
乘客进到电梯以后会将自己的目标楼层告诉电梯,这与调度器无关,因此调度只需要处理外部请求。
乘客乘坐电梯耗费的时间分为两个部分:等待的时间和乘坐电梯的时间。相比于等待时间来看,乘坐电梯的时间是比较短的,并且在调度开始的时候,也无法确切地估计到电梯往后的运行状态,因此我们决定只通过乘客的等待时间来决定调度哪个电梯。
乘客按下的某一个外部请求,调度器分别算出每台电梯响应该请求的时间,选择最小的进行调度。我将先从大的方向来讨论这个问题:
外部请求对于某一部运行中的电梯来说,有三种情况,第一,同电梯的运行方向相同,且在电梯将要到达的楼层;第二,同电梯的运行方向相同,但电梯已经过该楼层;第三,同电梯的运行方向相反。
针对这三种情况,电梯响应外部请求的顺序是不一样的。第一,电梯顺路,路过时直接停;第二,电梯保持当前方向运行到最远端,反向运行到最远端,再去响应当前的外部请求;第三,电梯保持当前方向运行到最远端,反向运行至当前外部请求处。
从上面的讨论中可以看出,对于调度器来讲,需要知道当前请求的方向、来源,电梯当前的楼层,方向,以及待电梯完成的其他请求,这也是我们为elevator类补写的几个方法。等待时间同样也分为两种:电梯运行的时间和电梯在其他楼层停留的时间,后者的运算还要知道电梯需要停几次,这也是elevator中新加的sumreq方法要完成的工作。
涉及到代码细节的地方,在此也做一些说明:
- sumreq方法并没有简单地将电梯的外部请求、内部请求队列相加,而是根据前面提到的三个状态算出不一样的请求数
- 调度器应该是仅知道来源楼层的,所以只有当请求处在电梯不能到达的楼层时,不调该电梯去到相应楼层,这就存在一个试探性调度的问题:如果去的电梯不能到达乘客需要到的楼层的话,乘客再次发出外部请求,此时记录该乘客拒绝进入几号电梯,在下次调度时,调度一个另外的电梯给它。
- 对于电梯已经完成了所有内部请求,我们两个对于调度产生了一点分歧:是调最先发出的外部请求来运行还是调离当前电梯最近的外部请求来运行,我们首先尝试了后面的方案,调离当前电梯最近的楼层来响应,这样的结果不是很好,上班高峰的测试用例表现还不如bus算法,结果也能说明这个问题:第1000个人在50tick时按下的请求,得到响应的时间非常晚:
这也启发我们用前面的方案,按进队先后调度,情况好了很多:
这篇博客由林江和我共同执笔,到这里本次结对编程终于完了。哈哈哈哈哈哈
结对编程总结 1175 1176