软工实践寒假作业(2/2)
这个作业属于哪个课程 | https://edu.cnblogs.com/campus/fzu/2020SPRINGS |
---|---|
这个作业要求在哪里 | https://edu.cnblogs.com/campus/fzu/2020SPRINGS/homework/10287 |
这个作业的目标 | 学习《构建之法》开发一个小项目、学习Git的使用、学习单元测试和性能分析等相关知识 |
作业正文 | https://www.cnblogs.com/vegetablefriend/p/12342661.html |
其他参考文献 | 《构建之法》 |
本次作业的Repository | https://github.com/VegetableFriend/InfectStatistic-main |
我的代码规范 | https://github.com/VegetableFriend/InfectStatistic-main/blob/master/example/codestyle.md |
对《构建之法》前三章的学习及PSP表格
对软件开发的学习从一开始的“玩具阶段”逐步提高到一个成熟项目阶段,这是一个必经之路。要想成为一个成熟的项目,首先要充分的进行单元测试,
要考虑到多种情况,毕竟程序crash和bug对用户体验的影响是极大的。其次要进行效能分析,效能好、效率高的软件在实际使用中将会更受青睐。要想成为
一名合格的软件工程师,首先要认清自己的技术水平,不要盲目自大眼高手低,Talking is easy,show me the code.也不要产生一些思维上的误区。
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 20 |
Estimate | 估计这个任务需要多少时间 | 10 | 10 |
Development | 开发 | 60 | 60 |
Analysis | 需求分析 (包括学习新技术) | 10 | 10 |
Design Spec | 生成设计文档 | 10 | 10 |
Design Review | 设计复审 | 60 | 30 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 60 | 30 |
Design | 具体设计 | 60 | 30 |
Coding | 具体编码 | 360 | 600 |
Code Review | 代码复审 | 60 | 120 |
Test | 测试(自我测试,修改代码,提交修改) | 120 | 240 |
Reporting | 报告 | 60 | 60 |
Test Repor | 测试报告 | 60 | 60 |
Size Measurement | 计算工作量 | 15 | 10 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 30 | 60 |
合计 | 975 | 1330 |
解题思路
首先提取需求
类似一个输入数据处理后输出结果的过程
要有文件操作
可以根据参数自定义输入输出的内容
控制台实现
总结的差不多了,可以开始考虑考虑代码怎么写了
代码的组织和流程
代码总体工作的流程图如下:
对于代码的组织,我的做法是代码分为好多模块,每个模块各司其职。
BaseData类中存入了该程序需要的全部数据,需要时可以从该类中获取
Tool类中就是一些工具代码,如读写文件、字符串处理等
Application类为应用主体,里面的四个方法调用即为流程图的几个模块如下
//MARK: 程序运行主类
class Application {
public:
static void run(int argc, char* argv[]) {
if (!Tool::getParameters(argc, argv)) {
cout << "参数不合法!" << endl;
return;
}
BaseData::init();
Tool::readDataFromFiles();
Tool::outputResult();
}
};
剩余的部分即为一些抽象的数据模型类,如InfectInfo疫情信息类、Action行为枚举等
enum Action {
///增加确诊
increase_sure,
///增加疑似
increase_doubt,
///减少确诊
decrease_sure,
///减少疑似
decrease_doubt,
///死亡
dead,
///治愈
cure,
};
///某省疫情信息
class InfectInfo {
public:
///该省名
string province;
///该省是否用到
bool exist = false;
///疑似人数
int doubt_count = 0;
///确诊人数
int sure_count = 0;
///治愈人数
int cure_count = 0;
///死亡人数
int dead_count = 0;
InfectInfo(string province) {
this->province = province;
}
private:
};
部分核心代码
读取、处理参数的代码
///数据、参数预处理 根据返回结果判断是否合法
static bool getParameters(int argc, char* argv[]) {
vector<string> strings;
vector<string> parameters;
///将除了第一个默认参数以外的参数填充到字符串数组
for (int i = 1; i < argc; i++) {
strings.push_back(argv[i]);
}
///读取省份参数
parameters = attributeParameters("-province", strings);
BaseData::provinces = parameters;
if (!validProvince()) {
cout << "省份参数有误" << endl;
return false;
}
///读取类型参数
parameters = attributeParameters("-type", strings);
if (!validType(parameters)) {
cout << "指定类型参数有误";
return false;
}
BaseData::types = transferStringToAction(parameters);
BaseData::origin_path = getDocumentPath(strings, "-log");
BaseData::target_path = getDocumentPath(strings, "-out");
///读取日期参数
parameters = attributeParameters("-date", strings);
if (parameters.size() == 0) {
getAllFiles(BaseData::origin_path, BaseData::files);
}
else {
if (!validDate(parameters[0])) {
cout << "日期不合法" << endl;
return false;
}
setFilesWillBeRead(parameters[0]);
}
///全部合法 成功返回
return true;
}
读取文件数据的字符串,并以行为单位传入处理函数
///逐行根据-date 参数读入数据,并写入键值映射表
static void readDataFromFiles() {
ifstream input_stream;
string data;
for (int i = 0; i < BaseData::files.size(); i++) {
input_stream.open(BaseData::files[i]);
while (getline(input_stream, data)) {
if (data.find("//") != -1) break;
getInfoFromString(data);
}
input_stream.close();
}
}
字符串处理函数
///根据传入的字符串 读取信息 并将其添加到映射表
static void getInfoFromString(string data) {
int first_index = 0;
int space_index = data.find_first_of(' ');
///此处获取第一个省份
string province = data.substr(first_index, space_index);
data = data.substr(space_index + 1, data.size());
///注册该省信息
BaseData::setExist(province);
///此处获取该行信息的人数
int last_space = data.find_last_of(' ');
int person_index = data.find_first_of("人");
string count_string = data.substr(last_space + 1, person_index - last_space);
int count = stoi(count_string);
data = data.substr(0, last_space);
///此处获取该省对应的动作 action
Action action;
space_index = data.find_first_of(' ');
string op = data.substr(0, space_index);
///根据获得的情况来作出对应的操作
if (op == "死亡") {
action = dead;
}
else if (op == "治愈") {
action = cure;
}
else if (op == "疑似患者") {
///首先执行一次疑似患者减少的操作
action = decrease_doubt;
BaseData::setAction(province, action, count);![](https://img2018.cnblogs.com/blog/1927218/202002/1927218-20200221214036742-765784393.png)
///再判断是确诊了 还是迁出了
last_space = data.find_last_of(' ');
string str = data.substr(last_space + 1);
if (str == "确诊感染") {
action = increase_sure;
}
else {
action = increase_doubt;
province = str;
}
}
else if (op == "排除") {
action = decrease_doubt;
}
else if (op == "新增") {
last_space = data.find_last_of(' ');
string str = data.substr(last_space + 1);
if (str == "感染患者") {
action = increase_sure;
}
else {
action = increase_doubt;
}
}
else if (op == "感染患者") {
action = decrease_sure;
BaseData::setAction(province, action, count);
last_space = data.find_last_of(' ');
string str = data.substr(last_space + 1);
action = increase_sure;
province = str;
}
BaseData::setAction(province, action, count);
}
根据处理函数的处理结果,设置对应的操作
static void setAction(string province, Action action, int count) {
for (int i = 0; i < BaseData::meta_data.size(); i++) {
if (BaseData::meta_data[i].province == province) {
switch (action) {
case dead:
BaseData::meta_data[i].dead_count += count;
BaseData::meta_data[i].sure_count -= count;
break;
case cure:
BaseData::meta_data[i].cure_count += count;
BaseData::meta_data[i].sure_count -= count;
break;
case decrease_sure:
BaseData::meta_data[i].sure_count -= count;
break;
case decrease_doubt:
BaseData::meta_data[i].doubt_count -= count;
break;
case increase_sure:
BaseData::meta_data[i].sure_count += count;
break;
case increase_doubt:
BaseData::meta_data[i].doubt_count += count;
break;
}
}
}
}
单元测试
代码写完了,一个很重要的工作就是进行测试,何为单元测试?我的理解就是分单元分模块进行测试,以对象的方法作为粒度。我刚好分出了几大功能模块,编写几个单元测试如下:
//MARK: 程序运行主类
class Application {
public:
static void run(int argc, char* argv[]) {
if (!Tool::getParameters(argc, argv)) {
cout << "参数不合法!" << endl;
return;
}
BaseData::init();
///单元测试代码
///对字符串处理功能的测试
//Tool::getInfoFromString("湖北 疑似患者 流入 福建 3人");
//Tool::getInfoFromString("福建 新增 感染患者 2人");
//Tool::getInfoFromString("福建 新增 疑似患者 5人");
//Tool::getInfoFromString("湖北 感染患者 流入 福建 2人");
//Tool::getInfoFromString("湖北 死亡 1人");
//Tool::getInfoFromString("湖北 治愈 2人");
///对参数提取功能的测试
// cout << "----------provinces" << endl;
// for(int i = 0 ; i < BaseData::provinces ; i++) {
// cout << BaseData::provinces[i];
// }
// cout << "----------files" << endl;
// for(int i = 0 ; i < BaseData::files ; i++) {
// cout << BaseData::files[i] << endl;;
// }
// cout << "----------origin path" << endl;
// cout << BaseData::origin_path << endl;
// cout << "----------target_path" << endl;
// cout << BaseData::target_path << endl;
// cout << "----------types" << endl;
// for(int i = 0 ; i < BaseData::types ; i++) {
// cout << BaseData::types[i] << endl;;
// }
///对参数合法性验证的测试
//当输入不合法的测试用例时,只需检测是否输出参数不合法即可
//当输入合法的测试用例时,不能显示不合法。
///正常运行代码 对程序整体功能进行评测
//Tool::readDataFromFiles();
//Tool::outputResult();
}
};
运行结果:
一下就发现了一个小bug...疑似患者为0-3 = -3 没有问题,问题出在福建的数据丢失了。第二个省也要标记为能够打印出来。
改好以后进行测试,福建的数据顺利的打印了出来,运行结果如下:
剩余的对字符串提取的结果均正常,不再重复展示。
接下来是对参数的测试,随着输入命令的变化,输出结果也不同,结果如下
最后是对参数合法性的校验,结果如下
这些都是错误的示范,可见都对应的报了错
各个“单元”运行正常,可以脱离编译环境进行完整的测试了。测试的几个结果如下:
不带日期对其余两参数进行测试
带日期对其余两参数进行测试
测试结果全部正确。
与文件相关的测试也已全部通过。包括替换不同文件夹以及不同磁盘等。
测试覆盖率分析性能优化相关
测试覆盖率的话由于时间问题没办法完全的覆盖到,这是我的问题。没能控制好时间。
性能优化问题,我尝试着使用了VS2017的Profile Tools进行性能监控,结果如下:
对CPU的分析很好理解,随着程序运行调用栈肯定是不断被push的,然后随着运行结束,内存使用也趋于0
以下是对时间的分析
有很多看不懂的东西...能看懂的里面最占用时间的是fclose操作,对这一块的学习还不够深入,有时间的话会尝试进行优化。
这次作业我的心路历程与收获
害,这次作业搞得我真的头皮发麻,有点难顶,时间非常紧张同时也感慨自己水平如此之低,做这么个小的项目都费这么大劲,属实有种眼高手低的味道。
软件工程并非是一门简单的学科,我对自己说。里面的学问可太大了,不仅仅是会敲个代码这么简单,还要再多加强学习。
通过这次作业,我收获到了许多东西,一个就如上面所说,“软件工程”绝非易事。再一个就是学到了有些时候需求不仅仅是完成这么简单,
还需要做充分的性能优化以提高使用体验,最后就是单元测试,大一C语言老师说的话我至今仍记得,不会debug就不要做程序员,所以单元测试的编写时非常有必要的,
而单元测试的粒度也要做把控,粒度太大测试覆盖率不高,而粒度太小反而加大了测试难度。
总而言之,通过这次作业,我认识到了自己的真实水平,以后在开发过程中一定会规划好时间。软件开发之路任重而道远。
将要学习的几个优秀开源Repository
1.https://github.com/SDWebImage/SDWebImage
基于Swift的SDWebImage库,实现异步的图片下载、图片缓存等功能。其中用到的一些策略和方法值得学习。
2.https://github.com/CyC2018/CS-Notes
技术面试的一些知识总结,留作以后复习使用。
3.https://github.com/opensource-apple/objc4
基于C/C++写的Obj-C开源代码,Apple公司提供,有时间可以研究一下。
4.https://github.com/Alamofire/Alamofire
基于Swift的网络请求库,里面的一些对RunLoop的处理值得学习,是我未知的领域
5.https://github.com/qunten/iOS-Core-Animation-Advanced-Techniques
iOS Core-Animation编程指导,最近在研究离屏渲染优化有关的东西,这个正好是我需要的