项目 | 内容 |
---|---|
这个作业属于哪个课程 | 软件工程 |
这个作业的要求在哪里 | 结对编程项目-最长英语单词链 |
我在这个课程的目标是 | 增加开发项目具体经验,提高团队协作能力 |
这个作业在哪个具体方面帮助我实现目标 | 体验结对编程,进行工程实践 |
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 30 |
· Estimate | · 估计这个任务需要多少时间 | 30 | 30 |
Development | 开发 | 1040 | 1190 |
· Analysis | · 需求分析 (包括学习新技术) | 90 | 150 |
· Design Spec | · 生成设计文档 | 60 | 50 |
· Design Review | · 设计复审 (和同事审核设计文档) | 30 | 60 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | 20 |
· Design | · 具体设计 | 90 | 70 |
· Coding | · 具体编码 | 450 | 540 |
· Code Review | · 代码复审 | 90 | 120 |
· Test | · 测试(自我测试,修改代码,提交修改) | 150 | 180 |
Reporting | 报告 | 180 | 160 |
· Test Report | · 测试报告 | 90 | 70 |
· Size Measurement | · 计算工作量 | 30 | 30 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 60 | 60 |
合计 | 1250 | 1380 |
信息隐藏:信息隐藏是面向对象的基础,该思想将某些功能的具体实现方法封装在函数里,外界使用该功能只需要调用函数便可以获得想要的结果,保证了数据的安全性、程序的简洁性。我们将接口设计的尽量简洁,使一个函数可以独立完成一个功能,而接口里的属性应是private,不被外界影响的。
接口设计:接口设计应该满足可以提供功能所需的所有参数的基础上尽可能简洁,且不同需求的接口要分开。我们严格遵守课程组提供的接口,接口中包含原始提供数据,控制参数,以及存储结果的容器。
松耦合:松耦合可以使该模块和其他程序通过接口组合,由于都是采用老师的接口,比较容易松耦合,但我们在之前处理了大小写部分,直接传输不合规范的数据时会存在问题。
设计包括代码如何组织,比如会有几个类,几个函数,他们之间关系如何,关键函数是否需要画出流程图?说明你的算法的关键(不必列出源代码),以及独到之处。(7')
在命令行处理中,需要先处理文本,统一大小写,处理参数异常。获取参数后将源单词和约束参数传给getWordLinks获取全部单词链,再按照不同的输出函数处理输出。
在core模块中,我们的计算模块接口实现了五个函数,分别是四个实现需求功能的接口函数(-n,-w,-m,-c四个需求)以及一个底层的具体实现计算的函数(根据单词数组和参数获得符合要求的单词链数组的函数)。四个接口函数供其他模块调用。每个接口函数都会调用底层函数来得到单词链数组,并根据自身需求加以处理返回。
这次的编程题目可以用有向有环图来表示,将一个字母视为一个结点,将单词视为边,一共有26*26个边,建立邻接矩阵,在每个边中存储相应的单词。如apple这个词,存在从a到e的边中。
寻找单词链时,从首字母开始,寻找在矩阵中是否存在由该字母开始的单词,若有,则将该单词标记为已用,并将该单词首字母设为首字母进行递归,直到某首字母没有可连接的单词。其中调用了一个getOneLink的函数来进行递归。若此时单词链中包含至少2个单词,则输出进单词链词组。若某单词首字母之前使用过且没有r参数,则存在单词环。
记录在改进计算模块性能上所花费的时间,描述你改进的思路,并展示一张性能分析图(由VS 2019的性能分析工具自动生成),并展示你程序中消耗最大的函数。(3')
从上图可以看出耗时最长的是遍历图的部分,应该尽量减少遍历次数。
在当前算法中,需要遍历a到z作为首个单词的首字母,若遇到h,则只需要从特定首字母开始遍历。而遇到t时,则仍会遍历所有首字母,在单词链获取完成后判断。因此可减少t的遍历次数。
所以对只有t没有h的情况进行改进,将邻接矩阵进行转置,将首字母和尾字母进行调换,使t可以类似于h,从特定尾字母开始遍历,减少了遍历次数。
调用者和被调用者地位平等,双方都有自己的权力和义务,需要双方严格遵守契约。需要规定好前置条件、后置条件、不变式,除参数之外,我们还需要约定在传参时对于参数的规范化应该在哪个模块实现。
好处是由于它明确地指定了模块单元,无需关注其他模块的运行情况,可以多个模块同步编程,不需要等一方写完另一方才能开始。且模块独立,功能清晰,容易测试。
坏处是契约需要进行修改时,需要双方同时改动,比较麻烦。
展示出项目部分单元测试代码,并说明测试的函数,构造测试数据的思路。并将单元测试得到的测试覆盖率截图,发表在博客中。要求总体覆盖率到 90% 以上,否则单元测试部分视作无效。(6')
在单元测试中,我们对计算模块进行了七个参数的验证,包括参数间的组合。
下面的单元测试测试了h和w的组合,构造了单词数组,并调用gen_chain_word接口,将返回后的结果与答案进行对比,验证正确。数据构造的思路是,含有一个符合h的单词链,但是有其他不符合h的单词链比该单词链长,以此来验证h和w的处理是否符合要求。
下面的单元测试测试了r和w的组合,验证思路与上述单元测试相同。数据构造的思路是,单词链中含有一个单词环,但是同时含有一个比该单词环更长的单词链,以此根据结果判断r和w是否符合要求。
下面的单元测试测试了h,t,r和w的组合,验证思路与上述单元测试相同。数据构造的思路是,单词链中含有一个单词环且这个单词环中有首尾符合h和t的一种单词链形态,但是同时含有一个比该单词环更长的单词链,以此根据结果判断h,t,r和w是否符合要求。
单元测试代码覆盖率结果:
在博客中详细介绍每种异常的设计目标。每种异常都要选择一个单元测试样例发布在博客中,并指明错误对应的场景。(5')
我们一共定义了8种类型的异常,每种异常会抛出对应的异常种类,在单元测试中捕获对应的异常类否则不通过。
TEST_METHOD(Test_WrongFile_Error) { char* words[101] = { "wordlist.exe","-r", "-n", "D:\\grade_three\\word.md" }; int arg_c = 4; try { main(arg_c, words); Assert::IsTrue(false); } catch (file_illegal_exception& e) { Assert::IsTrue(true); } }
2.文件查找异常,如果目标文件不存在,则报错。
(下例中的文件不存在。)
TEST_METHOD(Test_NonFile_Error) { char* words[101] = { "wordlist.exe","-r", "-n", "D:\\grade_three\\wordx.txt" }; int arg_c = 4; try { main(arg_c, words); Assert::IsTrue(false); } catch (file_nonexist_exception& e) { Assert::IsTrue(true); } }
3.未定义的参数,若命令行给出的参数非要求的参数则报错
-p非定义的参数
TEST_METHOD(Test_DefPara_Error) { char* words[101] = { "wordlist.exe","-p","-n", "D:\\grade_three\\word.txt" }; int arg_c = 4; try { main(arg_c, words); Assert::IsTrue(false); } catch (para_undefine_exception& e) { Assert::IsTrue(true); } }
4.参数格式错误,若-t,-h后的指令不是单个英语字母,而是‘1’等非英文字母或‘abc’等多个连续英文字母则报错。
TEST_METHOD(Test_ParaForm_Error) { char* words[101] = { "wordlist.exe","-t", "1","-n", "D:\\grade_three\\word.txt" }; int arg_c = 5; try { main(arg_c, words); Assert::IsTrue(false); } catch (para_form_exception& e) { Assert::IsTrue(true); } }
5.输出参数错误,如-n和-w一起使用,两参数要求的输出冲突,此时报错。
TEST_METHOD(Test_MulOut_Error) { char* words[101] = { "wordlist.exe","-w","-n", "D:\\grade_three\\word.txt" }; int arg_c = 4; try { main(arg_c, words); Assert::IsTrue(false); } catch (para_conflict_exception& e) { Assert::IsTrue(true); } }
6.输出参数错误,如参数中没有任何对输出进行要求的参数,只有-h,-t,-r时,无法输出,报错
TEST_METHOD(Test_ParaNout_Error) { char* words[101] = { "wordlist.exe","-t","a","-r", "D:\\grade_three\\word.txt" }; int arg_c = 5; try { main(arg_c, words); Assert::IsTrue(false); } catch (para_nonout_exception& e) { Assert::IsTrue(true); } }
7.无可输出的单词链,即所有可构成的单词链均不满足参数,无法输出,报错。下例中无法构成a开头,z结尾的两个单词以上的单词链,故有错误。
TEST_METHOD(Test_Nochain_Error) { char* words[101] = { "abc", "def", "fgh", "ijk", "uv", "yz" }; try { handleOutput(words, 6, 'c', 'a', 'z', true);//提供的参数为-c -h a -t z -r Assert::IsTrue(false); } catch (word_Nochain_exception& e) { Assert::IsTrue(true); } }
8.在没有-r的情况下给出的单词包括单词环。
下例中前两个单词构成了单词环。
TEST_METHOD(Test_WordCircle_Error) { char* words[101] = { "fddsu", "uasdasf", "ugfl", "laght", "adbon", "tasdu" }; char* result[101]; for (int i = 0; i < 101; i++) { result[i] = (char*)malloc(sizeof(char) * 601); } try { gen_chain_word(words, 6, result, 0, 0, false); Assert::IsTrue(false); } catch (const char* msg) { Assert::AreEqual("存在单词环", msg); } for (int i = 0; i < 101; i++) { free(result[i]); } }
在博客中详细介绍界面模块是如何设计的,并写一些必要的代码说明解释实现过程。(5')
使用命令行模块,对输入的参数进行解析,解析后对相应的模块进行调用,代码如下。
char type = '0'; //n,w,m,c char h = '0'; //h char t = '0'; //t int r = 0; //r string wstr = "";//wrongStr用于存不正确的参数 string rstr = "";//repeatStr用于存重复的有输出参数 string filePath; int paraNum = 0; string s; try { //处理命令行参数 for (int count = 1; count < argc; count++) { if (argv[count][0] == '-') { if (argv[count][1] == 'n' || argv[count][1] == 'w' || argv[count][1] == 'm' || argv[count][1] == 'c') { type = argv[count][1]; paraNum++; rstr = rstr + " " + argv[count][1]; //filePath = argv[++count]; } else if (argv[count][1] == 'h') { h = argv[++count][0]; string m = argv[count]; if (m.length() != 1) { throw para_form_exception(); } else if (!(h <= 'z' && h >= 'a' || h <= 'Z' && h >= 'A')) { throw para_form_exception(); } if (h <= 'Z') { h = h - 'A' + 'a'; } } else if (argv[count][1] == 't') { t = argv[++count][0]; string m = argv[count]; if (m.length() != 1) { throw para_form_exception(); } else if (!(t <= 'z' && t >= 'a' || t <= 'Z' && t >= 'A')) { throw para_form_exception(); } if (t <= 'Z') { t = t - 'A' + 'a'; } } else if (argv[count][1] == 'r') { r = 1; } else { wstr = wstr + " " + argv[count][1]; } } else { filePath = argv[count]; } } if (wstr.length() != 0) { throw para_undefine_exception(); } else if (paraNum > 1) { throw para_conflict_exception(); } else if (paraNum == 0) { throw para_nonout_exception(); } //得到单词数组 char* words[20000]; int wordsSize = getwords(filePath, words); //根据参数处理文件输出 handleOutput(words, wordsSize, type, h, t, r); } catch (exception& e) { e.what(); }
详细地描述 UI 模块的设计与两个模块的对接,并在博客中截图实现的功能。(4')
命令行输入参数,程序对输入的参数进行处理,并按要求进行输出。
调用handleOutput(words, wordsSize, type, h, t, r);
进入计算模块
提供两人在讨论的结对图像资料(比如 Live Share 的截图)。关于如何远程进行结对参见作业最后的注意事项。(1')
采用线上腾讯会议进行结对
结对编程优点:可以相互学习,若有一方掌握某些知识而另一方不掌握,学习新知识的时间大大减少;可以更快发现错误,自己疏忽的地方会被对方发现;锻炼沟通表达能力。
结对编程缺点:沟通费时,需要统一观点,明确目标。
结对伙伴优点:效率高,在他的带动下我也能提高效率;积极主动,主导项目的进展;容易沟通,有问题的时候反馈速度快。
结对伙伴缺点:不够细心。
我的优点:完成代码细心;容易沟通,尊重彼此意见;对待项目积极主动。
我的缺点:效率比较低。