前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >《敏捷软件开发:原则、模式与实践》笔记(2)

《敏捷软件开发:原则、模式与实践》笔记(2)

作者头像
sickworm
发布2019-12-23 11:02:13
3180
发布2019-12-23 11:02:13
举报
文章被收录于专栏:sickworm

第六章 一次编程实践

原文保龄球规则:(文末)

https://www.twblogs.net/a/5b957acb2b717750bda47bd5/zh-cn/

原文需求:

记录一届保龄球联赛的所有比赛,确定团队等级,确定每次周赛优胜者和失败者,每场比赛成绩

初步分析数据结构:

  1. 计分数据

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,}

不存储最终该轮得分,该轮得分由函数提供计算,防止冗余数据和出现数据冲突。

  1. 团队数据

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}

  1. 比赛数据

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 intROUNDS; // 如果是全中轮,则第二轮直接赋值 0,将特殊情况普通化。第十轮可能扔 3 次,所以一共 21 次。 private int[] throws = new intTOTAL_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() { throwscurrentThrowIndex++ = throws; if (throws == FULL_HITS) { if (isAllFullHits() || isAllOneShot()) { if (currentThrowIndex == TOTAL_THROWS) { currentRound++; return true; } } else { throwscurrentThrowIndex++ = 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 = throwsround * 2 + throwsround * 2 + 1; if (round == 0) { scoresround = score; return true; } int lastRound = round - 1; score += scoreslastRound; 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 += throwsnextRound * 2; calculateShots--; if (isOneShot(nextRound)) { nextRound++; continue; } score += throwsnextRound * 2 + 1; calculateShots--; nextRound++; } scoresround = score; return lastRoundDetermined && calculateShots = 0; } public void isPlaying() { return currentRound < ROUNDS; } public void getRounds() { return currentRound + 1; } private boolean isOneShot(round) { return throwsround * 2 == FULL_HITS; } private boolean isFullHits(round) { return throwsround * 2 + throwsround * 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 intROUNDS;    // 如果是全中轮,则第二轮直接赋值 0,将特殊情况普通化。第十轮可能扔 3 次,所以一共 21 次。    private int[] throws = new intTOTAL_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() {        throwscurrentThrowIndex++ = throws;        if (throws == FULL_HITS) {            if (isAllFullHits() || isAllOneShot()) {                if (currentThrowIndex == TOTAL_THROWS) {                    currentRound++;                    return true;                }            } else {                throwscurrentThrowIndex++ = 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 = throwsround * 2 + throwsround * 2 + 1;        if (round == 0) {            scoresround = score;            return true;        }         int lastRound = round - 1;        score += scoreslastRound;        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 += throwsnextRound * 2;            calculateShots--;             if (isOneShot(nextRound)) {                nextRound++;                continue;            }             score += throwsnextRound * 2 + 1;            calculateShots--;            nextRound++;        }         scoresround = score;        return lastRoundDetermined && calculateShots = 0;    }     public void isPlaying() {        return currentRound < ROUNDS;    }     public void getRounds() {        return currentRound + 1;    }     private boolean isOneShot(round) {        return throwsround * 2 == FULL_HITS;    }     private boolean isFullHits(round) {        return throwsround * 2 + throwsround * 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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019年3月10日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 第六章 一次编程实践
    • 原文保龄球规则:(文末)
      • 原文需求:
        • 初步分析数据结构:
          • 初步分析代码:
            • 阅读原文
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档