“我只是修改了一個 if 條件,沒有想到它導致持續整合失敗,部署也因此失敗,還影響了 QA 測試其它功能。”
Web 應用的質量提升,是一個非常有意思的話題。我們明知道有一系列的手段可以提升程式碼質量,但是限於多種原因,我們並不會去做。在我工作的第一個專案裡,由於大家都是年輕人(Junior Consultant),我們實施了一系列的基礎措施,來提升應用質量,諸如寫測試、追求測試覆蓋率、執行預提交指令碼等等。
在最近的這個專案裡,我們面臨著類似的問題——需要提升專案的程式碼質量。於是,便想寫一篇文章介紹一個相關的內容。這篇文章大致可以分為這幾個部分:
- Web 應用的質量問題
- 使用測試提升質量
- 使用 Lint 和 Git Hooks 檢測程式碼
- 如何防範危險提交
那麼,讓我們繼續回到老生常談的 “Web 應用的質量問題”。
Web 應用的質量問題
Web 應用通常面臨著上線和質量之間的博弈——只要不影響使用者體驗,小的 Bug 往往對於專案來說可以 “容忍” 的。這樣一來說可以早些上線,實現使用者價值。除此還有其開發的影響:一個是敏捷方式的開發週期,一個則是可以多次上線。
故而,Web 開發與一些特殊領域及行業的軟體開發不同,在這些特殊的行業裡,一個開發成本上億的軟體,可能只會執行、部署一次,不會有第二次機會,如原子彈的控制系統。還有一些型別的案例,就是智慧汽車上的自動駕駛系統,稍有不慎就是車損人忙。相當於 Web 應用雖然更新困難,可它們還是能遠端更新的。但是在這些系統上,它們就更追求系統的質量,而不是開發速度。Web 應用部署失敗可以回滾,雖然會帶來一定的錢力損失,但是極少帶來生命危險。
因此在質量和速度方面,在 Web 開發上因此保持著一個微妙的平衡。
可軟體開發不僅僅只有質量和速度的問題,還有一個產品問題——即,能做出符合使用者需求的產品。於是,就變成了質量-速度-需求,一個更復雜的平衡。為了交付出更符合使用者需求的產品,就不得不經常做一些需求變更。而取決於這些變更的時間,它往往會影響到程式碼質量和開發速度——實現一個需求的時間越短,那麼其測試的時間越短,Bug 出現的可能性就更高。過去我遇到過,今晚上線,下午臨時改需求。可想而知,測試人員是沒有時間測試的。
在這個時候,持續整合只能顯式地告訴我們,我們的測試掛了,我們的某些功能 broken 了,我們不應該部署這個新版本。然而並不是持續整合出問題了,我們就不能部署,我們仍然還是能部署的。
Blabla,那麼問題來了,最有效的方式呢?
使用測試提高質量
用於保證這個專案的質量,在程式碼提交之後,會經過一系列的測試:
- 單元測試
- 自動化 UI 測試
- 開發人員手動進行整合測試
- 測試人員進行 3~4 輪的測試
如果只是巨集觀來看一個專案的測試的話,那麼在一個敏捷專案裡,測試可以分為這麼幾個階段:
- Dev (開發)環境的 Desk Check,主要用於演示功能是否和需求一致。
- QA (測試)環境的測試,用於進行一些常規的測試。
- ST (System Test)環境的測試,常用於與第三方系統聯調。當出現第三方系統的時候,就需要該環境來整合。
- UAT(User Acceptance Test,使用者接受測試)環境的測試,通常用由業務方的程式碼來驗收產品是否符合需求。
如果一個 Web 應用能經過這麼一系列的測試,那麼它的質量在一定程度上是得到保障的。所以,開發人員如果不想活得太久,就可以 “不負責任” 地直接把功能扔給測試人員。笑~
可是,在有一些公司時吧,Bug 率可是會影響績效的,又或者是有這麼多 Bug 看上去不那麼專業,等等 blabla。
言而總之,總而言之,開發人員自己寫測試會更友好一些。按照測試金字塔理論來說,我們需要三種型別的測試:
- 單元測試,用於保證我們的基礎函式是正常、正確工作的。
- 服務測試,不僅僅自身的服務,也會測試第三方依賴服務。
- UI 測試,模仿使用者操作行為的測試。
對於一個前端專案來說,我們通常只需要兩種:單元測試
和 E2E 測試
。實際上,理論上應該還有 UI 元件的測試
,但是一般而言,我們在選用 UI 元件的時候,會考慮到元件的穩定性。
可是在多數國內公司裡,寫測試往往是不可能的。退而求次,我們就需要一種更簡單而友好的方式,來做這樣的事情。
使用 Lint 和 Git Hooks 檢測程式碼
在程式碼提交之前,我們還可以進行一些常見的操作:
- 靜態程式碼分析(lint),用於進行靜態程式碼分析,常見的如 Lint4j、TSLint、ESLint。
- 執行測試,為了不影響持續整合,我們需要在程式碼提交之前進行測試。
現代的編輯器(使用相應外掛)、IDE 可以提高很好的技術手段,在開發的過程中靜態程式碼分析,並隨時提高建議。如 Intellij IDEA 和 WebStorm 就會根據 TSLint,來提醒開發者 TypeScript 程式碼的一些規範問題。
這些分析工具主要進行一些程式碼上的分析,如《全棧應用開發:精益實踐》一書所說,一般會進行如下一系列的風格檢測:
- 規範函式名及變數
- 程式碼格式規範
- 限制語言特性
- 函式行數限制
- 多重巢狀限制
- 未使用程式碼
- 等等
而這些規範,如果沒有強制,那就是個遊戲。於是,我們通常會依賴於 Git Hooks 來做這樣的事。對於一個使用 Git 來管理原始碼的專案來說,Git Hooks 可以做這麼一些事情,可以在 .git/hooks
目錄下檢視:
applypatch-msg post-merge pre-auto-gc prepare-commit-msg
commit-msg post-receive pre-commit push-to-checkout
post-applypatch post-rewrite pre-push update
post-checkout post-update pre-rebase
post-commit pre-applypatch pre-receive
複製程式碼
一般而言,我們只會在兩個階段做相應的事情:
pre-commit
,預本地提交。通常會在該提交之前,進行一些語法和 lint 的檢測。pre-push
,預遠端提交。通常會在該提交之前,執行一些測試。
於是,在我們的這個前端專案裡,我們就又寫了這兩個 scripts
。對應的實現如下:
{
"precommit": "lint-staged",
"prepush": "ng test && ng build --prod"
}
複製程式碼
在 precommit
時,我們配合 lint-staged
和 prettier
來進行程式碼格式化:
"lint-staged": {
"src/app/*.{css,scss}": [
"stylelint --syntax=scss",
"prettier --parser --write",
"git add"
],
"{src,test}/**/*.ts": [
"prettier --write --single-quote",
"git add"
]
}
複製程式碼
事實上,使用 ng lint --fix
也是一個不錯的方式。
隨後,我們在 push 程式碼之前,即 prepush
,進行了測試及 Angular 的構建 production 的指令碼。由於單元測試執行得相當的快,它可以在幾分鐘內完成,快速對問題做出響應。而不是等到持續整合出問題時,再去修復。
但是 Git 提高了這一種的種選項,也提供了一個 --no-verify
的引數。它可以讓開發者不需要進行上面的驗證,就能提交程式碼。
我們往往無法阻止別人做這樣的事情,特別是當出現多個團隊協作的時候。
難以防範的危險提交
原本,我想將標題取為 “有風險的提交”,但是我覺得危險的提交更為可靠。
常見的有要去吃飯了、要下班了、要開會了等等,臨走前提交了一下程式碼。功能可能本身沒有問題,但是它 block 後續的一系列行為。
當然了出現不可坑的因素,如地震、火災等的時候,就不需要考慮這些事情了。
只是有了這些規範和實踐,可以幫助我們開發出更穩定的 Web 應用。
結論
開發速度和質量,是一個難以平衡的天平。在不同的時間裡,我們應該做不同的技術決策。