结对项目-最长单词链
项目 | 内容 |
---|---|
作业所属课程 | 2019软件工程_罗杰 |
作业要求链接 | 结对项目作业 |
课程目标: | 进行结对编程实践 |
该作业的帮助 | 实践结对编程,熟悉双人软工开发流程 |
队友的结对项目总结 | 链接 |
1、本次作业github项目地址
2、开发前PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
- Estimate | - 估计这个任务需要多少时间 | 60 | |
Development | 开发 | ||
- Analysis | - 需求分析(包括学习新技术) | 240 | |
- Design Spec | - 生成设计文档 | 90 | |
- Design Review | - 设计复审 (和同事审核设计文档) | 90 | |
- Coding Standard | - 代码规范 (为目前的开发制定合适的规范) | 30 | |
- Design | - 具体设计 | 300 | |
- Coding | - 具体编码 | 500 | |
- Code Review | - 代码复审 | 300 | |
- Test | - 测试(自我测试,修改代码,提交修改) | 300 | |
Reporting | 报告 | ||
- Test Report | - 测试报告 | 120 | |
- Size Measurement | - 计算工作量 | 30 | |
- Postmortem & Process Improvement Plan | - 事后总结, 并提出过程改进计划 | 30 | |
合计 | 2090 |
3、接口设计
看教科书和其它资料中关于Information Hiding, Interface Design, Loose Coupling的章节,说明你们在结对编程中是如何利用这些方法对接口进行设计的
- 三个概念总结如下:
- 1.Imformation hiding:模块内部的数据对外部调用者若不需要则不可访问,保证模块自身的安全性。
- 2.Interface design:将软件根据功能组成划分为模块,各模块通过接口交互。如此可以将工作划分,通过模块的组合、拓展不断实现更强的整体功能,同时维持程序的稳定性和安全性。
- 3.Losse coupling:模块之间切断不必要的依赖,将自己的功能实现完全在内部完成封装,只提供必要的外部接口,保证各模块交互时系统的健壮性。
- 践行概念:
- 1.信息隐藏:类内部数据和私有方法private,以public修饰方法时充分考虑必要性。
- 2.接口设计:本次作业并不设计很复杂的类设计,但我们也在并行开发前事先定义好接口,再进行实现,内部核心类的对外接口大多只有一个工作函数完成此类职能范围内的所有功能。
- 3.松耦合:我们的c++核心算法模块提供完整参数的gen_chain接口,c#错误处理模块接收命令行参数进行容错处理(因为一开始设计时理解为core模块无需处理命令行参数异常,从而将此模块独立),二者作为整体提供给外部作业要求的API接口。
4、计算模块接口的设计
计算模块是项目核心,计算功能封装在gen_chain_cpp函数接口中,重点在于从接受命令的外模块获取命令和单词,调用gen_chain_cpp函数计算出最长路径。
算法思路简介:根据函数获取的单词表,从中取得有效单词加入有效单词表中,建单词表的同时为每个单词建立后继表,计算入度,最终建立邻接链表。之后计算路径,对于是否有环的两种情况,首先会对单词进行拓扑排序,若能顺利进行拓扑排序,则是否有参数-r都采用动态规划算法;若不能顺利完成拓扑排序,则说明有单词环存在,此时检查是否有-r参数存在,没有则返回异常信息,有则采用深度优先算法逐个节点进行搜索。
算法关键及独到之处:在节点数据结构中同时存储了单词长度,因此计算时可根据-c和-w参数自由选择路径计算方式。在计算无环路径时,有无首字母要求的算法存在差异:若有首字母要求,则动态规划计算的起点只会选择符合首字母要求的单词,没有首字母要求时则是会选择入度为0的点作为起点进行计算。而对于有无确定尾字母的情况,根据动态规划算法的结果,每一个节点均会存储以该节点为结尾的最长链的路径和长度,因此只需在获取结果时选择符合尾字母要求的点,选出最长路径输出,无尾字母要求则直接从中选出最长路径输出。
5、UML图
6、计算模块接口部分的性能改进
计算模块接口部分的性能改进。 记录在改进计算模块性能上所花费的时间,描述你改进的思路,并展示一张性能分析图(由VS 2015/2017的性能分析工具自动生成),并展示你程序中消耗最大的函数
在完成程序的基本功能后,我们采用了一些数据对程序进行测试,在少量单词的数据下测试,我们修复了程序的一些逻辑问题,在能够正确运行小文本数据之后,我们利用一些大文本数据对程序进行测试,测试结果比较令人失望:由于数据结构的设计问题,程序出现了爆内存的问题。之后我们通过对数据结构进行重构优化,修复了这一问题,以下是一部分性能测试截图:
以上可以看出耗时最多的时进行动态规划计算时的函数,我们的动态规划计算性能还有提升空间。
以下是带有-r参数且图中有环的情况,可以看出searchpath作为深度优先递归函数被频繁调用:
由于采取暴力枚举的方法来进行有环路径的搜索,运行速度较慢,性能仍需要改进。
7、Design by Contract, Code Contract
-
Design by Contract,即契约式编程。我们在声明函数时对函数输入输出是有所设计、期望的,将这一期望明确写出称为contract。
-
这一行为的好处是每个人在契约范围内对自己的代码负责,有问题时可以迅速找到负责人。
-
在本次作业中,我们共同开发整个项目,因此并没有严格按照契约式编程的方式来完成程序,更多的进行口头沟通,提高效率。
8、计算模块部分单元测试展示
以下是单元测试覆盖率截图:
为了对计算模块所能遇到的各种情况进行测试,我们采用了大量测试样例对程序进行测试,以下仅展示部分单元测试代码:
9、计算模块部分异常处理说明
根据可能出现的情况,我们对以下9种异常情况做了处理:
1、出现除了"-r -c -w -t -h"以外的关键字,如"-x"
单元测试用例:
2、-c -w同时出现
单元测试用例:
3、同一种参数重复出现,如 -t a -t a 或 -w -w
单元测试用例:
4、-c和-w参数缺失
单元测试用例:
5、-h 后面缺少字符
单元测试用例:
6、-t 后面缺少字符
单元测试用例:
7、命令中出现多余字符
单元测试用例:
8、文件打开失败
9、缺少-r参数的情况下文本有环,该情况由计算模块通过拓扑排序后得出文本是否存在单词环,返回信息给界面模块进行异常处理
10、界面模块的详细设计过程
界面我们仅采用简单的命令行方式处理:
以下是命令行界面:
11、界面模块与计算模块的对接
界面模块的对接就是界面模块从命令行获取所需数据,之后调用计算模块方法对计算模块进行调用,计算模块返回处理结果信息后,界面模块作出反馈。
12、描述结对过程
- 在项目计划和设计的阶段,我们采用了完全的结对讨论。讨论前事先阅读相同的材料,讨论时针对算法问题提出各自的看法和修改意见。如此,我们确定了无环情况下基于拓扑排序和动态规划的最长路径算法,对于有环情况经过讨论我们认定为np问题,明确使用暴力解法,优化也仅针对降低访问成本进行。
- 在实现过程中,我们也充分讨论了关于命令行参数要处理异常的情况,而后才着手实现。实现过程中算法的数据结构做过几次修改,从一开始使用n*n临接矩阵到使用临界链表(错误实现,结构体传值而非传指针),到最后发现问题终于解决内存占用不断膨胀的问题。优化算法的过程中我们也细致地针对性能分析结果和大测试样本下的代码覆盖率进行一些分支必要性的讨论,不断优化。
13、结对编程反思
-
结对编程优缺点
- 正如课本所描述的,结对编程的灵魂在于不间断的复审,不断地交替领航员的位置维持高效率、高正确性的编程过程。
- 基于这样的分析,便能看到结对编程的条件:两个人需要至少需要在工作任务上掌握的技能相同并水平接近,从而才能以同等的技术水平相互审查提醒。
- 而不间断互审也是有一定代价的,如两个人需要进行充分的交流,想法上要同步,这是需要花费时间的;又如果两个人并不很好地满足结对编程的条件,如技能覆盖不同,则交流往往会变成单向的,互审的有效性往往也会变成单向有效。同样地,有些情况下,工作任务是可以平行进行的,如测试程序的编写和功能程序的编写时可以同时进行的,有些较复杂的类也是可以同步开发的,于是结对编程实际上建立在同步开发的机会成本上。
- 此外,实际践行过程中,我们还发现结对编程一旦采用需要尽可能全程采用。两人在共同时间无法调和的情况下迫不得已进行的开发,再次结对时往往需要较长间相互说明并理解工作成果,如果遇到问题,则更麻烦,被说明的人往往被原先写代码的人的思路牵着走,较难像正常结对编程互审时保持自己独立的思考,跳出思维惯性而去发现错误。
-
我和搭档的优缺点
本人 搭档 优点1 熟悉c#,较熟悉vs2017 调试能力强 优点2 较熟悉框架、接口设计 对题目需求理解较细致 优点3 待人友善,乐于沟通 待人友善,乐于沟通 优点4 能提出一些新的设计想法 能快速响应需求(设计)中的变化 缺点 debug时不细致,容易进入思维惯性找不到问题 对vs较不熟悉 - 总的来说,这次项目我们尝试了结对编程,但也有一些时间在进行各自开发。结对的时间里有时会因为双方对编程语言的熟悉程度不同有效率较低的情况,但慢慢地随着磨合的进行,结对工作的代码复审效率大有提高。
14、PSP表格回填
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
- Estimate | - 估计这个任务需要多少时间 | 60 | 60 |
Development | 开发 | ||
- Analysis | - 需求分析(包括学习新技术) | 240 | 400 |
- Design Spec | - 生成设计文档 | 90 | 90 |
- Design Review | - 设计复审 (和同事审核设计文档) | 90 | 60 |
- Coding Standard | - 代码规范 (为目前的开发制定合适的规范) | 30 | 10 |
- Design | - 具体设计 | 300 | 300 |
- Coding | - 具体编码 | 500 | 720 |
- Code Review | - 代码复审 | 300 | 300 |
- Test | - 测试(自我测试,修改代码,提交修改) | 300 | 240 |
Reporting | 报告 | ||
- Test Report | - 测试报告 | 120 | 120 |
- Size Measurement | - 计算工作量 | 30 | 30 |
- Postmortem & Process Improvement Plan | - 事后总结, 并提出过程改进计划 | 30 | 30 |
合计 | 2090 | 2360 |
- 实际开发过程中,用于学习、实践算法的时间超过预期,其余时间较接近。
15、模块松耦合
- 按照作业接口要求封装dll后,我们和16091049 16061057互换了Core,我们能正常使用对方的c++ dll,对方调用时却不能看到函数接口问题,但我方自测dll时用c#调用可以正常工作,可能与c++调用c#dll方式有一定关系。