阅读本文后,希望你能够有如下收获:
- 掌握保龄球的计分规则,下次打球不再迷茫。
- 通过保龄球的计分系统端到端业务功能分析,避免陷入细节。
- 通过业务抽象产出一份需求任务列表,从而指导下一步的TDD。
如若与你期望相符,欢迎你继续阅读!
需求背景
请你设计一个计算保龄球比赛一局总得分的程序,保龄球的计分规则如下:
- 每一局总共有十轮,每轮一开始会有十支球瓶,球手可以扔两次球,目标就是用尽量少的球把全部球瓶击倒。
- 如果第一球就把全部的球瓶都击倒了,也就是STRIKE,画面出现“X”,就算完成一轮了,所得分数是10分再加后面两球的倒瓶数。
- 如果第一球没有全倒,就要再打一球,如果第二球将剩下的球瓶全都击倒,也就是SPARE,画面出现“/”,也算完成一格,所得分数为10分再加下一轮第一球的倒瓶数。
- 如果第二球也没有把球瓶全部击倒的话,那分数就是第一球加第二球倒的瓶数,没有奖励(bonus),再接着打下一轮。依此类推。
- 第十轮有机会扔三次球。如果在第十轮出现STRIKE或者SPARE,则球手可再加打第三球。
- 全部十轮的得分相加就等于这一局的总得分。
上述记分牌建议你逐个数一下,能够帮助你理解透彻保龄球的积分规则,打下良好基础。
系统分析
拿到需求后,第一件重要的事情永远都是理解需求,第二件重要的事情是拆分需求。
通读业务描述,分析出系统要解决的核心业务问题是为每一局统计总分,这就是系统将提供面向用户的端到端功能。最终的需求分解始终应该聚焦在这个点上,要避免陷入某一轮的细节中。
每一局游戏又分为10轮,最终的总分是根据一局比赛中每轮中每次击倒的瓶数,按照一定的业务与规则计算出来。所以,为了统计每局结束后的总分,系统还要能够记录每次击倒瓶数,从而为统计总得分提供数据。
到此,我们识别出系统需要提供的核心功能是统计总分,还需要支撑的功能是能够记录每次击球数。
需求拆分
从需求描述中得到信息,每一轮中的倒瓶数的规则会对总分产生不同的影响,对于某一轮,有如下几种场景:
- 某轮中,两次扔球都没有碰到球,所得分数为0。
- 某轮中,两次扔球都没有将球瓶全部击倒,所得分数为两球的倒瓶数。
- 某轮中,第一球就把全部的球瓶都击倒,STRIKE,所得分数是10分再加后面两球的倒瓶数。
- 某轮中,第二球将剩下的全部球品击倒,SPARE,所得分数为10分再加下一轮第一球的倒瓶数。
其实场景1是场景2的极端情况,我回想起了第一次玩保龄球的时候,就出现过这种情况,所以我很快就想到了它。
经过上述分析,我们将每轮得分规则分为3大类。此时,你可能会问,这只是1轮的4种场景,如果要统计10轮的总得分,穷举出来,是不是应该得有3^10 = 59049种业务场景(一个极端负责任的QA或许会想办法去测试这么多场景,此QA能用程序生成测试数据~ ~)。理论上,这样穷举出来的结果就是现实中真实存在的全部场景。这样去拆分业务需求,本身是没有错的,但看到这个数字,还没有开始写测试,估计你内心就已经崩盘了。
英雄难过Tasking关,TDD的践行显然无望了。为了拯救英雄,我们需要进一步业务抽象。多琢磨琢磨,其实10轮只是1轮的重复,这里说的重复是计分规则重复。比如说:我第1轮打出STRIKE,和第5轮打出STRIKE计分规则没什么区别。
通过进一步业务抽象,我们可以通过某一轮打出STRIKE来覆盖包含了STRIKE的场景,确保了某一轮STRIKE计分准确,打出2轮、3轮或10轮STRIKE都是一样的。同理,SPARE和其他的场景也一样。
结合我们识别出来的系统核心端到端功能 -- 统计每一局的总分,我们对进行业务拆分,得到如下列表:
- 每一轮的两次扔球都没有碰到球,所得分数为0。
- 每一轮的两次扔球都没有全部击倒球瓶,所得分数为每次扔球的倒瓶数总和。
- 存在一轮SPARE,所得分数为每次扔球的倒瓶数总和再加SPARE轮后的一球的倒瓶数。
- 存在一轮STRIKE,所得分数为每次扔球的倒瓶数总和再加STRIKE轮后两球的倒瓶数。
- 十轮均为STRIKE,所得分数300。
你可能会问,明明需求描述上强调了第10轮的特殊性,是不是最起码要区分一下前9轮和第10轮。你想到了这一点是非常好的,如果你想到了,尽管添加到你的任务列表,然后你会得到一个更详细的任务列表,继续前进就好。
我解释一下这里没有区分的原因。从业务描述上,第10轮加了这一个特殊的区分,正好满足了:“打出STRIKE,奖励两球的倒瓶数,打出SPARE,就奖励一球的倒瓶数,都没打出,就是本身的两球倒瓶数”。所以,这个特殊化恰好跟前九轮保持了一致的业务规则,应用在场景3或4中了。