如何提升 Web 應用的程式碼質量

Phodal發表於2018-06-13

“我只是修改了一個 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-stagedprettier 來進行程式碼格式化:

  "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 應用。

結論

開發速度和質量,是一個難以平衡的天平。在不同的時間裡,我們應該做不同的技術決策。

相關文章