2023软工作业3【结对编程】

该项目关注接口设计,如InformationHiding和LooseCoupling,使用VS进行开发,实现了团队协作。通过预处理、构建有向图和DP算法解决最长单词链问题。测试覆盖了异常处理和性能优化,同时采用了QT进行GUI设计,展示了结对编程的优势和挑战。
摘要由CSDN通过智能技术生成
项目内容
这个作业属于哪个课程北航软工
这个作业的要求在哪里最长英语单词链
我在这个课程的目标是学会团队协作
这个作业在哪个具体方面帮助我实现目标组队项目,使用vs

0 项目信息

教学班级项目地址
周四下午pair-program

成员:徐子航,徐楚鸥


预计耗时

psp2.1预估耗时(min)实际耗时(min)
Planning计划9060
. Estimate· 估计这个任务需要多少时间3030
Development开发600900
. Analysis· 需求分析 (包括学习新技术)6050
. Design Spec· 生成设计文档5060
.Design Review· 设计复审 (和同事审核设计文档)3020
.Coding Standard· 代码规范 (为目前的开发制定合适的规范6030
.Design· 具体设计6050
.Coding· 具体编码400300
.Code Review· 代码复审6090
.Test· 测试(自我测试,修改代码,提交修改)200180
Reporting报告120200
.Test Report· 测试报告6040
.Size Measurement· 计算工作量3020
.Postmortem & Process Improvement Plan· 事后总结, 并提出过程改进计划3030
合计18802060

1 接口设计

1.1 Information Hiding

书中提到的接口设计中要求的Information hiding是为了模块内的主要功能和函数不暴露在外,否则可能会带来安全问题和让程序变得更加复杂。所以有效地隐藏信息和核心函数是十分重要的。所以我们总共留下了4个接口,分别是用来处理核心功能的process(),用于和GUI交互的三个接口,并且前后端交换数据并不会通过process()接口,而是将process()函数封装在三个单独功能的接口里。除此之外,对于不能被外界访问的数据用private,对于其他可以被外界访问的数据用public。这样

1.2 Interface Design

对于接口设计的理念是,一个接口就完成一个特定的任务,要对接口也进行模块化,对于其他功能或者是信息在这个接口是不可以接入的,只有找到专用的接口才能访问内部数据,这样可以使得编程更加模块化,系统化,有效防止信息泄露和其他情况。在此次结对编程中我们对外留下了process(char *wordList[], char *result[], int len, int type, bool letterSum, char head, char tail, char j),这是一个核心函数。通过这个接口就可以防止内部的信息泄露,较好地完成了模块的封装,也保障了信息的安全。
下面是具体的接口设计,

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

int gen_chains_all(char *words[], int len, char *result[]);

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

还有核心模块的接口:

int process(char *wordList[], char *result[], int len,
            int type, bool letterSum, char head, char tail, char forbid)

1.3 Loose Coupling

结合书中的松耦合的描述,指的是要将功能聚合在一个cpp或者函数内,然后对外交互信息的时候要实现松耦合,尽可能地将要替换一个功能或者应用到别的场景的时候能够最小化要解耦的地方。此次结对编程我们将核心计算部分集中在core.cpp这个文件中,通过三个既定的接口与GUI/CLI进行交互。这样就能实现Loose Coupling了。这样也方便和其它组别进行互换测试。

2 接口

2.1 接口设计

对于既定的接口,本质都是通过实现process()接口实现的,根据type,head,tail,forbid,enable_loop等参数实现gen_chain_word,gen_chains_all,gen_chain_char这个三个接口。
对于process()这个接口而言,是先通过init()函数初始化,若argv中带有-j那么直接在wordlist中删除头字母为限制字母的单词。再经过排序sort()erase()之后,根据type类型判断是-n,还是-w,-c。调用图如下:
在这里插入图片描述
对于process()而言,通过wordlist和参数构建图,对于允许单词环的,首先检查wordlist中是否有单词环,如果有,但是又参数中没有-r则应该报错,并且由于是NP-hard所以没有最优的性能,暴力求解。但是如果没有单词环,则应该构件DAG之后DP求解。

  • gen_chains_all():遍历所有的单词进行DFS,求最长链,对于超过要求20000长度的则按照异常处理
  • gen_chain_word()和gen_chain_char()只是在构件图的时候边的权重不同而已,在算法部分会详细讲解.

2.2 接口实现

2.2.1 输入预处理

首先我们对输入的单词数量进行去重,然后对于不满足条件的输入报异常,例如小于两个单词的则不需要求解。对于-j参数我们的处理是在预处理的时候对对应的首字母为制定字母的单词删去,即不参与构图。

2.2.2 构建有权有向图

对于单词a..b,则创建a和b节点,然后有一条从a到b的有向边。根据需求,如果求的是最多单词数量,则边的权重为1,否则为单词的字母数。以此类推构建一个有向图,此方法的时间复杂度为O(n^2).
我们将其划分为两种情况,其一为无隐含的单词环,其二是有隐含的单词环。对于第一种情况,我们可以使用DP求解。在DP的过程中要纪录每个点作为起始点的最长链,最后就可求得最长链的长度了。
获得拓扑序列的目的是对于那些有-h或者-t的情况则需要重构图,从而使得最后的重构图能够达到限制条件,时间复杂度为O(n^2+m)
对于有单词环的情况,由于没有最优解,所以我们采用getRingChain()函数进行DFS暴力搜索。

在这里插入图片描述

编译无警告的截图:

在这里插入图片描述

3 UML图

此次编程项目我们总共写了三个文件,分别是:

  • main.cpp用于处理输入,例如单词去重,对单词流的预处理,以及抛出部分异常等
  • core.cpp用于核心计算功能,包括建图,DFS,DP等核心功能集成在这个文件中,其中getScc()用于得到所有的额强连通分量,dfsAll()用于深搜遍历节点
  • core.h用于对外和GUI/CLI的接口
    在这里插入图片描述

4 性能改进

首先对于有向无环图中的参数-w-c,由于我们采用了拓扑排序和DP的放大求解,所以能够加快求解速度。对于-j我们的做法是在单词输入预处理的时候把首字母为指定字母的单词直接删去,不参与构建有向图,所以这也可以有助于性能的改进。

  • 简单样例
    在这里插入图片描述
  • 复杂样例

在这里插入图片描述

在这里插入图片描述
可以看到在出现单词环的时候,主要在dfsRing()getRingChain两个的函数上,
但是对于一些更为复杂的样例,加上具有-r的含有单词环的情况,程序性能就会急剧下降。

5 Design by Contract & Code Contract

Design by Contract 是契约式设计,例如大二下学期上的OO课程中的JML就是契约式设计。这种设计的好处在于接口具有不变性,方便人们调试和测试,但是确定契约也是一个繁杂和耗时的磨合的过程.
Code Contract 指的是在过程中涉及到的规则约束和编译检查相关的部分.
本次作业我们对契约式设计体现在接口的前后端规定,即每个方法都要满足前置条件、后置条件和不变式以及其他的状态约束条件.我们在构建有向图的时候就是所谓的前置条件,不变式就是在process()的过程不破坏图的结构,后置条件就是输出符合要求。

6 测试

测试我们是在Visual Studio2017内进行调试,后用2022Community版本查看了代码覆盖率。我们主要分为两个测试程序,分别是unitTest.cppmainTest.cpp,第一个是用于单元测试,包括参数的测试等,第二个用于主要函数的异常测试。
构造测试数据的思路是:穷尽不同组合的参数,对于不可能出现的参数组合,例如-n -r要报异常,下面是单元测试中的两个样例,其中test_gen_chain_word是对gen_chain_word这个接口进行测试。对于三个接口我们都构造了不同的数据。

  • unitTest.cpp的部分代码:
/*
		-w -t -j
		*/
		TEST_METHOD(test_w_t_j) {
			char* words[] = { "algebra", "apple", "zoo", "elephant", "under", "fox", "dog", "moon", "leaf", "trick", "pseudopseudohypoparathyroidism" };
			char* ans[] = { "elephant", "trick" };
			test_gen_chain_word(words, 11, ans, 2, 0, 'k', 'a', false);
		}
		/*
		-w -h -t -j 
		*/
		TEST_METHOD(test_w_h_t_j) {
			char* words[] = {"asdac","jiasdnc","qweudasunc","casdowdn","nasdnw" ,"nqwer","rhusad","radqwt","tqwdbf","dqwdf"};
			char* ans[] = { "qweudasunc","casdowdn" ,"nqwer" ,"rhusad","dqwdf" };
			test_gen_chain_word(words, 10, ans, 5,'q', 'f', 't', false);
		}

代码覆盖率

在这里插入图片描述

7 异常处理

  • 缺少功能参数 Missing function option, please choose -n or -w or -c

    功能参数包括 -n -w -c至少要有其一

TEST_METHOD(missing_arguments)
		{
			try {
				char * args[] = { "C:\\Users\\Q\\Desktop\\test.txt" };
				main(1, args);
			}catch(invalid_argument const &e){
				Assert::AreEqual(0, strcmp("Missing function option, please choose -n or -w or -c", e.what()));
				return;
			}
			Assert::Fail();
		}
  • 功能参数不兼容Function option -w, -n and -c conflict

    -w -n -c 不兼容

TEST_METHOD(duplicated_wnc) {
			try {
				char * args[] = { "-w", "-n","-c","C:\\Users\\Q\\Desktop\\test.txt" };
				main(4, args);
			}
			catch (invalid_argument const &e) {
				Assert::AreEqual(0, strcmp("Function option -w, -n and -c conflict", e.what()));
				return;
			}
			Assert::Fail();
		}
  • 未定义参数Undefined option '-x'

    出现未定义参数 -x等

TEST_METHOD(undefined_x) {
			try {
				char * args[] = { "-o", "C:\\Users\\Q\\Desktop\\test.txt" };
				main(2, args);
			}
			catch (invalid_argument const &e) {
				Assert::AreEqual(0, strcmp("Undefined option '-x'", e.what()));
				return;
			}
			Assert::Fail();
		}
  • 参数格式错误Argument of option '-x' should be a single alphabet

    -h -t -j 后的指令不是单个字母, (单个字符 or 单词)

TEST_METHOD(argument_pattern) {
			try {
				char * args[] = { "-h","word", "C:\\Users\\Q\\Desktop\\test.txt" };
				main(3, args);
			}
			catch (invalid_argument const &e) {
				Assert::AreEqual(0, strcmp("Argument of option '-x' should be a single alphabet", e.what()));
				return;
			}
			Assert::Fail();
		}
  • -h与-j不兼容Argument of -h and -j conflict. No answer

    -n 与 附加参数 不连用(不要求)

    -h 与 -j 指定字母一致则冲突

TEST_METHOD(conflict_hj) {
			try {
				char * args[] = { "-h","a", "-j","a","C:\\Users\\Q\\Desktop\\test.txt" };
				main(5, args);
			}
			catch (invalid_argument const &e) {
				Assert::AreEqual(0, strcmp("Argument of -h and -j conflict. No answer", e.what()));
				return;
			}
			Assert::Fail();
		}
  • 不支持-n与附加参数连用-n should be used independantly

    -n 与 附加参数 不连用

TEST_METHOD(independent_n) {
			try {
				char * args[] = { "-n","-r","C:\\Users\\Q\\Desktop\\test.txt" };
				main(3, args);
			}
			catch (invalid_argument const &e) {
				Assert::AreEqual(0, strcmp("-n should be used independantly", e.what()));
				return;
			}
			Assert::Fail();
		}
  • 文件不存在Can not find file

    输入文件不存在

TEST_METHOD(file_notfind) {
			try {
				char * args[] = { "-n","C:\\Users\\Q\\Desktop\\fileee.txt" };
				main(2, args);
			}
			catch (invalid_argument const &e) {
				Assert::AreEqual(0, strcmp("Can not find file", e.what()));
				return;
			}
			Assert::Fail();
		}
  • 文件不合法Wrong file format

    输入文件格式不合法

TEST_METHOD(wrong_fileformat) {
			try {
				char * args[] = { "-n","wrong_fileformat.txt" };
				main(2, args);
			}
			catch (invalid_argument const &e) {
				Assert::AreEqual(0, strcmp("Wrong file format", e.what()));
				return;
			}
			Assert::Fail();
		}
  • 隐含单词环Ring dectected without -r option

    没有-r的情况下包含了单词环

TEST_METHOD(ring_detect) {
			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,0, false);
			}
			catch (invalid_argument const &e) {
				Assert::AreEqual(0, strcmp("Ring dectected without -r option", e.what()));
				return;
			}
			for (int i = 0; i < 101; i++)
			{
				free(result[i]);
			}
			Assert::Fail();
		}
  • 单词链数量过多Too many word chains

    超过20000

