软件工程结对项目- 最长英语单词链

项目内容
这个作业属于哪个课程2023年北航敏捷软件工程
这个作业的要求在哪里结对项目-最长英语单词链
我在这个课程的目标是学习现代化的软件开发方法
这个作业在哪个具体方面帮助我实现目标对结对编程敏捷开发进行实践,对测试工作有了新的认识

一、项目地址

  • 教学班级:周四班
  • 项目地址:https://github.com/WAN-M/pair_programming2023.git
  • 结对成员:20373861

二、PSP

PSP2.1Personal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning计划6050
· Estimate· 估计这个任务需要多少时间6050
Development开发24002920
· Analysis· 需求分析 (包括学习新技术)120150
· Design Spec· 生成设计文档10090
· Design Review· 设计复审 (和同事审核设计文档)5060
· Coding Standard· 代码规范 (为目前的开发制定合适的规范)3020
· Design· 具体设计300280
· Coding· 具体编码9001000
· Code Review· 代码复审200120
· Test· 测试(自我测试,修改代码,提交修改)7001200
Reporting报告280380
· Test Report· 测试报告200300
· Size Measurement· 计算工作量5030
· Postmortem & Process Improvement Plan· 事后总结, 并提出过程改进计划3050
合计27403350

三、接口设计

1、Information Hiding

  • 信息封装与隐藏,在面向对象思维中,编写代码时需要对各模块进行封装,只暴露必要的接口,而不提供内部具体的实现逻辑,以达到数据的安全,以及降低程序的复杂度。
  • 在算法模块的实现中,利用C++面向对象的特性,将图、边、节点等常用数据结构单独封装为类,对异常、工具类也进行单独封装,以供求解器调用,求解器无需知道数据结构实现自身功能的具体方式,只需要调用正确的接口即可。
  • 在GUI模块中,对界面使用的各组件如文本块、按钮块等单独进行继承与封装,UI线程只需要加载这些模块,具体功能由模块各自内部封装实现,并且利用继承与重写,可以达到代码的复用,降低开发复杂度。

2、Interface Design

  • 良好的接口设计有助于各模块之间的分离,使模块间的职责更加清晰明确,对于开发者而言可以达到模块的复用以避免重复的工作,并且使用相同规范的接口有助于不同开发者之间相互调用。
  • 在最开始的设计中,没有将输入输出与算法相分离,因此设计的是统一的求解器接口solve(),通过该接口实现所有参数的分析处理。在后续的开发中,首先将文本处理模块与算法模块分离,再对基于solve()这个已实现的全功能接口进行封装,封装为课程设计所需的三个接口,由调用者决定具体使用哪个接口,达到接口的规范与统一,以便于交换核心模块时的处理。

3、Loose Coupling

  • 接口封装应当尽量降低模块间的耦合度,例如核心模块与程序模块交互只通过指针与整型,不使用各自模块内部的类进行交互,这样其他人调用核心模块不需要实现核心模块内部封装好的类,核心模块也不需要实现其他人所实现的类,保证了耦合度的降低与模块的复用性。
  • 在打包这两个模块中遇到的最需要注意的便是异常的交换,在接口没有明确说明如何传递异常的情况下,必须使核心模块抛出的异常可以被其他人的程序模块所捕获,因此异常均继承自父类exception并重写what()方法,使任何人可以通过catch直接捕获并读取内部信息。

四、接口实现

1、接口实现

  • 核心模块按照作业要求提供如下三个接口:
int gen_chains_all(char* words[], int len, char* result[]);

int gen_chain_word(char* words[], int len, char* result[], char head, char tail, char reject, bool enable_loop);

int gen_chain_char(char* words[], int len, char* result[], char head, char tail, char reject, bool enable_loop);
  • 参数说明:dll默认传入去重后的char **类型的words,以及其他需要的参数,若head,tail和reject未指定则传入0。

