结对编程-电梯调度算法的实现 (附加题部分请参考对应博客)

电梯调度算法的实现

(附加题部分请参考对应博客)

结对编程项目

毛宇(11061171) 程志(10061188)

在国庆假期中,我和程志共同呆在一起两天完成了这个形式非常新颖的作业,从学习c#,研究框架代码,到设计算法,实现和调试,不得不说每一步都是一个挑战。

 

关于结对伙伴

在项目开始之前,我们做了短暂的交流和自我介绍,并且通过此对对方有了一定的了解,程志的编程能力不是太强,但是他对于整体框架的理解确实非常迅速,在研究学习老师给出的框架的过程中,我很多次被各种相似的接口名或者类名给弄得头晕,但他总能够给出一个合理的解释,并且节约了我很多时间。此外,程志的计划性也非常强,在我们一起完成作业的时间中,他习惯于首先推出一个计划,并且严格去执行,从而使得整件事情进行得非常高效。但是程志的缺点在于他有时候会比较急躁,特别是我们陷入Code Jam的时候,调试的过程是非常需要耐心的,我觉得程志在这方面应该多多进步才行。相比于他,我自认为编程能力还比较强,对c#语言本身也算是掌握得比较熟练,所以,我主要担当起了编程的工作。其次我觉得我的速度也非常快,无论是在思考问题还是在敲代码。而我的缺点,程志也和我谈及过,就是在合作过程中,还是出现了一点分歧,不过可能考虑到他的感受,我没有直接和他明说。我之后也觉得在以后和任何人合作过程中,对事不对人,有啥分歧可以直接说出来,这样也有利于解决问题,不要太腼腆了。所以,不得不说,我认为我和程志这个组合是非常恰当的。

 

 

关于结对编程

结对编程,说得通俗一点,也就是两个好基友坐在同一台电脑前,共同完成编程的工作。这种情况下一般是一个人负责编程,另一个人负责“领航”以及“代码检查”,通过这种方式,来提高代码的质量和缩短编程所需要的时间。

 

优点

我觉得结对编程之所以有效无非是基于一个常识,那就是两个人的共同盲点并不多。举个例子,在这次与程志的合作过程中,由于我对老师的框架本身的理解还不算是非常深入,所以在编写调度的时候总是忽视掉historyDirection这个变量的处理,不得不说,幸好有程志反复提醒我,否则我会花上非常多的时间来调试这个问题。

 

除此之外,结对编程的效率绝对是非常高的,就我这次的体会来言,放在平时,我总是习惯于编好一个函数就去上网聊天什么的,一天下来,不仅代码的质量不高,数量也不多。但是和程志的合作过程中,由于我们时常对代码进行交流,我一次都没有走神,效率非常之高。通俗点来说,就是“完完全全沉浸在了代码的海洋中无法自拔”,不得不说这种感觉非常充实。

 

最最重要的一点就是,通过结对编程,我也学习到了很多知识,程志在很多方面都比我强,在编程的过程中,他时常指出我代码风格上的错误。比如我没有初始化的习惯等等。这个过程中的交流让人觉得受益匪浅。

 

缺点

相反的,结对编程也有一个缺点,那就是太累了,比如10月4号那天的下午,我和程志约好那个时候开始实现我们的算法,整个过程中,我们不断的交流,争论。 我主要是负责写代码,一边盯着屏幕一边认真听程志的一些指挥和建议,一个下午下来,我已经感觉有些心有余而力不足了,虽然我们的工作量提高了,错误率减少了,但是实在是让人心力疲惫。所以我感觉,结对编程这种方式实际上也加重了程序猿的负担,降低了他们的寿命。

 

另外,结对编程还有一个劣势,就是时间上不好安排,因为其实想要找到一个人有时间而且愿意与你坐在电脑前沉浸在代码的海洋里,共度一段美好时光是非常困难的事情,虽然在过程中效率高,但是结对编程作为一种软件工程的模式是很难得以推广的。

 

Information Hiding&& interface design&& loose coupling

信息隐藏:

在阅读了信息隐藏的章节后,我恍然大悟,原来信息隐藏不是信息安全的意思(额……),它是指一个模块内包含的信息(过程和数据)对于不需要这些信息的模块来说,是不能访问的(隐藏的),其实也就是强调了软件开发过程中的封装性。其实我对此非常有感触,信息隐藏其实是为了更好的利用信息,在这次编程的过程中,信息隐藏这个概念也算是一直在被体现,比如说我在设计我们的电梯调度算法的时候,设计到为Scheduler类增加一List用来存储电梯的任务信息,我特意把List设置为Default(也就是Protected),很好的隐藏了这个只在程序特定环节使用的变量。

信息隐藏实际上还有一个很大的用处,我们在 软件中隐藏了一些因素,那么在将来由于这些因素变化而需修改软件时,只需修改这些个别的模块,其它模块不受影响。总的来说信息隐蔽技术不仅提高了软件的可维护性,而且也避免了错误的蔓延,改善了软件的可靠性。这就是为什么书中提到信息隐藏已经成为了软件工程学中的一条重要原则的原因吧!

 

接口设计

谈到接口设计,这应该是我在这之前觉得最为头疼的问题,在阅读了相关章节以后,我认识到,接口设计其实是一门很深的学科,因为一个不恰当的接口设计会对性能产生不可挽回的影响,举个例子,一个类的接口不但定义了类可以完成的功能,而且还定义了创建对象以及执行其他方法的顺序,那么很多因素,比如这个对象是否可以充公,他是否要求用户创建中间对象,都会影响到程序的性能。此外,我个人觉得复杂度设计太高的接口会同时也会影响别的软件的可移植性,可维护性。

 

我觉得书中有一句话总结得非常好,那就是接口设计的四大原则:

  1. 简单原则
  2. 封闭原则
  3. 完整性原则
  4. 可置换原则

 

其中我印象最深刻的就是可置换原则,这是我从来没有考虑过的一个问题,因为我们可以保证一个功能模块不会过时,因此在接口设计中应该为其提供标准的接口规范,这样可以轻松地使用遵循接口规范的莫开来替换原来有的模块而不会影响到其他相关模式的调用方式。我觉得如果我能够在我的项目中使用这一点,那么软件的维护性将会大大增强。

 

松耦合

松耦合(loose coupling)最开始在我阅读材料的时候没有反应过来这是个什么意思,后面慢慢了解到松耦合说通俗一点就是一端的改变不能够影响到另一端。比如举例来说,如果软件的新版本被推出的话,用户就不必非要去修改一些东西,不必非要去改变路线,甚至不必经历停滞期,因为软件的新版本退出是可以被显示出来的。

 

后来我也了解到松耦合的设计通常是基于消息的系统,也就是说服务端和客户端只要签订了消息的协议,那么只要符合这个大的架构,他们的服务就可以根据需要去进行修改,而不必担心另一方。

 

谈到松耦合,就不得不说到紧耦合,因为紧耦合性通常可以提供性能好处。相对来说,松耦合可以降低两端的依赖性。具体来说哪个更好,其实也是一个依照情况而定的。

(其实对于loose coupling,虽然我阅读了相关材料,也学习了,但是由于是第一次接触这么新的概念,确实理解得不是非常深刻,我真心希望之后能在实践中对它有更加本质的了解。)

 

单元测试

在单元测试环节,我对NaiveScheduler类进行了测试(因为我的改动主要就是这个类,我其实没有写新的类),

 

 

 

 

 

 

其中RunTest方法由于调用比较复杂,很大程度上依赖于其他类,而我自己也是第一次用单元测试,实在是想不出办法来测试,导致最终的测试覆盖率只有12.93%,而且run函数的成功运行要涉及很多相关类的初始化,实在是难以操作。但是我觉得单元测试固然重要,但也产生了一个疑问,就是是不是所有的类都要做单元测试??可能考虑不周到,希望老师赐教。

