zoukankan      html  css  js  c++  java
  • 软工实践寒假作业(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编程指导,最近在研究离屏渲染优化有关的东西,这个正好是我需要的

  • 相关阅读:
    cf914D. Bash and a Tough Math Puzzle(线段树)
    RNQOJ [stupid]愚蠢的矿工(树形依赖背包)
    BZOJ4552: [Tjoi2016&Heoi2016]排序(线段树 二分)
    多项式系数学习笔记
    BZOJ4653: [Noi2016]区间(线段树 双指针)
    洛谷P3372 【模板】线段树 1(树状数组)
    BZOJ3261: 最大异或和(可持久化trie树)
    BZOJ4260: Codechef REBXOR (01Tire树)
    Android 关于显示键盘,布局错乱网上顶的问题
    Java 输入流读取文本文件换行符问题
  • 原文地址:https://www.cnblogs.com/vegetablefriend/p/12342661.html
Copyright © 2011-2022 走看看