美文网首页
降维打击,Diamond Kata

降维打击,Diamond Kata

作者: hxfirefox | 来源:发表于2018-11-18 18:48 被阅读10次
来自二向箔的降维攻击,就问你怕不怕

做软件开发的都知道,往往需求描述语言越少,需求本身的内涵和外延就越多,实现的难度反而增大。这次的星云道场,我们就挑战一道描述极少的Kata——Diamond。

Diamond kata

题设很容易,给出的范例也足够清晰,简单地说就要编写一个程序,在给定字母输入时,按照从‘A’到给定字母输出如上图“钻石”样式的图案。

数学渣的苦恼

显然,我们不会一次性完成这样Kata,进行简单地推导就能得到我们“小步快跑”的目标。

阶段性目标

上面的图形除了字母外显然还需要一些填充物,我用‘X’将这些填充物在图上标注出来。标记后的阶段性目标呈现出明显的规律:它实际上是一个边长为2 * (给定字母 - ‘A’) + 1的矩阵。

标记

这个结论让人一下子就联想到二维数组,二维数组的不错的可读可测试性使得测试代码变得非常规整和容易。

@Test
public void should_print_a_after_specifying_a() throws Exception {
    // given
    // when
    char out[][] = diamond.print('A');
    // then
    assertThat(out.length, is(1));
    assertThat(out[0][0], is('A'));
}

但是随着测试用例的添加,我却发现相应的实现代码却越来越难输出,尤其是当这个二维数组越来越大的时候,我会陷入到如何通过计算公式得到数组中每一个位置内容的细节迷雾中,往往会卡壳好久。这大概是由于我的数学比较弱菜,没法从整体上透视多维空间,所以这条路对我而言就是个死胡同。

数学太差,人神共愤

另辟蹊径,曲线救国

没办法只能换个思路来重新思考,上面描绘的“钻石”样式其实在还可以用代码语言重新“渲染”。

重新表达

这就好像把“钻石”一下子拍扁了,我挥手告别二维数组迎接我更加熟悉的一维字符串,现在至少从形式上看容易操作了,不过一步到位写出输入B或C的实现还是有点难。怎么办?既然一次看的、想的东西太多,我们就少看些、少想些,继续减少我们要输出的内容。

@Test
public void should_print_indentation_after_specifying_b() throws Exception {
    // given
    // when
    // then
    assertThat(diamond.print('B'), is(" A"));
}

// -----用例实现分割线-----
// 我是上面用例的快速实现
public String print(char specified) {
    String result = "";
    if (specified != 'A') {
        result += " ";
    }
    return result + 'A';
}

就先从最简单的缩进开始,只考虑最快速和简单的实现,接下来的每一步都是在前一步之上的叠加。按照这个方法,接下来我准备来点小挑战,让测试用例输出指定字母B。

@Test
public void should_print_a2b_after_specifying_b() throws Exception {
    // given
    // when
    // then
    assertThat(diamond.print('B'), is(" ABB"));
}

// -----用例实现分割线-----
// 我是上面用例的快速实现
public String print(char specified) {
    String result = "";
    for (char current = 'A'; current <= specified; current++) {
        if (specified != current) {
            result += " ";
        }
        result += current;

        if (current != 'A') {
            result += current;
        }
    }
    return result;
}

现在看上去离我们的目标又进了一步,那就把剩余的字母也输出吧。

@Test
public void should_print_a2b_diamond_after_specifying_b() throws Exception {
    // given
    // when
    // then
    assertThat(diamond.print('B'), is(" ABB A"));
}

这里与之前有稍许不同,从形式上看像是从指定字母到A的倒序输出,似乎还要借助一个从指定字母到A的循环而循环中的内容又与正序是相同的,显然分析的结果已经暗示我这里存在可以复用的代码。

那么复用点在哪里呢?我们以指定字母为中心点两边的输出互为镜像,也就是说两边的输出只是组装的顺序不同而已,如果能够记录每次迭代的输出然后加以不同组装顺序就可以得到我们需要的字符串了,而两种不同组装顺序的字符串完成拼接的逻辑控制点就是迭代到达指定字母。

public String print(char specified) {
    String top = "";
    String bottom = "";
    for (char current = 'A'; current <= specified; current++) {
        String line = alphabetPrint(specified, current);
        top = top + line;
        if (current != specified) {
            bottom = line + bottom;
         }
    }
    return top + bottom;
}

