软件工程实践第二次作业——个人实战

这个作业属于哪个课程 2022福大-软件工程、实践-W班
这个作业要求在哪里 软件工程实践第二次作业——个人实战
这个作业的目标 实现一个命令行程序,根据一定的运行格式可以输出22年冬奥会的奖牌总榜以及每日赛程,自行设计单元测试,填写PSP表格,按照要求将文件上传到gitcode上,同时为这次的作业编写对应的博客,做好总结和反思。
其他参考文献 Git入门,邹欣老师的单元测试和回归测试

一、Gitcode项目地址

项目地址:221900101_陈少峰_软件工程实践第二次作业*

二、PSP表格

PSP Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划
- Estimate - 估计这个任务需要多少时间 20 30
Development - 开发
- Analysis - 需求分析 (包括学习新技术) 30 120
- Design Spec - 生成设计文档 30 30
- Design Review - 设计复审 15 15
-Coding Standard 代码规范 (为目前的开发制定合适的规范) 30 30
- Design - 具体设计 30 60
- Coding - 具体编码 120 120
- Code Review - 代码复审 20 40
- Test -测试(自我测试,修改代码,提交修改) 120 150
Reporting -报告
- Test Repor - 测试报告 30 25
- Size Measurement - 计算工作量 25 20
- Postmortem & Process Improvement Plan - 事后总结, 并提出过程改进计划 10 20
合计 480 660

三、解题思路描述

1、分析需求

本次作业主要内容是,实现两个模块,一个是查询冬奥会的每日赛程,一个是输出奖牌榜。除此之外,还需要对命令行的输入以及输入的命令进行判错操作。

2、数据的获取以及预处理

特别声明,本文所用爬虫仅用于教学课程中

数据主要来自:冬奥专栏
**思路:**从所给的信息来看,可以迅速的想到,通过手动获取,即一个一个敲打,收集,并进行转换对应格式,但是太繁琐,太慢了,直接pass掉,然后想到可以爬虫,可以通过编程将网页上的内容爬取出来,接着去东奥专栏页面通过开发者工具去分析它的源码,发现有对应数据的API,最后选择用Pyhton语言用beautifulsoup4库获取对应API源码,并将他转换成对应的json格式,存储到文件中。

给出对应页面的API如下:每日赛程 奖牌总榜

其中每日赛程的API,可以通过修改请求头中的startdatecn的值,按照MMDD的格式生成对应日期的赛程信息API,以下给出0202的网址信息以作展示理解。

https://api.cntv.cn/Olympic/getBjOlyMatchList?startdatecn=20220202&t=jsonp&cb=OM&serviceId=2022dongao

