专栏首页小巫技术博客TDD练功房之FizzBuzz

TDD练功房之FizzBuzz

题目内容:

说明

  • 限时5分钟完成
  • 可以选用擅长的语言完成,例如 C、C++、Java、C#、Javascript、Python、Scala 等
  • 代码完成后必须附上 Readme 纯文本文档(推荐使用 markdown 排版)
  • 必须有自动化测试代码进行验证
  • Readme 文档中应描述如何运行单元测试或主程序来证明题目的正确性(至少针对测试用例输入能够得到对应输出)

题目内容 有一名体育老师,在某次离下课还有五分钟时,决定玩一个报数游戏。此时有100名学生在上课,游戏的规则如下:

  1. 老师先说出两个不同的特殊数(都是个位数),比如3, 5;让所有学生拍成一队,然后按顺序报数;
  2. 学生报数时,如果所报数字是「第一个特殊数(3)」的倍数,或者包含「第一个特殊数(3)」,那么不能说该数字,而要说Fizz;
  3. 学生报数时,如果所报数字是「第二个特殊数(5)」的倍数,或者包含「第二个特殊数(5)」,那么不能说该数字,而要说Buzz;
  4. 如果所报数字同时是「两个特殊数」的倍数,也要特殊处理。例如,如果是「第一个(3)」和「第二个(5)」特殊数的倍数,那么也不能说该数字,而是要说FizzBuzz
  5. 学生报数时,如果所报数字包含了「特殊数」,那么也不能说该数字,而是要说对应的英文单词(见规则1和规则2)。例如,要报13的同学应该说Fizz;要报52的同学应该说Buzz。
  6. 如果在一次报数中,匹配上述多个规则,Fizz和Buzz都只能出现一次。
  7. 否则,直接说出要报的数字。

在开始做题之前,先问一下自己几个问题:

  • 什么是单元测试?
  • 为什么要写单元测试?
  • 怎么写单元测试?

这里我就不鼓吹单元测试有多么好,能带来什么好处。一切都以结果为导向,笔者也是本着学习的心态,跟着大神们去领略代码之美,让自己也能写出健壮的代码,告别996(OS:虽然我现在也没有996)。

先花5~10分钟理解题目,基本能知道这是个报数游戏,我们输入一个数字,让它输出是结果有4种:

  • Fizz
  • Buzz
  • FizzBuzz
  • 数字本身

如果对代码没有要求的话,我们最开始可能会写很多if else的代码,例如下面代码:

 public class FizzBuzz {

    public static void sayNumberGame(int firstNum, int secondNum) {

        // 从1~100报数

        for (int i = 1; i <= 100; i++) {

            // 即是第一个特殊数字的倍数又是第二个数字的倍数说FizzBuzz

            if (isMultipleNum(firstNum, i) && isMultipleNum(secondNum, i)) {

                System.out.println(String.format("%s Don't Say number, but say FizzBuzz", i));

                continue;

            }



            // 第一个特殊数字的倍数说Fizz

            if (isMultipleNum(firstNum, i)) {

                System.out.println(String.format("%d is a multiple of %d or contains %d Say Fizz", i, firstNum, firstNum));

                continue;

            }

            // 第二个特殊数字的倍数说Buzz
            if (isMultipleNum(secondNum, i)) {

                System.out.println(String.format("%d is a multiple of %d or contains %d Say Buzz", i, secondNum, secondNum));

                continue;

            }
            // 不满足以上所有条件
            System.out.println(String.format("%s Say number", i));

        }

    }



    public static boolean isMultipleNum(int targetNum, int sayNum) {

        String targetNumStr = String.valueOf(targetNum);

        String sayNumStr = String.valueOf(sayNum);

        return sayNum % targetNum == 0 || sayNumStr.contains(targetNumStr);

    }



    public static void main(String[] args) {

        System.out.println("00.FizzBuzz!!!");

        sayNumberGame(3, 5);

    }

}

上面代码有什么问题?

  • 太多if else
  • 重复代码太多
  • 没有单元测试,通过人肉测试
  • 没有自动化测试
  • 通过println打印内容
  • 方法超出5行
  • 没有端到端测试 这个是我最开始无脑贴代码被老师怼的点评。后面想了想确实有道理,明明代码可以写得更好,作为一个有追求的程序员必须得改。

