本次为我们两个人的第一次结对编程。从总体而言,我们对结对编程比单人编程略显不适应。但是经过一段时间的磨合,我们逐渐的习惯了这种编程方式。
1、 结对编程的优缺点
结对编程的优点:
(1) 在两个人对于彼此都适应之后,编程的速度有了很大的提升。两个人可以同时对两个不同的模块进行编写,这也加快了程序的生成速度。
(2) 由于两个人同时思考,对于问题的想法也增加了。可以拓宽思路,对于算法的提出有着很大的帮助。
(3) 由于有别人的监督,中间基本上也不会出现玩的情况。两个人可以互相为对方的监督,这样就大大减少了中间玩的时间,使得可以以最好的状态和最快的速度完成工作。
结对编程的缺点:
(1) 没有默契的两个人一开始一般会比较不适应这种方式。有些人会感到不适应。会有一种别人在监视自己的感觉。
(2) 两个人的时间安排会有不同,从而使得真正结对编程时间较少。
2、 工作分配及个人优缺点
工作分配:罗凡负责领航,刘耀先负责代码实现。
分配的理由及个人优缺点:
(1) 罗凡
优点:
<1>信息搜集能力比较强,善于收集利于算法提出的资料;
<2>有耐心,能费时间仔细检查代码,在队友遇到问题时候能提出各种各样的建议;
<3>有新奇的想法,并且能正确详细地传达给同伴;
<4>为人有领导能力,在不能进行判断该按照哪个方向进行编程时会及时提出正确的思路。
缺点:
编程会犯一些比较低级的错误
(2) 刘耀先
优点:
<1>代码编写实践操作比较多,对于编程中出现的问题可以进行适应性处理,按照需求设计算法,会按照需求改写代码;
<2>性格上一丝不苟,对于枯燥的代码编写工作能够沉得住气;
<3>代码编写习惯很好;
<4>经常与同学交流,询问结对编程的同学的建议,并听取有用的建议改善程序。
缺点:
经常不能得出很好的算法。
这样的分组正是为了能够试我们能达到更好的配合。而实际上这也使得我们在最短的时间内做到了比较好的磨合。
3、设计方法
对于程序的编写,我们可以从三个角度来看。
(1)information hiding(信息屏蔽)
我们写代码时,有些内容属于不能让别人知道的。这在实际应用中有着很大的用处,比如我们封装就是为了能够让别人不能盗版我们的软件。这也就是信息屏蔽的作用。在这方面,c#可以使用抽象类进行实现。模块可以操作一个抽象体。由于模块依赖于一个固定的抽象体,因此它可以是不允许修改的;同时,通过从这个抽象体派生,也可扩展此模块的行为功能。(2)interface design(接口设计)
我们设计一个模块,最终目的还是要让用户使用。但是在信息是屏蔽的状态下用户无法使用我们的模块,这时我们就需要开一个interface让用户使用。Interface有三个重要的元素:UI界面、外部接口和内部接口。用外部接口来说,我们通过ControlPanel类就可以实现键盘接入。不同的接口需要有不同的类来实现。
<3> loose coupling(耦合性)
系统会越来越复杂,这就导致我们的test和debug会越来越复杂。我们如果能让各个类之间的联系变少,那么相应的这个程序也会变得更加清晰,这对我们的后续工作有很大的帮助。比如,相比起5个类之间只有5个联系的程序,5个类互相都有联系的程序会显得更加混乱而复杂。这也就要求我们在保证程序正确高效运行的情况下尽量降低耦合度。
4、DbC
DbC(Design by Contract)是指契约式模式,其中包括三种:
(1)Post-conditions 后置条件postcondition 表示调用一个方法一定会得到的结果。类似断言Assertion,如果语言不支持断言,那么我们就必须自己写断言,也就是测试驱动了。
(2)Pre-conditions 前置条件precondition ,预先保证后置条件必须满足前置条件。前置条件必须满足,后置条件必须实现,通过契约的前置和后置条件的结合,就不会出现有隐藏的功能obligations,这样,事情清清楚楚地被摆出来。这样设计才能落实为代码,保证正常的对象调用。
(3)类不变量class invariant 表示对象状态的断言,执行完任何操作后都都应该被满足,不变量还是对聚合体进行完整性严格定义。不变性意义非常重要,不变性意思是对象的所有属性必须由其方法来保证一致性。DDD推广到根对象要通过方法保证其聚合体内所有对象的属性保持一致性。比如ForumThread的addMessage就保证加入新的Message以后,其聚合体内相关属性一致修改,保持不变性是一个排他性过程,或者说需要由锁或事务来完成,这又和伸缩性scalable设计有关。
5、代码协定
代码协定提供一种使用代码指定前置条件、后置条件和对象固定条件的方式。 前置条件是在输入方法或属性时必须满足的要求。 后置条件描述方法或属性代码退出时的预期。 对象固定条件描述正常状态下的类的预期状态。
代码协定包含用于标记代码的类、用于编译时分析的静态分析器和运行时分析器。 代码协定的类可在System.Diagnostics.Contracts命名空间中找到。
代码协定的优点包括:
改进测试:代码协定提供静态协定验证、运行时检查和文档生成。
自动测试工具:可以使用代码协定来筛选掉不满足前置条件的没有意义的测试参数,从而生成更有意义的单元测试。
静态验证:静态检查器可以在不运行程序的情况下确定是否存在任何违反协定的情况。 它会检查隐式协定(如 null 取消引用和数组绑定)和显式协定。
引用文档:文档生成器在现有的 XML 文档文件增加协定信息。 还提供了可与 Sandcastle 一起使用的样式表,以便生成的文档页具有协定节。
所有 .NET Framework 语言都可以立即使用协定;您不必编写特殊的分析器或编译器。 利用 Visual Studio 外接程序,您可以指定要执行的代码协定分析的级别。 分析器可以确认协定的格式正确(执行类型检查和名称解析),并且可以使用 Microsoft 中间语言 (MSIL) 格式生成编译格式的协定。 通过在 Visual Studio 中创作协定,您可以利用该工具提供的标准 IntelliSense。
协定类中的大多数方法都是在一定条件下编译的;也就是说,只有在使用 #define 指令定义特殊符号 CONTRACTS FULL 时,编译器才会发出对这些方法的调用。 可以利用 CONTRACTS FULL 通过代码编写协定,而不必使用 #ifdef 指令;可以产生不同的生成,有些生成包含协定,有些不包含协定。
6、Unit Test单元测试
单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。Pair Project讲的是两人合作,既然程序是两个人写的,那就会出现一个人写的模块被另一个人写的模块调用的情况。很多误解、疏忽都发生在两个模块之间。因此,为了能让自己写的模块尽量无懈可击,单元测试就是一个很有效的解决方案。衡量一个测试的性能,代码覆盖率是一个重要的指标,设计一个好的Case应使得代码覆盖率尽量高。
测试代码如下:
覆盖率图:
7、UML图
UML是一种用来对真实世界物体进行建模的标准标记,这个建模的过程是开发面向对象设计方法的第一步。一个好的UML图可以减轻程序员很大的负担。
以下为UML图:
8、算法实现
电梯调度问题算得上是经典训练题了。
读完题目和代码,我和罗凡最先达成的共识就是,把原本的每层都停改变为“招手即停”,即如果有人设定目标楼层,则电梯通往目标楼层;否则电梯不停留。但是显然这样的想法实在是太简单了。
因为我们还要考虑电梯里面有人的情况:
如果电梯里的乘客想往某个方向前进,电梯外却有其他乘客想往同/另一方向前进,这时电梯该怎么走?
如果电梯里的多名乘客想去不同楼层,电梯该如何选择停留顺序?
当这两个问题同时发生时,电梯该怎么办?
于是我们查阅了一些资料。CNBLOG是个非常好的网站,因为上面不仅有众大神给出的成熟算法(http://www.cnblogs.com/jianyungsun/archive/2011/03/16/1986439.html),我们从这里得到了一些启示。
在比较过众多算法过后,我们放弃了对不同楼层进行权值计算的算法,而选择“搭车而行最省事儿”的方法来实现电梯调度。
正如我们思考的问题所提到,电梯的运行分为两种状态——电梯外的乘客发出指令与电梯内的乘客发出指令,我们将电梯的状态就简单分为static和dynamic两种,并用staticElev_dispatch和dynamicElev_dispatch来调度电梯。
staticElev_dispatch:
在电梯内部没有指令时,若电梯外部有指令,则让电梯向指令方向前进。前进时判断:
如果当前前进方向有其他外部指令,则让电梯优先按着行驶方向最远的那一站前进;
如果当前前进方向没有其他外部指令,则让电梯运送完当前乘客后,按反方向的最远的那一站前进。
之所以这么做,就是为了实现“搭车而行最省事”的想法,即电梯开往最远端的那一站时,能把顺路的乘客都带上,从而提高效率。
“带上乘客”则由dynamicElev_dispatch来实现。
dynamicElev_dispatch:
电梯前进时,如果顺路有乘客,则带上他,但这必须首先满足一个条件:电梯还能装下——人数不大于规定人数且总重不大于规定总重。
当然,这里还涉及如何判定乘客在电梯内还是电梯外的问题,讨论后解决。
第一组
第二组
第三组