Uncle Bob 的保齡球套路

Rex Wang
9 min readFeb 24, 2020

--

在多本書籍闡述測試驅動開發(Test Driven Development,TDD)的好處之後,我又回頭看了Uncle Bob的保齡球套路,深信這個套路就如同武術家練習的拳譜、運動選手的基本動作,或是軍警不斷練習的拔槍與演習動作。那就讓我來整理一下這個套路的準備工作與相關細節吧。

保齡球套路中,須先理解保齡球的規則,說明如下:

  1. 一場保齡球共有十局(frame),每局有十隻球瓶(pin),最高可打兩球,最高可得十分。
  2. 第一球十隻球瓶全倒稱為Strike,第二球才把剩下的打倒稱為Spare。
  3. 每局計分方式為倒下的瓶數,如果有Strike或是Spare可再加上Bonus。
  4. Strike的Bonus為,下兩球倒下的瓶數加總。
  5. Spare的Bonus為,下一球倒下的瓶數。
  6. 第十局沒有Bonus,但Strike可再打兩球 或Spare可再打一球。
  7. 如果十局的第一球皆為全倒,則稱為Perfect Game,滿分為300,總球數為12。

我們的目標要建立一個Game class,並在其中撰寫 roll 與 score兩個公有方法;

Game Class Diagram

另外要寫下五個測試案例 testAllZeros、testAllOnes、testOneSpare、testOneStrike與testPerfectGame。五個情境說明如下:

  1. testAllZeros:十局的20球皆掛零。
  2. testAllOnes:十局的20球皆得一分。
  3. testOneSpare:第一局即發生 Spare。
  4. testOneStrike:第一局即發生 Strike。
  5. testPerfectGame:十局皆為Strike,並可加碼打兩球,這兩球也是 Strike。

根據Uncle Bob的說明,每一條Test Code與production code的編寫大約僅需 30秒,這樣的開發進度是相當驚人的,因此我們需要更多的練習,才能讓基本的問題成為我們直覺的反射動作。

第一步:由 IDE 產生出一個標準的 Maven專案。

第二步:新增一個test case 叫做 testAllZero,其中要 new 一個 Game class,因為缺乏Game class,所以會出現欠缺 class 的錯誤,馬上動手在 src/main/java 下產生一個對應的 Game class,消除 test case 的錯誤。

Test Code
Production Code

第三步:加入一個執行20次的 for 迴圈;在 迴圈中呼叫 Game 裡面的 roll method,由於 Game 裡面還沒有任何 method,再度跳到 Game class 裡新增 可傳入得分數的 roll method,暫時還不需要撰寫 roll 裡面的細節。

Test Code
Production Code

第四步:迴圈結束後,執行一個斷言,其中呼叫Game裡的 score method,取得分數;由於Game 裡還沒有score method,因此再度回到Game裡新增一個 score method,我們暫時先設定回傳值為 -1,執行測試程式,沒有意外的出現錯誤;重新修改 score 回傳值為0,執行測試程式,得到正確的結果。

Test Code
Production Code

第五步:再新增一個 test case 叫做 testAllOnes,內容與 testAllZeros幾乎一樣,這裡我們先複製一份相同的程式碼過來,調整一下,傳送1給roll,斷言裡應該是從 score method取得20;執行測試結果,也沒有意外的得到錯誤,因為我們還沒寫 score的計算內容。

Test Code

第六步:到Game class 裡編寫計算內容,新增一個私有變數score,並在roll 裡累加每個傳入的得分數,而score method 只需回傳私有變數score值。

Production Code

第七步:再到 test case,我們可以得到正確的內容,完成兩個測試情境。

第八步:這裡我們發現測試程式有重複的地方,因此我們開始進行重構;我們把重複的for迴圈部分搬出來,另外建一個 rollMany,分別傳入迴圈上限數與得分數;先移除 testAllZero的 for 迴圈,並以 rollMany取代;確認沒問題之後,再以同樣的方式重構 testAllOnes,並取得正確結果;完成 test case 部分的重構。