代码测试覆盖率结果:

 

 

算法分析

 

 

描述:我们的算法实际上是分别对于电梯的不同状态(currentDirection==No?)来采用不同的策略,分别为strategy_0和strategy_1

 

一. 在strategy_0中,也就是电梯的currentDirection==NO的时候:

  1. 首先检查是否有完成的任务,如果有,就从该电梯任务列表中去掉这个已经完成的任务
  2. 然后检查是否电梯还有未完成的任务
    1. 如果有,就去完成,秉承的规则是如果电梯之前是在向上走,那就完成任务列表最开头的任务,向下走就完成任务列表最末尾的任务(任务列表中的任务按照任务的楼层从高到低排序)
    2. 如果该电梯的任务列表中所有任务都完成。检查一下所有的乘客请求:
      1. 如果当前所有的乘客请求为0个,那么电梯不动(这里的不动实际上是最好的选择,在这里我们本来以为让电梯去1楼或者0楼这个交互性比较强的位置比较合适,又尝试了取在10楼,测试证明,电梯不动的效率最高)
      2. 如果不是,那么按照以下优先级顺序来选择加入新的任务

     1.在下面,且往下面走的

     2.在下面,且往上面走的

     3.在上面,且往下面走的

     4.在上面,且往上面走的

这里解释一下我为什么要这么做:

第一.我们希望我们的电梯往下走,因为,我们的无论在任何情况下0,1楼的活动情况绝对是最频繁的

第二.我们希望我们的电梯所接的客的目标位置(A little bit mouthful),能够和电梯原来运行方向相同

 

二.  在strategy_1中:

  1. 首先判断电梯方向,如果往上,那么就把该电梯的任务列表中的第一个元素(最高层的任务作为移动的目的地),相反,如果往下,就把电梯的任务列表中的最后的一个元素作为移动的目的地。
  2. 判断电梯方向,如果向上,就把在这在电梯的目的地和电梯的当前位置之间的任务加入(在这之前要判断一下电梯的载重,如果不够70(平均体重)就忽略这个任务),同理,如果向下,就把在这在电梯的目的地和电梯的当前位置之间的任务加入(在这之前要判断一下电梯的载重,如果不够70就忽略这个任务)

 

 

UML

 

下面简答说一下我们认为我们算法比较独特的地方:

  1. 对于电梯的调度思路,我们针对不同的电梯情况进行了不同的处理,这样算法整体从逻辑上来说比较好。
  2. 算法比较简洁,效率也比较高,完成第一组测试用例用时75.8 tick左右,相比buss算法(300+)有了很大的改善。(第二组第三组分别为456tick和200+tick)
  3. 算法针对电梯运行的实际情况进行了一些调整处理,使得算法的效率提高。(主要是方向上的选择,已经过测试)

 

 

 

总结

这次的结对编程作业给我印象比较深刻的地方就是,在结队编程的过程中,我对软件工程的开发模式有了很深刻的理解,以前从来没有想过编程可以这样的进行,不过,让人惊奇的是,它对工程有很大的促进作用。此外,我依然通过这次作业认识了新的朋友——程志,总之,我收获非常大。

                                                         -----毛宇

 

作为团队作业的过渡,结对编程给了大家接下来完成更大的工程一个很好的缓冲,让大家学会了如何协作、如何共同解决问题,而且在交流中开阔了大家的视界,更好的点子在碰撞中应运而生。不过结对编程也有瑕疵,关系处理不好的话,大家很可能因为一点小摩擦而闹不愉快,导致工作难以实施。在与我的拍档毛宇合作中,我学到了毛宇的诸多优点:比如说为人温和,易于接触,而且他脑袋灵活,改进算法的时候想了多种解决方案,同时,毛宇对学习的热爱也让我很难忘,将老师给的链接基本上都看了并且钻研了下。我觉得结对编程这个形式非常好。

 ——程志

 

(还有附加题的相关解法,请参考附加题的博客)

原文地址:https://www.cnblogs.com/RylynnMao/p/3358169.html