到目前为止(2013-10-8),我们组的Pair Project已大致成型,根据老师作业要求中提到的各种要求,基本功能设计完毕,用例测试成绩虽不能说优异,但也算是我们利用了国庆长假,倾尽腹中所学,做了我们最大的努力,所以在此作一些关于这几天开发工作的总结。
组员
首先先说明我们的Pair小组组员:顾育豪,我们的项目总工程师;申开亮,也就是我,我技术不怎么好,也就是为什么博客的撰写工作主要由我一手敲完。
很幸运,我们俩住在同一个寝室,这里为平时两人的结对编程工作提供了极大的便利。
工作照片
结队编程
以前从没做过什么大的项目,什么java、uml的编程大作业虽说也是由团队协作完成,但绝对是没有结队编程这一说法的,都是有技术比较牛的一个队员全权负责设计、代码、测试全部技术工作,而其他队员则顶多做一些擦边球的散碎工作,甚至只为挂个名,总之也能混过去。
按照结对编程的工作模式,也就是两个人写一个程序,其中,一个人叫Driver,另一个人叫Observer,Driver在编程代码,而Observer在旁边实时查看Driver的代码,并帮助Driver编程。并且,Driver和Observer在一起时可以相互讨论,有效地避免了闭门造车,并可以减少后期的code review时间,以及代码的学习成本。
有实验证明,平均下来,结对编程时间花销比单人编程增加10%的时间,但也会比单人编程减少15%的代码BUG。如果再算上后期代码的维护和学习成本,结对编程比单人编程更有效率,还更为节省成本。无论是对开发团队还是对于Business,结对编程都会是非常不错的Programming Practice。
结对编程的优点:
- 程序员互相帮助,互相教对方,可以得到能力上的互补。
- 可以让编程环境有效地贯彻Design。
- 增强代码和产品质量,并有效的减少BUG。
- 降低学习成本。一边编程,一边共享知识和经验,有效地在实践中进行学习。
- 在编程中,相互讨论,可能更快更有效地解决问题。
结队编程缺点:
- 对于有不同习惯的编程人员,可以在起工作会产生麻烦,甚至矛盾。
- 有时候,程序员们会对一个问题各执己见(代码风格可能会是引发技术人员口水战的地方),争吵不休,反而产生重大内耗。
- 两个人在一起工作可能会出现工作精力不能集中的情况。程序员可能会交谈一些与工作无关的事情,反而分散注意力,导致效率比单人更为低下。
- 结对编程可能让程序员们相互学习得更快。有些时候,学习对方的长外,可能会和程序员们在起滋生不良气氛一样快。比如,合伙应付工作,敷衍项目。
- 面对新手,有经验的老手可能会觉得非常的烦躁。不合适的沟通会导到团队的不和谐。
- 新手在面对有经验的老手时会显得非常的紧张和不安,甚至出现害怕焦虑的的精神状态,从而总是出现低级错误,而老手站在他们后面不停地指责他们导致他们更加紧张,出现恶性循环。最终导致项目进展效率低下,并且团队貌合神离。
- 有经验的人更喜欢单兵作战,找个人来站在他背后看着他可能会让他感到非常的不爽,最终导致编程时受到情绪影响,反而出现反作用。
我们各自的优缺点
当然,这些都是结队编程所具有的普适性的优缺点。对于我这个从来没有过结队编程经验的新手来说,或许工作效率甚至不如自己单独完成那么高。一开始不知从哪里下手,两个人似乎也没什么默契,总是想不到一块儿,或者就是针对一个问题得不到一致的看法,导致工作没法往后继续,不过慢慢的熟悉工作模式之后,也总结了一些合作经验,感觉也越来越好,或许下一次就能更好的和他人合作了,下面就我个人的看法,以及征求得到顾育豪同学的认可,对我们各自优缺点做一些系统的总结。
对于我,从技术方面,也就是编程水平而言,学过c,学过java,压根没看过c plus plus,看过一点c#,当成java看了,唯一知道它是微软独家冠名发布的,有点一家独大的样子,这些都是从参考书上听说的,我只能保留自己的意见了,编程水平跟不上或许是软件项目中最不应该出现的缺点之一了;再来,关于程序设计这门技术活,我真不知道到底是归然于天赋,还是可以通过经验所得不断提高,总之这两方面我都是做得很不好的,这想必又是一大软件开发忌讳,所以这次的总工程师就非育豪同学莫属了。不过也并不是有多遥不可及,在挫折中成长吧,完成这次作业后,技术上还是有一定的提升的。
抛开技术层面,对于两个人合作来说,我觉得自己还是比较细心、比较缜密的,整个工作过程中也充满了热情,尽量不拖后腿,尽量给予希望,从最初的讨论设计、分工部署、辅助编程、测试等工作丝毫不懈怠,不推卸,我也算弥补了自己技术方面的缺憾。当然,这些也是育豪同学所具备的优点,当然他比我有更多的优点,至少他做的实际工作要比我多得多,能者多劳嘛,从开始的建模、后来的编码,他都起到了主导作用。
如果说他的缺点,据他本人所言,也是初次接触c#,这真是一个小小的灾难,得亏有其他语言的编程基础,很多时候都能触类旁通,他说这是缺点,就算是吧。其他方面,其实他比我更细心,更仔细,有时候这些可能会成为绊脚石。
Information Hiding
可以肯定,信息隐藏设计方法是面向对象设计方法的一大重要内容:每个模块对其他说有模块都隐藏自己的设计决策,模块应该规定并设计成为在模块中包含的信息不被不需要这些信息的其他模块访问。要利用好信息隐藏设计方法,首先得明确信息隐藏设计方法的意义,当我们开始考虑人的可变因素时,复杂度的来源则发生了变化。人是有着许多与生俱来的特质的,比如说:人是会犯错的,人同一时间可以处理的事情是有限度的。因为这些特性,人在应对软件自身所拥有的复杂度的同时,又带来了偶然的,与个人特质相关的复杂度。比如说:局部变量的名称可能碰巧和一个全局变量相同,而我们又误把这个局部变量作为全局变量使用了。信息隐藏则是减少这类偶然性复杂度的一个有效手段。从信息隐藏的程度上来看,对方法而言,局部变量最佳,类的成员或属性稍差,全局变量最差。局部变量把信息隐藏在方法之内,成员或属性把信息隐藏在类之内,而全局变量等于不做信息隐藏。
所以,在使用信息隐藏设计方法是,得明确模块独立层次以及信息隐藏程度对于软件实际设计的意义何在,信息隐藏做的越彻底,层次也必然就会越多,层次过多又会增加代码的复杂性,把握好各模块的结构设计,适当隐藏,适当降低复杂度,做到恰到好处为宜。
interface design
信息隐藏是抽象数据类型,类等之所以存在的一个比较主要的理由。但信息隐藏并不只在使用抽象数据类型和类的时候才有意义。定义接口的时候,甚至实现的时候信息隐藏同样可以发挥作用。
由接口自身的定义,我们联想到最简单的接口应用就是强制某个类必须具备某种方法,譬如说我要设计一个围棋程序,其中的“棋子”如果是一个类,那么星罗密布的围棋子其实都是自身的拷贝和翻版,因此我可以实现NET自带的IClonable类;又如NET自带的数据库类中由于需要消耗大量内存和磁盘开销,因此及时释放内存很有必要;此时我们可以实现IDispose接口来强制让这个类带有Dispose方法,用于销毁内部消耗大量资源的托管和非托管组件和实例等东西。
其次,实现了一个特定接口的类可以通过接口来调用。这就意味着对于客户端而言,无需知道接口内具体方法是如何实现的,只要知道能够实现这个效果是哪个类,直接用接口引用这个类的实体就可以了。这就是我们所谓的“面向接口编程”。
NET一些特定场景下大量使用到接口,譬如最典型的泛型List中Sort方法就是传入一个实现了IComparer<T>的接口——只要你把实现了这个接口的实例当成参数传入到此方法中,则Sort内部自动调用你的方法排序,相对于NET类库而言方法是黑箱的:它只需要结果,根本不关心过程。这种“屏蔽实现端,让客户只知道该知道的东西的”的设计理念还广泛地应用于各类OOP设计模式中——比如工厂模式,适配器模式等。
遵循这些接口设计原则,直接屏蔽一切不相干信息,而只对外开放主要属性方法的使用调度政策,使得接口设计功能足够,且不出现冗余。
loose coupling
紧耦合往往会使部件的维护和重复使用变得非常困难,因为一个部件中的修改就自动意味着其他部件中的修改。同理,在应用程序不得不适应变化了的业务要求时,紧耦合就要做额外的工作,因为一个应用程序中的一次修改将迫使开发人员对其他相连接的应用程序进行修改。
传统上,应用程序与应用程序之间或者应用程序与服务之间的连接是紧耦合的,如用CORBA(公共对象请求代理体系结构)那样。此差异是很重要的,所以我们在实际开发过程中,力求把模块间的耦合降到最低,而努力让一个模块做到精益求精。
Design by Contract, Code Contract
DbC、CC的核心思想是对软件系统中的元素之间相互合作以及“责任”与“义务”的比喻。这种比喻从商业活动中“客户”与“供应商”达成“契约”而得来。
这种设计模式有如下一些优势:
契约就是权利和义务的正式形式。
对于类的一个方法,都有一个前提条件以及一个后续条件,前提条件说明方法接受什么样的参数数据等,只有前提条件得到满足时,这个方法才能被调用;同时后续条件用来说明这个方法完成时的状态,如果一个方法的执行会导致这个方法的后续条件不成立,那么这个方法也不应该正常返回。
然而,这样的做法又有一些约束。
现在把前提条件以及后续条件应用到继承子类中,子类方法应该满足:
1. 前提条件不强于基类.
2. 后续条件不弱于基类.
换句话说,通过基类的接口调用一个对象时,用户只知道基类前提条件以及后续条件。因此继承类不得要求用户提供比基类方法要求的更强的前提条件,亦即,继承类方法必须接受任何基类方法能接受的任何条件(参数)。同样,继承类必须顺从基类的所有后续条件,亦即,继承类方法的行为和输出不得违反由基类建立起来的任何约束,不能让用户对继承类方法的输出感到困惑。
于是乎,遵循契约设计模式的一些特征,我们在设计类时,做了如下的一些考虑,对于类的一个方法,都有一个前提条件以及一个后续条件,前提条件说明方法接受什么样的参数数据等,只有前提条件得到满足时,这个方法才能被调用;同时后续条件用来说明这个方法完成时的状态,如果一个方法的执行会导致这个方法的后续条件不成立,那么这个方法也不应该正常返回。
Unit test
覆盖率:
实现算法
电梯分成两种情况进行控制,一种是电梯正在上升(或下降),此时判断乘客队列是否有顺路的,即判断发出请求楼层的高度是否高于电梯此时的高度,发出请求的楼层是否低于电梯的目的楼层,以及剩余载重量是否高于最大体重(或判断发出请求楼层的高度是否低于电梯此时的高度,发出请求的楼层是否高于电梯的目的楼层,以及剩余载重量是否高于最大体重)。如果顺路,则电梯的目的楼层改为发出请求的楼层。另一种是电梯停在某层,如果电梯里面还有人,则继续执行未完成的任务;如果没有人,则参照实际生活中的电梯,如果电梯之前是往上而且上面楼层还有乘客未接送,则电梯继续往上,否则若下面楼层有未接乘客,则电梯往下,若所有楼层都没有未接乘客,则电梯静止不动。