寒假作业 疫情统计(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错误的行数无法覆盖,小部分二选一的分支无法覆盖。
8、git仓库链接、代码规范链接
9、心路历程和收获
最大的感受就是,由于第一次接触单元测试和覆盖率,来回折腾了很久,尤其是对IDE没有很好支持的人来说。
(jetbrains全家桶真的太强了...)
由于自己英语很烂,看很多插件和库的教程也是迷迷糊糊,希望自己能更好提高探索未知技术的能力。
然后是撰写博客和完备的测试,这对初入茅庐的我略有陌生,但是它对养成良好的代码习惯和反思自己大有裨益。
最后是习惯于使用github非常重要,完善的项目管理功能可以帮助我们专注于代码本身,而不必花心思在版本迭代、云端备份等鸡零狗碎的东西上。
10、技术路线图相关的5个仓库
新冠肺炎地图:涉及很多地图相关应用,以及很多eCharts特性的实践
vue移动端耦合度更高的demo:基于移动端的vue项目,不过少部分特性已经过时
聊天室:简易的websocket的demo,熟悉websocket-client的相关API
新冠肺炎数据:对丁香园网站的新冠肺炎数据爬取,并用eCharts进行汇总展示
es6全面的教程:但还是要结合实际,比如generator用的人实在太少了