2、算法设计

  • 对于本次单词链相关任务,单词之间以首尾字母构成关系,具有明显有向图的特征。因此我们将单词链任务转换成有向图上的算法,实现起来直观简洁。

  • 我们小组尝试了两种建图方式,一种是每个单词作为结点,另一种是每个字母作为结点,边为单词。其实我们感觉两种建图方式对于不优化的搜索效率基本一样,但后者有一个明显的优势,也是我们算法实现中贪心策略体现的重要部分:在结点为字母的图中,点到点的搜索优先选择权值最大的。比如abcd de dddde这一样例,显然在搜索d到e的路径时,可以直接选择最长的ddde,而不用在考虑较短的de。这一性质在实际搜索时可以剪枝相当一部分,不用搜索各种路径,大幅提升性能。我们同样采用简洁直观的实现方式:对于每个结点,用容器(我们使用的list)分开存储其到下一结点的边,存完所有边后对容器按权值从大到小排序,且维护一个指针指向容器开始。在搜索时,每次只遍历指针指向的边,遍历一条边后指针后移,回溯时指针前移,这样可保证搜索正确性且每次都只遍历当前最长边。

  • 搜索时要不断维护当前结果和已知最长结果,同样为了减少中间结果拷贝开销,我们以string &传递,每次修改同一string。

  • 在具体实现以字母为点的算法时,考虑到可以以多个点为起点或多个点为终点,我们在图中引入虚点SOURCE和TARGET作为超级源、超级汇。这样就确保了起点终点唯一,算法代码实现起来更为简洁。

  • 所有单词链算法是我们比较纠结的点。若是只需要输出所有单词链个数,动态规划可以以O(n)的复杂度求解。但要输出所有路径则让人很难权衡。如果同样根据动态规划思路求解,则需要保存每个点到终点的单词链,这样对于新的路径产生时,可以维持O(n)的复杂度输出。但这样的内存复杂度则提升到指数级。因此,我们认为采取遍历所有路径一条一条输出的算法,将时间复杂度提高而非“以空间换时间”。

  • 采用以字母为结点的建图方式还有一大好处,即无论图有环无环,在保证每次搜索搜最长边的前提下,都可采用同一搜索策略完成算法。而以单词为结点的建图方式,我们根据性能需要还图分为有环图和无环图分别处理,有环图暴搜,无环图通过负权边用spfa求得最长路,实现起来更繁琐复杂。

3、结构设计

  • 由于我们小组最后采用基于以字母为结点的图的算法,这里只介绍相关设计。

  • 首先,输入的参数个数较多,且-n和-w-c所需的参数不同,因此各参数在算法中如何传递需要解决。此外,建好的图也需要在算法直接流动,图的对象由谁保管也需要慎重设计。考虑到每次调用dll时,参数只有一种组合,图只有一个,我们小组决定在项目中以单例模式建立Global类,其中包含Graph对象和Parameter对象。这种设计相当于将图和参数信息设计为全局变量,项目任何地方需要可以直接查看。

  • 算法实现部分我们融合在Solver类里。Solver类具有public静态方法solve供外界调用,然后Solver根据全局参数信息调用对应函数求解。

  • 结构如下:

    • algorithm模块:EdgeNodeGraphGlobalSolver
    • var模块:Parameter、常量信息
    • tools模块:全局工具函数
    • exception模块:自定义异常类,继承自exception

五、编译情况

六、UML

七、性能优化

1、优化前算法

  • 最初版中采用的以单词为结点的建图方式,采用Visual Studio性能探查器,数据为随机生成与固定数据相结合。
  • 排名靠前的为自动数据生成器相关代码,因此大部分CPU占用由自动数据生成器与系统调用代码组成,算法已经有较好的性能,但是对节点非常多的数据会非常吃力。

2、优化后算法

  • 我们小组的性能改进策略如下:
  • 采用以字母为结点的方式建图,同时确保每次遍历只遍历点对点间的最长边的贪心策略。
  • 容器中存储指针,以减少拷贝开销。
  • 搜索时最先走完自环。
  • 相比之下,CPU使用量也有所降低
  • 经过对a-e的完全图数据测试最长单词链,程序从优化前跑不出结果到优化后能在几十秒跑出结果,性能具有很大提升。

八、Design by Contract & Code Contract

1、契约式编程

  • 契约式编程组成部分:前置条件、后置条件、不变量条件

  • 优点:

      当双方都保证实现约束条件时,开发工作可以变得非常高效,因为程序保证了正确性
    
  • 缺点

      契约作用于双方,每一方都必须完成任务,有一方违反则会失败,并且需要一个验证机制,来保证确实是按约束条件coding的
    
  • 我们对api接口做出了约定:传入的数据必须为全部小写,传入的单词必须去重,传入之前需要对指针数组开辟20000个char*空间。

2、Code Contract

  • .Net提供了Code Contract,用于创建软件契约,可以用代码的形式检查前置条件、后置条件和不变量条件
  • 非常值得一试,但由于没有使用C#开发,因此没有使用该插件

九、单元测试

1、数据构造

  • 构造数据采用自动化+手动补全两种方式相结合
  • 对于自动化部分,采用随机数获取单个字符,但是采用一定规则来限制单词长度和首尾字母,以此来保证样例自身的正确性与多样性,对于部分测试样例只需要保证首尾字母正确性,单词长度也可以在一定范围内自动变化。c++不设置随机数种子时不会以时间为种子,因此可以保证每次自动生成的数据一致。
  • 设置一定的规则,来确保生成的数据合理,如生成25个首尾相连的长单词、生成325个节点的复杂树结构、生成带大量环的随机数据等。
  • 针对边界情况、特殊情况等手动构造数据,对于自动化测试无法覆盖的分支单独分析,单独构造针对性数据。

2、覆盖情况

  • 使用OpenCppCoverage插件进行覆盖率检测,达到99%覆盖率,

