结对编程项目-最长英语单词链

本文介绍了结对编程项目中关于接口设计、性能优化及异常处理的实践经验。作者详细阐述了如何运用InformationHiding、InterfaceDesign和LooseCoupling原则,并展示了在C++中实现的UML图、PSP表格和单元测试。此外,还讨论了计算模块的性能改进策略,如减少BFS调用和优化数据结构,以及CLI和GUI界面的设计与实现。
摘要由CSDN通过智能技术生成

1. 项目地址

社区2022年北航敏捷软件工程社区-CSDN社区云
作业要求结对编程项目-最长英语单词链-CSDN社区
我在这个课程的目标合作开发一个优秀的软件
这个作业在哪个具体方面帮助我实现目标设计并实现一个小项目
项目地址xxqwbjfy/pair_gramming (gitee.com)
班级周五班

2. PSP表格

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

3. UML图

项目采用c++开发,UML图如下

首先是基本函数的实现:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ieOfNcYj-1649089411854)(D:\markdownimg\image-20220404161555571.png)]

然后是核心模块core,其中实现了一个内存释放函数以及课程组给定接口:
在这里插入图片描述

4. 模块接口的设计与实现过程

约定

首先是对core.dll参数的约定

参数意义
words一个字符指针数组,要求使用new和delete进行内存的申请和调用,必须传入没有重复单词的words
lenwords的长度,最大为10000
results结果存储数组,要求使用new和delete进行内存的申请和调用,最大长度为20000
head单词链首字母,必须为字母
tail单词链尾字母,必须为字母
enable_loop是否允许隐藏单词环
返回值单词链的数目(gen_chains_all)或者单词链中单词的数量(其他)

然后是基础函数bfs参数的约定

参数意义
first_lattervector数组,根据首字母的不同存储单词
flagset数组,防止单词链中出现重复单词
ansvector,用于存储结果
chain用于记录当前单词链
one_n若为1,统计该单词文本中共有多少条单词链,包含嵌套单词链
two_w若为1,计算最多单词数量的英语单词链
three_m若为1,计算首字母不同的单词数量最多单词链
four_c若为1,计算字母最多的英语单词链
five_h指定单词链的首字母,若希望首字母为a,请传入1,z请传入26
five_t指定单词链的首字母,若希望尾字母为a,请传入1,z请传入26
six_r若为1,表示允许单词文本隐含单词环
返回值如果不允许单词环却存在单词环,返回-1,否则返回0

实现

首先调用devide_words对words中的字母进行分类,根据首字母的不同拆分为vector数组first_latter。

然后对words中的每一个单词调用bfs(广度优先遍历),将结果保存在ans中

最后,通过anstoresult将ans中的内容保存到result中,anstoresult的返回值即为core中指定接口的返回值。

5. 参考资料中 Information Hiding、Interface Design、Loose Coupling章节,说明这些方法在接口设计中的实际运用

5.1 Information Hiding

在原本失败的版本中,以类Word_set为例,Word_set中包含一个Vector和一个index属性,其中储存了所有单单词,使用者无法直接对这两个私有属性进行访问,而只能通过实现定义好的public 方法来传入单词,这样保证了类中的内容能够以开发者约定好的方式进行修改,而不是随意访问造成数据错误的问题。

在后面的新版本代码中,由于数据结构比较简单,并没有刻意使用类进行封装。

5.2 Interface Design

通过接口设计实现相应的方法,可以方便使用者进行调用,也能够保证开发者和测试者能够正确对接。core.cpp里面的gen_chain_word、gen_chains_all、gen_chain_word_unique、gen_chain_char就是课程组指定的接口,这些接口相较于bfs方法传入的参数更少,更加方便使用。

5.3 Loose Coupling

松耦合的基本概念是:允许改变或者当问题发生在“电线的一端时”来避免影响到其他的端点。也就是说,改变或者供应者或者服务的问题不能影响到用户----或者用户的问题不应影响到供应者或者服务。例如,为了方便代码实现bfs,我使用vector来保存单词链的求解结果,但是对使用者来说,只需要按约定传入result指针数组即可获得结果,而不用担心内部是如何保存和实现的。

6.模块接口部分的性能改进

性能分析

通过以下数据对Wordlist进行性能分析(Wordlist -r -n input.txt),结果如下

ba ab
ca ac cb bc
da ad db bd dc cd

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z8RfVvuz-1649089411855)(D:\markdownimg\image-20220404173928437.png)]

从图中可以看出,bfs占据了绝大部分cpu。

改进思路

因为bfs是一个递归函数,所以会调用很多次。

首先是减少bfs的调用数量,实现更加严格的退出条件,因为有时候已经检测到程序出错了,就不需要继续进行bfs。通过设置特定的返回值,告诉主程序已经出现已经出现错误(例如,input.txt中的单词不合法)来防止主程序继续对words中的单词进行循环调用bfs,在检测到错误后,直接用break跳出循环。

其次增加bfs的速度,通过首字母对单词进行分类,这样以后,bfs在寻找单词链的下一个单词的时候就不需要遍历words,从而提高bfs的运行速度。

