软件工程:结对编程1
结对编程人员:
徐钧鸿 学号 12061193
李睿琦 学号 12061187
一、结对编程的优点和缺点
优点:
(1)在开发层次,结对编程能提供更好的设计质量和代码质量,两人合作能有更强的解决问题的能力。
(2)对开发人员自身来说,结对工作能带来更多的信心,高质量的产出能带来更高的满足感。
(3)在企业管理层次上,结对能更有效地交流,相互学习和传递经验,能更好地处理人员流动。因为一个人的知识已经被其他人共享。
(4)结对编程的过程也是一个互相督促的过程,每个人的一举一动都在别人的视线之内,所有的想法都要受到对方的评价。由于这种督促的压力,使得程序员更认真地工作。结对编程“迫使”程序员必须频繁地交流,而且要提高自己的技术能力以免被别人小看。
(5)结对编程让两个人所写的代码不断地处于“复审”的过程。复审是不断地审核,提高设计和编码质量的过程,这样能够及时地发现问题和解决问题,避免把问题拖到后面的阶段。
(6)结对编程中驾驶员和领航员的互换可以让程序员轮流工作,从而避免出现过度思考而导致观察力和判断力下降。
缺点:
(1)结对编程是一个相互学习、相互磨合的渐进过程。开发人员需要时间来适应这种新的开发模式。刚开始的结对编程很可能不比单独开发效率更高。
(2)如果团队的人员要在多个项目中工作,不能充分保证足够的结对编程时间,那么成员要经常处于等待的状态,反而影响效率。
(3)当两个人的技术水平或经验不均衡的时候,那么水平较弱或缺乏经验的一方往往会成为旁观者,或者需要对方给予一定的讲解,这无疑拖延了编程的进度。
(4)当其中一人希望独自思考一段时间时,有可能会被认为是一种不合作的表现。
二、结对的成员的优点和缺点
姓名 |
优点 |
缺点 |
||
徐钧鸿 |
代码编写与调试经验相对丰富 |
编写程序时逻辑性、层次性强 |
体力充沛,可以长时间连续进行编码 |
在遇到非常规bug的时候会比较烦躁 |
李睿琦 |
接受能力强,能够通过搜索互联网快速学习不熟悉的语法内容 |
英语阅读水平较好,能够充分理解作业要求 |
善于学习,能够不断反思不断进步 |
完美主义倾向,容易将数学思维带入到工程实践中去,导致很多想法无法实现 |
三、关于利用Information Hiding, interface design, loose coupling等设计方法
信息隐藏(Information hiding)是程序设计过程中的一种隔离原则,可以防止用户接触到一个类的某些部分。一个程序模块可以将它的信息隐藏起来,对外仅仅展现出一种接口。当这个模块的具体实现发生改变时,只要保证它的接口不发生变化,则就算不修改模块外的其他代码,程序依旧可以正确执行。这种接口的设计保证了整个工程的稳定性。
所以在进行信息隐藏时,一定要认真地研究各个模块的接口的设计,设计得越恰当,当需要修改模块的实现时会显得更加容易。接口设计(Interface design)主要考虑的就是两个模块之间连接和发送信号的方式。
接口的设计要满足的一个原则就是松耦合(loose coupling)。当一个类为了使另一个类做出一些改变而包含一个指向它的指针的时候,这两个类是强耦合的。在这种情况下,当后者发生改变时,前者很难保持不变。而如果一个类包含的指针指向的不是类而是一个接口,当实现这个接口的类的具体实现发生改变时,由于接口没有改变,则不需要修改引用接口的类,程序依旧可以正确运行。松耦合的接口设计使得程序设计的可拓展性大大增强。新设计的类通过实现某个接口,可以轻轻松松地替换当前的某个类,而无需修改调用这些接口的类。
四、Design by Contract, Code Contract的优点与缺点
契约式设计(Design by Contract)是一种软件设计方法,它规定软件设计者应该为软件的各组成部分定义规范的、清晰的、可以实现的接口规格,这些规格的定义扩充了抽象数据类型关于前提条件、后置条件和不变式的一般形式。在这里规格的含义与通常意义上讲的商业合同的性质是一样的,它的本质就是一种契约。
契约式编程的优势在于我们只需要观察模块的接口规格就可以迅速了解到这个模块的功能作用以及接口形式。但另一方面,若契约被违反(有意地或无意地)则程序中则一定会有bug。
在完成作业的过程中,我们详细阅读了程序的接口和实现相关接口的类,从接口和类的内容中分析提取出规格,在修改这些类的时候我们依靠这些规格来实现代码,即通过规格来指导过程的再设计。
五、UML图
六、算法说明
其实在拿到整个程序的时候整个人够不好了。将近1000行代码的阅读+理解,再在一个很不熟悉的环境(C#)下进行编码,起始时间很辛苦的事情。不过最后看看这个过程还是很有意义的。
下面讲一下代码的实现思路
在看代码的时候知道,整个电梯分为乘客、电梯运行和电梯调度几个部分。而乘客和电梯运行这两个部分已经实现的相当完整,基本不用改动,而我们需要做的就是在Schedule类的调度算法中调用Elevator类的ReqStopAt()方法,让电梯停在该停的地方,以实现电梯调度。而在已经写好的Schedule类,有一个IRequest类的Queue,是装载当前时间点之前的发出的指令。而在老师提供的简单调度算法中没有使用,但是已经成功的将指令加入到队列之中了,所以我决定实用这个请求队列,来控制整个调度算法。
首先,电梯是不知道电梯外的人的目标楼层与体重的,这与现实相符。
在设计算法的时候,先假设不考虑代码的细节(比如电梯上升一层需要五秒这种一步一步的事情,当做直接5秒一步上来)。我们设计出来的算法是:
针对电梯外的指令x,发出楼层n
①如果电梯i在这个时刻恰好到达楼层n,并且一定要停下来,那么这次开门的时间原本就是要浪费的。所以就讲指令x分配给电梯i。
②如果不存在①的情况,但是恰好有电梯j路过楼层n,而且电梯方向与请求的方向相同,那么就停下来将指令x带走
③如果没有前两种情况,指令待机
针对电梯内指令:
既然乘客已经上了电梯,就一定要将乘客送到目标。而且在设计算法中我们设定电梯内指令的优先级大于电梯外指令(虽然这个条件会使代码达到不了最优平均时间,但是相对来说比较符合现实生活。而且,代!码!好!写!)。这样,电梯就会在同次同方向运行时将目标指令完成。
而在设计的过程中,由于电梯可以一次带走多条指令,而且还会有电梯内指令,所以我在电梯里设计了一个记录目标楼层的布尔型数组,用来记录此电梯需要在哪些楼层停下。这样,在之前的算法中,只需要将电梯需要停的楼层的序号对应的布尔型变量标记为TRUE(原来都是FALSE),在整个指令循环结束的时候进行判断就可以确定该电梯最近一个的目标楼层了。
而且,在指令集合里面,有完成的也有没完成的指令,所以我们需要给指令添加一个标志位,表示这个指令是否已经完成。原本我是想设计成0,1,2的标志位,分别表示没完成,已经或者马上就能完成(比如电梯在8层已经向上走,9层指令就是马上完成)和很快就能完成(用于优化,比如9层向上指令就可以选取8层或7层向上电梯,但是7层不是马上来,所以置为2)。不过由于优化的代码处理起来比较麻烦,加上时间紧迫,所以我在程序里将有把指令置为2的,但是作用于0相同。
对于空闲电梯的使用方法,我认为将空闲电梯转起来是非常不错的。因为,转动起来的电梯很有可能在分布相对比较平均的数据中,更容易快速搭载乘客。所以,当有电梯空闲时,我会让电梯走向最早的未完成指令的楼层。至于中间会不会被其他乘客搭载就看天命了。
还有一个关键点是对于乘客上电梯的条件判断。原始程序在两个地方都有判断(Passenger类的EnterElevator()方法和ElevatorStopped()方法)。但是,如果一条指令在ElevatorStopped()方法中被拒绝,在我的程序设计中这条指令不会回到调度器的指令库里。在这里产生了n多个bug,最后才发现在调试的时候,很多的错误就是在这里出现的,浪费的很多时间。所以,我在ElevatorStopped()方法只判断了电梯的停靠楼层是不是乘客的请求发出楼层和乘客的is_Inside和is_Arrived这两个状态,而将其余的所有判断送到EnterElevator()方法进行统一处理。