十、异常处理

1、文件类异常

  • 文件不存在、文件不可读
  • 数据文件类型不是.txt

2、参数类错误

  • 参数组合冲突,如-n与-w一起用
  • -h -t -j后指定多个字符
  • -h -t -j后未指定字符
  • -h -t -j后指定非字符
  • 未知的参数
  • 未指定任何参数

3、算法类异常

  • 数据有环但未指定-r
  • result长度超过20000
  • 不存在满足条件的单词链,如首字母禁止与强制相同

十一、界面设计

1、界面布局

  • 使用python调用PYQT编写,界面美化使用qss代码渲染,使用视频如下:

结对

2、功能设计

  • 左侧选择参数栏,每个按钮使用QToolBar()中的addAction构造,并通过自定义方法实现按钮图标的点击切换、按钮之间的相互关联,使用触发器监听点击事件。
  • 右侧文本栏,继承QPlainTextEdit,重写resizeEvent方法,数字栏继承普通QWidget,重写updateContents等方法,两者结合达到文本编辑器效果。
  • 保存导出,使用QFileSystemModel控件获得系统目录,并生成树结构
  • 其余细节功能有些涉及自定义信号槽,来实现一些自定义的监听器;布局使用QHBoxLayoutQVBoxLayout两种布局,使用弹簧等控件来控制位置。

十二、模块对接

1、C++命令行程序与核心模块对接

  • dll中需要暴露的接口使用extern "C" __declspec(dllexport)修饰,命令行程序中使用LoadLibraryA函数动态加载dll,主程序中先定义函数指针typedef int(*AddFunc)(char* words[], int len, char* result[]);,再通过GetProcAddress找到函数入口地址,最后调用函数。

2、GUI模块调用dll

  • 出于Loose Coupling考虑,我们认为已经在命令行中写过的文件处理、文本分析机制不应当再在python中重复第二遍,这样在修改一出往往会牵一发而动全身,因此使用python直接复用命令行模块,进而调用核心模块。
  • 首先,必须满足题目只有一个运行程序的要求,因此我们将C++编写的命令行程序以二进制资源文件的格式打包进python生成的exe文件中,python每次自动使用该文件,以达到一个运行程序的目的。
  • 其次,python使用popen管道,可以捕获所有输出,并监控命令行程序是否正常结束,最后将输出打印在文本区。

十三、结对过程

  • 我们采用线下+线上方式进行共同开发。

  • 由于结对队友在白天有实习需求,因此我们主要在晚上的共同时间一起线下开发。而白天不方便一起在线下编码时,则采用腾讯会议讨论的方式推进。

  • 在结对开始的时候,我们先各自阅读指导书,然后一起分享各自的理解并讨论指导书中的细节。在充分分析题目需求后,我们开始讨论项目设计,明确core模块、GUI和CLI分别如何交互。之后我们再分析各模块的实现方法,包括编程语言、大致架构等。设计完成后我们开始结对编码实现各模块,分工主要为一个人写core模块,一个人写GUI,且两人轮换开发。完成编码后我们一起进行测试工作、对接工作,最终做完所有项目。

十四、优缺点总结

1、结对编程优缺点

  • 优点:

      (1)结对编程时两人思路可以总是保持一致,即使有分歧也可以在问题扩大之前快速解决。
      (2)效率高,代码质量高,出错率降低。
      (3)双方可以相互学习,互相学习对方编程中优秀的经验。
    
  • 缺点

      (1)在正式进入项目开发前需要时间相互了解。
      (2)正式进入开发后有可能因为性格问题导致无法解决的分歧进而解体。
      (3)作为学生很难每条腾出固定的时间进行结对,无法模拟上班的真实情况。
    

2、个人优缺点

队友自己
优点:算法能力非常优秀优点:对于Python使用PYQT写客户端界面非常熟悉
优点:对于C++以及CMake等工具使用熟练优点:对visual studio的开发环境配置有一定的了解
优点:工作效率高,开发速度快优点:学过一些测试相关理论,对自动化测试比较热衷
缺点:对于测试和仓库管理等有所欠缺缺点:算法能力不是很强

十五、实际PSP

  • 二、PSP 部分

十六、额外任务

1、另一小组

  • 19376309
  • 20373862

2、互换运行

  • 本组GUI运行对方组核心模块
  • 对方组运行本组核心模块

3、遇到的问题

  • 对方组也使用python,由于python调用dll比较困难,对方组并没有采用类似我们组将命令行程序打包成资源文件的做法,因此单独写了一个python调用的接口,而我们组没有在dll中设置该接口,导致对方GUI无法调用我方核心模块,只能使用命令行程序调用。
  • 我们组使用GUI内置了转换成资源文件的命令行程序,因此可以直接调用对方组dll。
  • 最受影响的是异常处理部分,解决方案是我们均捕获exception基类来获取对方的异常,而没有编译对方的类
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值