题目要求
1)在文章开头给出Github项目地址。(1')
2)在开始实现程序之前,在下述PSP表格记录下你估计将在程序的各个模块的开发上耗费的时间。(0.5')
3)看教科书和其它资料中关于Information Hiding, Interface Design, Loose Coupling的章节,说明你们在结对编程中是如何利用这些方法对接口进行设计的。(5')
4)计算模块接口的设计与实现过程。设计包括代码如何组织,比如会有几个类,几个函数,他们之间关系如何,关键函数是否需要画出流程图?说明你的算法的关键(不必列出源代码),以及独到之处。(7')
5)阅读有关UML的内容:https://en.wikipedia.org/wiki/Unified_Modeling_Language。画出UML图显示计算模块部分各个实体之间的关系(画一个图即可)。(2’)
6)计算模块接口部分的性能改进。记录在改进计算模块性能上所花费的时间,描述你改进的思路,并展示一张性能分析图(由VS 2015/2017的性能分析工具自动生成),并展示你程序中消耗最大的函数。(3')
7)看Design by Contract, Code Contract的内容:
http://en.wikipedia.org/wiki/Design_by_contract
http://msdn.microsoft.com/en-us/devlabs/dd491992.aspx
描述这些做法的优缺点, 说明你是如何把它们融入结对作业中的。(5')
8)计算模块部分单元测试展示。展示出项目部分单元测试代码,并说明测试的函数,构造测试数据的思路。并将单元测试得到的测试覆盖率截图,发表在博客中。要求总体覆盖率到90%以上,否则单元测试部分视作无效。(6')
9)计算模块部分异常处理说明。在博客中详细介绍每种异常的设计目标。每种异常都要选择一个单元测试样例发布在博客中,并指明错误对应的场景。(5')
10)界面模块的详细设计过程。在博客中详细介绍界面模块是如何设计的,并写一些必要的代码说明解释实现过程。(5')
11)界面模块与计算模块的对接。详细地描述UI模块的设计与两个模块的对接,并在博客中截图实现的功能。(4')
12)描述结对的过程,提供非摆拍的两人在讨论的结对照片。(1')
13)看教科书和其它参考书,网站中关于结对编程的章节,例如:
http://www.cnblogs.com/xinz/archive/2011/08/07/2130332.html
说明结对编程的优点和缺点。
结对的每一个人的优点和缺点在哪里 (要列出至少三个优点和一个缺点)。(5')
14)在你实现完程序之后,在附录提供的PSP表格记录下你在程序的各个模块上实际花费的时间。(0.5')
回答
- 问题中带#的部分是我的回答内容,其余部分请移步 http://www.cnblogs.com/wzjb/p/7667822.html 梓嘉在它的博客里做了很好的回答与说明,在设计和实现的许多环节里,梓嘉
都做出了非常大的贡献,而我则因为种种原因(拖延症,时间安排等),没有非常好的尽到应尽的责任,甚至拖慢了项目的进度。
github
https://github.com/ZiJiaW/SudokuGame
3)
设计
- 关于Information Hiding
其概念的具体内涵在我看来就是,尽可能隐藏、封闭实现的细节,将具体复杂琐碎的数据和操作用函数化、私有化、局部化的方法加以限制,使其不需要也不能被外部了解。
这样一来好处有两点:
- 减少其他人或者日后的自己理解主要逻辑流程的难度
- 方便维护,如要更改具体实现的细节,升级或者是修复Bug都可以在局部解决。
在实现中,我举个例子
我在类的设计中使用了ArgumentHandler和FileHandler
class ArgumentHandler
{
public:
ArgumentHandler();
void ParseInput(int argc, char **argv);
State GetState();
unsigned int GetCount();
const char * GetPathName();
Difficulty GetDifficulty();
unsigned int GetLower();
unsigned int GetUpper();
private:
..
}
这个类的作用就是处理命令行参数,并且提供各种Get返回处理的结果,作为外部使用者而言,不需要知道任何private的内容就可以使用它,其内部储存的数据对外界是不可见的(没有Set)这样一来其能确保安全性。
- 关于Interface design和LooseCoupling
继续以上一个例子进行说明,我们在结对编程时需要处理比个人项目而言更多的参数,但是对于ParseInput而言,其输入端不需要改变,其原有的输出也不用改变,唯一需要改变的就是ParseInput内部的实现。像这样的设计还被用来将数独生成和求解的算法细节与类的公共接口分开,将缓冲的内部实现对外部隐藏等。
其作用就是将功能实现代码,与使用功能的代码区分开来,以Interface作为协议,这么做的好处很明显,划清责任能方便共同开发。
契约式编程的好处是使的代码的流程和结构变得非常的精致,每一个函数的行为规范都有明确设计,这样一来大部分的错误和问题变得有径可循。
其坏处是使的代码的改动变得不那么容易,因为这意味着重新设计契约-而这最好是在一开始就能确定下来,否则其损失的成本可能不能被其带来的好处所弥补。
我们在实现项目时并没有完全按照契约式编程的设计来做。
对于前置条件,我们往往假定所有传入的参数都是合理的,而不对其做检查,这样可能有风险但是其使得开发变得快捷高效。
对于SideEffect, 这在实现中经常被用来返回数据(通过指针)
对于后置条件,一般函数名能够很好的说明返回值的意义,例如Get,Is开头的函数。不过确实有一些函数的返回值,其意义较难以从函数名中看出来,例如Solve这个函数,我规定其返回
的值为传入的数独中可解数独的数量,这点可能确实不明显。
9)
/*计算模块中使用的主要是参数错误异常,在函数的开头就对参数做详细的分析,确保其符合规范,否则就抛出参数无效异常。
其提示依据不同的函数而不同*/
void generate(int number, int mode, int result[][81]){
if (number < 0)
{
throw invalid_argument("The argument of \"-n\" shouldn`t be less than zero");
}
else if (number > gMaxGenRanAmount)
{
throw invalid_argument("The argument of \"-n\" shouldn`t be bigger than 10000");
}
else if (mode < 1 || mode>3)
{
throw invalid_argument("The argument of \"-m\" should be in the range of [1,3]");
}
else if(result==NULL)
{
throw invalid_argument("The argument of result shouldn`t be NULL");
}
}
bool solve(int puzzle[], int solution[81])
{
if (puzzle == NULL)
{
throw invalid_argument("The argument of puzzle shouldn`t be NULL");
}
else if (solution == NULL)
{
throw invalid_argument("The argument of solution shouldn`t be NULL");
}
}
结对编程的优点
- 弥补个人能力的短板和重大的缺陷
- 互相督促,对其他人负责的心理能很好的促进进度。
- 分工合作,节省精力
- 相互学习
缺点
- 进度难以把控
- 合作可能会低效率,责任不清楚的地方可能两个人做的还不如一个人好
PSP
部分数据来自后期估计..