这个作业属于哪个课程 | 2022年福大-软件工程;软件工程实践-W班 |
---|---|
这个作业要求在哪里 | 软件工程实践第二次作业——个人实战 |
这个作业的目标 | 1.冬奥会的赛事数据的收集 2.实现一个能够对国家排名及奖牌个数统计的控制台程序并进行充分测试 3.使用gitcode,导入第三方库并配置本地环境 |
其他参考文献 | 各类搜索引擎 |
文章目录
Gitcode项目地址
PSP表格
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 180 | 65 |
• Estimate | • 估计这个任务需要多少时间 | 180 | 65 |
Development | 开发 | 2700 | 845 |
• Analysis | • 需求分析 (包括学习新技术) | 540 | 60 |
• Design Spec | • 生成设计文档 | 300 | 55 |
• Design Review | • 设计复审 | 60 | 10 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 300 | 60 |
• Design | • 具体设计 | 180 | 60 |
• Coding | • 具体编码 | 600 | 240 |
• Code Review | • 代码复审 | 120 | 120 |
• Test | • 测试(自我测试,修改代码,提交修改) | 180 | 480 |
Reporting | 报告 | 485 | 363 |
• Test Report | • 测试报告 | 240 | 120 |
• Size Measurement | • 计算工作量 | 5 | 3 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 240 | 240 |
合计 | 3565 | 2222 | |
因为写代码有时候会因为各种各样的事情被打断,忘记关掉番茄钟,所以这里的时间是大致估算,但不会差很多。 |
解题思路描述
1.语言选择
- 其实在java和c++中犹豫了一段时间,毕竟班上绝大部分人是用java写的,可以方便参照,但是自己个人对c++的编码能力远超java(可能因为当年灿辉老师的严格要求吧),而且性能测试什么的总觉得很复杂,最怕安装配置路径了一想到就头大,vc居然可以自带性能测试,很心动了,最后还是选择了c++
2.解题步骤
(此处的赛事文件获取仅供教学使用,绝无恶意)审核请不要卡我,呜呜呜,作业马上要截止了,发不上去辛苦就白费了,谢谢谢谢
- 这次代码要做什么呢:我认为(1:获得json数据(2:处理json数据并存入某处(3:处理命令并输出解答。经济基础决定上层建筑
- ·····先思考最简单的方式:
- 去东奥官网上,用开发者模式获取json文件,复制到本地。当然,会遇到转码的问题,懒得自己写代码?不要担心,可以用在线转换器,转成中文。
- 遇到命令之后,用jsoncpp解析文件,输出结果到output ,否则就算算法很高级性能很好,基础功能没完成也是白搭,这种处理方法后续算法升级也相对方便。
(好的想法放到后面再升级系统可以多收几次钱)
没有数据库的情况下,程序里的数据不能过夜,因此,我认为exe运行有的三个可能可以做手脚的地方是:(a:数据源(b:读入读出方式 (c:存放数据
-
- 第一阶段:
- 把原始json文件进行解析之后的输出结果存放到本地的txt文件,需要输出结果时候可以直接读出,减少了json数据处理次数。 (处理a)
-
- 第二阶段:
- 把原始json文件解析成,只有需要的字符串的行,这样只要scanf就能获取,不需要获取无用数据(处理a),还能把cin和getline换成scanf,printf读入读出,效率提高(处理b)
09:05 冰壶 女子冰壶循环赛第10轮 加拿大VS美国 国家游泳中心 09:05
- 在本地建一个用于存储这些重点数据的链表,然后每次指令读取之后,判断是否之前已经读取过所需文件,如果已经读取过,就不用再读取。
-
- 第三阶段:
- 把经过第二阶段处理的json文件合并成一个文件,这样可以减少开关流的消耗(处理b)
- 在本地建一个用于存储这些重点数据的链表,减少读取文件的消耗(处理c)
接口设计和实现过程
1.接口设计和代码组织
代码分为一个类,OlympicSearch,七个函数,表面上看,没有什么特别的地方,但是往下看
我把具体代码用下面的符号分割成了三个模块,对应我的设计
每日赛程处理模块(原始数据处理)+公用函数模块(负责判断指令和输出结果)=输出每日赛程功能
总奖牌处理模块(原始数据处理)+公用函数模块(负责判断指令和输出结果)=输出总奖牌功能
2.运行流程如下
2.独到之处
我认为我代码的独到之处是想到把输入的数据源进行预处理这一步,此外是空行的处理,代码的组织都用到了模块组织的思路,像是搭积木一样,清晰明了。
关键代码展示
1.回车的处理
这里最妙的地方就是,我的回车处理。
我把针对每条指令的结果划分为一个模块,模块内部的回车处理是:
if(不是第一行) 输出回车;
输出内容;
这样每条指令两端就都没有回车了
剩下是拼接指令模块为一整个文件,同样的方法
if(不是第一个指令的模块) 输出回车;
输出内容;
这样文件两端就都没有回车了
就免去了删除文件最后一个多出来的回车的痛苦
//函数名:beginSearch(char* a,char* b)
//作用:开始搜索
//返回:是否搜索成功
//参数:a命令输入的文件地址,b命令输出的文件地址
bool beginSearch(char* a,char* b)
{
ifstream in(a, ios::in);
ofstream out(b, ios::out);
int row = 0;
string order;
if (!in.is_open())//打开文件错误报告
{
cout << "Error opening file" << a << "\n";
return false;
}
while (getline(in, order))
{
trim(order);//去除多余空格
if (order.compare("") == 0) continue;//跳过空行
if (row) out << endl;//为每一个单元之间增加回车
row++;
if (outres(out, judge(order)) == 0)//执行并判断是否命令输出出错
{
cout << row << ":此行文件命令输出出错(行数计算不包括空行)\n";
cout << "错误内容输出如下:" << order << endl;
}
}
in.close();
out.close();
return 1;
}
2.输入每日赛程模块
这里是新学到的json处理。输入奖牌模块和这个函数处理类似,就不单独放出来了
/***************************输出每日赛程模块********************************/
//函数名:storeDaily
//作用:把每日赛程中的有用数据取出,储存到本地json,作为结果库
//返回:是否成功存储
bool storeDaily()
{
for (int i = 2; i <= 20; i++) //从02号到20号的数据取出并输出
{
Reader reader;//用来读原json文件
Value root;
string path;
//寻找路径
if (i < 10) path = daypath + "020" + std::to_string(i);
else path = daypath + "02" + std::to_string(i);
ifstream in(path + ".json", ios::in);
ofstream out(path + "res.json", ios::out);
if (!in.is_open())//打开文件错误报告
{
cout << "Error opening file" + daypath + "\n";
return false;
}
if (reader.parse(in, root))
{
//读取数组信息
string time;//startdatecn
string sport;
string name;
string battle;
string venue;
for (unsigned int i = 0; i < root["data"]["matchList"].size(); i++)
{
if (i) out << "\n";
time = root["data"]["matchList"][i]["startdatecn"].asString().substr(11, 5);
out << "time:" << time << '\n';
sport = root["data"]["matchList"][i]["itemcodename"].asString();
out << "sport:" << sport << '\n';
name = root["data"]["matchList"][i]["title"].asString();
battle = root["data"]["matchList"][i]["homename"].asString() + "VS" + root["data"]["matchList"][i]["awayname"].asString();
if (battle != "VS") name += " " + battle;
out << "name:" << name << '\n';
venue = root["data"]["matchList"][i]["venuename"].asString();
out << "venue:" << venue << '\n';
out << "-----";
}
}
else//读取json错误报告
{
cout << "parse error\n" << endl;
return false;
}
in.close();
out.close();
}
return true;//成功读取
}
3.对输入结果的判断
先判断出可产生的结果只有四种类型,然后想到用int代码来代表不同结果
//函数名:judge(order)
//作用:判定命令结果
//返回:命令类型 -1:error错误指令、-2:无法识别的日期、正数:输出日期结果、0:输出奖牌榜
//参数:命令order
int judge(string order)
{
//日期比较复杂,先判断日期
if (order.compare("schedule") == 0) return -2;//处理掉单独"schedule"的输入
string o = order.substr(0, 9);
if (o.compare("schedule ") == 0)
{
if (obeyschedule(order)) return ((int)(order[11] - '0') * 10 + order[12] - '0');
else return -2;
}
else
{
if (order.compare("total") == 0) return 0;
return -1;
}
}
性能改进
最原始的阶段,代码运行速度惨不忍睹:
进入上文说的第一阶段,优化了一下,把原始数据处理了一下,优化了一些些:
如果来得及,如果有下次,或许可以完成一下我画的饼(之前写的模块优化的三个阶段)
单元测试
这是我的测试样例,我把它们编入txt文件然后输出结果,然后收集同学运行完之后的结果进行比对,为此我还特意写了个test.cpp用于比对输出结果和发出文件不相同的提示,顺便帮同学发现了bug,当然也发现了自己的小bug并且进行了修正.很有成就感,说不定下次还能用上。
total
schedule 0218
schedule 0201
schedule 0201 ww
to tal
schedule 2224422
123436
1111 1111 1111
schedule0202
total
schedule 0233
to tal
异常处理
总览全局,基本上大部分都是文件打开会出错,所以我对每一个文件打开都进行了错误检测,一旦发现打开失败,立即停止接下来的操作,并且马上报告错误退出函数。
- n多个文件异常处理
- 读取json文件错误
- 命令行输入错误
心得体会
- 总的来说,就是痛苦,非常痛苦,兵荒马乱,bug百出,为什么呢?大概是那句老话“行百里者半九十”,总觉得代码思路都有,问题不大,但是没想到花了很多时间改bug,还有和第一次使用的git code磨合,学习jsoncpp库的导入,本地visual studio的配置,每一项都让我头大,不过幸好,连熬了几个夜,最后还是做完了。
- 其实我是个对自己要求很宽松的人,但是没想到在我如此宽松的估计下,测试和debug的时间居然超出了估计很多,果然预留充足的时间很重要,不然ddl之前狂debug很痛苦。
- 一定要认真的读题目并且向助教问清楚具体的输入输出格式,不能想当然,否则就会出现我这样,费心构思编写了一个自认为精彩而且跑得快的读取方法,但是因为和测评机最终的判定方式不同而失去作用。
- .git管理源代码。管理很重要,这次就遇到了后续代码出错的情况,于是回溯了以前的版本,真的有很大帮助,避免了出现错误前功尽弃,重新编写
- 分解任务,任务变成小块更好处理,而且有目标感也不容易放弃