《敏捷軟體開發:原則、模式與實踐》筆記(2)
- 2019 年 12 月 23 日
- 筆記
第六章 一次編程實踐
原文保齡球規則:(文末)
https://www.twblogs.net/a/5b957acb2b717750bda47bd5/zh-cn/
原文需求:
記錄一屆保齡球聯賽的所有比賽,確定團隊等級,確定每次周賽優勝者和失敗者,每場比賽成績
初步分析數據結構:
- 計分數據
record { uint32 id primary auto_increase, uint8 round0_0, uint8 round0_1, uint8 round1_0, uint8 round1_1, … uint8 round9_0, uint8 round9_1, uint8 round10_0, uint8 round10_1, uint8 round10_2, }
123456789101112131415 |
record { uint32 id primary auto_increase, uint8 round0_0, uint8 round0_1, uint8 round1_0, uint8 round1_1, … uint8 round9_0, uint8 round9_1, uint8 round10_0, uint8 round10_1, uint8 round10_2,} |
---|
不存儲最終該輪得分,該輪得分由函數提供計算,防止冗餘數據和出現數據衝突。
- 團隊數據
team { uint32 id primary auto_increase, string name, string religin, uint32 level }
1234567 |
team { uint32 id primary auto_increase, string name, string religin, uint32 level} |
---|
- 比賽數據
match { uint32 id primary auto_increase, uint64 time, uint32 team_a, uint32 team_b, uint32 record_a_id, uint32 record_b_id, uint32 winner_id, }
12345678910 |
match { uint32 id primary auto_increase, uint64 time, uint32 team_a, uint32 team_b, uint32 record_a_id, uint32 record_b_id, uint32 winner_id,} |
---|
winner_id 稍微考慮了一下 2 隊比賽和多隊比賽的可能性(不熟悉規則),以及後期搜索數據的效率。所以不使用 bool 類型。
通過比賽 id 可以構建比賽的三角形淘汰圖。
疑問:是否需要計算中的輪數的分值?為了用戶體驗,默認需要。
初步分析程式碼:
public class Score { public static final int ROUNDS = 10; public static final int FULL_HITS = 10; public static final int TEN_ROUNDS_THROWS = 20; public static final int TOTAL_THROWS = TEN_ROUNDS_THROWS + 1; // 10 輪計分 private int[] scores = new int[ROUNDS]; // 如果是全中輪,則第二輪直接賦值 0,將特殊情況普通化。第十輪可能扔 3 次,所以一共 21 次。 private int[] throws = new int[TOTAL_THROWS]; public void currentRound = 0; public void currentThrowIndex = 0; // 用於友好標記不再變化的分數 public void determinedScoreRound = -1; public void throw(int hits) { if (!isPlaying()) { throw new IllegalStateException("it is ended"); } if (hits < 0 || hits > FULL_HITS) { throw new IllegalStateException("illegal throws score"); } boolean isRoundEnd = updateThrowsAndRounds(); if (isRoundEnd) { updateScores(); } } private boolean updateThrowsAndRounds() { throws[currentThrowIndex++] = throws; if (throws == FULL_HITS) { if (isAllFullHits() || isAllOneShot()) { if (currentThrowIndex == TOTAL_THROWS) { currentRound++; return true; } } else { throws[currentThrowIndex++] = 0; currentRound++; return true; } } return false; } private void updateScores() { if (isOneShot(beforeLastRound)) { final int calculateShots = 2; } while (int i = determinedScoreRound + 1; i < currentRound; i++) { if (updateScore(i)) { determinedScoreRound = i; } } } /** * @return boolean is the score determined **/ private boolean updateScore(int round) { int score = throws[round * 2] + throws[round * 2 + 1]; if (round == 0) { scores[round] = score; return true; } int lastRound = round – 1; score += scores[lastRound]; boolean lastRoundDetermined = determinedScoreRound >= lastRound; int calculateShots = 0; boolean needDeteminedRound = round; if (isOneShot(round)) { int calculateShots = 2; needDeteminedRound = round + 2; } else if (isFullHits(round) { int calculateShots = 1; needDeteminedRound = round + 1; } int nextRound = round + 1; while (calculateShots > 0 && nextRound < currentRound) { score += throws[nextRound * 2]; calculateShots–; if (isOneShot(nextRound)) { nextRound++; continue; } score += throws[nextRound * 2 + 1]; calculateShots–; nextRound++; } scores[round] = score; return lastRoundDetermined && calculateShots = 0; } public void isPlaying() { return currentRound < ROUNDS; } public void getRounds() { return currentRound + 1; } private boolean isOneShot(round) { return throws[round * 2] == FULL_HITS; } private boolean isFullHits(round) { return throws[round * 2] + throws[round * 2 + 1] == FULL_HITS; } // 10 輪補中 private boolean isAllFullHits() { if (currentThrowIndex < TEN_ROUNDS_THROWS) { return false; } for (int i = 0; i < currentRound; i++) { if (!isFullHits(i)) { return false; } } return true; } // 10 輪全中 private boolean isAllOneShot() { if (currentThrowIndex < TEN_ROUNDS_THROWS – 1) { return false; } for (int i = 0; i < currentRound; i++) { if (!isOneShot(i)) { return false; } } return true; } }
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146 |
public class Score { public static final int ROUNDS = 10; public static final int FULL_HITS = 10; public static final int TEN_ROUNDS_THROWS = 20; public static final int TOTAL_THROWS = TEN_ROUNDS_THROWS + 1; // 10 輪計分 private int[] scores = new int[ROUNDS]; // 如果是全中輪,則第二輪直接賦值 0,將特殊情況普通化。第十輪可能扔 3 次,所以一共 21 次。 private int[] throws = new int[TOTAL_THROWS]; public void currentRound = 0; public void currentThrowIndex = 0; // 用於友好標記不再變化的分數 public void determinedScoreRound = -1; public void throw(int hits) { if (!isPlaying()) { throw new IllegalStateException("it is ended"); } if (hits < 0 || hits > FULL_HITS) { throw new IllegalStateException("illegal throws score"); } boolean isRoundEnd = updateThrowsAndRounds(); if (isRoundEnd) { updateScores(); } } private boolean updateThrowsAndRounds() { throws[currentThrowIndex++] = throws; if (throws == FULL_HITS) { if (isAllFullHits() || isAllOneShot()) { if (currentThrowIndex == TOTAL_THROWS) { currentRound++; return true; } } else { throws[currentThrowIndex++] = 0; currentRound++; return true; } } return false; } private void updateScores() { if (isOneShot(beforeLastRound)) { final int calculateShots = 2; } while (int i = determinedScoreRound + 1; i < currentRound; i++) { if (updateScore(i)) { determinedScoreRound = i; } } } /** * @return boolean is the score determined **/ private boolean updateScore(int round) { int score = throws[round * 2] + throws[round * 2 + 1]; if (round == 0) { scores[round] = score; return true; } int lastRound = round – 1; score += scores[lastRound]; boolean lastRoundDetermined = determinedScoreRound >= lastRound; int calculateShots = 0; boolean needDeteminedRound = round; if (isOneShot(round)) { int calculateShots = 2; needDeteminedRound = round + 2; } else if (isFullHits(round) { int calculateShots = 1; needDeteminedRound = round + 1; } int nextRound = round + 1; while (calculateShots > 0 && nextRound < currentRound) { score += throws[nextRound * 2]; calculateShots–; if (isOneShot(nextRound)) { nextRound++; continue; } score += throws[nextRound * 2 + 1]; calculateShots–; nextRound++; } scores[round] = score; return lastRoundDetermined && calculateShots = 0; } public void isPlaying() { return currentRound < ROUNDS; } public void getRounds() { return currentRound + 1; } private boolean isOneShot(round) { return throws[round * 2] == FULL_HITS; } private boolean isFullHits(round) { return throws[round * 2] + throws[round * 2 + 1] == FULL_HITS; } // 10 輪補中 private boolean isAllFullHits() { if (currentThrowIndex < TEN_ROUNDS_THROWS) { return false; } for (int i = 0; i < currentRound; i++) { if (!isFullHits(i)) { return false; } } return true; } // 10 輪全中 private boolean isAllOneShot() { if (currentThrowIndex < TEN_ROUNDS_THROWS – 1) { return false; } for (int i = 0; i < currentRound; i++) { if (!isOneShot(i)) { return false; } } return true; }} |
---|
閱讀原文
做出思考後開始看文章。
首先發現文章一開始提出了 Frame 和 Throw 的概念,而我的程式碼跳躍性的直接用 int 和 int[] 作為表示。儘管文中也討論了是否需要這兩個對象,但我覺得確實對象化確實是應對複雜軟體的良好解決辦法。
到了文章中部,他們也用到了 21 和 currentThrow,currentFrame 這兩幾概念,但很快被質疑了,因為他們不易理解。而不易理解意味著難讀懂,更意味著程式容易出錯。
同樣文中的 scoreForFrame 和我的 updateScore 功能相似。但他們一開始就想到這樣設計,因為他們是測試驅動的,或者說是使用用例驅動的。而我是在編寫的最後發現原有辦法(每次 throw 更新幾個 round 的值)難以編寫才想出來的。
文中沒有 scores 數組,取值由函數代替。這符合盡量簡單的原則,依照他們的思路,確實也不需要這個。我現在覺得我這個 scores 數組也非常累贅。
文中先考慮一般情況,再考慮特殊情況,這也是正確的。我在實現一般情況的時候總是會想特殊情況,並將其兼容,這樣不利於一個正常流程的實現。
文中的程式性能較差,因為每次獲取分數都要從 0 算起,但也減少了很多沒必要的變數,例如我的 determinedScoreRound。再說,這程式需要考慮性能嗎?
文中程式碼再持續不斷的被重構。每次增加新功能和修改程式碼,都會重新跑一次測試用例。這非常舒服。
文中目前貌似沒有處理全中和補中要投多一次的情況?測試用例只覆蓋了分數,沒有輪數。
不太贊同為了獨立 handleSecondThrow 把好幾個局部變數變成全局變數。不過後面的重構也優化了一些,也許先移出去簡化結構也是一種好的辦法。但 ball 這個臨時狀態變數還是存在。
ball 也被移到一個計算分數的類 Scorer 去了。
文中最後否定了 Frame 和 Throw 這兩個類,增加了 Scorer 類。文中倡導從 Game 開始設計,即自上而下設計。
文中通過限制輪數最大為 11 來處理多投一次的情況,超過 11 輪還是等於 11 輪。是否允許多投 1 或 2 次取決於輸入(裁判)。
文中提到,大意:增加各種類來提高軟體通用性不等於易於維護(需求變更),易於理解才時易於維護的。
版權所有,轉載請註明出處: https://sickworm.com/?p=1683