Linux中國

ZOMBIES:為什麼簡潔性是交付健壯軟體的關鍵(五)

當你堅持最簡場景時,你最終會得到最簡單的解決方案。

在前面的文章中,我已經解釋了為什麼將編程問題看作一整群喪屍來處理是錯誤的。我用 ZOMBIES 方法來解釋為什麼循序漸進地處理問題更好。

ZOMBIES 表示以下首字母縮寫:

  • Z – 最簡場景(Zero)
  • O – 單元素場景(One)
  • M – 多元素場景(Many or more complex)
  • B – 邊界行為(Boundary behaviors)
  • I – 介面定義(Interface definition)
  • E – 處理特殊行為(Exercise exceptional behavior)
  • S – 簡單場景用簡單的解決方案(Simple scenarios, simple solutions)

在系列的前四篇文章中,我展示了 ZOMBIES 方法的前六個原則(LCTT譯註:原文為前五個,應為筆誤)。

第一篇中 實現了最簡場景,它為代碼提供了最簡可行路徑。第二篇文章中執行了 單元素場景和多元素場景上的測試。第三篇中介紹了 邊界和介面。 第四篇中處理 特殊行為。在本文中,我將介紹最後一項:簡單場景用簡單的解決方案。

簡單場景用簡單的解決方案

回顧這個網購 API 的實現過程,你會發現總是有目的性地堅持考慮最簡單的場景。在這個過程中,最終你會得到最簡單的解決方案。

ZOMBIES 方法通過堅持簡潔性來幫助你交付健壯優雅的解決方案。

勝利了嗎?

似乎一切工作都結束了,一個不那麼認真負責的工程師很可能會宣布勝利。但開明的工程師總是會探索得更深一點。

我一直推薦做 變異測試 mutation testing 。在圓滿結束這個練習項目,開始新的征程之前,用變異測試來敲打敲打你的解決方案是明智的。況且你也不得不承認,變異很適合對付喪屍的。

你可以在開源網站 Stryker.NET 上進行變異測試

Mutation testing

看起來有一個存活的變異體。這可不是好兆頭。

這意味著什麼呢?當你自認為解決方案無懈可擊的時候,Stryker.NET 卻表示在你的地盤上並非一切安好。

讓我們來看看這個存活下來的煩人變異體:

Surviving mutant

變異測試工具將

