寒假作业 疫情统计(2/2)

1、前言

这个作业属于哪个课程 班级链接
这个作业要求在哪里 作业要求
这个作业的目标 使用github、规范代码
作业正文 作业正文
其他参考文献 暂无

2、PSP

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

3、思路描述

![]()

4、实现过程

![]()

5、代码说明

这里是代码地址,有详细注释

命令行参数与统计结果的数据结构如下:



代码仅由以下三部分构成,共130余行:

1.获得命令行参数的代码:

if(argv[0]!='list') throw new Error('仅能接受list命令!')
   argv.slice(1).forEach(v => {
      var checked = false  //checked为true,说明该参数是一个以-为前缀的key
      for (item of Object.keys(CmdParam))
         if (v == '-' + item) {
            reading = item
            checked = true
            break
         }
      !checked && CmdParam[reading].push(v) //统计键值对
   })
   if(!fs.existsSync(CmdParam.log[0])) throw new Error('输入的日志目录不存在!')
   if (fs.readdirSync(CmdParam.log[0]).sort().reverse()[0].split('.')[0] < CmdParam.date[0])
      throw new Error('日期超出范围!(-date不会提供在日志最晚一天后的日期)');
   if (!CmdParam.date[0]) 
      return fs.readdirSync(CmdParam.log[0]).sort().reverse()[0].split('.')[0].split('-') //最大日期
   else //指定了日期
      return CmdParam.date[0].split('-')