Test Code

第九步:我們要開始測試 spare,新增一個 test case testOneSpare,其中先呼叫三次roll,分別傳入3、7、3,表示第二球為spare,第一局的得分數加上第三球的分數,再運用rollMany傳入剩下的球數17與分數0;根據保齡球規則,我們可以斷言score method應該回傳 ((3+7)+3)+3=16;因為還沒有設計這個演算法,自然這個測試不會成功。

Test Code

第十步:我們開始設計這個演算法,但是我們發現原來的設計有些不合理的地方,需要先做重構;為了確保任何重構是正確無誤的,我們需要在調整之前,先把沒有辦法成功使用的testOneSpare mark起來;我們先把每局擊倒的數量記錄下來,所以新增一個全域array,並由roll method負責這個工作;再把計算擊倒瓶數的功能搬到score,也就是在每次呼叫score時,會重新計算正確的分數。我們可以在 score裡面使用一個從0到20的for loop ,但是保齡球的bonus計算方式,將for loop 改成 0到10應該會更適合;期間調整變數名稱,並給賦予更有意義的名稱。再度進行測試,得到正確結果。

Production Code

第十一步:開始加入spare演算法,進行 testOneSpare 驗證,取得正確結果。

Production Code

第十二步:重構score method ,將判斷是否為spare的判斷式重構成一個 private method。

Production Code

第十三步:testOneSpare,新建私有方法將發生spare的兩個 roll遷移過去,並改為呼叫新的方法;重新測試,確認正確無誤方可進到下一步 。

Test Code

第十四步:新增 testOneStrike,先呼叫三次roll,分別傳入10、3、4,再呼叫rollMany,傳入16球與0分;測試時一樣得到錯誤結果。

Test Code

第十五步:加入 strike演算法,進行testOneStrike,得到正確的結果。

Production Code

第十六步:重構 score method ,將 strike 判斷式搬到另一個新建的isStrike method。

Production Code

第十七步:重構 testOneStrike,新建私有方法將幾個 roll遷移過去,並改為呼叫新的方法;重新測試,確認正確無誤。

Test Code

第十八步:重構score method,將幾個計算式用更直覺的方式呈現;新增的private method 有 sumThisFrame、spareBonus、strikeBonus,取代較不直覺的計算;記得在新增與調整每一個計算式時,皆需重新測試,直到正確才可進行下一個計算式調整。

Production Code

第十九步:進行一個極端的狀況測試,也就是十局皆為Strike,如果發生這個狀況,總共應該可以打12球,可得到滿分300;我們新增一個 testPerfectGame,並做測試取得正確結果。

Test Code

以上為Uncle Bob的保齡球套路,其中的架構或是程式碼編排並非完美,而且有非常多步驟是熟練的Developer可以跳過去的,但是這是為了訓練TDD與重構的直覺反應,個人覺得非常有意思也有幫助。有興趣的朋友,也可以以 Bowling Game Kata 作為關鍵字Google其他人的分享。

簡單總結一下 TDD的概念:

1. 沒有測試程式,就不寫產品程式。
2. 只撰寫剛好無法通過的單元測試。
3. 只撰寫剛好能通過當前測試失敗的產品程式。
4. 對待你的 Test Code 要跟 Production Code 一樣
5. 測試才是最好的文件,可以執行的文件
6. 測試應該不只是品管工具,更是設計工具…

參考資料:

泰迪軟體的保齡球套路

https://youtu.be/Om0co6xNNBI

Uncle Bob 的 The Bowling Game Kata

http://butunclebob.com/ArticleS.UncleBob.TheBowlingGameKata

Uncle Bob 的 Clean Coder https://www.tenlong.com.tw/products/9789862017883?list_name=srh

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Rex Wang
Rex Wang

Written by Rex Wang

職業生涯歷經不同身份與角色,應用開發是我無法忘情的工作之一;工作中的經驗與個人的學習,逐漸對於應用開發、專案執行有一些想法與感觸,因此想藉此園地留下紀錄

No responses yet

Write a response