private String alphabetPrint(char specified, char current) {
    String result = "";
    if (specified != current) {
        result += " ";
    }
    result += current;

    if (current != 'A') {
        result += current;
    }
    return result;
}

一个小而完整的“钻石”字符需要输出的字母已经齐备了,现在要做的就是加上分隔符和分行符。完成这些后,我们再把测试往前推进一步,实现指定字母C的输出。

@Test
public void should_print_a2c_diamond_after_specifying_c() throws Exception {
    // given
    // when
    // then
    assertThat(diamond.print('C'), is("  A\n B B\nC   C\n B B\n  A\n"));
}

指定字母C与指定字母B的输出的差别在于分隔符和缩进的数量,此时就可以推导一下它们的计算公式了,略加计算就可以获得数量与指定字母之间的关系。

private String alphabetPrint(char specified, char current) {
    String result = "";
    if (specified != current) {
        result += duplicate(" ", specified - current);
    }
    result += current;

    if (current != 'A') {
        result += duplicate(" ", 2 * (current - 'A') - 1);
        result += current;
    }
    return result + "\n";
}

private String duplicate(String s, int times) {
    String dup = "";
    for (int i = 0; i < times; i++) {
        dup += s;
    }
    return dup;
}

至此Diamond要求的功能全部实现,如果不放心还可以再测试一下其他字母。

总结

对我而言最初的设计不能说是不合理的,二维数组确实是一个比较直观的表达,可无奈我的数学太弱没办法在一次设计过程中完整地输出需要的计算公式,这使得我不得不放弃而采用一种更贴合我自身能力的设计方式,通过降维的方式把二维数组化为一维字符串加以处理,并在一维字符串的处理过程继续降低需要关注的变化,使我能够一次只看到一个变化因子。

当我通过降维实现了全部功能后,再回头去看最初的设计,实际上也可以通过减少变化因子的方法来逐步完成二维数组中内容。

如此我们便从Diamond中又一次收获了编码过程中“二向箔”武器——分离关注点。这种通过主动地减少需求中需要同时关注的变化,再通过逐步迭代的方式进行完善的方法,的确是编码中有效地解决复杂问题的不二法宝。

相关文章

  • 降维打击,Diamond Kata

    做软件开发的都知道,往往需求描述语言越少,需求本身的内涵和外延就越多,实现的难度反而增大。这次的星云道场,我们就挑...

  • 降维打击,升级认知

    “降维打击”不是让自己降维去打击,而是通过把对方的维度降低,抽走三维的一维变成二维的,实现打击。“降维打击”这个科...

  • 浅谈“降维打击”思维

    浅谈“降维打击”思维 导语:降维打击,顾名思义,首先要降维。降维打击就是将攻击目标本身所处的空间维度降低,致使目...

  • 富人的思考方式是什么?

    01 有一种思考方式叫“降维打击”。 对,就是三体里面的降维打击——消灭你,与你无关的那种。 什么叫降维打击? 就...

  • 三宝妈百日分享之十四 降维打击

    “降维打击”出自中国最牛逼的科幻作家刘慈欣的《三体》中,原文是“降维攻击”,后来都用成“降维打击”。指的是...

  • 知识碎片2(含日记)

    一、知识碎片 1.粥佐罗:升维训练、降维打击;升维输入、降维输出 升维训练、降维打击:一位女拳击运动员,对待比赛非...

  • 降维打击

    在科幻小说三体中,大刘所描述的三体人无论是个人的智力水平还是群体的科技能力,都超越地球人好多个数量级,地球人用来对...

  • 降维打击

    降维打击来自科幻小说《三体》,指的是将攻击目标本身所处的空间维度降低,致使目标无法在低维度的空间中生存从而毁灭目标...

  • 降维打击

    有一句话叫,不要用行动上的勤奋来掩盖,思维上的懒惰。什么意思呢?就是有的人做事情非常的勤奋,但是很少去思考一下,更...

  • 降维打击

    本文先结合一些案例解释在商业市场等战场上“降维打击”的含义和应用原理,然后进一步讨论“降维”之说对于日常问题分析的...

网友评论

      本文标题:降维打击,Diamond Kata

      本文链接:https://www.haomeiwen.com/subject/ojevfqtx.html