2.匹配文件内容的代码:

   data = data.split('\n')   //分行,data是单个文件内的全部数据
   data.forEach((line) => {
      var res = []
      function match(reg, hasTwoPvovinces) { //匹配正则,清洗数据以及初始化
         if (/\/\//.test(line)) return false //如果是注释,忽略掉
         var res = reg.exec(line)
         if (!res) return
         for (key of Object.keys(Total)) { //初始化数据数字为Number类型,防止NaN
            if (!Total[key][res[1]]) { //非undefined类型代表已经初始化过
               provinces.add(res[1])  //顺便收集唯一省份
               Total[key][res[1]] = 0
            }
            if (hasTwoPvovinces && !Total[key][res[2]]) { //同上,并且判断要收集第三项数据
               provinces.add(res[2])  //顺便收集唯一省份
               Total[key][res[2]] = 0
            }
         }
         return res.slice(1, 4) //返回正则匹配的组内容
      }
      if (res = match(/(\S{2,3})\s新增 感染患者 (\d*)人/g))  //正则并收集数据
         Total.ip[res[0]] += Number(res[1])
      if (res = match(/(\S{2,3})\s新增 疑似患者 (\d*)人/g))
         Total.sp[res[0]] += Number(res[1])
      if (res = match(/(\S{2,3})\s疑似患者 流入 (\S{2,3})\s(\d*)人/g, true)) {
         Total.sp[res[0]] -= Number(res[2])
         Total.sp[res[1]] += Number(res[2])
      }
      if (res = match(/(\S{2,3})\s感染患者 流入 (\S{2,3})\s(\d*)人/g, true)) {
         Total.ip[res[0]] -= Number(res[2])
         Total.ip[res[1]] += Number(res[2])
      }
      if (res = match(/(\S{2,3})\s死亡 (\d*)人/g)) {
         Total.ip[res[0]] -= Number(res[1])
         Total.dead[res[0]] += Number(res[1])
      }
      if (res = match(/(\S{2,3})\s治愈 (\d*)人/g)) {
         Total.cure[res[0]] += Number(res[1])
         Total.ip[res[0]] -= Number(res[1])
      }
      if (res = match(/(\S{2,3})\s疑似患者 确诊感染 (\d*)人/g)) {
         Total.ip[res[0]] += Number(res[1])
         Total.sp[res[0]] -= Number(res[1])
      }
      if (res = match(/(\S{2,3})\s排除 疑似患者 (\d*)人/g))
         Total.sp[res[0]] -= Number(res[1])
   })

3.整理数据并输出的代码:

var provincesSorted = []
   for (let item of Provinces.keys()) { //提取集合里的省份
      provincesSorted.push(item)
   }
   provincesSorted = provincesSorted.sort((a, b) => { //进行汉字拼音排序
      return prior.indexOf(a) - prior.indexOf(b)
   })
   for (let item of Object.keys(Total)) {  //统计'全国'的数据
      let sum = 0
      for (let num of Object.values(Total[item]))
         sum += num
      Total[item]['全国'] = sum
   }
   provincesSorted.unshift('全国') //确保‘全国’一定在其他省份的前面
   if (!CmdParam.type.length) CmdParam.type = ['ip', 'sp', 'cure', 'dead'] // 不指定-type即为输出全部四项
   provincesSorted.forEach((v) => {
      if (!(CmdParam.province.length == 0 || CmdParam.province.includes(v))) return //筛选-province省份
      article += `${v}`   //开始处理输出数据的附加项-type
      if (CmdParam.type.includes('ip')) article += ` 感染患者${Total.ip[v]}人`
      if (CmdParam.type.includes('sp')) article += ` 疑似患者${Total.sp[v]}人`
      if (CmdParam.type.includes('cure')) article += ` 治愈${Total.cure[v]}人`
      if (CmdParam.type.includes('dead')) article += ` 死亡${Total.dead[v]}人`
      article += `\n`
   })
   article += `// 该文档并非真实数据,仅供测试使用\n// 命令:node InfectStatistic ${cmd}`
   fs.writeFileSync(CmdParam.out[0], article, 'utf-8')  //最后写入文件

6、单元测试截图和描述

1.正确性测试






2.日志记录混合

测试结果

3.非法时间测试

最晚日志日期的当天,可以正常输出



最晚日志日期的下一天,抛出错误


4.同时限定省份和类型


5.不提供日期参数,则设置为日志最大日期

6.mocha与chai单元测试框架结果


7、覆盖率优化和性能测试,性能优化截图和描述

尴尬的地方来了,
最强的覆盖率工具Istanbul无法传入命令行额外参数,这就使其永远无法覆盖到占比不小的参数处理部分,
覆盖率也就失去了意义...
折腾了很久也没能找到合适的替代品(也许,几乎没有人会在命令行node里传额外参数吧)
所以我只能,把命令行参数处理部分换掉,如下:

分析:抛出Error错误的行数无法覆盖,小部分二选一的分支无法覆盖。

减少代码耦合会使代码更简洁易懂、后期维护成本低,但也会略微损失性能,比方说用迭代器遍历对象的键。 性能优化方面,在采取“正则预编译”、“手动列省份顺序来替换汉字排序localeCompare()”的措施后,耗时明显降低。 ![](https://img2018.cnblogs.com/blog/1918331/202002/1918331-20200214181125789-844964291.png) ![](https://img2018.cnblogs.com/blog/1918331/202002/1918331-20200214181132612-639554039.png) ![](https://img2018.cnblogs.com/blog/1918331/202002/1918331-20200214181152453-399772363.png) 待完善的部分:代码风格的面向对象部分不足;改为异步读写文件会提高效率,但由于处理顺序的限制,理论上讲性能提升很有限。

8、git仓库链接、代码规范链接

仓库链接
代码规范链接

9、心路历程和收获

最大的感受就是,由于第一次接触单元测试和覆盖率,来回折腾了很久,尤其是对IDE没有很好支持的人来说。
(jetbrains全家桶真的太强了...)
由于自己英语很烂,看很多插件和库的教程也是迷迷糊糊,希望自己能更好提高探索未知技术的能力。
然后是撰写博客和完备的测试,这对初入茅庐的我略有陌生,但是它对养成良好的代码习惯和反思自己大有裨益。
最后是习惯于使用github非常重要,完善的项目管理功能可以帮助我们专注于代码本身,而不必花心思在版本迭代、云端备份等鸡零狗碎的东西上。

10、技术路线图相关的5个仓库

新冠肺炎地图:涉及很多地图相关应用,以及很多eCharts特性的实践
vue移动端耦合度更高的demo:基于移动端的vue项目,不过少部分特性已经过时
聊天室:简易的websocket的demo,熟悉websocket-client的相关API
新冠肺炎数据:对丁香园网站的新冠肺炎数据爬取,并用eCharts进行汇总展示
es6全面的教程:但还是要结合实际,比如generator用的人实在太少了

posted @ 2020-02-06 11:38  满小意  阅读(587)  评论(5编辑  收藏  举报