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


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

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

    再把题目叙述一遍

    输入:一个小于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会不会允许程序员这么做,就是另外一回事了。
  • 相关阅读:
    day09页面的声明周期函数
    day8小程序的运行环境与基本架构
    day09小程序复习
    day08前后端交互
    day07获取图片
    day07获取用户地址信息
    MySQL 主从同步延迟的原因及解决办法
    升级Oracle 19c经验: TTS 在使用datapump导matadata时EXCLUDE=STATISTICS 不启作用
    12c,19c自动kill长时间未活动会话特性
    SuSE11单实例二进制安装MySQL5.7
  • 原文地址:https://www.cnblogs.com/figure9/p/1888007.html
Copyright © 2020-2023  润新知