这个作业属于哪个课程 | 2022福大-软件工程、实践-W班 |
---|---|
这个作业要求在哪里 | 软件工程实践第二次作业——个人实战 |
这个作业的目标 | 实现一个命令行程序,根据一定的运行格式可以输出22年冬奥会的奖牌总榜以及每日赛程,自行设计单元测试,填写PSP表格,按照要求将文件上传到gitcode上,同时为这次的作业编写对应的博客,做好总结和反思。 |
其他参考文献 | Git入门,邹欣老师的单元测试和回归测试 |
项目地址:221900101_陈少峰_软件工程实践第二次作业*
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 |
本次作业主要内容是,实现两个模块,一个是查询冬奥会的每日赛程,一个是输出奖牌榜。除此之外,还需要对命令行的输入以及输入的命令进行判错操作。
数据主要来自:冬奥专栏
**思路:**从所给的信息来看,可以迅速的想到,通过手动获取,即一个一个敲打,收集,并进行转换对应格式,但是太繁琐,太慢了,直接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
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DNVhWXt0-1646466437000)(https://img-community.csdnimg.cn/images/4654e340c35d4ae2834e627af149ea19.png “#left”)]
- 查询每日赛程
输入要查询的日期,格式为MMDD的字符串,搜索本地对应日期赛程信息文件,对具体赛程信息进行匹配对应内容,之后进行格式化输出。
- 查询奖牌总榜
查询本地奖牌榜信息的文件,对奖牌榜信息进行匹配对应内容,然后进行格式化输出。
根据具体需求,保持主干框架不变的情况下,对程序进行修改并完善。例如添加判错功能,添加其他查询功能等
主要程序文件
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的时间。
想清楚了再去做,不要想当然的去解决问题。