寒假作業 疫情統計(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用的人實在太少了


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM