本次项目进行结对编程的训练。
项目要求 http://www.cnblogs.com/jiel/p/3997895.html#3045439
结对编程人员:12061228晏旭瑞、12061203黄可嵩
一. 关于结对编程
虽说编写过的代码量并不算太多,远远没有达到大一时c语言老师要求的十万行,但是毕竟也接触编程两年多了,这次确实第一次进行结对编程,或者说是合作编程。
本次结对编程的过程和结果都跟我预想的差不多。由于两名编程人员都有较为严重的拖延症,因此给了三周时间去完成的任务,直到截止日期前不到一周的时间才开始阅读给定接口以及基础代码,便于接下来的增量开发。由于代码量并不小,因此都不太想看,而是上手就来编程,并且是各自编程,而并不是之前老师上课所说的你编一段我写一段。每当一人想要去帮助另一人一起编写代码,他就会发现自己看不太懂对方的代码,对方也解释不清楚,于是还是各自为政。最后在一人越改错误越多的情况下,另一名人员才放弃希望,自己开始编程,并最终在截止时间之后(两人都睡过头了)提交了一夜赶制出来的代码,此时还没有完成单元测试,没有制作出UI界面,甚至都还没有进行优化。可以说这第一次结对编程是一次相当失败的编程经历,但同时也给了我一些启示。
首先是对于重要的任务制定计划。既然事情多,那么就要分出主次,尤其是对于过期之后就难以补救的事情,比如作业deadline,以及人的身体健康,就像我现在凌晨还在这里写博客一样,这都是事后诸葛亮,而事前没有准备好,导致了实际上更大的损失。
第二是明白合作的重要性。所谓结对编程,团队编程,这些概念的提出并非空穴来风。像我们这些大学生,平时都是自己编程,不愿意把自己的算法分享给别人,也不愿意别人来看自己的代码,更懒得去看别人的代码,都认为自己是最棒的,自己的程序只有自己才能看懂。这实际上是不懂得团队合作重要性的表现。刚刚我还在邹欣老师的一篇博客里看到,微软的创始人,苹果的创始人,facebook的创始人,c语言和unix的创始人,等等许多计算机大牛,他们都是经历共同协作才开发了震惊世人的计算机产品,让人们牢记并且敬仰。连这些大牛都需要合作,何况我们呢?结对编程在理论上来说能够充分利用二人的优点,集思广益,加快算法设计和代码编写的速度,并在后期代码优化以及测试的模块具有推进作用。虽然实际上可能二人并不一定能合作的这么好,但是两个人合作的确是能够在某些时候产生不一样的效果,比如产生启迪以及灵光闪现,很多想法都是在讨论中产生的,包括我们这次并不优秀的算法,但是由于讨论,却产生地更加快了。这不得不说是合作的好处。
第三是明白代码优化的重要性。其实我们二人本次编程的时间很有限,大部分都是光说不练纸上谈兵,在谈论算法,争论谁的更好,一段时间之后互相不理开始自己干自己的,然后又开始讨论。可以说,我们在代码设计的初期就在考虑优化的问题,一开始就想获得完美无缺的算法,以致弄得非常复杂,最后两个人都绕不清了,这也直接导致了其中一人连续三四天通宵写调程序,不知道把程序重写了多少遍,但是却是改好一个错误又出来新的,当然这也有他埋头写程序听不进另一人的意见的原因。最终还是不愿意复杂化,而是遵循傻瓜调度的另一人的算法完成了基本要求,甚至还产生了比预想的良好的结果。我认为,一开始确定一个算法是好的,但是设计不应该占据太长时间,毕竟很多问题只有编写了程序才明白,很多没有考虑到的地方只有在调试程序的时候才会被发现,也就是说,代码优化是重要的,是一个逐步的,与实践相辅相成的过程。
虽然这里互相批评或者说自我批评了这么多,说二人合作不够,准备和重视不足,懒惰不思进取等等原因导致了结果的不优秀,但是两名人员在算法设计与讨论方面,或者说算法设计,确定算法框架之时,还是开展了一段时间的合作。同时在截止日期快要到来的时候,二人决定依然完成本次项目的所有要求,并且在最后做出了较为明确的分工,虽然为时已晚但是起码态度开始端正,也完成了各自的任务。
下面是一些结对编程(主要是讨论算法设计)的照片:
下面是对结对编程优缺点的一些总结,当然其中包含了很多他人的经验以及我们自己获得的一些感受。
下面是一些结对编程的优点:
1.程序员互相帮助,互相请教对方,可以得到能力上的互补。
2.可以让程序员增强纪律性和更好的管理时间。
3.增强代码和产品质量,并有效的减少BUG。
4.降低学习和开发成本。一边编程,一边共享知识和经验,有效地在实践中进行学习。
5.在编程中,相互讨论,可能更快更有效地解决问题。
6.降低维护的风险,如果一个程序员离开了团队,那么还有另一个了解代码的结构。
当然,结队编程也会有一些不好的地方:
对于有不同习惯的编程人员,可以在起工作会产生麻烦,甚至矛盾。
1.有时候,程序员们会对一个问题各执己见,争吵不休,反而产生重大内耗。
2.面对新手,有经验的老手可能会觉得非常的烦躁。不合适的沟通会导到团队的不和谐。
3.新手在面对有经验的老手时会显得非常的紧张和不安,甚至出现害怕焦虑的的精神状态,从而总是出现低级错误,而老手站在他们后面不停地指责他们导致他们更加紧张,最终导致项目进展效率低下。
4.大多数人更喜欢单兵作战,找个人来站在他背后看着他可能会让他感到非常的不爽,最终导致编程时受到情绪影响,反而出现反作用。
下面是对本次结对编程人员的每一个人的优点和缺点的描述,从而让两人更好地了解自我,改善自我。
晏旭瑞
优点
1.善于使用各种工具
2.代码风格清晰,简单明了,复杂问题简单处理
3.思维灵活,经常有新的想法,不拘泥于固定的算法和套路
缺点
1.遇到困难的问题会松懈
黄可嵩
优点:
1.完成欲超强,可以通宵达旦的敲代码
2.逻辑思维强,胜任各种复杂的算法
3.编程序的时候坚持不放弃,不轻易失望
缺点:
1.思维僵化,总是往复杂的方面考虑问题,很少得到简明的解决方案
2.固执自我,合作精神非常缺乏
3.思维不够严谨,对问题分析不够严密,经常需要很久的时间后才能发现潜藏的问题
4.不到最后关头没有动力,做事不够专心
二. 关于设计方法
由于现代软件工程技术的发展,各种各样的设计方法层出不穷,各种各样的框架代码库也是应运而生。在本次项目中,通过对教师提供的基础代码的阅读理解以及增量开发,我对以下几个设计方法有了更深的体会与了解。
首先是关于Information Hiding。跟以前面向对象建模技术课时相似,软件工程课的老师同样强调数据隐藏与封装性,这在这次的基础代码中体现的尤为明显。每个类中的成员变量都是用private访问控制符修饰的,实现了对外的数据隐藏,限制了类之外对类的成员变量的存取。不得不说,这样一来我对代码的编写变得有些麻烦,因为成员变量都是私有的,不方便在类外进行调用,也不方便进行检测观察,甚至在单元测试中都不便进行测试。然而,信息隐藏在当代软件工程中,尤其是在面向对象建模技术中占有重要地位,要不然java、c#等流行语言也不会设计访问控制符了。有的网友甚至说,如果你不使用Informaion Hiding,你就等着在公司被人嘲笑吧,当别人都在嘲笑使用public变量的你时,你就会感觉到不使用Information Hiding是个错误的决定。
其次是关于Interface Design。本次项目的基础solution中,包含了一个project,内有一个cs文件commons,定义了一大堆借口以及结构体,甚至还有枚举类型,而这些定义在其他的工程中分别被使用。在对对象进行操作时,程序中大量使用了foreach语句,而foreach语句中从集合中取出的对象,是用接口定义的。或者说,foreach语句从包含具体对象的集合中每一次取出一个接口类型的变量进行操作,这些集合中的类毫无疑问都实现了这个接口。刚开始我还不知道这样做的含义,只是单纯地觉得,把所有的接口定义和结构体定义都专门放在一个文件中,然后让其他一系列工程中的文件来实现他们,有一种纲举目张的感觉。包含接口的那个文件就像是一个总纲,包含了整个solution中所有project中类的标准接口,包括成员变量和成员方法,其他projcet中的类来实现这些接口,看起来就像是从这些类中抽象出了共性,并使用接口规范化类的设计。现在看来,这样等于是在程序层面就提取出了抽象层次以及实现层次,通过接口的设计就能大致了解类的功能作用,整个程序的数据结构以及数据操作的概况显得一清二楚。同时在从表中取出对象进行操作时,取出接口而不是对象类型有利于增加程序的可读性,并直接规范化对应调用方法的的参数格式,并可以传入更多的不同的对象,只要他们实现了统一接口 ,那么就可以拿来使用,这在不建立接口的面向对象程序中是无法做到的。
第三是loose coupling。这是我之前没有听说过的概念,中文翻译为松耦合。我在wikipedia上了解了loose coupling的相关知识。
在没有了解之前,我曾经觉得loose coupling应该跟封装差不多,大概就是类与类之间的关系变小变少吧,了解之后才知道根本不是一回事。简单来说,如果一个对象保有直接指向另一个实例化的对象的引用,那么这就是strong coupling,这一种coupling是不可代替的,或者说是不能轻易改变的,这个类一直就是依赖固定类型的实例化的对象。而在loose coupling中,一个对象仅仅保有对接口的引用,也就是说这个对象是依赖于一组由接口规范化定义好的变量与方法,而不是一个特定的实例化了的对象。那么在loose coupling中,这种依赖关系变得不再唯一。由于依赖的是接口,所有的实现了接口的对象都可以作为被依赖者传入,并且可以随时更换而不影响逻辑的完备性与程序的正确性。同时,由于使用的是接口,那么就有有可能使用多种不同的类与对象,这样也有利于程序的扩展性。由于依赖的是接口定义的契约,因此在依赖关系的具体实现上提供了一定的灵活性。在本次项目的基础代码中,在存取控制电梯类与乘客类的函数或者语句块中,大量使用了操纵接口变量,实际上使用的是实现了这个接口的对象。
最后就是Design by Contract & Code Contract。同样,第一次看到这两个词,感觉挺新鲜,上wikipedia一查看,原来也是已经在面向对象建模技术课程中学习过的知识,即契约编程。准确来说,在面向对象建模技术课程中学习了有关Design by Contract的内容,并没有学习Code Contract。但鉴于Code Contrcat实际上是微软在.NET编程环境上对Design by Contract的代码支持与实现,因此放在一起来讨论,并且以Design by Contract为中心。Code Contract的实现其实也是基于前置条件、后置条件、不变式的模式,采用了运行时检查、静态检查并将契约信息写入已有的xml文件的方式具体实现。Design by Contract,核心是Contract,在面向对象的程序中以前置条件、后置条件、不变式的形式表示出来。前置条件其实就是对输入的要求与检查,检查输入是否合法,以保证处理输入者的安全性;后置条件是对输出的要求与检查,检查输出是否合理,以保证输入者得到合法的输出;不变式实质上也是一种条件,只不过是一种对类内成员的从始至终的必须满足的条件。在本次项目中,由于时间仓促,我们在编程的途中没有进行契约的建立以及检查,但是在上学期的面向对象建模技术课程中,我们曾经在至少一周的项目中进行了契约编程,对其有一定的认识。契约编程实际上正如wikipedia所说,是让软件开发人员以规范准确地定义和契约来保证程序的正确性以及合法性。在编程的前期制定契约,在编程的过程中编写实现契约,在编程之后检查契约,理论上是有助于保障模块乃至程序整体的安全性以及对需求的满足程度的。在契约编程过程中,客户和厂家遵循彼此提出的双向的规则,能够减少冲突,节省时间和精力。不过对于我来说,契约编程还是显得有些麻烦而且臃肿。很多时候,调用一个模块时,我在外界已经判断了某些条件,换句话说,如果不满足指定条件,那么模块甚至都不会调用,参数都不会传进来,亦即前置条件在进入模块之前就得到了设计,不必要在在模块中设计;后置条件同样,在模块外部添加的对结果的判断,使得后置条件有时候并不需要检查,模块外部会有其他的部分检查。同时契约编程写起来显得很臃肿而且麻烦。最麻烦的是不变式。诚然,不变式看似能够始终规范类中的值为合法范围,但是,对象是动态的,是会在运行时环境下发生改变的。这时候如果硬要规定一个不变式,那么无疑将阻碍数据的操作以及控制。
三. 关于单元测试
单元测试是对模块进行集中测试,测试一个模块的基本功能点,保证模块运行的正确性。这在结对编程或者团队编程中显得尤为重要。
说实话,由于程序设计的问题,我们的程序各个模块之间的耦合度比较高,进行单元测试的时候有些不方便,因为这个模块一些部分的运行依赖于其他的模块中的一些部分,而且有时这个模块的运行结果并不是显式的,并不好进行观察判断是否合法。尤其是在遇到私有变量以及私有方法时,vs2012 c#中的单元测试更是让人无奈。
本次项目是进行增量编程,因此只对改动较大的类NaiveScheduler做单元测试。
由于本次是进行增量编程,并且使用的测试用例是老师提供的测试原基础程序的用例,因此从某些角度来看,这是一个回归测试。
在单元测试类中,覆盖了被测试类中所有可能的函数执行路径,并且测试了所有重点的非私有函数,还测试了一个较为关键的私有方法,因此能够保证每个方法执行的正确性,从而保证整个类的正确性与合法性。
被测试类代码:
1 public class NaiveScheduler : IScheduler 2 { 3 Queue<IRequest> _PassengerQueue; 4 List<IElevator> _Elevators; 5
6 public NaiveScheduler() 7 { 8
9 } 10
11 public Queue<IRequest> getqueue { get { return _PassengerQueue; } } 12 public List<IElevator> getelev { get { return _Elevators; } } 13
14 public int getflag(string str, int x, int y) 15 { 16 if(str.Equals("stopflag")) 17 { 18 return stopFlags[x, y]; 19 } 20 else if(str.Equals("waitflag")) 21 { 22 return waitFlags[x, y]; 23 } 24 return -1; 25 } 26
27 public void clear(int[,] save, int wide, int len) 28 { 29 for (int i = 0; i < wide; i++) 30 { 31 for (int j = 0; j < len; j++) 32 { 33 save[i, j] = 0; 34 } 35 } 36 } 37
38 public void Initialize(List<IElevator> Elevators) 39 { 40 _PassengerQueue = new Queue<IRequest>(); 41 _Elevators = Elevators; 42 clear(stopFlags,4,21); 43 clear(waitFlags, 2, 21); 44 return; 45 } 46
47 public bool QueueReq(IRequest req) 48 { 49 lock (_PassengerQueue) 50 { 51 _PassengerQueue.Enqueue(req); 52 } 53 //添加*******************************************
54 int floor; 55 switch (req.UpDown) 56 { 57 case Direction.No://req.Type == RequestType.DestinationReq
58 int id = req.ElevatorReqIn.ID; 59 floor = req.DestinationReqDest; 60 stopFlags[id, floor] = 1; 61 break; 62 case Direction.Up: 63 floor = req.DirectionReqSource; 64 waitFlags[0, floor] = 1; 65 break; 66 case Direction.Down: 67 floor = req.DirectionReqSource; 68 waitFlags[1, floor] = 1; 69 break; 70 //default: 71 // floor = 0;//不会执行此处,否则出错 72 // break;
73 } 74 //添加*******************************************
75 return true; 76 } 77
78 bool IsAtTopFloor(IElevator elev) 79 { 80 ElevatorStatus status = elev.CurrentStatus; 81 return elev.CurrentStatus.CurrentHeight >= elev.HighestFloor * elev.FloorHeight; 82 } 83
84 bool IsAtBottomFloor(IElevator elev) 85 { 86 ElevatorStatus status = elev.CurrentStatus; 87 return elev.CurrentStatus.CurrentHeight <= elev.LowestFloor * elev.FloorHeight; 88 } 89
90 public void StopAtEachFloor(IElevator elev) 91 { 92 ElevatorStatus status = elev.CurrentStatus; 93
94 if (status.CurrentDirection != Direction.No) 95 return; 96 if (IsAtTopFloor(elev)) 97 { 98 elev.ReqStopAt(elev.NextAvailableFloor(status.CurrentHeight, Direction.Down)); 99 elev.HistoryDirection = Direction.Down; 100 } 101 else
102 { 103 if (IsAtBottomFloor(elev)) 104 { 105 elev.ReqStopAt(elev.NextAvailableFloor(status.CurrentHeight, Direction.Up)); 106 elev.HistoryDirection = Direction.Up; 107 } 108 else
109 { 110 switch (elev.HistoryDirection) 111 { 112 case Direction.No: 113 case Direction.Up: 114 elev.ReqStopAt(elev.NextAvailableFloor(status.CurrentHeight, Direction.Up)); 115 elev.HistoryDirection = Direction.Up; 116 break; 117 case Direction.Down: 118 elev.ReqStopAt(elev.NextAvailableFloor(status.CurrentHeight, Direction.Down)); 119 elev.HistoryDirection = Direction.Down; 120 break; 121 } 122 } 123 } 124
125 } 126
127
128
129
130
131
132 public void Run() //scan the request and make correct decision to control elevator;
133 { 134 foreach (IElevator elev in _Elevators) 135 { 136 //StopAtEachFloor(elev);
137 myScheduler(elev); 138 } 139 return; 140 } 141
142 //添加******************************************* 143 //声明三个二维数组
144 private int[,] stopFlags = new int[4, 21];//stopFlags[i,x]表示电梯i要停的楼层号,i=0,1,2,3
145 private int[,] waitFlags = new int[2, 21];//waitFlags[0,x]表示向上的请求楼层,waitFlags[1,x]表示向下的请求楼层号
146
147 private void myScheduler(IElevator elev) 148 { 149 ElevatorStatus status = elev.CurrentStatus; 150 int id = elev.ID; 151 int d;//方向
152 if (elev.isMoving)//一、电梯处于运动状态,判断下一层要不要停
153 { 154 d = status.CurrentDirection == Direction.Up ? 0 : 1; 155 int nextFloor = elev.NextAvailableFloor(status.CurrentHeight, status.CurrentDirection); 156 bool yes = false; 157 //有人要下或有人在等电梯或达到了最高或最低请求楼层则停
158 yes = stopFlags[id, nextFloor] == 1
159 || waitFlags[d, nextFloor] == 1
160 || nextFloor == elev.HighestFloor || nextFloor == elev.LowestFloor; 161 if (yes) 162 elev.ReqStopAt(nextFloor); 163 } 164 else//二、电梯处于停止状态
165 { 166 if (status.CurrentDirection == Direction.No)//2.改变电梯的方向,只需进行一次
167 { 168 if (IsAtTopFloor(elev)) 169 { 170 elev.ReqStopAt(elev.LowestFloor); 171 elev.HistoryDirection = Direction.Down; 172 } 173 else
174 { 175 if (IsAtBottomFloor(elev)) 176 { 177 elev.ReqStopAt(elev.HighestFloor); 178 elev.HistoryDirection = Direction.Up; 179 } 180 else
181 { 182 switch (elev.HistoryDirection) 183 { 184 case Direction.Up: 185 elev.ReqStopAt(elev.HighestFloor); 186 break; 187 case Direction.No: 188 case Direction.Down: 189 elev.ReqStopAt(elev.LowestFloor); 190 elev.HistoryDirection = Direction.Down;//为了能处理一开始的情况而添加的
191 break; 192 } 193 } 194 } 195 } 196
197 d = elev.HistoryDirection == Direction.Up ? 0 : 1; 198 //1.移除处理完的请求,每个tick进行检查
199 stopFlags[id, status.CurrentFloor] = 0;//电梯里面所有的人出来
200 if (!elev.isOverweight && elev.isReachable)//外面所有的人都进来了
201 waitFlags[d, status.CurrentFloor] = 0; 202 else waitFlags[d, status.CurrentFloor] = 1;//必须的,因为超重会隔一个tick再产生信号
203
204 } 205 } 206
207 //添加*******************************************
208 }
unit test 测试类代码:
1 /// <summary>
2 ///This is a test class for NaiveSchedulerTest and is intended 3 ///to contain all NaiveSchedulerTest Unit Tests 4 ///</summary>
5 [TestClass()] 6 public class NaiveSchedulerTest 7 { 8
9
10 private TestContext testContextInstance; 11
12 /// <summary>
13 ///Gets or sets the test context which provides 14 ///information about and functionality for the current test run. 15 ///</summary>
16 public TestContext TestContext 17 { 18 get
19 { 20 return testContextInstance; 21 } 22 set
23 { 24 testContextInstance = value; 25 } 26 } 27
28 #region Additional test attributes
29 //
30 //You can use the following additional attributes as you write your tests: 31 //
32 //Use ClassInitialize to run code before running the first test in the class 33 //[ClassInitialize()] 34 //public static void MyClassInitialize(TestContext testContext) 35 //{ 36 //} 37 //
38 //Use ClassCleanup to run code after all tests in a class have run 39 //[ClassCleanup()] 40 //public static void MyClassCleanup() 41 //{ 42 //} 43 //
44 //Use TestInitialize to run code before running each test 45 //[TestInitialize()] 46 //public void MyTestInitialize() 47 //{ 48 //} 49 //
50 //Use TestCleanup to run code after each test has run 51 //[TestCleanup()] 52 //public void MyTestCleanup() 53 //{ 54 //} 55 // 56 #endregion
57
58
59 /// <summary>
60 ///A autotest for NaiveScheduler Constructor 61 ///</summary>
62 [TestMethod()] 63 public void NaiveSchedulerConstructorTest() 64 { 65 NaiveScheduler target = new NaiveScheduler(); 66 //Assert.Inconclusive("TODO: Implement code to verify target");
67 Assert.IsNotNull(target); 68 } 69
70 /// <summary>
71 ///A autotest for Initialize 72 ///</summary>
73 [TestMethod()] 74 public void InitializeTest() 75 { 76 List<SenElevator> AllElevators; 77 PassengerLoader _passengerLoader; 78 ElevatorLoader _elevatorLoader; 79 _passengerLoader = new PassengerLoader("passenger1.xml"); 80 _elevatorLoader = new ElevatorLoader("elevators.xml"); 81 AllElevators = _elevatorLoader.Load(); 82 List<IElevator> elevs = new List<IElevator>(); 83 foreach (IElevator e in AllElevators) 84 elevs.Add(e); 85
86 NaiveScheduler target = new NaiveScheduler(); // TODO: Initialize to an appropriate value
87 Assert.IsNull(target.getqueue); 88 target.Initialize(elevs); 89 Assert.IsNotNull(target.getqueue); 90 Assert.IsNotNull(target.getelev); 91
92 List<IElevator> Elevators = null; // TODO: Initialize to an appropriate value
93 target.Initialize(Elevators); 94 Assert.IsNull(target.getelev); 95 //Assert.Inconclusive("A method that does not return a value cannot be verified.");
96 } 97
98 /// <summary>
99 ///A autotest for QueueReq 100 ///</summary>
101 [TestMethod()] 102 public void QueueReqTest() 103 { 104 int layer=10; 105 PassengerReq req; 106 IRequest temp; 107 IElevator elev; 108 List<SenElevator> AllElevators; 109 ElevatorLoader _elevatorLoader; 110 _elevatorLoader = new ElevatorLoader("elevators.xml"); 111 AllElevators = _elevatorLoader.Load(); 112 List<IElevator> elevs = new List<IElevator>(); 113 foreach (IElevator e in AllElevators) 114 elevs.Add(e); 115 NaiveScheduler target = new NaiveScheduler(); // TODO: Initialize to an appropriate value
116 target.Initialize(elevs); 117 req = new PassengerReq(RequestType.DirectionReq, 0, layer, Direction.Up); 118 //Assert.IsNotNull(req);
119 target.QueueReq(req); 120 temp = target.getqueue.Dequeue(); 121 Assert.IsNotNull(temp); 122 Assert.AreEqual(-1, target.getflag("no", 0, 1)); 123 Assert.AreEqual(1,target.getflag("waitflag",0,req.DirectionReqSource)); 124 req = new PassengerReq( 125 RequestType.DirectionReq, 0, layer, Direction.Down); 126 target.QueueReq(req); 127 temp = target.getqueue.Dequeue(); 128 Assert.IsNotNull(temp); 129 Assert.AreEqual(1, target.getflag("waitflag", 1, req.DirectionReqSource)); 130 req = new PassengerReq( 131 RequestType.DestinationReq, 0, layer, Direction.No); 132 elev = new SenElevator(new List<uint>(), 0, 1000, 13, 10, 0, 20, 20); 133 req.SetReqInElevator(elev); 134 target.QueueReq(req); 135 temp = target.getqueue.Dequeue(); 136 Assert.IsNotNull(temp); 137 Assert.AreEqual(1, target.getflag("stopflag", req.ElevatorReqIn.ID, req.DestinationReqDest)); 138 //Assert.Inconclusive("Verify the correctness of this test method.");
139 } 140
141 /// <summary>
142 ///A autotest for Run 143 ///</summary>
144 [TestMethod()] 145 public void RunTest() 146 { 147 List<SenElevator> AllElevators; 148 ElevatorLoader _elevatorLoader; 149 _elevatorLoader = new ElevatorLoader("elevators.xml"); 150 AllElevators = _elevatorLoader.Load(); 151 List<IElevator> elevs = new List<IElevator>(); 152 foreach (IElevator e in AllElevators) 153 elevs.Add(e); 154 NaiveScheduler target = new NaiveScheduler(); // TODO: Initialize to an appropriate value
155 target.Initialize(elevs); 156 target.Run(); 157 Assert.IsNotNull(target); 158 //Assert.Inconclusive("A method that does not return a value cannot be verified.");
159 } 160
161 /// <summary>
162 ///A test for StopAtEachFloor 163 ///</summary>
164 [TestMethod()] 165 public void StopAtEachFloorTest() 166 { 167 IElevator elev; 168 NaiveScheduler target = new NaiveScheduler(); // TODO: Initialize to an appropriate value
169 elev = new SenElevator(new List<uint>(), 0, 1000, 13, 10, 0, 20, 20); 170 elev.ReqStopAt(10); 171 target.StopAtEachFloor(elev); 172
173 elev = new SenElevator(new List<uint>(), 0, 1000, 13, 10, 0, 20, 200); 174 target.StopAtEachFloor(elev); 175 Assert.AreEqual(elev.CurrentStatus.CurrentDirection, Direction.Down); 176 Assert.AreEqual(elev.HistoryDirection, Direction.Down); 177
178 elev = new SenElevator(new List<uint>(), 0, 1000, 13, 10, 0, 20, 0); 179 target.StopAtEachFloor(elev); 180 Assert.AreEqual(elev.CurrentStatus.CurrentDirection, Direction.Up); 181 Assert.AreEqual(elev.HistoryDirection, Direction.Up); 182
183 elev = new SenElevator(new List<uint>(), 0, 1000, 13, 10, 0, 20, 100); 184 target.StopAtEachFloor(elev); 185 Assert.AreEqual(elev.HistoryDirection, Direction.Up); 186
187 elev = new SenElevator(new List<uint>(), 0, 1000, 13, 10, 0, 20, 100); 188 elev.HistoryDirection = Direction.Up; 189 target.StopAtEachFloor(elev); 190 Assert.AreEqual(elev.CurrentStatus.CurrentDirection, Direction.Up); 191 Assert.AreEqual(elev.HistoryDirection, Direction.Up); 192
193 elev = new SenElevator(new List<uint>(), 0, 1000, 13, 10, 0, 20, 100); 194 elev.HistoryDirection = Direction.Down; 195 target.StopAtEachFloor(elev); 196 Assert.AreEqual(elev.CurrentStatus.CurrentDirection, Direction.Down); 197 Assert.AreEqual(elev.HistoryDirection, Direction.Down); 198
199
200 //Assert.Inconclusive("A method that does not return a value cannot be verified.");
201 } 202
203 /// <summary>
204 ///An artificial test for myScheduler 205 ///</summary>
206 [TestMethod()] 207 public void mySchedulerTest() 208 { 209 List<SenElevator> AllElevators; 210 ElevatorLoader _elevatorLoader; 211 _elevatorLoader = new ElevatorLoader("elevators.xml"); 212 AllElevators = _elevatorLoader.Load(); 213 List<IElevator> elevs = new List<IElevator>(); 214 foreach (IElevator e in AllElevators) 215 elevs.Add(e); 216
217 IElevator elev; 218 NaiveScheduler target = new NaiveScheduler(); // TODO: Initialize to an appropriate value
219 elev = new SenElevator(new List<uint>(), 0, 1000, 13, 10, 0, 20, 200); 220 elev.isReachable = true; 221 elev.isOverweight = false; 222 elevs.Add(elev); 223 target.Initialize(elevs); 224 target.Run(); 225 Assert.AreEqual(elev.CurrentStatus.CurrentDirection, Direction.Down); 226 Assert.AreEqual(elev.HistoryDirection, Direction.Down); 227
228 elev = new SenElevator(new List<uint>(), 0, 1000, 13, 10, 0, 20, 0); 229 elev.isReachable = false; 230 elev.isOverweight = false; 231 elevs.Add(elev); 232 target.Initialize(elevs); 233 target.Run(); 234 Assert.AreEqual(elev.CurrentStatus.CurrentDirection, Direction.Up); 235 Assert.AreEqual(elev.HistoryDirection, Direction.Up); 236
237 elev = new SenElevator(new List<uint>(), 0, 1000, 13, 10, 0, 20, 100); 238 elev.isReachable = false; 239 elev.isOverweight = true; 240 elevs.Add(elev); 241 target.Initialize(elevs); 242 target.Run(); 243 Assert.AreEqual(elev.HistoryDirection, Direction.Down); 244
245 elev = new SenElevator(new List<uint>(), 0, 1000, 13, 10, 0, 20, 100); 246 elev.HistoryDirection = Direction.Up; 247 elev.isReachable = true ; 248 elev.isOverweight = true ; 249 elevs.Add(elev); 250 target.Initialize(elevs); 251 target.Run(); 252 Assert.AreEqual(elev.CurrentStatus.CurrentDirection, Direction.Up); 253 Assert.AreEqual(elev.HistoryDirection, Direction.Up); 254
255 elev = new SenElevator(new List<uint>(), 0, 1000, 13, 10, 0, 20, 100); 256 elev.HistoryDirection = Direction.Down; 257 elevs.Add(elev); 258 target.Initialize(elevs); 259 target.Run(); 260 Assert.AreEqual(elev.CurrentStatus.CurrentDirection, Direction.Down); 261 Assert.AreEqual(elev.HistoryDirection, Direction.Down); 262
263 elev = new SenElevator(new List<uint>(), 0, 1000, 13, 10, 0, 20, 190); 264 elev.HistoryDirection = Direction.Up; 265 elev.ReqStopAt(20); 266 elev.isMoving = true; 267 elevs.Add(elev); 268 target.Initialize(elevs); 269 target.Run(); 270 Assert.AreEqual(elev.CurrentStatus.CurrentDirection, Direction.Up); 271 Assert.AreEqual(elev.HistoryDirection, Direction.Up); 272
273 elev = new SenElevator(new List<uint>(), 0, 1000, 13, 10, 0, 20, 10); 274 elev.HistoryDirection = Direction.Down; 275 elev.ReqStopAt(0); 276 elev.isMoving = true; 277 elevs.Add(elev); 278 target.Initialize(elevs); 279 target.Run(); 280 Assert.AreEqual(elev.CurrentStatus.CurrentDirection, Direction.Down); 281 Assert.AreEqual(elev.HistoryDirection, Direction.Down); 282
283 //Assert.Inconclusive("A method that does not return a value cannot be verified.");
284 } 285 }
unit test 测试界面:
对于每个可能的函数路径都做了测试,对大部分重要方法进行了直接测试。
unit test 代码覆盖率:(针对NaiveScheduler类)
可以看到,该单元测试对于该类的代码覆盖率为100%,测试到了所有可能的函数执行路径,保证每一个函数执行的正确性,从而保证类的质量。
四. UML图
UML图实际上是形式化的表示程序结构的一种工具,包括用例图、类图、对象图、状态图、活动图、序列图、协作图、构件图、部署图等9种图。本项目中主要构建UML类图。
关于画uml图的教程,网上有很多。
但有两点不是很清楚
1.Program类中有如下内容:
static IScheduler NaiveScheduler;
…
NaiveScheduler = Scheduler.SchedulerFactory.CreateScheduler();
而NaiveScheduler类实现了IScheduler接口。
那么应该在Program类到IScheduler 接口还是NaiveScheduler类之间表示组合关系?类似的情况还有很多。。。
我是这样想的,因为除了实现这种关系以为其他的都是实体与实体之间的关系,所以就表示成了到NaiveScheduler类间的组合关系。
2.SenElevator类中引发时间导致SenPassenger类执行ElevatorStopped方法,这样的话应该在SenElevator类到SenPassenger类之间画关联关系还是在SenPassenger类到SenElevator类之间画依赖关系?考虑到关联关系在代码中的一般表现形式为一个对象是另外一个对象的属性,所以我表示成了依赖关系。
3.Program类中包含名为AllElevators的字段,其中包含SenElevators对象的集合。那么Program类到SenElevator类之间的关系怎样表示?
在vs2012的类图中,有一种叫做的集合关联的关系就是表示这种的关系,但是在类图编辑器中无法画出这种关系。所以我就用普通的组合关系代替。
4.虽然SenElevator类声明并定义了InternalClock类型的对象,但是根本没有用到,完全可以删除后者,所以在类图中也没有画出来。
五. 算法设计
在前面的段落也提到,虽然本次结对编程的两名人员基本各自为政,合作意识薄弱,但是在算法设计的环节上,二人进行了长期而激烈的讨论,想法有共同也有差异。
最初二人共同考虑的是分两种情况:每个电梯内部维持一个数组,数组的元素是结构体,机构体内有三个int型,分别表示向上的楼层情趣,向下的楼层请求,电梯内请求。首先定义当前每个电梯的区间,也就是电梯目前需要到达的最高层和最底层。区间的确定需要考虑梯内请求、梯外同方向请求,梯外反方向请求等等。对于请求队列中的一个请求,如果它在某一个电梯的区间中,则将该电梯中维持的数组的对应楼层项的对应方向的int型置为1,一个请求可以让多个电梯数组置为1,因为这些电梯返回都要经过该楼层,那么谁先到,谁接管该楼层请求,并将其他电梯中对应层数对应方向的数组的值清为0,则其他电梯不需在该楼停浪费时间。对于另一种楼层请求,它不在任何电梯的区间内,则该请求从四个电梯中选择一个最近的并且同向的电梯,将该电梯数组对应位对应方向的值置为1.接下来考虑电梯的运动。电梯运动的总原则是:如果当前电梯方向向上,选择在该电梯楼层以上并且是向上的最近楼层请求作为ReqStopAt的参数即下一个目标楼层,如果没有这样的请求,选择最远的并且在该电梯上方的方向向下的楼层请求;如果依然没有这样的请求,电梯方向置为Direction.No;如果当前电梯方向向下,情况类似可推。如果当前电梯方向为Direction.No,选择最近的请求作为目标。
这个算法比较复杂,当时两人讨论了很久,后来晏旭瑞认为太复杂而另寻它法,黄可嵩进行了该算法的实现,并一度成功过,其程序当时对rush hour的电梯请求响应不错,但是无法通过随机人数上下电梯的测试,在优化之后可以通过随机数,但是rush hour开始出现到了最终无限循环的情况,经输出法调试,发现有一部分人始终没有上电梯,还有一部分人在电梯内一直没能出去。其中原因比较复杂,后来黄可嵩考虑主要原因有两个:一个是换乘的问题。黄可嵩在程序中设置了较为复杂的换乘逻辑,导致原本复杂的程序增添了多次电梯的进出和乘客的等待而更加复杂。实际上,换乘并非最优。考虑本次项目的测试用例,两个rush hour的测试,一个是大部分人起点为0层和1层,一个是大部分人终点为0层和1层。四个电梯中有一个负责0~10,两个负责1楼直接到10层以上以及10~20,最后一个负责全部楼层,这样0、1楼层的集中要求是可以得到满足的;另一个是关于标识符的清零问题。比如某一位乘客到了一层,开门,消除所有电梯数组对应楼层的对应项的值为0,然而这样做并不妥当。如果有多个人都是同样的楼层外请求,那么这一下清零就表示把那些可能未能进电梯(电梯太拥挤)的人的标志清零了,而且他们的请求IRequest已经在一开始交给某一个电梯接管时从请求队列中清除了,这样就造成了很多死乘客,他们的要求永远得不到满足。
以下是晏旭瑞的简便做法: project在每一层都停(不包括电梯本身不能停的楼层),如果next层没有人要下也没有人要上则电梯不停,这就是我的思路。为此需要为每个电梯建立一个21维的数组用以保存该电梯内的乘客准备停的楼层。还应该为所有电梯建两个公共数组,长度还是21,一个保存向上的请求楼层,另一个用以保存向下的楼层号。每当有请求发出,则根据请求的类型和方向置相应的数组元素值为1,每当有乘客出电梯则置电梯私有的数组的元素为0,而当电梯所在楼层的所有人都进入电梯后置公共数组相应的元素值为0。
当然,依旧是一个傻瓜调度的算法,仅仅是又花了一点而已,毫无使用价值。但是从完成任务角度的看,我这个算法简单清晰,易于调试和优化,average ticks缩短了1/3以上,并不比其他复杂度很高的算法效果差多少。
实际上作业交完之后二人讨论,晏旭瑞的这个算法性能已经不错了,如果优化一下可以更好,关键在于他判断了是否所有人都进电梯来判断是否对数组置位;其次就是它每层都检查,可以保证程序的正确性,
黄可嵩目前仍然在进行电梯的编程。虽然程序提交截止日期已过,但是我相信能够写出一个让自己满意的电梯系统。