if(total > 500.00) {

變異為:

if(total >= 500.00) {

然後運行測試,結果對於這個變化沒有一個測試失敗。如果業務處理代碼中發生了一處變動卻沒有任何一個測試失敗,這就意味著你遇到一個存活的變異體。

為什麼要在意變異體

為什麼存活的變異體是麻煩的徵兆呢?因為你寫的業務處理邏輯代碼控制著整個系統的行為。如果業務處理邏輯改變,系統的行為也應該隨之改變。而系統行為的改變應該會導致測試表示的期望被違反。如果沒有期望被違反,那就說明這些期望對系統行為的描述還不夠準確。這也意味著你的業務處理邏輯中存在漏洞。

要解決這個問題,你需要幹掉這個存活下來的變異體。要怎麼做呢?一般來說,有存活的變異體意味著有期望被遺漏了。

仔細檢查代碼,梳理已定義的期望,看看漏掉了什麼:

  • 期望 0:新建購物框里有零個商品(這隱含了總價為 ¥0)。
  • 期望 1:向購物框添加一件商品的結果是購物框里有一件商品,如果這件商品的價格是 ¥10,那麼總價為 ¥10。
  • 期望 2:向購物框添里加入一件價值 ¥10 的商品,再加入一件價值 ¥20 的商品,總價是 ¥30 。
  • 期望 3:關於從購物框移除商品的期望。
  • 期望 4:總價大於 ¥500 時打,享受九折優惠。

缺了什麼呢?根據變異測試報告,你沒有定義訂單總價剛好為 ¥500 的銷售策略。你已經定義訂單總額大於 ¥500 和小於 ¥500 時的情況。

定義邊界情況的期望:

[Fact]
public void Add2ItemsTotal500GrandTotal500() {
    var expectedGrandTotal = 500.00;
    var actualGrandTotal = 450;
    Assert.Equal(expectedGrandTotal, actualGrandTotal);
}

第一步先寫一個假的實現讓測試失敗。現在共有 9 個微測試。其中 8 個通過,第 9 個失敗了:

[xUnit.net 00:00:00.57] tests.UnitTest1.Add2ItemsTotal500GrandTotal500 [FAIL]
  X tests.UnitTest1.Add2ItemsTotal500GrandTotal500 [2ms]
  Error Message:
   Assert.Equal() Failure
Expected: 500
Actual: 450
[...]
Test Run Failed.
Total tests: 9
     Passed: 8
     Failed: 1
 Total time: 1.5920 Seconds

將硬編碼值替換成正樣例的預期代碼:

[Fact]
public void Add2ItemsTotal500GrandTotal500() {
    var expectedGrandTotal = 500.00;
    Hashtable item1 = new Hashtable();
    item1.Add("0001", 400.00);
    shoppingAPI.AddItem(item1);
    Hashtable item2 = new Hashtable();
    item2.Add("0002", 100.00);
    shoppingAPI.AddItem(item2);
    var actualGrandTotal = shoppingAPI.CalculateGrandTotal(); }

共添加了兩件商品,一件價值 ¥400,另一件價值 ¥100,總價是 ¥500。調用計算總價的函數,期望的總價是 ¥500。

運行,9 個測試全部通過!

Total tests: 9
     Passed: 9
     Failed: 0
 Total time: 1.0440 Seconds

現在是揭曉真相的時刻。這個新增的期望能夠清理掉所有的變異體嗎?運行變異測試來看看結果:

Mutation testing success

成功了!10 個變異體全都被幹掉了。太棒了!現在你可以放心地發布這個 API 了。

結語

如果從這次練習中有什麼收穫的話,那就是 技術性拖延 skillful procrastination 這一概念的提出。這個是一個重要的概念,因為我們中的許多人往往會在客戶描述完他們的問題之前,就盲目地去設想解決方案。

主動拖延

拖延對於軟體工程師來說並不是一件容易的事情。我們總是急於動手寫代碼。我們熟悉各種設計模式、反模式、編程原則和現成的解決方案。我們總是迫不及待想將它們應用到可執行的代碼中,並且傾向於一次性做大量的工作。所以在行動之前仔細考慮每個步驟是一種美德。

這個練習說明 ZOMBIES 方法能夠通過一系列深思熟慮的小步驟來幫你實現解決方案。但是有一點需要特別注意,根據 Yagni 原則,這些深思熟慮常常會飛得太遠,以至於最終形成一個大而全的系統。這會產生臃腫、緊密耦合的系統。

迭代式開發與增量式開發

在這次練習給我們的另一個重要收穫是讓我們意識到保持系統持續可用的唯一方法是採用迭代式的開發方法。你通過改造已有代碼開發出整個購物 API。這種改造工作是在迭代優化解決方案的過程中不可避免的。

很多團隊混淆了迭代和增量。這是兩個完全不同的概念。

增量式方法是假設是你有完整清晰的需求,然後通過增量累加的方式來構建解方案。大體上來說,你一點點地構建各個部分,然後將所有的部分組裝在一起。大功告成!解決方案已經準備好交付了!

相比之下,迭代式方法中,你並不很確定自己需要交付給客戶的是什麼。因為這個原因,你更加小心謹慎。你會小心翼翼地避免破壞能夠運行的系統(也就是說系統處於穩態)。如果不得不擾動已有的平衡,你也會採取最小侵入性的方式。你專註於用儘可能小的工作量來快速完成每次得改動工作。你傾向於讓系統儘快回到穩態。

這就是為什麼迭代式方法在真正實現一個功能之前總是先提供一個假實現。你硬編碼一系列的期望,以驗證小的修改不會導致系統無法運行。然後進行必要的修改,用實際處理代碼替換硬編碼的值。

作為經驗法則,在迭代方法中,你的目標是通過設計期望(微測試),對代碼進行不斷改進。每進行一次改進,你都要檢驗系統,以確保它處於工作狀態。以這種方式不斷前進,最終會達到滿足所有期望的程度,並且此時代碼已經被重構,沒有任何存活的變異體了。

一旦達到了這種狀態,你就可以相當自信地交付解決方案了。

由衷感謝 Kent BeckRon JeffriesGeePaw Hill 在我的軟體工程學習道路的啟發。

願 ZOMBIES 方法在軟體開發的征程上幫助到你。

(題圖:MJ/ca463fc6-021b-4818-ba3d-9cd3c8736577)

via: https://opensource.com/article/21/2/simplicity

作者:Alex Bunardzic 選題:lkxed 譯者:toknow-gh 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國