一,关于结对编程
结对编程的优点:
1)在开发层次,结对编程能提供更好的设计质量和代码质量,两人合作能有更强的解决问题的能力。
2)对开发人员自身来说,结对工作能带来更多的信心,高质量的产出能带来更高的满足感。
3)在心理上, 当有另一个人在你身边和你紧密配合, 做同样一件事情的时候, 你不好意思开小差, 也不好意思糊弄。
4)在企业管理层次上,结对能更有效地交流,相互学习和传递经验,能更好地处理人员流动。因为一个人的知识已经被其他人共享。
结对编程的缺点:
1)处于探索阶段的项目,需要深入地研究,在这种情况下,需要一个人独立钻研,结对编程会浪费两个人的时间。
2)在做后期维护的时候,如果维护的技术含量不高,可以一个人完成,结对编程就会降低人均效率。
3)如果验证测试需要运行很长时间,那么两个人在那里等待结果是有点浪费时间。
结对编程:
小组成员:
谢勤政:
优点:
1)擅长读写代码,进行了本次作业的编码工作。
2)会灵机应变处理编程中出现的事情,按照需求设计算法,会按照需求改写代码。
3)经常与同学交流,询问结对编程的同学的建议,并听取有用的建议改善程序。
缺点:
编程不够细心,有时候会犯低级错误。
吴润凡:
优点:
1)有耐心,能费时间仔细检查代码。
2)在队友遇到问题时候能提出各种各样的建议。
3)有新奇的想法,并且能正确详细地传达给同伴。
缺点:
编写代码的能力不足。
二,设计方法
1)信息隐蔽:信息隐蔽原则建议模块应该具有的特征是:每个模块对其他所有模块都隐蔽自己的设计决策。隐蔽意味着通过定义一系列独立的模块可以得到有效的模块化,独立模块相互之间只交流实现软件功能所必需的那些信息。抽象有助于定义构成软件的过程(或信息)实体。隐蔽定义并加强了对模块内过程细节的访问约束和对模块所使用的任何局部数据结构的访问约束。将信息隐蔽作为模块化系统的一个设计标准,在测试和随后的软件维护过程中需要进行修改时,可提供最大的益处。由于大多数数据和过程对软件的其他部分是隐蔽的,因此在修改过程中不小心引入的错误不太可能传播到软件的其他地方。
2)接口设计:软件的接口设计相当于一组房屋的门窗和外部设施的详细绘图(以及规格说明)。这些绘图描绘了门窗的尺寸和形状、门窗的工作方式、设施(例如,水、电、气、电话)链接入室的方式和平面图上描绘的在室内的分布方式。图纸可以告诉我们门铃在哪、是否使用内部通信以通知有客来访以及如何安装保安系统。门窗、外部设施的详细图纸(以及规格说明)作为平面图的一部分,大体上告诉我们时间和信息如何流入和流出住宅以及如何在平面图的房间内流动。软件接口设计元素描述了信息如何流入和流出系统以及被定义为体系结构一部分的构件之间是如何通信的。
3)耦合性:随着通信和写作数量的增长(也就是说,随着类之间的联系成都越来越强),系统的复杂性也随之增长了。同时随着系统复杂度的增长,软件实现、测试和维护的困难也随之增大。耦合是累之间彼此联系程度的一种定性度量。随着类相互依赖越来越多,类之间的耦合程度一会增加,在构件级设计中,一个重要的目标就是尽可能保持低耦合。
4)DBC分三种:
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 指令;可以产生不同的生成,有些生成包含协定,有些不包含协定。
三,Unit Test 单元测试
Pair Project讲的是两人合作,既然程序是两个人写的,那就会出现一个人写的模块被另一个人写的模块调用的情况。很多误解、疏忽都发生在两个模块之间。为了能让自己写的模块尽量无懈可击,单元测试就是一个很有效的解决方案。衡量一个测试的性能,代码覆盖率是一个重要的指标,设计一个好的Case应使得代码覆盖率尽量高。
四,UML图
大致结构如下:
五,结对编程作业的算法
首先说明,文中提到“外部需求”即是电梯外的正在等待的人的需求,“内部需求”是指正在电梯内搭乘电梯的人的需求。
大致想法为仿造现实中电梯运行的方法,在考虑效率的同时也要考虑到公平性。
但是稍有修改:现实中的电梯,在电梯中没有搭乘的人的请求,电梯外也没有等待的人的请求的时候,会自动回到1层。但是为了保证效率,决定在这种情况下让电梯向10层靠拢。这样在算术平均的情况下电梯能更快地到达出现需求的层数。
改写Scheduler.cs,用startNew()和pickUp()两个函数来做主要工作。在原本是StopAtEachFloor()在Run()函数被调用的地方,删除StopAtEachFloor()函数的调用,改为判定,当现在电梯运动方向为Direction.No的时候,即是在电梯停止的时候,调用startNew();在判定电梯正在运动的时候调用pickUp()函数。
startNew()函数的主要作用是,在电梯没有内部需求的时候,检查电梯外有没有人在等待,即有没有外部需求,如果有,将这个外部需求转换为电梯内部需求,让电梯向需要的方向运动。且会进行判定。如果当前历史行进方向有外部需求,则电梯优先到历史行驶方向最远端外部需求处。如果当前历史行进方向没有外部需求,则另一方向必有外部需求(根据之前的if语句完全没有外部需求则不会进入判定,详见代码),则向另一方向的外部需求最远端行进。该函数之所以只考虑最最远端,是因为该函数是与pickUp()函数协同工作的,在去最远端的过程中,电梯在行驶则会调用pickUp()函数,将路上能带的人带上。接着判定,如果电梯没有外部需求也没有内部需求,则会让电梯进行如下动作:如果电梯在10层之上,则让电梯往下一层行进一层,如果电梯在10层之下,则让电梯往上一层行进一层。这样做的目的是让电梯向整栋楼的*靠近,在有需求的时候算术平均更快地到达需求的地点。
pickUp()函数的作用是,在电梯行驶时调用,在电梯行驶的路线上将能够带上的人带上,如果当时电梯的重量剩余不足45(即不够一个人),则不停下开门。其作用见上文startNew()函数,与之协同工作。
大体思路是如此,在实现的时候代码中也有注释,老师给的代码修改过的地方仅限于Scheduler.cs,别的地方没有进行修改。
这个写法存在一个比较严重的缺陷,就是这个优化办法没有考虑上下班高峰期的特例处理。而是每时每刻都会考虑公平性,因此会损失一部分的效率。
按照此算法变成,测试给的三组样例,得到的结果为如下:
第一组: 第二组: 第三组:
相比原本每层都停的算法在效率上有了显著提升。