像说话一样写代码
 
    前段时间听到一个做测试的同事说写代码好难啊,我就开玩笑问他,“说话难吧?”。“那也挺难的”,他笑着回答。其实我总感觉写代码和说话是一回事,当我们可以把问题说清楚的时候代码也就水到渠成了。为了更好地让大家感觉到这点,我们就开始边说话边写代码吧!
 
1 层次感。当你说明如何做一个复杂的事情的时候,通常你会说,“首先……;然后……;最后……。对于第一步……(可能又是一串的首先,然后,最好)”。从总到分,层次感让听众可以比较清晰地接受。代码的表达可能是这样的:
像说话一样写代码_说话        class Man
像说话一样写代码_说话        {
像说话一样写代码_说话                void Execute()
像说话一样写代码_说话                {
像说话一样写代码_说话                        ExecuteStep1();
像说话一样写代码_说话                        ExecuteStep2();
像说话一样写代码_说话                        ExecuteStep3();
像说话一样写代码_说话                }    
像说话一样写代码_说话             void ExecuteStep1()
像说话一样写代码_说话             {
像说话一样写代码_说话                     ExecuteStep1-1();
像说话一样写代码_说话                     ExecuteStep1-2();
像说话一样写代码_说话             }
像说话一样写代码_说话                ……
像说话一样写代码_说话        }
但如果同样的过程变成了这样:
像说话一样写代码_说话        class Man
像说话一样写代码_说话        {
像说话一样写代码_说话                void Execute()
像说话一样写代码_说话                {
像说话一样写代码_说话                        ExecuteStep1();
像说话一样写代码_说话                        ExecuteStep2-1();
像说话一样写代码_说话                        ExecuteStep2-2();
像说话一样写代码_说话                        ExecuteStep3();
像说话一样写代码_说话                }    
像说话一样写代码_说话                ……
像说话一样写代码_说话        }
    可能结果是一样的,但层次感的破坏使得代码读起来很“颠簸”。因为你可以试想下把原来的问题按这种代码逻辑解释,是否也会一样的“颠簸”呢?
 
2 先后顺序。办理房贷的时候工作人员通常会这样对你说,“你把这些证件和资料都准备好了,我们这边就可以放贷了。否则,我们将要想其它办法了。”代码版的这种表述如下:
像说话一样写代码_说话        class Man
像说话一样写代码_说话        {
像说话一样写代码_说话                void ProcessLoan ()
像说话一样写代码_说话                {
像说话一样写代码_说话                        if(AllCertificatesReady())
像说话一样写代码_说话                        {
像说话一样写代码_说话                                ApproveLoan ();
像说话一样写代码_说话                        }
像说话一样写代码_说话                        else
像说话一样写代码_说话                        {
像说话一样写代码_说话                                DeclineLoan ();
像说话一样写代码_说话                                ErrorNotification();
像说话一样写代码_说话                        }
像说话一样写代码_说话                }
像说话一样写代码_说话        }
    如果代码写成下面这样。从结果上看是等效的,但如果用语言表达就可能变成,“如果你没有把这些证件和资料都准备好,我们将要想其它办法了。否则,我们这边就可以放贷了。”是不是觉得有点不自然和不容易接受啊。同样读下面这段的时候你是否会感觉需要一个停顿和思索,这样就破坏了代码的流畅。
像说话一样写代码_说话        class Man
像说话一样写代码_说话        {
像说话一样写代码_说话                void ProcessLoan()
像说话一样写代码_说话                {
像说话一样写代码_说话                        if(!AllCertificatesReady())
像说话一样写代码_说话                        {
像说话一样写代码_说话                                DeclineLoan();
像说话一样写代码_说话                                ErrorNotification();
像说话一样写代码_说话                        }
像说话一样写代码_说话                        else
像说话一样写代码_说话                        {
像说话一样写代码_说话                                ApproveLoan();
像说话一样写代码_说话                        }
像说话一样写代码_说话                }
像说话一样写代码_说话        }
    所以,在分支语句中最好把正常的、发生概率比较高的先处理,然后处理低概率的异常和错误的。
 