TEST_METHOD(wordlen_overflow) {
			try {
				char * args[] = { "-n","wordlenoverflow.txt" };
				main(2, args);
			}
			catch (invalid_argument const &e) {
				Assert::AreEqual(0, strcmp("Too many word chains", e.what()));
				return;
			}
			Assert::Fail();
		}

8 GUI界面

我们采用的是QT进行设计。最开始我们采用的是visual studio2017中的C# windows进行设计,但是由于不懂如何写C++ wrapper,且C#和C++一些类型转换较为复杂,整了很久的char* result[]的数据传递,但是一直都是null,所以最后就转用了QT5.12.

在这里插入图片描述
开始分析按钮对应:

void Widget::on_begin_clicked()
{

    myTimer->setInterval(100);
    myTimer->start(100);
    startCalc();
    myTimer->stop();

}

其中的startCalc()就是核心计算部分,调用DLL进行计算,对于异常具有输出处理功能。在点击了开始分析按钮的时候就开始计时,计算结束的时候停止计时。由于对于一些简单的案例所需时间较短,所以为了保留两位数之后数会很小。
core.dll对接:
在这里插入图片描述

  • 基本界面:
    在这里插入图片描述

  • 导入文件:
    在这里插入图片描述

  • 导出文件:
    在这里插入图片描述

  • 正常处理:
    在这里插入图片描述
    在这里插入图片描述

  • 异常处理:
    在这里插入图片描述
    在这里插入图片描述

9 杰对讨论

在这里插入图片描述

优缺点

本人:

  • 优点:
    1.积极沟通,避免了额外的时间开销
    2.代码编写规范,阅读时没有障碍0
    3.善于前端制作,在前后端对接出现问题时迅速完成重构
  • 缺点:
    1.不熟悉vs,qt,上手很慢

同伴:

  • 优点:
    1.积极讨论,善于沟通
    2.比较有生产力
    3.认真学习之前没接触过的软件缺点:
  • 缺点:
    对报告比较头疼,第一次接触c++项目启动阶段遇到了一些困难

10 互换模块

我们的互换小组是:
20373080 庞睿加,20373384 朱彦安
我们通过互换core.dll模块进行,我们统一的接口和课程组要求的接口相同.
我们采用了我们的测试模块和他们的core.dll进行测试,没有出现问题,如下是截图:
在这里插入图片描述

11 总结

这次两周的结对编程我学习到了很多.首先,我学会了如何进行合理地分配两人的任务,如何有效地沟通和高效地协作.其次,之前从来没有碰过vs,qt软件和c#等编程语言,所以上手很慢,印象深刻的是在写GUI可视化的时候,我最开始用的c#来写,但是由于c++和c#之间的类型的转换不太清楚,对c++ wrapper啥的不太了解,所以最后改用了qt,由于qt也是用c++,所以方便了很多.最后,很感谢结对的队友,十分负责,能够很及时地交流和沟通!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值