一个编程小题目引发的思考(下)

此篇文章接上篇 一个编程小题目引发的思考(上)

其实很多园友已经给出答案了,不过我在这里还是要写一下自己的思路

再把题目叙述一遍

 

输入:一个小于12位的十进制正整数

 

输出:打印此数字的十进制计算器表示 例: 输入:145 输出:

         __

   ||__||__

   |   | __|

于是我又重新思考了一下这道题目,并Review了一下当前的解决方案,发现这个冗长的switch是个很大的问题,这是我想到了代码大全2里提到的表驱动编程方法(就是用一个表来代替冗长的分支控制逻辑)。 小心起见,我从一个方法开始,对其进行了重新组织:

    private void PrintTopBody(int value)

    {

        if (value != 0)

        {

            string[] table = { S1, S0, S1, S1, S0, S1, S1, S1, S1, S1 };

            PrintTopBody(value / 10);

            Console.Write(table[value % 10]);

        }

    }

测试之后发现运行结果正常,我考虑可以对另外两个方法进行这样的重构,但我发现这样写出的代码依然不好维护,虽然短了很多,但是S1,S0等莫名奇妙的全局变量仍然令人很头疼。 所以接下来就是怎么消除这些恼人的全局字符串常量了。 一时间没想到什么方法,于是又重新运行了一下这个程序,得到了下面的结果:

     __  __      __  __  __  __  __  __

   | __| __||__||__ |__    ||__||__||  |

   ||__  __|   | __||__|   ||__| __||__|

这时我突然发现,这个结果不就是一个字符串表吗?为什么不直接利用这个表呢? 于是我打开Regex,利用正则替换,将上面的字符重组为一个二维字符串表格:

    private static readonly string[,] TABLE = 

    {

        {"    "," __ "," __ ","    "," __ "," __ "," __ "," __ "," __ "," __ "},

        {"   |"," __|"," __|","|__|","|__ ","|__ ","   |","|__|","|__|","|  |"},

        {"   |","|__ "," __|","   |"," __|","|__|","   |","|__|"," __|","|__|"},

    };

相应的,我新创造了一个LCDPrinter1类,并按照之前的逻辑,编写了对应的方法,代码如下:

    class LCDPrinter1

    {

        private static readonly string[,] TABLE = 

        {

            {"    "," __ "," __ ","    "," __ "," __ "," __ "," __ "," __ "," __ "},

            {"   |"," __|"," __|","|__|","|__ ","|__ ","   |","|__|","|__|","|  |"},

            {"   |","|__ "," __|","   |"," __|","|__|","   |","|__|"," __|","|__|"},

        };

        public void PrintNum(int value)

        {

            for (int i = 0; i < 3; i++)

            {

                PrintOneLayer(value, i);

                Console.WriteLine();

            }

        }

        private void PrintOneLayer(int value, int layer)

        {

            if (value != 0)

            {

                PrintOneLayer(value / 10, layer);

                Console.Write(TABLE[layer, value % 10]);

            }

        }

    }

然后进行测试,我输入123,但惊奇的发现结果是:

 __  __

 __| __||__|

|__  __|   |

非常像一个off-by-one错误,我看了下字符串表格的定义,原来是0的位置错了,修改之后重新运行,结果正常。 下面是最终的代码:

    class LCDPrinter1

    {

        private static readonly string[,] TABLE = 

        {

            {" __ ","    "," __ "," __ ","    "," __ "," __ "," __ "," __ "," __ ",},

            {"|  |","   |"," __|"," __|","|__|","|__ ","|__ ","   |","|__|","|__|",},

            {"|__|","   |","|__ "," __|","   |"," __|","|__|","   |","|__|"," __|",},

        };

        public void PrintNum(int value)

        {

            for (int i = 0; i < 3; i++)

            {

                PrintOneLayer(value, i);

                Console.WriteLine();

            }

        }

        private void PrintOneLayer(int value, int layer)

        {

            if (value != 0)

            {

                PrintOneLayer(value / 10, layer);

                Console.Write(TABLE[layer, value % 10]);

            }

        }

    }

可以发现这个解决方案不但短小(只有25行),而且清晰易懂,逻辑一目了然,相比之前那个150行的解决方案,可谓是天壤之别。

 


反思:

  • 作为一个程序员,当我接到一个task的第一反应就是CODING(我想这也是大多数程序员的通病吧),然而这时我可能并没有对这个任务有一个清晰的认识,然后写出一摊虽然可以run但是看起来莫名其妙的代码。在完成任务之后马上进行下一个task,然后这一摊weird code就被搁置在那里。等过了一段时间之后,连我自己都看不懂了,想改也没法改,一是没有时间,二是可能有一些人用到了我的代码,修改的话会引发其各种不想看见的连带效应。
  • 所以Jon Bentley在他的Programming Pearls一书中提到:Good programmers are a little bit lazy: they sit back and wait for an insight rather than rushing forward with their first idea。而我们在编程时是怎么做的呢?真实的情况时,我们往往过早的陷入到了实现功能的误区中,而忘记了原本问题到底是什么。即使是到后来insight灵光一现,也已经是too late to modify了。所谓磨刀不误砍柴工,就是这个道理。
  • 在Geogre Polya的神作How to solve it?一书中,Polya为解决问题定义了一个系统化的方法:理解题目->规划解决方案->执行解决方案->对解决过程进行反思。Polya提到,我们很多人都只注意到了前三步,而最后一步,也是他认为非常重要的一步却被忽视了。要知道我们解决新的问题往往是基于我们已有的经验的,而这些经验并不是由重复性的工作中而来,而主要从对工作的反思中而来。
  • 此外,科学家往往有这样的思维,那就是越复杂的问题的解释往往是非常简单的。Dirac甚至说:“一个理论家宁可要一个美的方程,也不要一个丑的但结果与实验数据更一致的方程。”举个简单的的例子,我们在小时候的数学考试中,如果得到的答案是1、2或者是10,我们往往会欣然接受答案;但如果计算的答案是11/17、1.947此类的数字时,我们往往会怀疑自己是不是算错了,原因很简单,这些答案的样子太邪恶了。
  • 回到程序员的视角,如果当我们对一个问题给出一个自己都认为丑陋无比的解决方案时,这时很可能就是哪里出了问题:对问题的理解不够深入?使用了错误的数据结构?此时不应该去继续CODE,而是应该进行仔细的思考,换句话说,在一些情况下,程序员应该像Dirac那样,对优美的CODE有着近乎偏执的追求。当然了,那些manager会不会允许程序员这么做,就是另外一回事了。

你可能感兴趣的:(编程)