软工实践寒假作业(2/2)

这个作业属于哪个课程 2020春|S班(福州大学)
这个作业要求在哪里 寒假作业(2/2)
这个作业的目标 1.学习使用github
2.制定代码规范
3.开发一个疫情统计程序
4.给出此次作业的PSP表格
5.进行单元测试和覆盖率测试
6.阅读《构建之法》
7.本次的心路历程和收获
作业正文 1.Github仓库地址
2.PSP表格
3.解题思路
4.设计实现过程
5.代码说明
6.单元测试
7.单元覆盖率优化和性能测试
8.代码规范
9.心路历程与收获
10.学习路线相关的5个仓库
其他参考文献
PS:C++实现



Github仓库地址

Github仓库地址:https://github.com/Cazenove/InfectStatistic-main



PSP表格

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



解题思路

读完题目,我最先考虑的是使用什么数据结构,我创建了一个结构体用于保存省份的疫情数据,然后使用STL的map来建立结构体与省份名称的映射关系。这样,在接下来的操作中就可以直接通过省份名称来访问信息数据。设计完数据结构,根据作业需求中的程序使用方法来看,程序运行的流程主要有五个步骤:1.解析参数 -> 2.读取日志文件 -> 3.统计信息 -> 4.输出文件

虽然可以着手开始写代码,但是在没有事前准备的情况下直接开始写,那就与以往无异了,于是我继续阅读了作业中的其他要求。

参考《腾讯c++代码规范》编写了代码规范,并初步设计了程序模块、类图、数据结构、算法流程等部分。



设计实现过程

模块结构图与总流程图


代码组织

省份类

/***********************
Description:存放某省份的数据以及对数据的操作
***********************/
class CProvince
{
    public:
    unsigned int ip;//确诊患者数量
    unsigned int sp;//疑似患者数量
    unsigned int cure;//治愈患者数量
    unsigned int dead;//死亡患者数量
    bool isInLog;//是否在日志文件中出现过
    bool isPrint;//该省份是否要打印
    
    CProvince();//初始化
    
    void AddIP(int num);//感染患者增加
    void AddSP(int num);//疑似患者增加
    void MoveIP(string strProvince,int num);//感染患者流入其他省
    void MoveSP(string strProvince,int num);//疑似患者流入其他省
    void IPtoDead(int num);//感染患者死亡
    void IPtoCure(int num);//感染患者治愈
    void SPtoIP(int num);//疑似患者确诊为感染患者
    void SubSP(int num);//排除疑似患者
};

命令参数处理函数

void ProcessOption(int argc,char *argv[]);//解析参数值
void list(string logPath, string outPath, string date, vector<string> type, vector<string> province);//通过解析的参数值调用相应操作

文件操作函数

bool isLogFile(string fileName);//通过文件名判断是否是日期文件
void ReadAllLog(string path, string date);//读取文件目录
void ReadLog(string filePath);//读取并解析文件
void OutLog(string filePath, vector<string> type, vector<string> province);//输出文件

关键函数流程图

ProcessOption函数:用于解析参数值

ReadLog函数:用于读取和处理文件



代码说明

主要数据结构:

map<string,CProvince> mapProvince;

思路:通过map建立string与CProvince(上文有给出类结构)之间的映射关系,后续可直接通过省份名称来访问类。

初始化函数:

static string PROVINCENAME[35] = 
{
    "全国","安徽","澳门","北京","重庆","福建","甘肃",
    "广东","广西","贵州","海南","河北","河南","黑龙江",
    "湖北","湖南","吉林","江苏","江西","辽宁","内蒙古",
    "宁夏","青海","山东","山西","陕西","上海","四川",
    "台湾","天津","西藏","香港","新疆","云南","浙江"
};
void init()//初始化
{
    mapListValue["-log"] = logValue;
    mapListValue["-out"] = outValue;;
    mapListValue["-date"] = dateValue;
    mapListValue["-type"] = typeValue;
    mapListValue["-province"] = provinceValue;

    mapDataValue["ip"] = ipValue;
    mapDataValue["sp"] = spValue;
    mapDataValue["cure"] = cureValue;
    mapDataValue["dead"] = deadValue;

    mapProvince.clear();//清空map
    string strProvince;
    for(int i=0; i<35; i++)
    {
        CProvince cprovince;
        mapProvince[PROVINCENAME[i]] = cprovince;//建立省份名称与信息存储结构的映射关系
    }
}

思路:初始化其实是本程序非常重要的一个环节,我事先用string数组将省份的顺序存入了程序中,在初始化的过程中,省份名称以及省份类的映射关系已经全部建立好了,后续的文件输出操作与这一步息息相关。由于C++不支持string类型的switch,前面的mapListValue和mapDataValue用于建立string和枚举类型的对应关系,用于将后面的if else结构转变为switch结构。

文件输出函数:

void OutLog(string filePath, vector<string> type, vector<string> province)
{
    ofstream ofLog(filePath.c_str(),ios::out);//创建并写入新的日志文件
    if(!ofLog)
    {
        cout<<filePath<<" open failure.\n";
        exit(0);
    }
    for(int i=0; i<35; i++)//按照省份列表的顺序输出每个省份的数据信息
    {
        //指定打印参数中出现过的省份,若参数为空,输出日志文件中出现过的省份
        if((mapProvince[PROVINCENAME[i]].isPrint) || (!province.size() && mapProvince[PROVINCENAME[i]].isInLog))
        {
            ofLog<<PROVINCENAME[i]<<" ";
            for(int j=0; j<type.size(); j++)
            {
                switch(mapDataValue[type[j]])
                {
                    case ipValue:
                        ofLog<<"感染患者"<<mapProvince[PROVINCENAME[i]].ip<<"人 ";
                        break;
                    case spValue:
                        ofLog<<"疑似患者"<<mapProvince[PROVINCENAME[i]].sp<<"人 ";
                        break;
                    case cureValue:
                        ofLog<<"治愈"<<mapProvince[PROVINCENAME[i]].cure<<"人 ";
                        break;
                    case deadValue:
                        ofLog<<"死亡"<<mapProvince[PROVINCENAME[i]].dead<<"人 ";
                        break;
                    default:
                        break;
                }
            }
            ofLog<<"\n";
        }
    }
    ofLog<<"// 该文档并非真实数据,仅供测试使用";
    ofLog.close();
}

思路:遍历所有的省份,在-province参数中出现的参数值isPrint将被标记为true,在日志文件中操作过的参数值isInLog将被标记为true;当-province有参数值,则输出isPrint为true的省份,否则,输出isInLog为true的省份。而在ip、sp、cure、dead的输出顺序中,我将这几个参数值存入了一个vector中,当没有参数值时,vector存入默认顺序的参数,当有参数值时,则存放给定的顺序。在输出时,根据vector的参数顺序来进行文件输出。


单元测试

使用Visual Studio 2017的Microsoft CppUnitTest框架编写的C++测试单元。
测试有返回值的函数:

测试无返回值的函数:

使用相同的方法,编写了十组UnitTest

完整运行结果:



(PS:下方命令注释由于存放路径差异与样例有一定出入,但是是符合逻辑的。)



单元测试覆盖率优化和性能测试

单元测试覆盖率优化


经过覆盖率检验,发现部分方法的覆盖率较低,可能是因为没有覆盖到一些错误处理的分支,于是改善了一下单元测试。

覆盖率得以提高,剩下一部分未覆盖的是异常处理情况。Visual Studio的测试似乎无法检测exit()。

性能测试

CPU使用率:



代码规范

https://github.com/Cazenove/InfectStatistic-main/blob/master/221701210/codestyle.md



心路历程与收获

在阅读《构建之法》和完成本次作业的过程中,我获益良多。我认真思考了一个问题:怎样才算一个优秀的程序员?因为我个人是高中NOIP开始接触编程,所以我个人的理解是能写出巧妙的代码,但是在学习《构建之法》后,我发现这个观点是有问题的。编程能力诚然是程序员最为重要的能力之一,但我们的课程叫做“软件工程”,“软件工程是把系统的、有序的、可量化的方法应用到软件的开发、运营和维护上
的过程。”,工程能力也是程序员的重要能力,在编程比赛中获奖的选手,可能写的代码只有自己看得懂,如果在项目中以这种方式写代码,那将很难与他人进行协作,一个人可能可以完成中小型项目,但大型项目一定是需要协作的,更不用说项目周期长了,自己都不知道之前的自己写了什么。软件工程之所以叫做工程,正是因为它与传统的土木工程等一样,有了系统、有序、可量化的方法。
这一次的作业算是让我们体验了一套完整的项目开发周期,以往做大作业、比赛项目的时候,由于开发规模较小(还有懒),往往没有先进行设计和完成后续的单元测试、覆盖率测试等,这导致了软件后期优化难以进行。在这次作业里,起初我以为代码是这次作业里面最难的部分,但是后来发现,单元测试才是最麻烦的。我在覆盖率测试的时候遇到了问题,Visual Studio默认的测试项目覆盖率一直有问题,用了google test也将一些与项目无关的东西计入覆盖率导致覆盖率奇低,由于大部分同学都是使用java,与我的测试工具不太相同,我尝试了许多方法换了许多工具都无法解决,经过一天多的研究,我才最终解决,我把解决方法写在了博客里:Visual Studio C++覆盖率测试异常的解决方法
我想,这也是为什么会要求我们写博客的原因。我在解决问题的时候,多么希望我能看到这样一篇博客。俗话说好记性不如烂笔头,未来的我如果忘记这个问题怎么解决,也可以查看以前写的记录。



学习路线相关的5个仓库

ActionRPGGame

https://github.com/iniside/ActionRPGGame
简介:一个动作RPG的游戏Demo,主要是为了演示作者用UE4做的一个RPG游戏框架。

MySQLConnectorUE4Plugin

https://github.com/KhArtNJava/MySQLConnectorUE4Plugin
简介:一个UE4插件,可以使UE4连接到MySQL数据库,支持C++和蓝图方式。在虚幻引擎4.13版本开发,支持32位和64位

UE4_DarkSoulsCamera

https://github.com/donanroherty/UE4_DarkSoulsCamera
简介:在虚幻4引擎中实现的黑魂风格相机,它支持锁定目标,在可用目标之间滚动,摄像机相对角色移动以及可选的软锁定系统,该系统可自动管理锁定范围内最接近的目标。项目是用C++构建的,带有一些用于动画树的蓝图逻辑。

GameSavePlugin

https://github.com/marshal-it/GameSavePlugin
简介:虚幻4的游戏保存插件,能够保存数据对象类,目前所有操作接口都已经默认在UGameplayStatics。

sphinx-ue4

https://github.com/shanecolb/sphinx-ue4
简介:Sphinx-UE4是用于虚幻引擎4的语音识别插件,使用了Pocketsphinx库。

posted @ 2020-02-17 15:34  Cazenove  阅读(293)  评论(0编辑  收藏  举报