TL;DR
Code Review 速查手冊
參考資料
https://composity.com/post/too-busy-to-improve
https://commadot.com/wtf-per-minute/
https://dl.acm.org/doi/10.1145/3585004#d1e372
https://google.github.io/eng-practices/review/reviewer/standard.html
https://book.douban.com/subject/35513153/
https://zhuanlan.zhihu.com/p/549019453
名詞解釋
CR: Code Review
CR:程式碼審查
CL: Stands for "changelist", which means one self-contained change that has been submitted to version control or which is undergoing code review. Other organizations often call this a "change", "patch", or "pull-request".
CL:代表“變更列表”,表示已提交到版本控制或正在進行程式碼審查的獨立更改。其他組織通常將其稱為“更改”、“補丁”或“拉取請求”。
LGTM: Means "Looks Good to Me". It is what a code reviewer says when approving a CL.
LGTM:意思是“對我來說看起來不錯”。這是程式碼審查員在批准 CL 時所說的。
CR意義
靈魂拷問:為什麼我們接手的每個程式碼庫都如此難以維護?
重要原因之一:Code Review 執行不徹底
萬能藉口:太忙!
-
疲於應付眼前
-
不可見,意識不到
-
CR 非功能性開發
-
CR 不是當務之急,沒有眼前收益
-
危害被低估,忽視“複利”的威力(非線性)
意義
現代程式碼評審【modern Code Review】,業內認為有效的、基於最佳實踐的質量保證工作流,可透過人工審程式碼降低風險、增強可維護性和提升研發效率,同時可以有效提升個人和團隊技術能力。更是一種對程式碼精益求精、追求極致的態度、是“工匠精神”的一種體現。
CR原則
-
只要CL可以提高整體程式碼健康狀態,就應該傾向於批准合入,即使CL並不完美
-
基於技術事實和資料的溝通
-
基於技術事實和資料否決個人偏好和意見
-
軟體設計問題不能簡單歸結為個人偏好
-
-
解決衝突:不要因為無法達成一致而卡殼
-
善用工具
-
基於Lint、公司程式碼規範等工具
-
大模型輔助
-
發起CR
發起前的準備
-
推薦自己做一個 checklist
-
把自己當作 Reviewer 來對自己的程式碼進行 CR
-
預估程式碼可能出問題的地方
-
進行充分自測,保證程式碼可執行
-
不要指望別人幫你找出問題
checklist 可參考Code Review 速查手冊
利用自動化工具進行前置檢查
-
單元測試檢查
-
新增單元測試檢查
-
方法行數過多
-
圈複雜度過高
-
程式碼規範檢查
-
lint 檢查
-
體積監控
建議平均時長不要超過10分鐘, 所以 e2e,效能檢查等建議不阻塞發起MR流程
合理的規模
https://smartbear.com/learn/code-review/best-practices-for-peer-code-review/
-
一次評審200LOC為佳,最多400LOC
-
評審量應低於500LOC/小時
-
一次評審不要超過60分鐘
-
採用輕量級評審方式
-
全員參與程式碼評審
-
每週花費0.5~1天開展CR
-
嚴格且徹底的評審
如何拆分 CL
https://google.github.io/eng-practices/review/developer/small-cls.html
Commit 描述
Bad Case:
“修復錯誤”是不充分的 CL 描述。什麼 bug?你做了什麼來修復它?
其他類似的不良描述包括:
-
邏輯修復
-
新增補丁
-
增加XX規則
-
刪除XX邏輯
Good Case:
◆ 摘要:【xx模組】新增xx功能
◆ 背景: 新功能需求,要求xxx, 詳見[卡片連結]
◆ 說明: 由於xx,新增xx處理模組…
-
摘要:刪除 RPC 伺服器訊息自由列表的大小限制
-
說明:像 FizzBuzz 這樣的伺服器有非常大的訊息,可以從重用中受益。使自由列表更大,並新增一個 goroutine 隨著時間的推移慢慢釋放自由列表條目,以便空閒伺服器最終釋放所有自由列表條目。
必要時,應使用 cz 等工具進行規範。
心態
-
一次 CR 其實是開啟的一次“對話”
-
應該期待評論和反饋,並及時進行回覆
-
做好心理準備自己的程式碼可能會有缺陷
-
CR 的目的之一就是發現問題, 所以不要有牴觸
CR內容
程式碼是寫給人看的, 不是寫給機器看的,只是順便計算機可以執行而已。------《計算機程式的構造和解釋》
應該被 CR 的內容:
自上而下,優先順序從高到低:
模組 | 簡介 |
---|---|
設計 | 程式碼是否設計良好並適合您的系統? |
功能 | 程式碼的行為是否符合作者的預期?程式碼的行為方式對其使用者有好處? |
安全性 | 程式碼是否安全合規? |
複雜性 | 程式碼可以更簡單嗎?當其他開發人員將來遇到此程式碼時,他們是否能夠輕鬆理解和使用此程式碼? |
測試 | 程式碼是否具有正確且設計良好的自動化測試? |
命名 | 開發人員是否為變數、類、方法等選擇了明確的名稱? |
註釋 | 註釋是否清晰有用? |
風格 | 程式碼是否遵循京東程式碼風格規範? |
文件 | 開發人員是否更新了相關文件? |
https://google.github.io/eng-practices/review/reviewer/looking-for.html
CR流程順序
https://google.github.io/eng-practices/review/reviewer/navigate
京東實際程式碼片段評審講解
設計
應該有合理的職責劃分,合理的封裝
good case
componentDidMount() {
this.fetchUserInfo();
this.fetchCommonInfo();
this.fetchBankDesc();
}
bad case
componentDidMount() {
const { location, dispatch, accountInfoList } = this.props;
const { token, af } = getLocationParams(location) || {};
dispatch({
type: 'zpmUserCenter/fetchUserInfo',
payload: {
token,
},
}).catch(e => {
const zpmOpenAuthUrlLogin = decodeURIComponent(getCookie('zpmOpenAuthUrlLogin'));
// 如果token過期則跳轉回第三方平臺
if ([TOKEN_EXPIRED_CODE, '300000'].includes(e.errorCode) && (isSaveUrl(af) || isSaveUrl(zpmOpenAuthUrlLogin))) {
setTimeout(() => {
window.location.href = isSaveUrl(af) ? af : zpmOpenAuthUrlLogin;
}, 2000);
}
});
if (!this.showWhichHeader() && !this.showGatewayHeader()) {
dispatch({
type: 'zpmUserCenter/fetchAccountInfo',
payload: {
token,
},
});
}
this.getBlackList()
}
問題1,fetchUserInfo 未進行封裝
問題2,af 命名過於隨意
問題3,‘300000’ 魔法字串
問題4,選擇使用 af 或 zpm 這兩個URL的邏輯建議封裝,避免多次呼叫 isSaveUrl
優秀程式碼設計的特質 CLEAN
• Cohesive:內聚的程式碼更容易理解和查詢bug
• Loosely Coupled:松耦合的程式碼讓實體之間的副作用更少,更容易測試、複用、擴充套件
• Encapsulated:封裝良好的程式碼有助於管理複雜度,也更容易修改
• Assertive:自主的程式碼其行為和其所依賴的資料放在一起,不與其它程式碼互相干預(Tell but not Ask)
• Nonredundant:無冗餘的程式碼意味著可以只在一個地方修復bug和進行更改
應避免過度設計
別人在閱讀程式碼時,能清晰辨別我在程式碼中的設計模式,並且能夠隨著這個模式繼續維護
功能
邏輯正確,邏輯合理,避免晦澀難懂的邏輯
bad case:一段表單程式碼(原始碼過長,僅貼出其中典型的一段)
{ hasQuota ? (
['11', '12'].indexOf(invoiceType) === -1 ? (
<div className="m-b-4 row">
<div className="col-11">
<FormItem
label="基礎核驗"
>
{basicVerifyStatus ? '已透過' : <div>
未透過
<Tooltip title={basicVerifyMsg} placement="bottom">
<span className='iconfont icon-tishi theme-primary m-l-1 font-14' />
</Tooltip>
</div>}
</FormItem>
</div>
<div className="col-11">
<FormItem
label="剩餘額度"
>
{formatAmount(availableLimit)}
</FormItem>
</div>
</div>) :
<div className="m-b-4 row">
<div className="col-11">
<FormItem
label="基礎核驗"
>
{basicVerifyStatus ? '已透過' : <div>
未透過
<Tooltip title={basicVerifyMsg} placement="bottom">
<span className='iconfont icon-tishi theme-primary m-l-1 font-14' />
</Tooltip>
</div>}
</FormItem>
</div>
</div>)