7. Design by Contract,Code Contract

Design by Contract

契约式设计

在这种设计中,调用者和调用者地位平等,双方必须彼此履行义务,才可以行驶权利。调用者必须提供正确的参数,被调用者必须保证正确的结果和调用者要求的不变性。双方都有必须履行的义务,也有使用的权利,这样就保证了双方代码的质量,提高了软件工程的效率和质量。

在代码中的体现,以gen_chains_all为例,调用者必须传入正确的参数,程序才会将正确的结果保存在result指针数组中。否则程序会返回对应的错误信息。

缺点是一旦启用契约,在实际应用时难以对契约进行修改或者停用。

Code Contract

代码契约

代码契约提供了在 .NET Framework 代码中指定前置条件、后置条件和对象固定的方法。 前置条件是输入方法或属性时必须满足的要求。 后置条件描述在方法或属性代码退出时的预期。 对象固定描述处于良好状态的类的预期状态。主要运用于c#,本次作业中体现不明显。

8、单元测试展示

对core中四个函数进行单元测试,构造测试的主要思想为对四个方法的每一个参数进行测试,部分测试用例如下:

TEST_METHOD(TestMethod3)//测试出head或者tail传入0的情况
{
    int len = 4;
    char* words[] = { "ab", "bc", "bd", "dc" };
    int num = 3;
    char* ans[] = {
    	"ab\nbd\ndc\n"
    };
    char** result = new char* [MAXANS];
    int _num = gen_chain_word(words, len, result, 'a', 0, false);
    Assert::AreEqual(num, _num);
    vector<string> _ans, _result;
    for (int i = 0; i < 1; i++) {
    	_ans.push_back(ans[i]);
    	_result.push_back(result[i]);
    }
    sort(_ans.begin(), _ans.end());
    sort(_result.begin(), _result.end());
    for (int i = 0; i < 1; i++) {
    	Assert::AreEqual(_ans[i], _result[i]);
    }
    free_result(result, 1);
    delete[]result;
}
TEST_METHOD(TestMethod6) //解决了words中重复单词问题,约定words中不能存在重复单词
{
    int len = 6;
    char* words[] = { "ab", "bc", "cd", "ccm", "ce", "ex"};
    int num = 4;
    char* ans[] = {
    	"ab\nbc\nce\nex\n"
    };
    char** result = new char* [MAXANS];
    int _num = gen_chain_word_unique(words, len, result);
    Assert::AreEqual(num, _num);
    vector<string> _ans, _result;
    for (int i = 0; i < 1; i++) {
    	_ans.push_back(ans[i]);
    	_result.push_back(result[i]);
    }
    sort(_ans.begin(), _ans.end());
    sort(_result.begin(), _result.end());
    for (int i = 0; i < 1; i++) {
    	Assert::AreEqual(_ans[i], _result[i]);
    }
    free_result(result, 1);
    delete[]result;
}

无奈使用的vs2022是社区版本,无法分析代码覆盖率。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nUEHBLeq-1649089495475)(D:\markdownimg\image-20220404173928437.png)]

9. 计算模块部分异常处理说明

我们并没有实现标准的异常处理,只是在出现错误的时候打印出错误信息并正常结束程序,这些错误包括:

1、调用wordlist时参数错误

if (strlen(argv[arg_index - 1]) == 1 || !isalpha(argv[arg_index][0]))
{
	cout << "不合法的参数传递" << endl;
	return 1;
}
five_t = argv[arg_index][0] - 'a' + 1;
if (five_t < 0)
{
	five_t += 32;
}
break;
default:
    cout << "不支持的参数" << endl;
    return -1;
    break;
if (!input)
{
	cout << "不存在此文件" << endl;
	return 3;
}
if (get_argv(argc, argv) != 0)
{
	cout << "参数解析失败" << endl;
}

2、输入文本存在单词环

if (bfs(first_latter, flag, chain, ans, one_n, two_w, three_m, four_c, five_h, five_t, six_r) != 0)
{
	cout << "存在单词环" << endl;
	ans.clear();
	return 0;
}

3、单词链数量过多

if (size_ans > 20000)
{
		cout << "单词链数量过多" << endl;
		ans.clear();
		return 0;
}

4、不存在符合条件的字符串

else
{
	cout << "不存在符合条件的字符串" << endl;
}

5、core接口参数有误

if (!((isalpha(head) || head == 0) && (isalpha(tail) || tail == 0)))
{
	cout << "head 和 tail 参数必须为字母" << endl;
}

6、加载dll失败

	HMODULE hDll = 0;
	hDll = LoadLibrary("D:\\2022chun\\software\\repo\\core\\x64\\Release\\core.dll");
	if (!hDll) {
		cout << "动态链接库 core.dll 加载失败" << std::endl;
		return -1;
	}

	typedef int (*chain_word)(char* words[], int len, char* result[], char head, char tail, bool enable_loop);
	chain_word gen_chain_word;
	gen_chain_word = (chain_word)GetProcAddress(hDll, "gen_chain_word");
	if (gen_chain_word == 0) {
		cout << "动态链接库中方法加载失败" << endl;
		FreeLibrary(hDll);
		return -1;
	}