3、核心功能的初步框架设计

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DNVhWXt0-1646466437000)(https://img-community.csdnimg.cn/images/4654e340c35d4ae2834e627af149ea19.png “#left”)]

4、核心功能实现的初步构思

- 查询每日赛程
输入要查询的日期,格式为MMDD的字符串,搜索本地对应日期赛程信息文件,对具体赛程信息进行匹配对应内容,之后进行格式化输出。

- 查询奖牌总榜
查询本地奖牌榜信息的文件,对奖牌榜信息进行匹配对应内容,然后进行格式化输出。

5、反思,总结与修改

根据具体需求,保持主干框架不变的情况下,对程序进行修改并完善。例如添加判错功能,添加其他查询功能等

四、接口设计和实现过程

主要程序文件

OlympicSearch.cpp                //程序入口
lib.h                            //功能函数的声明
lib.cpp                          //功能函数的定义

主要函数

string Get_total();                                          //解析并获取奖牌榜信息
string Get_schedule(string time);                            //解析并获取赛程信息
void Foramt_output(char* outfile, string str, bool flag);    //格式化输出信息
string Judge_cmd(string cmd);                                //判断命令

程序运行流程图以及函数关系图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VPDwMI00-1646466437002)(https://img-community.csdnimg.cn/images/334fa20c4b534d05a123b460cea5a01d.png “#left”)]

  • 获取奖牌榜i信息和赛程信息,主要是通过JSONCPP库对已经存储在本地的json文件进行读取与解析,将关键信息按照格式化输出的格式存储在unordered_map中,以便于下次读取不用再次读取文件。

  • 判断命令,主要通过stringstream将以空格隔开的一行字符串,分割成一个个字符串,然后读取stringstream流中的字符串按照不同的情况返回正确或者错误的信息。

五、关键代码展示

解析获取奖牌总榜
string Get_total()
{
	Json::Reader reader;
	Json::Value root;
	ifstream in("src\\data\\total.json", ios::binary);

	if (!in.is_open())
	{
		cout << "Can't open total.json\n";
		return "No";
	}

	string content;
	if (reader.parse(in, root))
	{
		const Json::Value nation_rank = root["data"]["medalsList"];

		for (int i = 0; i < nation_rank.size(); ++i)
		{
			content = content+ "rank" + nation_rank[i]["rank"].asString() + ":" + nation_rank[i]["countryid"].asString() + "\n";
			content = content + "gold" + ":" + nation_rank[i]["gold"].asString() + "\n";
			content = content + "silver" + ":" + nation_rank[i]["silver"].asString() + "\n";
			content = content + "bronze" + ":" + nation_rank[i]["bronze"].asString() + "\n";
			content = content + "total" + ":" + nation_rank[i]["count"].asString() + "\n";
			content = content + "-----";
			if (i != nation_rank.size() - 1)
				content = content + "\n";
		}
		return content;
	}
	else
	{
		cout << "Parse error\n";
		return "No";
	}
}
解析获取赛程信息
string Get_schedule(string time)
{
	/*
	* string time:the watch time,format:mmdd
	*/
	Json::Reader reader;
	Json::Value root;
	ifstream in("src\\data\\schedule\\"+time+".json", ios::binary);

	if (!in.is_open())
	{
		cout << "Can't open total.json\n";
		return "No";
	}

	string content;
	if (reader.parse(in, root))
	{
		const Json::Value matchlist = root["data"]["matchList"];
		for (int i = 0; i < matchlist.size(); ++i)
		{
			content = content + "time" + ":" + matchlist[i]["startdatecn"].asString().substr(10, 6) + "\n";
			content = content + "sport" + ":" + matchlist[i]["itemcodename"].asString() + "\n";
			content = content + "name" + ":" + matchlist[i]["title"].asString();
			if (matchlist[i]["homename"].asString() != "")
				content = content + " " + matchlist[i]["homename"].asString() + "VS" + matchlist[i]["awayname"].asString();
			content = content + "\nvenue" + ":" + matchlist[i]["venuename"].asString() + "\n";
			content = content + "-----";
			if (i != matchlist.size() - 1)
				content = content + "\n";
		}
		return content;
	}
	else
	{
		cout << "parse error\n" << endl;
		return "No";
	}
}
判断命令错误
string Judge_cmd(string cmds)
{
	/*
	* string cmds:read from cmd
	*/
	stringstream ss;
	ss << cmds;
	string cmd;
	ss >> cmd;
	if (cmd == "total")
	{
		if (!ss.eof())
			return "Error";
		return cmd;
	}
	else if (cmd == "schedule")
	{
		if (ss.eof())
			return "Error";
		ss >> cmd;
		//命令格式不对
		if (!ss.eof())
			return "Error";
		//长度不一致,内容格式不对
		if ((cmd.size()<4||cmd.size()>4))
			return "N/A";
		if ((cmd.substr(0, 2) != "02"))
			return "N/A";
		//日期不对,日期范围为 0202--0220
		if ((cmd[2] == '0' && (cmd[3] < '2' || cmd[3]>'9')) ||
			(cmd[2] == '1' && (cmd[3]<'0' || cmd[3]>'9'))||
			(cmd[2]=='2'&&cmd[3]!='0'||
			(cmd[2]<'0'||cmd[2]>'9'))
			)
			return "N/A";
		return cmd;
	}
	return "Error";
}

六、性能改进

程序优化前

借由Vistual Studio 2019 自带的性能测试工具,将数据的输入规模调整到1000次的命令查询,其中包含了各种命令的使用,以放大程序热点。结果如下图所示,本次性能分析主要分析运行时间,以及耗时部分。

运行时间以及CPU的运行分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DnDplP2n-1646466437003)(https://img-community.csdnimg.cn/images/27ac8b2bac214b10a02c6e6bdbf3f344.PNG “#left”)]

耗时部分的前五个分布情况
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eLwuFK74-1646466437003)(https://img-community.csdnimg.cn/images/75292a13e9cc4372816cfa47cb6c8487.PNG “#left”)]

程序的性能瓶颈主要在IO方面,由于一开始采取的策略是对每一次命令的执行都对文件进行检索,以及IO的输入和输出,IO的耗时占据较大,所以更改为用unordered_map进行存储已经查询过的内容。

程序优化后

运行时间以及CPU的运行分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lC9z9evk-1646466437004)(https://img-community.csdnimg.cn/images/6c61f625b1f44aa3aeda251fa6f1c6da.PNG “#left”)]

耗时部分的前五个分布情况

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CiVBCf94-1646466437005)(https://img-community.csdnimg.cn/images/09f347c83dc547b1b80d38aa1f75c41d.PNG “#left”)]

经过对比,程序的运行时间得到了很好的加速,同时IO部分的耗时也得到了缩减。

七、单元测试

单元测试主要针对命令行解析的返回值的正确性。以下列出一些相关的测试。

关于判断命令的正确性的判断,返回Error,测试代码如下

TEST_METHOD(TestMethod1)
{
		string str = "Error";
		Assert::AreEqual(str, Judge_cmd("schedule1"));
		Assert::AreEqual(str, Judge_cmd("xxxx"));
		Assert::AreEqual(str, Judge_cmd("Schedule 0202"));
		Assert::AreEqual(str, Judge_cmd("total1"));
		Assert::AreEqual(str, Judge_cmd("Total"));
		Assert::AreEqual(str, Judge_cmd("SCHEDULE 0202"));
		Assert::AreEqual(str, Judge_cmd("查询 0202"));
		Assert::AreEqual(str, Judge_cmd("total 123"));
		Assert::AreEqual(str, Judge_cmd("schedule1 0202 123"));
}

关于schedule指令,参数日期错误或者参数错误返回N/A,测试代码如下

TEST_METHOD(TestMethod2)
{
		string str = "N/A";
		Assert::AreEqual(str, Judge_cmd("schedule 0111"));
		Assert::AreEqual(str, Judge_cmd("schedule 0000"));
		Assert::AreEqual(str, Judge_cmd("schedule 0222"));
		Assert::AreEqual(str, Judge_cmd("schedule 02021"));
		Assert::AreEqual(str, Judge_cmd("schedule 123"));
		Assert::AreEqual(str, Judge_cmd("schedule 202"));
		Assert::AreEqual(str, Judge_cmd("schedule xxxx"));
		Assert::AreEqual(str, Judge_cmd("schedule xxx"));
		Assert::AreEqual(str, Judge_cmd("schedule xxxxx"));
		Assert::AreEqual(str, Judge_cmd("schedule 哈哈"));
}

关于schedule指令,参数日期正确,返回对应日期格式,测试代码如下

TEST_METHOD(TestMethod3)
{
	    string s = "schedule ";
	    string str = "0202";
		while (str[3] <= '9')
		{
			Assert::AreEqual(str, Judge_cmd(s+str));
			str[3] += 1;
		}
		str[2] = '1';
		str[3] = '0';
		while (str[3] <= '9')
		{
			Assert::AreEqual(str, Judge_cmd(s + str));
			str[3] += 1;
		}
		str = "0220";
		Assert::AreEqual(str, Judge_cmd(s + str));
}

关于total命令,正确输出total,代码如下

TEST_METHOD(TestMethod4)
{
		string str = "total";
		Assert::AreEqual(str, Judge_cmd("total"));
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZM3hXsOg-1646466437005)(https://img-community.csdnimg.cn/images/d5445e43dc9a4b029c522b87154b19b8.PNG “#left”)]

代码覆盖率如下,没有覆盖的地方主要是一些注释以及括号,代码基本覆盖
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O1pM68pO-1646466437006)(https://img-community.csdnimg.cn/images/5d50b8ba75cd4de8ba45039c7b32a159.PNG “#left”)]

尽可能多的去进行各种情况测试,能够帮助我们构建更健壮的代码,减少了bug发生的可能,以及提高用户使用的体验感。测试的结果能够满足大半
部分情况,可能还存在尚未发现的错误,但程序不就是在debug上慢慢成长的吗?

八、异常处理

异常处理有五个:
1、输入文件无法打开:输出”Can’t open input file\n",并退出程序。
2、total文件无法打开:输出"Can’t open total.json\n",退出获取奖牌榜信息函数。
3、schedule文件无法打开:输出”Cant’t open “ + time + “.json\n”,并推出获取赛程信息函数。
4、Json文件无法解析:输出 “Parse error\n”,并退出当前函数。
5、输出文件无法打开:输出"Can’t open output file\n",并退出当前程序。

九、心得体会

  • 需求分析很重要,需要花费较多的时间去分析需求,从而构建一个较为完整的代码结构。

  • 要学会针对问题去解决,这不仅仅适用于性能优化上,同时也适用于其他的编程方面。

  • Debug的时间 远远超过了Coding的时间。

  • 作业部分有些地方不是特别能够理解,理解和纠结了很久,例如接口部分,不清楚具体的要求是什么,有没有一个标准之类的。希望下次的作业,作业的要求能够清晰点。

  • 多和同学进行交流,同学碰到的问题有可能就是你作业过程中也会遇到的问题,这大大的减少了debug的时间。

  • 想清楚了再去做,不要想当然的去解决问题。

你可能感兴趣的:(随笔,链表,指针,数据结构)