一、结对编程
结对成员: 康家华(学号后三位190)
1. 结对编程照片
2. 结对编程的优点
1) 单人编程时,由于每个人的精力有限,无法长时间的高度集中精力实现高质量编程。而在结对编程时,由于两个人的身份可以互换,能够有效地提高效率。
2) 在实现某项功能的时候,如果一个人的算法不够优秀,那么对方能够提出更为简单易行的算法,从而提高代码质量。
3) 如果是单人编程,在设计算法时很有可能会有漏洞,而且如果在设计算法之前没有考虑到一些边界条件的话,测试时也很难发现这些bug。而在结对编程时,两个人的思维会比较全面,更容易发现代码中的不足之处。
3. 结对编程的缺点
1) 如果在结对编程时,两个人的性格、工作时间、思维方式等个人条件相差过大的话,很有可能会影响到合作的质量。以这次项目为例,由于这次项目是随机组队的,所以有很大的几率碰到不合适结对编程的队友,导致合作的不愉快。(不过很幸运,我这次合作很是很愉快哒)
2) 在结对编程之前,需要进行一些准备工作,例如共同讨论设计思路,确定怎样实现算法,以及用户界面的设计等等,结对编程可能会比单人编程消耗更多的时间。以这次项目为例,由于这次项目是在个人项目的基础上修改的,如果两个人的第一次代码风格或者算法差异过大的话,在编写新项目的时候可能会遇到困难。
4. 结对伙伴的优缺点
1) 优点:
a) 编程效率很高,每次都能在很短的时间内完成很高质量的代码。
b) 代码很严谨,写出来的软件考虑到了很多细节问题。
c) 代码风格简单易懂(可能是因为和我的风格比较像),在看他写的代码的时候,很少出现看不懂的情况。
总而言之,真的是个很靠谱的小伙伴,完成代码不仅质量高而且速度快,每次看到他写的代码的时候经常会发现很多自己考虑不到的细节,而且经常会用一些让我自己完全想不到的非常简洁的算法(比如说在判断表达式相等的时候,他就给出了一个很巧妙的想法,很简单地就解决了这个问题)。
2) 缺点:
a) 其实没什么缺点,因为真的太靠谱了,如果要说哪里还可以改进的话,代码的注释相对比较少,如果是第一次接触这个项目的话,理解起来可能会遇到一些问题。
5. 我的优缺点
1) 优点:
a) 思维比较严密,能够在设计算法或者设计单元测试的时候覆盖绝大多数的情况。
b) 易于交流,两个人在合作的时候很顺利,没有遇到什么冲突或者问题。
c) 思维能力强,基本上遇到问题都能在短时间内想出对应的算法。
2) 缺点:
a) 有的时候会比较懒,在想出算法了以后不太愿意写代码来实现。
b) 效率不够高,虽然在这次项目中自己的任务都按时完成了,不过很大一部分原因是分配给我的任务都是比较简单的,而真正复杂的部分我的结对伙伴都已经完成了。
二、设计方法
1. Information Hiding
信息隐藏是面向对象程序设计中非常重要的一个因素,如果能够将每个模块内部的复杂信息隐藏在这个模块内部而不暴露给外界,那么能够有效地提高整个项目的封装性。
以这次的项目为例,Core模块内部有很多的属性以及为了实现算法而构造的辅助函数,但这些都被设置为Private类型,不能被外界观察到,外部真正可以调用的只有Calculate、Produce、Judge、Setting这四个函数,并且每个函数的参数、返回值的意义非常简洁明了,调用者完全不需要考虑这些函数是怎样实现的。
2. Interface Design
面相接口的程序设计思想在团队协作开发软件的时候尤其重要,可以有效地提高团队工作的效率和同步性。就像在某个项目开始实现之前,如果能够确定好每个模块所需要实现的接口,那么整个团队就可以同时开始实现所有的模块,而不需要一层一层地去完成整个项目。
以这次项目为例,虽然这个项目很小,不太需要接口的设计,但是从中还是能体会到面向接口编程的特点的。比如说核心模块Core和用户界面的交互,如果实现能够确定写来Core需要实现的接口,那么Core模块和用户界面就完全可以同时进行,在项目比较大的时候,这个方法能够很大程度上提高整个团队的效率。
3. Loose Coupling
在我看来,Loose Coupling(松耦合)和信息隐藏以及面相接口编程在本质上是差不多的,如果能够实现Information Hiding 和Interface Design,在接口设计时就能够避免多个模块同时访问某一个变量或者某一组数据,就能够保证所有模块之间的数据不会相互影响而造成不良后果,自然也就实现了Loose Coupling。
三、契约编程
在契约式编程中,我们需要事先制定每个类的不变式以及每个方法的前置条件和后置条件,在实现具体方法的时候,就很容易检测方法实现的是否正确,并且针对边界数据或者不合理的数据也有了明了的解决方式。
如果实现了契约编程,在外部调用每个方法的时候,如果输入的参数不符合前置条件,就会因为断言失败而直接结束整个方法的执行,而不会因此产生不可控的后果。此外,如果某个方法的代码实现出现了错误,说明在输入参数满足前置条件的情况下,方法执行结果不满足后置条件或者不满足不变式中的某一条断言,这样就能够非常迅速地定位到错误的原因,节约大量的调试时间。
在这次的项目中,我们没有选择使用契约编程,因为在个人项目中已经将整个程序的大体框架构建、调试完毕,使用契约编程的意义就不是很大了。不过,还是可以从一些细节看出契约编程的好处,例如在类的计算方法中,如果能使用前置条件规定输入参数必须满足的要求,那么在出现数据溢出或者除数为零的情况时,就能很简单地判断出错误从而抛出相应的异常。
四、单元测试
针对Core这个类,我们设计了23个测试点,针对Core类的代码覆盖率达到了98.74%,并且测试全部通过,具体截图如下:
五、UML
针对本次项目我们画出了UML类图,具体如下:
六、算法实现
由于我和结对小伙伴的第一次项目的代码风格以及算法核心思想比较相近,所以在算法制定方面没有遇到什么非常大的困难,只需要以第一次的代码为基础,加以修改即可。
整个Core包括四个关键的方法:Calculate(计算某个表达式的值)、Produce(生成指定数量的题目和答案)、Judge(比对指定的题目文件和答案文件)、Setting(设置produce方法的各种参数)。
Calculate和Judge方法主要是依赖于Expression类来实现的。Expression类中的Calculate方法能够计算某个表达式的具体结果,将答案以字符串的形式存入对应的属性中,如果正确运算则返回true,遇到错误(例如除数为零)则返回false。
Produce方法则是依赖于Expression类的构造方法实现的,随机产生运算数、运算符和括号,构建表达式并利用Calculate方法计算结果。如果出现生成错误(例如除数为零或者生成了一样的表达式),那么则重新随机生成一次即可,直到生成数量达到了要求的数量为止。
Setting方法的传入参数为Dictionary类型数值,所以可以不同参数值的设置可以作为一个参数进行传递,算是算法中比较巧妙的一点。