项目 | 内容 |
---|---|
这个做业属于的课程是 | 2019BUAA软件工程 |
做业要求是 | 结对编程做业 |
我在此次的目标是 | 体会结对编程、锻炼合做能力 |
这个做业在哪些方面帮助我实现目标 | 对结对编程有了更深的理解,为多人合做打下基础 |
github地址html
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) |
---|---|---|
Planning | 计划 | 20 |
· Estimate | · 估计这个任务须要多少时间 | 20 |
Development | 开发 | 840 |
· Analysis | · 需求分析 (包括学习新技术) | 60 |
· Design Spec | · 生成设计文档 | 20 |
· Design Review | · 设计复审 (和同事审核设计文档) | 10 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 |
· Design | · 具体设计 | 50 |
· Coding | · 具体编码 | 500 |
· Code Review | · 代码复审 | 40 |
· Test | · 测试(自我测试,修改代码,提交修改) | 150 |
Reporting | 报告 | 60 |
· Test Report | · 测试报告 | 30 |
· Size Measurement | · 计算工做量 | 10 |
· Postmortem & Process Improvement Plan | · 过后总结, 并提出过程改进计划 | 20 |
合计 | 920 |
三个概念学习总结以下:前端
在本项目中,是这样借鉴这些方法的:git
在计算模块中,一共有4个类,包括:Input类,处理命令行输入的类;Readin类,从文件中读入单词的类;Genlist类:搜索并生成单词列表的类;Core类:封装好的接口类。程序员
Input类有两个函数github
函数名 | 函数做用 | 调用关系 |
---|---|---|
CompareStr | 用来比较读入的字符串最后是否是”.txt“ | 无 |
InputHandle | 用来处理命令行的输入,修改Input类变量 | CompareStr |
Readin类中有四个函数算法
函数名 | 函数做用 | 调用关系 |
---|---|---|
compare | 重定向sort的比较函数,变成降序 | 无 |
compare_str | 重定向sort的比较函数,改为字符串比较 | 无 |
GetWords | 从文件中读取单词,到Words里,修改类成员变量 | 无 |
ClassifyWords | 将已经读入的单词分类,创建26*26个vector,存放以某个字母开头以某个字母结尾的全部单词,并将他们排序 | compare、compare_str |
GenList类中有十个函数编程
函数名 | 函数做用 | 调用关系 |
---|---|---|
PrintRoad | 输出搜到的路径,路径是有环的时候搜到的 | 无 |
ClassifyWords | 单词分类,将已经读入的单词分类,创建26*26个vector,存放以某个字母开头以某个字母结尾的全部单词,并将他们排序 | 无 |
SerchList | 在已经排过序的单词中查找单词链,适用于无环模式,运用深度优先搜索的方式,而且采用剪枝 | 无 |
SerchCircle | 查找单词列表中有没有环,若是不是-r模式,有环须要报错 | 无 |
SerchListCircle | 有环的模式下查找单词链,运用深度优先搜索的方式 | 无 |
输出查找到的单词链,单词链是无环模式下找到的 | 无 | |
SerchListLastMode | 规定结束字母而且是无环模式下找到单词链 | 无 |
gen_chain_word | 模式是-w的时候,按照传入的各个参数查找单词链,存到result里,返回单词链长度 | PrintRoad、ClassifyWords、SerchList、SerchCircle、SerchListCircle、Print、SerchListLastMode、GetOutDeg |
gen_chain_char | 模式是-c的时候,按照传入的各个参数查找单词链,存到result里,返回单词链长度 | PrintRoad、ClassifyWords、SerchList、SerchCircle、SerchListCircle、Print、SerchListLastMode、GetOutDeg |
GetOutDeg | 获得全部字母的出度 | 无 |
Core类中有两个函数:数组
函数名 | 函数做用 | 调用关系 |
---|---|---|
gen_chain_word | 实例化GenList类,调用GenList类中gen_chain_word,实现封装 | GenList->gen_chain_word |
gen_chain_char | 实例化GenList类,调用GenList类中gen_chain_char,实现封装 | GenList->gen_chain_char |
使用的时候,首先调用Input类中的InputHandle来处理命令行参数,处理完后,这个类中的变量已经被修改,以后拿到修改后的变量来调用Readin类中的GetWords函数,获得单词数组,以后实例化Core类,根据Input类中的类变量来肯定调用Core中的哪个函数,若是是-w模式就调用gen_chain_word函数,若是是-c模式就调用gen_chain_char函数。安全
算法有两部分,一部分是不能够存在环的,另外一部分是能够存在环的。函数
不能够存在环的部分,主要算法是深度优先搜索,算法把每一个字母做为图的一个节点,由于不存在环,因此从一个字母到一个字母只能走一遍,因此能够忽略相同开始和结束字母的全部单词。以后进行深度优先搜索,在搜索的过程当中,进行剪枝的操做,当一个点已经走完以后,这个点到结尾的最大深度已经能够知道,这个时候咱们记录下来这个最大深度,若是这个点出度为0,那么这个点的最大深度为0,在进入递归前,判断这个点有没有被走过,若是被走过了,就不进入这个点的递归,这样就减小了不少进入递归的次数,从而使速度变快。
这个算法的独到之处在于这个算法减小了递归的次数,作到了一个剪枝的操做,而且以后记录下来了最大的长度,至关于每一个边最多只走一遍,也就是算法的最大复杂度为26*26,相似于动态规划的速度,而且代码从深度优先搜索上的基础而来,代码也比较好写易懂。
对于存在环的部分,使用深度优先搜索,由于能够存在环,因此以单词做为节点,进行了部分剪枝,当一个当前最大路径中存在一个字母,从这个字母开始的全部边都走过,那么这个时候,就不须要以这个点为起点开始搜路,由于以这个点开始的全部点都被走过,路径的最长长度也小于这个如今的最长长度,通过这个剪枝操做,可使得速度快不少。
在查找与当前节点相连的边的时候,记录下了那些字母可能与这个点相连,这样遍历的时候就不须要遍历26个字母,加快了查找的速度。进行了剪枝操做,使得速度变快,总体代码思路也是从深度优先搜索开始设计,因此代码比较好写易懂。
查找环的部分采用拓扑排序的办法。首先查找收尾字母同样的单词序列,若是长度大于两个,那么就成环,以后,按照单词的收尾字母来初始化每一个字母的入度,从入度为0的点开始删掉,而且与它相连的点的入度减一,重复这个操做直到没有点能够被删掉,最后遍历每一个点的入度,若是有大于0的点,那么说明成环。
采用拓扑排序的方式,避免了深度优先搜索的速度慢,而且代码简洁效率高。
UML使用VS绘制,类图画法分享以下:在VS2017的安装包内,安装其余工具中的类设计器,以后进入vs2017,找到须要查看的项目,右键项目点击查看会找到类图,以后能够看到每一个类的具体信息,以后本身修改添加调用信息。
UML图以下:
在改进计算模块上,一共花费了大约12小时,主要改进了算法在无环的时候的搜索速度,程序最开始的时候,计算无环的算法是无任何剪枝的深度优先搜索,这样,最慢的状况是26!,显然,这样搜索的速度太慢,因此要进行优化,这部分,咱们主要进行了剪枝的优化。
首先我记录了每个节点到最终节点的距离,若是这个点自己就是最终节点,那么就记录它的距离为0,而且,若是这个点已经有到结尾的最大距离,那么就不进入这个点的递归,也就是不进入下一层,这样就少了不少重复递归的操做,走过的边不会再次被走到,因此就使得代码运行速度显著提升。而且,有些计算vector的size的函数在for中被重复使用,在把这些提出来之后,代码运行速度变高。开始,成环部分进行深度优先搜索的时候,每次进入递归都要把建好的单词图复制一份传入下一层,这个复制操做很慢,因此改进的过程当中,把这个图放到类变量中,就不须要每次都传进去,这样速度快了十倍以上,以后,思考了剪枝的方式,在当前最长路径中若是有一个字母,它的全部后继都被用到,这个时候,最长的单词链不可能以这个字母为开头,因此能够减小遍历的次数,速度快了五倍左右。
消耗最大的函数:
由于算法在寻找路这一方面作的比较优秀,因此慢在单词的分类,分类中有单词的查重功能,在找到当前的单词就查找一下有没有和他重复的,这个时候,strcmp就会慢不少。
在成环的时候,性能分析和不成环就不同。
消耗最大的函数:
这个时候,算法主要在递归中,因此全部的消耗基本在进入递归的函数中,函数出来也消耗了大量写入的操做。
如下内容参考自一篇优秀的博客
Design by Contract指的是契约式编程,Code Contract指的是代码契约,两者的主要观点是一致的:
契约式编程,源自生活中供应者与客户之间的“契约/合同”,契约用于两方,每一方都期待从契约中得到利益,同时也要接受一些义务。一般,一方视为义务的对另外一方来讲是权利。契约文档要清楚地写明双方的权利与义务。
一样的道理也适用于软件,简而言之,就是函数调用者应该保证传入函数的参数是符合函数的要求,若是不符合函数要求,函数将拒绝继续执行。
在软件体系中,程序库和组件库被类比为server,而使用程序库、组件库的程序被视为client。根据这种C/S关系,咱们每每对库程序和组件的质量提出很严苛的要求,强迫它们承担本不该该由它们来承担的责任,而过度纵容client一方,甚至要求库程序去处理明显因为client错误形成的困境。客观上致使程序库和组件库的设计和编写异常困难,并且质量隐患反而更多;同时client一方代码大多松散随意,质量低劣。这种情形,就好像在一个权责不清的企业里,必然会养一批尸位素餐的混混,苦一批不辞辛苦,不计得失的老黄牛。引入契约观念以后,这种C/S关系被打破,你们都是平等的,你须要我正确提供服务,那么你必须知足我提出的条件,不然我没有义务“排除万难”地保证完成任务。
优势:
缺点:须要断言机制来验证契约是否成立,但并不是全部的程序语言都有断言机制。
在本项目中,咱们在代码中设置了一些断言机制,代表某个函数对于传入的参数的一些最基本的要求,好比 处理文件内容的函数GetWords会要求传入的文件名不能为null。
计算模块部分的单元测试代码 部分展现以下:
TEST_METHOD(TestMethod1) { // TODO: 在此输入测试代码 Input *input = new Input(); int n = 3; char * instr[] = { " ", "-w","..\\Wordlist\\a.txt" }; char ** result = new char *[11000]; int len = 0; input->InputHandle(n, instr); Readin *readin = new Readin(); readin->GetWords(input->FileName); int i = 0; for (i = 0; i < 8; i++) { Core *core = new Core(); switch (i) { case(0): len = core->gen_chain_word(readin->Words, readin->WordNum, result, '0', '0', false); Assert::AreEqual(len, 29); break; case(1): len = core->gen_chain_word(readin->Words, readin->WordNum, result, 'd', '0', false); Assert::AreEqual(len, 27); break; case(2): len = core->gen_chain_word(readin->Words, readin->WordNum, result, '0', 'e', false); Assert::AreEqual(len, 27); break; case(3): len = core->gen_chain_word(readin->Words, readin->WordNum, result, 'd', 'e', false); Assert::AreEqual(len, 25); break; case(4): len = core->gen_chain_char(readin->Words, readin->WordNum, result, '0', '0', false); Assert::AreEqual(len, 29); break; case(5): len = core->gen_chain_char(readin->Words, readin->WordNum, result, 'd', '0', false); Assert::AreEqual(len, 27); break; case(6): len = core->gen_chain_char(readin->Words, readin->WordNum, result, '0', 'e', false); Assert::AreEqual(len, 27); break; case(7): len = core->gen_chain_char(readin->Words, readin->WordNum, result, 'd', 'e', false); Assert::AreEqual(len, 25); break; } delete core; } }
计算模块的单元测试的函数是TestCore,被测试的函数是gen_chain_char和get_chain_word。构造测试数据主要是两个思路:
单元测试获得的测试覆盖率截图以下,可见覆盖率达到了90%以上:
计算模块部分的异常处理有两种:
(1)按照文本内容和命令要求,不存在可行解。这包括:
(2)命令中没有-r,可是文本内容能够成环。一个单元测试样例的输入是:abc cfs ehshoda sefe sewqq
这里顺便列出整个项目的全部异常种类的设计:
抛出异常的模块 | 抛出异常的场景 | 错误提示语 |
---|---|---|
Input(命令行参数处理模块) | -h 后紧跟着的参数不是单字符 | Too Long Begin! |
-t 后紧跟着的参数不是单字符 | Too Long End! | |
-h或-t后紧跟着的参数是单字符,但不是字母 | Need a alapa | |
文件名后还有参数 | Too many parameters | |
没有-w或-c | need one -c or -w | |
不是-w -c -r -h -t,也不是以.txt结尾的文件名 | illegal parameter | |
命令行参数中没有文件名 | No a legal file | |
Readin(文件内容处理模块) | 没法打开命令行中指定的文件(好比文件不存在) | Fail to open the file |
文本中单词个数过多 | Too many words | |
单词的长度过长 | Too long word! | |
Core(计算核心模块) | 按照文本内容和用户命令的要求,没有找到解 | No solution |
命令中没有-r,可是文件中出现了单词环 | Become Circle | |
Main(主函数) | 其它异常状况 | Error |
界面模块使用VS+Qt,这里分享两个安装教程:教程1,教程2。
整个界面是一个QDialog(因为是一个小软件因此没有选择QMainWindow),界面模块主要包括“视图view”和“控制controller”两部分:
(1)经过所见既所得的Qt Designer设计UI视图,对布局作了一些考虑,放一张布局设计图:
总体采用纵向布局,其中最上面的部分采用水平布局,各个部分的功能以下:
(2)经过信号与槽机制设置“哪一个组件的什么事件会触发哪一个类的什么方法”。按照上述每一个组件的功能设置槽函数,并在go按钮点击事件对应的槽函数中调用文本处理模块readin和计算模块core,完成视图、控制器、模型的对接。
比较难写的槽函数在于文件的导入和导出,咱们参考了这篇博客使用了QFileDialog,下面给出咱们这部分的代码:
void Dialog::on_btn_import_clicked() { QString filepath = QFileDialog::getOpenFileName(this,tr("choose file")); if(filepath!=NULL){ QByteArray ba = filepath.toLatin1(); char *filepath_c; filepath_c = ba.data(); ui->le_path->setText(filepath); FILE *fp; fopen_s(&fp,filepath_c,"r"); fseek(fp,0,SEEK_END); int filesize = ftell(fp); fseek(fp,0,SEEK_SET); char *buf = new char[filesize+1]; int n = fread(buf,1,filesize,fp); if(n>0){ buf[n] = 0; ui->te_in->setText(buf); } delete[] buf; fclose(fp); } } void Dialog::on_btn_export_clicked() { QString filename = QFileDialog::getSaveFileName(this,tr("save as")); QByteArray ba = filename.toLatin1(); char *filepath_c; filepath_c = ba.data(); QString text = ui->tb_out->toPlainText(); QByteArray ba2 = text.toLatin1(); char *text_c; text_c = ba2.data(); if(filename.length()>0){ FILE *fp; fopen_s(&fp, filepath_c, "w"); fwrite(text_c,1,text.length(),fp); } }
前面提到,咱们在GO按钮点击事件对应的槽函数中 调用文本处理模块Readin和计算模块Core,从而完成视图、控制器、模型的对接。
具体来讲,咱们把槽函数都放到了Dialog类中,当用户点击GO按钮后,会触发Dialog类中的on_btn_go_clicked()槽函数,这个函数将视图中文本输入框的内容传递给核心控制器Calculator类,设置其成员变量textIn,而后调用Calculator类的核心函数core()进行计算,计算结果再经过setText()函数设置到视图上的文本输出框中。
上面这个过程是视图与控制器的对接,而控制器和计算模型的对接主要体如今Calculator类的核心函数core()中:将textIn内容传递给Readin实例化对象并调用其getWords方法,从而将文本内容处理为单词数组。随后,根据用户的参数设置,分-w和-c两类,分别调用Core.dll的gen_chain_word和gen_chain_char函数(经过dll),从而 或得出计算结果(长度和单词链)、或接收抛出的异常(无解或无-r却成环)。最后,将计算结果或异常提示信息设置到控制器Calculator类的textOut变量中,再由控制器传递到视图层在输出文本框中。
实现的功能截图以下:
(1)初始界面:
(2)一个正确的样例:
(3)一个测试异常的样例:
整个项目的实现过程当中,因为时间上很差安排、不大熟悉、性格比较内向等缘由,咱们大多数时候仍是并行工做,保持线上交流讨论、互报进度,每隔1-3天线下开个小会,进行需求分析、工做分配、问题讨论、模块对接等工做。具体以下:
时间/事件 | 16061155王冰 | 16061093谢静芬 |
第一次开会前 | 粗读项目核心要求,开始编写初版代码 | 研读和分析项目要求,记录问题,考虑如何分工 |
第一次开会 | 讨论需求中不明确的地方;而后一致认为,因为时间紧任务重、二人时空上不大方便进行结对编程(没法像其余组那样串宿舍结对编程...)等缘由,决定二人仍是并行工做;建仓库,进行分工 | |
第一次开会后 | 编写完成初版核心代码,包括命令行参数处理、文本单词提取、最长链计算 | 设计异常种类、构造测试用例、安装和学习qt、代码复审 |
第二次开会前 | 进行测试,修复一些bug,测试性能,尝试优化性能 | 设计和编写GUI,代码复审,开始书写博客的共同部分 |
第二次开会 | 将GUI和计算核心进行对接(暂未转成dll),修复了一些bug。各自查阅并尝试进行dll的生成和调用(失败了) | |
第二次开会后 | 继续尝试进行dll的生成和调用、书写博客的共同部分中关于计算核心和优化等内容 | 书写完成博客的共同部分中的其它部分、找到一些不错的连接便于队友学习博客中提到的一些概念 |
第三次开会 | 将GUI和计算核心dll进行对接,成功!与另外一组同窗的模块进行交换对接,成功! | |
第三次开会后 | 各自完成博客中的非共同部分 | |
总结 | 真是太优秀了! | 打杂也很认真! |
讨论时的照片:
结对编程的优势:
缺点:
我认为的,结对二人的优缺点:
优势 | 缺点 | |
---|---|---|
16061155 王冰 | 1.代码能力强,项目核心代码的编写者(十分感谢王冰队友的carry!) 2.交流沟通、获取信息的能力较强,所以解决告终对过程当中的不少小问题。 3.十分负责任,合做真诚。 |
1.偶尔会粗心、考虑不严密 2.晚睡晚起 |
16061093 谢静芬 | 1.善于作计划、分配和管理。 2.逻辑清晰严密,较擅长测试和写文档(误)。 3.搜集资料、学习新东西的能力较强。 |
1.编程和算法能力较弱。 2.人际交流能力较弱。 3.脾气容易暴躁。 |
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 20 |
· Estimate | · 估计这个任务须要多少时间 | 20 | 20 |
Development | 开发 | 840 | 1780 |
· Analysis | · 需求分析 (包括学习新技术) | 60 | 120 |
· Design Spec | · 生成设计文档 | 20 | 15 |
· Design Review | · 设计复审 (和同事审核设计文档) | 10 | 10 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | 5 |
· Design | · 具体设计 | 50 | 40 |
· Coding | · 具体编码 | 500 | 1500 |
· Code Review | · 代码复审 | 40 | 30 |
· Test | · 测试(自我测试,修改代码,提交修改) | 150 | 60 |
Reporting | 报告 | 60 | 105 |
· Test Report | · 测试报告 | 30 | 40 |
· Size Measurement | · 计算工做量 | 10 | 5 |
· Postmortem & Process Improvement Plan | · 过后总结, 并提出过程改进计划 | 20 | 60 |
合计 | 920 | 1905 |
咱们队把咱们队的Core.dll与其余队的Core.dll进行了交换,并使用咱们的GUI与他们的dll进行耦合,结果比较符合要求,成功运行。
最终,咱们与16061173鲍屹伟和16061135张沛泽一组、16061167白世豪和16061170宋卓洋一组以及16061144余宸狄和16061137张朝阳一组交换了程序,进行了松耦合。
在咱们的GUI上调用其余组的dll:
16061173鲍屹伟和16061135张沛泽调用咱们的dll:
16061167白世豪和16061170宋卓洋调用咱们的dll:
16061144余宸狄和16061137张朝阳调用咱们的dll: