codereview後的收穫

Abstract

剛進入公司開發了兩塊代碼,遇到了負責的老大,開始了規範的pr和codereview學到了一些東西,和你們分享總結一下。javascript

命名規範和表達問題

這應該算是修改的比較多的問題了,舉幾個比較典型的栗子:java

1. 表達問題

大概是以下的一段拋錯,其實就是在數據庫裏查一個東西沒有查到嘛數據庫

throw new Error('Did not find the package whose ID is 2.')
複製代碼

下面是和大佬的一段對話:(大佬:D,垃圾:L)api

  • D: 你英語是體育老師教的吧。。。哪有你這樣寫的。。。
  • L: 哇!就是個人英語是體育老師教的,我才專門在網上翻譯的。
  • D: 怕是假的翻譯軟件喲。
  • L: 谷歌翻譯有圖有真相!

image

  • D: 你這是語文沒學好(一臉無語,默默貼出他的谷歌翻譯)

image

無論說是英語很差仍是語文很差,其實歸結於本身沒有多思考一下,沒有作到那種精益求精的工匠精神。我相信沒有人能說我那句話錯了,但事實就是它很差。promise

2. API設計規範

// bad
get('/package/package_with_app_version?appid=xxx&version=xxx');
// good
get('/package?appid=xxx&version=xxx');
複製代碼

通過多方查閱,關於api的設計規範,都比較推崇RESTful的API風格。bash

經過HTTP Method來描述動做服務器

Method 對應數據庫行爲 描述
GET SELECT 從服務器取出資源
POST CREATE 在服務器新建一個資源
PUT UPDATE 在服務器更新資源(客戶端提供改變後的完整資源)
PATCH UPDATE 在服務器更新資源(客戶端提供改變的屬性)
DELETE DELETE 從服務器刪除資源

example:網絡

// 獲取包列表
get('/package');
// 獲取ID是12的包
get('/package/12');
// 嚴格遵照RESTFUL的話,應該是經過這兩個參數篩選返回列表
// 但我以爲在業務裏不太合適,因而根據業務改成,直接返回對應的包
get('/package?appid=xxx&version=xxx');
// 若是想要嚴格遵照規範的話應該寫成這樣
get('/app/:appid/package/:version')
// 指定第幾頁,以及每頁的記錄數。
get('/package?page=2&per_page=100');
// 更新包,客戶端提供包的全部信息,相似於替換
put('/package/:id');
// 更新包,客戶端提供須要更新的信息
patch('/package/:id');
// 刪除包
delete('/package/:id');
// 建立包
post('/package');
複製代碼

更規範的設計API,能夠一目瞭然的瞭解這個API將會發生的動做。app

代碼錯誤

1. 在讀取文件拼接buffer的時候出現的問題

下面是錯誤的寫法,大佬看了一眼說不對,讓改。鬱悶死我了,我花了好長時間去試驗最終證實了,個人寫法在解析的時候確實會出現亂碼。貼代碼異步

const fs = require('fs');
let rs = fs.createReadStream('./Zn-utf8.txt');
let body = '';
rs.on("data", (chunk) => {
  body += chunk;
});
rs.on("end", () => {
  console.log(body);
});
複製代碼

image

真的有亂碼了!

錯誤: 在這裏真的出現亂碼了,下面來分析一下緣由。

一個英文字符佔用一個字節,而一個漢字佔用三個字節。當文件較大須要進行切割的時候。

假設:每17個字符進行一次切割,在讀取

'一個漢字佔用三個字節的時候'
==>
'一(1,2,3)個(4,5,6)漢(7,8,9)字(10,11,12)三(13,14,15)個(16,17,18)字(19,20,21)節(22,23,24)'
==>
chunk1:'一個漢字三??'+chunk2:'?字節'
複製代碼

而在上面代碼中拼接字符串的時候,出現了隱式的toString(),把不完整的chunk轉成了字符串,致使不完整的漢字不能正確解析出現亂碼。

這就是爲何會出現那三個亂碼的緣由

解決方法:

let body = [];
rs.on('data', (chunk) => {
  body.push(chunk);
});
rs.on('end', () => {
  body = Buffer.concat(body).toString();
  console.log(body);
});
複製代碼

2. 使用定時器檢查狀態

let status = await checkStatus();
if (status === 'success' || status === 'faild') {
  if (status === 'success') {
    console.log('成功!接下來該作啥作啥。')
  } else if (status === 'faild') {
    console.error('失敗!想幹啥都不行。');
  }
} else {
  this.statusTimer = setInterval(async () => {
    status = await checkStatus();
    if (status === 'success' || status === 'faild') {
      clearInterval(this.statusTimer);
      if (status === 'success') {
        console.log('成功!接下來該作啥作啥。')
      } else if (status === 'faild') {
        console.error('失敗!想幹啥都不行。');
      }
    }
  }, 200);
}
複製代碼

大概的邏輯就是,發一個異步請求去檢查狀態checkStatus(),可能會有三個狀態:success、faild和pending;若是是pending狀態,就會開啓一個setInterval,每200毫秒檢查一次,知道產生結果爲止。

聽起來好像沒有什麼問題,我最初也是這樣考慮的,但是結果確出乎個人意料,由於在本地調試,請求時間短到沒有發現這個問題,大佬一眼就看出來了,放一段代碼和運行結果,你們就一目瞭然。

// 概念版的上面那段邏輯
var timer = setInterval(async () => {
	console.log('發送請求以前作的事')
    await new Promise(resolve => setTimeout(resolve, 5000));
    console.log('收到請求以後作的事');
 }, 1000)
複製代碼

image

每一秒執行一次函數,await會阻塞函數後面的內容五秒,用clearInterval清掉定時器,表明收到了想要的結果,再也不執行函數。然而還有五次‘收到請求以後作的事’被輸出出來。

也就是說當網絡狀況很差的時候,我以前的代碼會形成,檢查到狀態是成功之後清調了定時器之後,極可能還有數次函數沒有執行完,會屢次檢查到狀態成功,觸發成功之後乾的事情,形成嚴重後果。

優化方案,藉助遞歸+sleep函數解決問題:

用sleep也只是爲了減小無效的請求,主要解決問題的仍是遞歸。

async function checkStatus() {
  const status = await checkStatus();
  if (status === 'success' || status === 'faild') {
    if (status === 'success') {
      console.log('成功!接下來該作啥作啥。')
    } else if (status === 'faild') {
      console.error('失敗!想幹啥都不行。');
    }
  } else {
    console.log('pending...');
    await new Promise(resolve => setTimeout(resolve, 500));
    await checkStatus();
  }
}
複製代碼

完美解決問題

異常捕獲

關於異常捕獲被老大點了兩次名:

  1. 你的請求都不try...catch一下的,拋錯了怎麼捕獲?
  2. 我改了之後>>>>>>你怎麼處處都在try...catch寫着好玩麼?

因而本身默默研究了一下,什麼地方能捕獲到異常,何時捕獲不到,應該在哪些地方捕獲異常,是否是每個請求都應該try...catch?

下面與你們簡單分享一下很是low的捕獲經驗,我知道有不少更好的捕獲模式,例如我就特別喜歡egg的異常處理,在service層controller層的錯誤都通過包裝,把錯誤拋出來,經過中間件進行統一處理。

  • Q:何時捕獲不到?
  • A:在JS中,對於異步回調函數,trycatch就無能爲力。你捕獲不到,就只有被原生的uncaughtException捕獲到全部沒有被捕獲的錯誤,在這時就不能作合適的錯誤處理了。
  • Q:應該如何捕獲異步函數?
  • A:說白了只要不跳出了異步調用鏈就能捕獲。解決方法有不少例如借用promise將異步的錯誤reject捕獲。或者藉助async await,也能夠輕易的捕獲到異常。
  • Q:是否是全部地方都應該捕獲異常呢?
  • A:答案固然是否是的。這個尺度本身把我,異常會向上冒,在裏層throw出的異常,在往上冒的過程當中就會上層的catch捕獲,因此確定不須要處處都寫。將異常拋出,到你以爲應該輸出錯誤的時候就輸出錯誤,須要對錯誤進行處理的時候再捕獲,這個尺度我可能還須要更多的經驗去感覺。

關於這一塊,整理學習了一些,但確定還遠遠不夠,慢慢積累吧這一塊的經驗。但如今起碼能夠用最少的try...catch,把錯誤都處理了。

學會站在用戶的角度思考問題

關於這一點和代碼無關和程序無關和邏輯無關,就是一個視角問題。

下面是開發過程當中對CLI命令的設計

初版

# 在本地生成
seeder-package [packageName] -o ./dist/package.tgz
# 部署測試環境(自動上線)
seeder-package [packageName] -d ./dist/package.tgz
# 部署生產環境(須要手動上線)
seeder-package [packageName] -d ./dist/package.tgz -e production
# 更新版本號
seeder-package [packageName] --pv major
seeder-package [packageName] --pv minor
seeder-package [packageName] --pv patch
seeder-package [packageName] --pv 3.2.5
# 查看當前版本號
seeder-package [packageName] --pv current
複製代碼

第二版

# 在本地生成
seeder [packageName] -o ./dist/package.tgz
# 把本地指定路徑的包部署到測試環境
seeder [packageName] -d ./dist/package.tgz
# 打包並部署到測試環境
seeder [packageName] -d -o
# 在測試環境拉最新版本的包 部署到生產環境
seeder [packageName] -d --prod
# 更新版本 打包 部署測試 上線
seeder [packageName] -o -d --pv [xxx]
# 更新版本 打包 部署測試 上線 的簡寫
seeder [packageName]
複製代碼

第三版

# 輸出本地文件
seeder [packageName] -o xxx/xxx.tgz
# 打包部署並更新 patch 版本號
seeder [packageName]
# 打包部署並更新指定版本號
seeder [packageName] --pv [xxx]
# 同步到生產
seeder [packageName] -p
複製代碼

三個版本能夠說變化很是的大

初版 ==> 第二版

這一次變化是我本身就想作的,由於在開發過程當中我就感覺到,可用性太差,每次都須要輸入太多東西。總的來講這一次變化就是爲了***讓用戶減小輸入的內容***。

  • 將seeder-package指令 縮短爲 seeder
  • 將須要主動申明部署環境,-e 環境的默認值爲測試環境,只有生產環境的時候才須要設置。
  • 將打包部署兩個命令合併,而且不須要傳後面長長的參數。
  • 相似於 -o -d --pv [xxx]這樣的指令能夠同時完成幾個工做。
  • 作出了簡寫

第二版 ==> 第三版

矛盾和收穫都發生在這一次。


個人觀點: -o 輸出本地文件 -d部署 --prod 描述環境 --pv版本相關操做,一個指令一個動做,很是清晰。要幹什麼事兒就用什麼指令,不該該再簡化了,邏輯很是清晰。 可以省去的輸入項我都已經去掉了。


大佬的觀點: 再簡化,簡化到極致(以至於我認爲會對命令動做描述不清楚),大佬認爲我說的我那樣的清晰的邏輯是針對開發者的,不能站在一個技術的角度,以爲這樣實現起來邏輯清晰。注意了劃重點了 我一直欺騙本身的邏輯清晰原來是本身實現起來邏輯清晰。(恍然大悟)


因而第三次進行了大改造,將命令簡化到了缺一不可。這兩次是對代碼的改動是最大的,但也是我這一次收穫最大的,學會了什麼叫人性化,什麼叫站在用戶的角度思考問題,忘掉本身是一個開發者。

感謝

在過去一直以爲codereview很沒有用,這一次收穫真的挺大的,還有不少偏業務的問題不方便總結出來,還有不少沒有特色的問題也沒有列出來,此次開發還學會了寫測試用例。

敲代碼有時候不是爲了實現需求。更像是打磨工藝品,秉承工匠精神,你的態度決定了你代碼的質量。感謝大佬教給本身的,相信之後也會學到不少,本身的代碼質量也會蹭蹭蹭往上提。


來自一個踏上職場兩週的孩子的感悟

把這些分享給你但願與你一塊兒進步 仍是至關的爽這個代碼敲着就很是的舒服了

相關文章
相關標籤/搜索