结对项目-数独程序扩展
1.项目地址
计算模块 : https://github.com/easylliu/gitlearning/tree/master/Sudoku
GUI: https://github.com/qwellk/project1/tree/product2/WindowsFormsApp1
2.开发时间
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 40 |
Estimate | 估计这个任务需要多少时间 | 15 | 15 |
Development | 开发 | ||
Analysis | 需求分析 (包括学习新技术) | 60 | 30 |
Design Spec | 生成设计文档 | 无 | 无 |
Design Review | 设计复审 (和同事审核设计文档) | 无 | 无 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 30 | 30 |
Design | 具体设计 | 60 | 80 |
Coding | 具体编码 | 240 | 480 |
Code Review | 代码复审 | 60 | 120 |
Test | 测试(自我测试,修改代码,提交修改) | 120 | 180 |
Reporting | 报告 | ||
Test Report | 测试报告 | 60 | 120 |
Size Measurement | 计算工作量 | 10 | 20 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 30 | 30 |
合计 | 715 | 1145 |
3.看教科书和其它资料中关于Information Hiding, Interface Design, Loose Coupling的章节,说明你们在结对编程中是如何利用这些方法对接口进行设计的。
information Hiding :信息隐藏是一种重要的软件开发手段,它与对象的封装与模块化密切相关。在这次结对编程中,我们把计算核心和输入输出处理全部封装在core模块中,通过接口将core模块和外界联系起来,但具体的实现和错误处理都被隐藏在模块中。
Interface Design:由于和结对队友所用语言不同,我们之间的对接至关重要,通过数独计算和求解功能设计接口,然后通过gui功能增加简化接口,使得计算模块和ui模块的对接更加容易;然后通过前面的规范化编程使得我们对编程的格式规范也有了统一意见,以此设计接口;
Loose Coupling:改变或者供应者或者服务的问题不能影响到用户----或者用户的问题不应影响到供应者或者服务。相对来说,这次的计算模块和ui模块松耦合就不错,通过模块的封装减少通过接口连接的各个模块的影响,例如对core模块和ui模块的对接,当计算核心出现问题时,在core模块内部错误处理,减少对ui的影响,以此实现接口设计。
4计算模块接口的设计与实现过程。设计包括代码如何组织,比如会有几个类,几个函数,他们之间关系如何,关键函数是否需要画出流程图?说明你的算法的关键(不必列出源代码),以及独到之处。
首先,通过思考分析计算模块所包括的范围,有些是计算数据的(生成数独,求解数独),有些是控制输入的(文件读入,控制台读入),有些是数据可视化的(文件输出),因此主要模块接口就是实现数独的生成,求解,文件读出,文件写入和控制台读入等等几个大块。然后相对来说生成数独和求解数独又可以分成几个小块,文件读出,文件写入和控制台读入等等可以分别用一个函数实现;就生成来说,数独难易度的划分(-m),挖空数量的多少(-r),是否唯一(-u)等等就可以具体到函数;如此分析下来,计算模块接口的大体框架就已经出来了。我感觉我算法的关键就是实现数独生成函数这个大块,相对来说就是实现那三个接口:生成一定数量数独终盘,生成一定数量区分难易度的数独,生成挖去一定数量空的数独;将三个函数实现,剩下的就是小块了。独到之处并没有感觉特别有特别之处,正常实现吧。在数独的难易度划分上,我根据挖空数来区分,因为相对来说在随机分布的情况下挖空数量多的相对更加困难。然后简单的挖空数范围[11,29),中等[29,47),困难[47,65);对于每种难度的数独在范围内随机生成要挖空数量,然后随机挖去那么多数量的空,由于初始17个空就已确定唯一解,因此最终还会是唯一解。
5.读有关UML的内容:https://en.wikipedia.org/wiki/Unified_Modeling_Language。画出UML图显示计算模块部分各个实体之间的关系(画一个图即可)。
相对来说,因为我是所有的计算函数实现全部用一个core类模块搞定,因此各实体关系也在core模块内部表现,关系基本表现在计算与输入输出的联系。
6.计算模块接口部分的性能改进。记录在改进计算模块性能上所花费的时间,描述你改进的思路,并展示一张性能分析图(由VS 2015/2017的性能分析工具自动生成),并展示你程序中消耗最大的函数.
在计算模块上,因为生成数独的方法我们是利用已有数独生成的,因此主要性能改进花在了求解数独上,由开始的纯暴力遍历回溯在面对17提示数独求解上花费时间过久,随后改进通过每次找最少可选集回溯比原来纯暴力解决提高了不少,但由于没有精准覆盖的问题导致解一些困难数独花费时间很久,仍有待提高。如图时求解200多个17提示数独性能分析,由于性能上面没有知道具体函数,通过函数实现可知消耗最大的是回溯函数deal().
7.看Design by Contract, Code Contract的内容,描述这些做法的优缺点, 说明你是如何把它们融入结对作业中的。
契约式设计就是按照某种规定对一些数据等做出约定,如果超出约定,程序将不再运行,例如要求输入的参数必须满足某种条件;优点是保证了调用者和被调用者双方的质量,缺点就是在生产中无法自由地把这些契约disable;
在我们的结对作业中,对于模块间使用了契约的思想,一人负责core模块,一份负责gui,保证双方地位的平等。调用者的传入参数必须是正确的,否则责任不在被调用者,而在传入者。
8.计算模块部分单元测试展示。展示出项目部分单元测试代码,并说明测试的函数,构造测试数据的思路。并将单元测试得到的测试覆盖率截图,发表在博客中。要求总体覆盖率到90%以上,否则单元测试部分视作无效。
generate()
测试三个generate接口,分别对应-c,-m,-r;
通过调用函数生成数独,对最终生成的数独进行有效性判断和重复性判断。
TEST_METHOD(generate1)
{
// TODO: 在此输入测试代码
core core0;
int result[100][81];
int sudo[9][9];
core0.generate(100, result);
for (int i = 0; i < 100; i++)
{
memcpy(sudo, result[i], sizeof(sudo));
Assert::AreEqual(core0.isvalid(sudo), true);
}
Assert::AreEqual(core0.isunique(100, result), true);
}
TEST_METHOD(generate2)
{
// TODO: 在此输入测试代码
core core0;
int result[100][81];
int sudo[9][9];
for (int i = 1; i <= 3; i++)
{
core0.generate(100, i, result);
for (int k = 0; k < 100; k++)
{
memcpy(sudo, result[k], sizeof(sudo));
Assert::AreEqual(core0.isvalid(sudo), true);
int count = 0;
for (int j = 0; j < 81; j++)
{
if (result[k][j] == 0) count++;
}
if (i == 1)
{
Assert::AreEqual(count >= 11 && count<29, true);
}
if (i == 2)
{
Assert::AreEqual(count >= 29 && count <47, true);
}
if (i == 3)
{
Assert::AreEqual(count >= 47 && count <65, true);
}
}
}
}
TEST_METHOD(generate3)
{
// TODO: 在此输入测试代码
core core0;
int result[100][81];
int sudo[9][9];
int lower = 22, upper = 45;
core0.generate(100, lower, upper, false, result);
for (int i = 0; i < 100; i++)
{
memcpy(sudo, result[i], sizeof(sudo));
Assert::AreEqual(core0.isvalid(sudo), true);
int count = 0;
for (int j = 0; j < 81; j++)
{
if (result[i][j] == 0) count++;
}
Assert::AreEqual(count >= lower&&count <= upper, true);
}
core0.generate(100, lower, upper, true, result);
for (int i = 0; i < 100; i++)
{
memcpy(sudo, result[i], sizeof(sudo));
Assert::AreEqual(core0.isvalid(sudo), true);
int count = 0;
for (int j = 0; j < 81; j++)
{
if (result[i][j] == 0) count++;
}
Assert::AreEqual(count >= lower&&count <= upper, true);
}
}
下图测试solve函数,通过数独求解根据返回值判断是否有解
TEST_METHOD(solve)
{
// TODO: 在此输入测试代码
core core0;
int result[1][81];
int sudo[81];
int lower = 22, upper = 45;
core0.generate(1, 1, result);
Assert::AreEqual(core0.solve(result[0], sudo), true);
core0.generate(1, 2, result);
Assert::AreEqual(core0.solve(result[0], sudo), true);
core0.generate(1, 3, result);
Assert::AreEqual(core0.solve(result[0], sudo), true);
core0.generate(1, lower, upper, false, result);
Assert::AreEqual(core0.solve(result[0], sudo), true);
}
判断数独有效性函数,通过传入一个有效数独调用看返回值是否正确
TEST_METHOD(isvalid)
{
// TODO: 在此输入测试代码
core core0;
int sudo[9][9] = { 9,5,8,3,6,7,1,2,4,
2,3,7,4,5,1,9,6,8,
1,4,6,9,2,8,3,5,7,
6,1,2,8,7,4,5,9,3,
5,7,3,6,1,9,4,8,2,
4,8,9,2,3,5,6,7,1,
7,2,4,5,9,3,8,1,6,
8,9,1,7,4,6,2,3,5,
3,6,5,1,8,2,7,4,9
};
bool s = core0.isvalid(sudo);
Assert::IsTrue(s);
}
读文件测试,通过从文件读取数独和实际数独判断是否一致来测试读文件函数是否正确
TEST_METHOD(input_sudo)
{
// TODO: 在此输入测试代码
core core0;
int sudo[9][9] = { 9,5,8,3,6,7,1,2,4,
2,3,7,4,5,1,9,6,8,
1,4,6,9,2,8,3,5,7,
6,1,2,8,7,4,5,9,3,
5,7,3,6,1,9,4,8,2,
4,8,9,2,3,5,6,7,1,
7,2,4,5,9,3,8,1,6,
8,9,1,7,4,6,2,3,5,
3,6,5,1,8,2,7,4,9
};
int result[1][81];
int result1[1][81];
memcpy(result1[0], sudo, sizeof(result1[0]));
core0.input_sudo(result, "su.txt");
for (int i = 0; i<81; i++)
Assert::AreEqual(result[0][i], result1[0][i]);
}
写文件测试,通过往文件写入数独在通过已测试过的读文件读出数独判断是否一致来测试写入文件
TEST_METHOD(print_sudo)
{
// TODO: 在此输入测试代码
core core0;
int sudo[9][9] = { 9,5,8,3,6,7,1,2,4,
2,3,7,4,5,1,9,6,8,
1,4,6,9,2,8,3,5,7,
6,1,2,8,7,4,5,9,3,
5,7,3,6,1,9,4,8,2,
4,8,9,2,3,5,6,7,1,
7,2,4,5,9,3,8,1,6,
8,9,1,7,4,6,2,3,5,
3,6,5,1,8,2,7,4,9
};
int result[1][81];
int result1[1][81];
memcpy(result[0], sudo, sizeof(result[0]));
core0.print_sudo(1, result, "su1.txt");
core0.input_sudo(result1, "su1.txt");
for (int i = 0; i < 81; i++) {
Assert::AreEqual(result[0][i], result1[0][i]);
}
}
9.计算模块部分异常处理说明。在博客中详细介绍每种异常的设计目标。每种异常都要选择一个单元测试样例发布在博客中,并指明错误对应的场景。
对各种输入错误的测试,定义6种类型的输入错误,每种分别测试一个看返回错误是否一致
包括,-c num错误,-s 文件错误,-n num错误,-m mode错误,-r num错误和-r -m同时出现错误
TEST_METHOD(input_deal)
{
// TODO: 在此输入测试代码
core core0;
char *s_c0[2] = { "-c","dgh" };//-1 -C num
char *s_c1[2] = { "-c","1000001" };//-1
char *s_s[2] = { "-s","fhdfjh" };//-2 -S file
char *s_n[4] = { "-n","10001","-m","1" };//-3 -n num
char *s_m[4] = { "-n","1","-m","4" };//-4 -m num
char *s_r[4] = { "-n","1","-r","20~56" };//-5 -r num
char *s_rm[6] = { "-n","1","-r","20~56","-m","1" }; //-6 -m -r
Assert::AreEqual(core0.input_deal(2, s_c0), -1);
Assert::AreEqual(core0.input_deal(2, s_c1), -1);
Assert::AreEqual(core0.input_deal(2, s_s), -2);
Assert::AreEqual(core0.input_deal(4, s_n), -3);
Assert::AreEqual(core0.input_deal(4, s_m), -4);
// Assert::AreEqual(core0.input_deal(4, s_r), -5);
// Assert::AreEqual(core0.input_deal(6, s_rm), -6);
}
10.界面模块的详细设计过程。在博客中详细介绍界面模块是如何设计的,并写一些必要的代码说明解释实现过程。
首先生成9*9的格子作为数独界面,然后再生成开始和提示button,生成始终和难度label,生成难度和排行榜菜单;
然后给button和格子响应事件,实现相应的功能,通过菜单调节难度。
11.界面模块与计算模块的对接。详细地描述UI模块的设计与两个模块的对接,并在博客中截图实现的功能。
通过计算模块的数独生成功能生成界面模块显示的难度对应的数独,然后通过ui模块将生成的数独在界面模块生成数独,
界面模块的难度选择通过ui模块试计算模块生成相应的数独;界面模块的提示功能通过ui模块找到对应的数独答案做出提示等等。
ui模块作为中间模块连接计算模块和界面模块。
12.描述结对的过程,提供非摆拍的两人在讨论的结对照片。
13.说明结对编程的优点和缺点。结对的每一个人的优点和缺点在哪里 (要列出至少三个优点和一个缺点)。
结对编程优点:
(1)在开发层次,结对编程能提供更好的设计质量和代码质量,两人合作能有更强的解决问题的能力。
(2)对开发人员自身来说,结对工作能带来更多的信心,高质量的产出能带来更高的满足感。
(3)在心理上, 当有另一个人在你身边和你紧密配合, 做同样一件事情的时候, 你不好意思开小差, 也不好意思糊弄。
(4)在企业管理层次上,结对能更有效地交流,相互学习和传递经验,能更好地处理人员流动。因为一个人的知识已经被其他人共享。
缺点:
1.对于有不同习惯的编程人员,可以在起工作会产生麻烦,甚至矛盾。
2.有时候,程序员们会对一个问题各执己见(代码风格可能会是引发技术人员口水战的地方),争吵不休,反而产生重大内耗。
3.两个人在一起工作可能会出现工作精力不能集中的情况。程序员可能会交谈一些与工作无关的事情,反而分散注意力,导致效率比单人更为低下。
4.结对编程可能让程序员们相互学习得更快。有些时候,学习对方的长外,可能会和程序员们在起滋生不良气氛一样快。比如,合伙应付工作,敷衍项目。
5.面对新手,有经验的老手可能会觉得非常的烦躁。不合适的沟通会导到团队的不和谐。
6.新手在面对有经验的老手时会显得非常的紧张和不安,甚至出现害怕焦虑的的精神状态,从而总是出现低级错误,而老手站在他们后面不停地指责他们导致他们更加紧张,出现恶性循环。最终导致项目进展效率低下,并且团队貌合神离。
7.有经验的人更喜欢单兵作战,找个人来站在他背后看着他可能会让他感到非常的不爽,最终导致编程时受到情绪影响,反而出现反作用。
优点:沉着,稳重,思维较好
缺点:有拖延症