这次我先写测试,将不同情况的输出分别写了测试方法来验证:

public class FizzBuzzTest {
    @Test
    public void testSayFizz() {
        assertEquals("Fizz", FizzBuzz.fizzBuzz(3, 3, 5));
    }

    @Test
    public void testSayBuzz() {
        assertEquals("Buzz", FizzBuzz.fizzBuzz(5, 3, 5));
    }



    @Test
    public void testSayFizzBuzz() {

        assertEquals("FizzBuzz", FizzBuzz.fizzBuzz(15, 3, 5));

    }



    @Test
    public void testOnlySayNum() {

        assertEquals("1", FizzBuzz.fizzBuzz(1, 3, 5));

    }
}

注:这里我用的IDE是Intellij,测试框架用的是Junit4.

代码我也进行了重构:

public class FizzBuzz {
    private static final String FIZZBUZZ = "FizzBuzz";
    private static final String FIZZ = "Fizz";
    private static final String BUZZ = "Buzz";

    public static String fizzBuzz(int sayNum, int firstNum, int secondNum) {

        // 即是第一个特殊数字的倍数又是第二个数字的倍数说FizzBuzz
        if (isFizzBuzz(sayNum, firstNum, secondNum)) {
            return FIZZBUZZ;
        }

        // 第一个特殊数字的倍数说Fizz
        if (isFizz(sayNum, firstNum)) {
            return FIZZ;
        }

        // 第二个特殊数字的倍数说Buzz
        if (isBuzz(sayNum, secondNum)) {
            return BUZZ;

        }

        // 不满足以上所有条件
        return String.valueOf(sayNum);
    }



    public static boolean isFizzBuzz(int sayNum, int firstNum, int secondNum) {
        return isFizz(sayNum, firstNum) && isBuzz(sayNum, secondNum);

    }

    public static boolean isFizz(int sayNum, int targetNum) {

        return isMultipleOrContainNum(sayNum, targetNum);

    }

    public static boolean isBuzz(int sayNum, int targetNum) {

        return isMultipleOrContainNum(sayNum, targetNum);

    }

    public static boolean isMultipleOrContainNum(int sayNum, int targetNum) {
        return (sayNum % targetNum == 0 || formatNumToString(sayNum).contains(formatNumToString(targetNum)));

    }

    public static String formatNumToString(int num) {
        return String.valueOf(num);
    }
}

可以看到重构后的代码已经差别很大,但可测性明显提升了不少,基本可以从方法名表达意图,可读性也提升了。

最后自然是全绿通过:

本文分享自微信公众号 - 巫山老妖(wwjblog),作者:污748

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-05-19

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Activity启动模式解析

    用户1130025
  • Realm技术选型初体验

    用户1130025
  • Git 工作流的一些经验分享

    用户1130025
  • Spring Cloud Gateway 全局通用异常处理

    在传统 Spring Boot 应用中, 我们 @ControllerAdvice 来处理全局的异常,进行统一包装返回

    冷冷
  • 魔改 TypeAdapterFactory

    感慨:Retrofit2 虽好,但是,有时候总感觉 Java 这门语言还是美中不足啊!

    HelloVass
  • getParameterMap()返回参数需要对应实体类类型,否则收不到----打卡

  • 原 WCF学习之旅----基础篇之Ente

    魂祭心
  • Filter

    过滤器是实现了Filter接口的一个java类,是Servlet的高级应用,可以处理request和response,该接口有下面三种方法

    晚上没宵夜
  • 简单才是美! SpringBoot+JPA

    SpringBoot 急速构建项目,真的是用了才知道,搭配JPA作为持久层,一简到底! 下面记录项目的搭建,后续会添加NOSQL redis,搜索引擎elast...

    Mshu
  • AsyncListDiffer-RecyclerView最好的伙伴

    导读,近些年来 Android 一直在优化 RecyclerView 刷新效率,相继出了 DiffUtil,AsyncListDiffer ,我在我的开源库 F...

    程序亦非猿

扫码关注云+社区

领取腾讯云代金券