3 多选项。记得有回我去联通营业厅申请一张新的电话卡,就向服务员问起了资费。“市话每分钟多少钱?”,“国内长途呢?”,“国际长途呢?”……,回答前面几个问题的时候那服务员态度还挺好的,但到了后面就显得有点不耐烦了。我后来想想,如果代码写成下面这样子,我们会有什么感觉呢?
像说话一样写代码_说话        class TeleCom
像说话一样写代码_说话        {
像说话一样写代码_说话                double GetFeePerMinute(int type)
像说话一样写代码_说话                {
像说话一样写代码_说话                        switch(type)
像说话一样写代码_说话                        {
像说话一样写代码_说话                                case 市话: fee = 0.2;
像说话一样写代码_说话                                case 国内长途: fee = 0.5;
像说话一样写代码_说话                                case 国际长途: fee = 2.5;
像说话一样写代码_说话                                ...
像说话一样写代码_说话                        }
像说话一样写代码_说话                        return fee;
像说话一样写代码_说话                }
像说话一样写代码_说话        }
    最后服务员拿了一个电话卡的宣传单给我,“你自己看看吧!”。宣传单上面有一张资费表格,一目了然。所以上面的代码可以是这样的:
像说话一样写代码_说话        class TeleCom
像说话一样写代码_说话        {
像说话一样写代码_说话                double GetFeePerMinute(int type)
像说话一样写代码_说话                {
像说话一样写代码_说话                        fee = FeeTable(type);
像说话一样写代码_说话                }
像说话一样写代码_说话        }
    编程过程中通常会遇到这样多选项的问题,这可能是switchif…else if…else语句派上用场的时候,通常也会使引入代码“坏味道”的时候。所以使用之前你是否应该考虑用一些设计上或者实现上的技巧避免冗长的代码呢?
 
4 关注点。一般每次说话都会有一个关注点,以便更好地传递信息或解决问题。当然,侃大山除外。因此说话有时候很忌讳漫无主题和不时跑题,这类现象对应的代码可能是这样的:
像说话一样写代码_说话        class Man
像说话一样写代码_说话        {
像说话一样写代码_说话                void Talk()
像说话一样写代码_说话                {
像说话一样写代码_说话                        Problem1 Block;
像说话一样写代码_说话                        Problem2 Block; // By the way
像说话一样写代码_说话                        Problem1 Block; // Back again
像说话一样写代码_说话                }
像说话一样写代码_说话        }
    但一旦有了关注点,代码就会变成:
像说话一样写代码_说话     class Man
像说话一样写代码_说话        {
像说话一样写代码_说话                void Talk()
像说话一样写代码_说话                {
像说话一样写代码_说话                        Problem1 Block;//Always focus on it
像说话一样写代码_说话                }
像说话一样写代码_说话        }
    这样,代码就会遵循函数级别的“单一职责原则”,从而便于阅读和维护。
 
5 专家原则。某天,有个同事想问你一个你不是很有把握的技术方面的问题,你出于面子鼓起勇气向他解答。随着解释的深入,你开始发现自己也迷糊了。最终不但没有解决你同事的问题,而且把你自己也套进去了。对应的代码可能会是:
像说话一样写代码_说话 class Man
像说话一样写代码_说话 {
像说话一样写代码_说话         void ExplainSomething()
像说话一样写代码_说话         {
像说话一样写代码_说话                 Complicated and error-prone code block here...
像说话一样写代码_说话         }
像说话一样写代码_说话 }
    这时你可能会很无奈的说,“张工对这很熟悉,这问题可以问问他”。其实这里遵循的就是“专家原则”,就是可能的情况下,应该让最合适最胜任的人处理问题。这样故事就可以用如下代码表示。
像说话一样写代码_说话 class Man
像说话一样写代码_说话 {
像说话一样写代码_说话         void ExplainSomething()
像说话一样写代码_说话         {
像说话一样写代码_说话                 expert.ExplainSomething()
像说话一样写代码_说话         }
像说话一样写代码_说话 }
 
6 解释。如果你说一件事的时候不断有人打断你问你刚说的东西。你有可能就要意识到要不你说的问题太难了,要不你就没有把问题说清楚,因此再多的解释可能都无助于最终把事情说清楚。对于写代码,注释就是解释。除非在非常必要的时候进行适当的注释,否则只会增加阅读代码“噪声”。写一些“挂羊头卖狗肉”的注释更是害人非浅。
 
    很多时候对于同一个问题我们可能说得很好,却没办法去用代码自然表达,原因就在于编程的时候太关注编程技术和语言本身了,而忽略了生活中自然的表达。不过说是一回事,实际做起来又是另一回事。尽管在平常的编程实践中我经常提醒自己注意这些,但还是会有言行不一的时候。所以也希望以此博文和大家共勉。