10. 界面模块详细设计过程

CLI界面

cli界面通过读取命令行参数对程序进行控制,在完成参数解析后,对计算模块进行相应的调用,具体实现如下:

ifstream input;

int one_n = 0; //统计该单词文本中共有多少条单词链,包含嵌套单词链
int two_w = 0; //计算最多单词数量的英语单词链
int three_m = 0; //计算首字母不同的单词数量最多单词链
int four_c = 0; // 计算字母最多的英语单词链
int five_h = 0; //指定单词链的首字母
//char four_h_argv = '\0';
int five_t = 0; //指定单词链的尾字母
//char five_t_argv = '\0';
int six_r = 0; //表示允许单词文本隐含单词环
int arg_index = 0;

//本项目对于输入的参数和文件名的顺序没有假设
int file_flag = 0;


int get_argv(int argc, char* argv[])
{
	if (argc > 1)
	{
		for (arg_index = 1; arg_index < argc; arg_index++)
		{
			if (strlen(argv[arg_index]) > 4)
			{
				if (file_flag == 1)
				{
					cout << "不合法的参数传递" << endl;
					return 1;
				}
				string input_file(argv[arg_index] + strlen(argv[arg_index]) - 4);
				if (input_file != ".txt")
				{
					cout << "错误:需要输入txt文件" << endl;
					return 2;
				}
				input.open(argv[arg_index]);
				file_flag = 1;
			}
			else if (argv[arg_index][0] != '-' && strlen(argv[argc - 1]) == 2)
			{
				cout << "不合法的参数传递" << endl;
				return 1;
			}
			else
			{
				switch (argv[arg_index][1])
				{
				case 'n':
					one_n = 1;
					break;
				case 'w':
					two_w = 1;
					break;
				case 'm':
					three_m = 1;
					break;
				case 'c':
					four_c = 1;
					break;
				case 'h':
					arg_index++;
					if (strlen(argv[arg_index - 1]) == 1 || !isalpha(argv[arg_index][0]))
					{
						cout << "不合法的参数传递" << endl;
						return 1;
					}
					five_h = argv[arg_index][0] - 'a' + 1;
					if (five_h < 0) //处理大写
					{
						five_h += 32;
					}
					break;
				case 't':
					arg_index++;
					if (strlen(argv[arg_index - 1]) == 1 || !isalpha(argv[arg_index][0]))
					{
						cout << "不合法的参数传递" << endl;
						return 1;
					}
					five_t = argv[arg_index][0] - 'a' + 1;
					if (five_t < 0)
					{
						five_t += 32;
					}
					break;
				case 'r':
					six_r = 1;
					break;
				default:
					break;
				}
			}
		}
	}
	else
	{
		input.open("input.txt");
	}
	if (!input)
	{
		cout << "不存在此文件" << endl;
		return 3;
	}
	return 0;
}

GUI简介

用户界面由python实现,并打包成相应的exe文件,通过下载git中的gui文件夹,即可通过其中的main.exe启动用户界面,如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xBOS6PZT-1649089411856)(D:\markdownimg\image-20220404235912900.png)]

使用方法

用户在左边输入单词文本,或者通过上传文本文件按钮上传单词文本。需要注意的是,上传单词文本以后,系统默认读取上传的文本文件,而不是左边文本输入框的文本。若需要测试输入框的文本,请单击取消上传

左下角为程序运行日志,如果运行失败会显示错误信息,否则会在右边输出结果,可点击保存结果,将输出结果保存的指定文件夹下。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-klaNkQez-1649089411857)(D:\markdownimg\image-20220405000517662.png)]

11、界面模块与计算模块的对接

界面模块通过以下方法调用计算模块(以-n为例),使用popen调用命令行,并将得到的结果打印到日志中。

# 功能函数
    def n(self):
        if self.file == 0:
            self.write_text()
        a = os.popen("Wordlist.exe -n ./resourse/input_{}.txt".format(self.file))
        a = a.readlines()
        a = a[len(a) - 1]
        self.write_log_to_Text(a)
        # self.write_log_to_Text(self.error(a))
        if a == "运行成功\n":
            self.cat()

12、结对过程

在线下对需求和代码规范进行确定以后,结对编程主要以线上方式进行。

13、结对编程优缺点

优点

1、两个人可以相互为对方的代码找bug,写出正确的程序

2、两个人能够相互监督,提高编码效率

3、两个人可以减轻编码的压力

缺点:

1、时间消耗较大

2、选择结对对象比较困难

队员优缺点分析

颜月吴时雨
优点熟悉c++、学习能力较强、写代码能力较强熟悉git、熟悉使用vs2022、沟通能力较强
缺点对git不熟悉不熟悉c++

14. PSP 表格记录实际花费的时间

见第二节的表,由于前期选择的算法错误,并且不太熟悉dll和单元测试,导致编码时间和学习新技术的时间超过预期。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值