对几个软件开发传统观点的质疑和反驳

WindEagle 2012-11-06

下面这些观点都是程序员在教科书上、在编码规范里、在正统的软件工程流程里流传开来的,帮助了许多人在程序员启蒙期间养成了良好的习惯、原则。对许多人(包括曾经的我)来说,似乎是理所当然的。但是随着阅历的增长,视角在变化、看法也在变化,曾经的好恶现在都可能大翻身了。

为代码写足够的注释,让代码易于理解

“所有程序员都会写自己看得懂的代码,但只有优秀的程序员才写大家看得懂的代码。”这话没错,但是——

  1. 什么才是“大家看得懂”的定义?我有必要让我的C++代码对于一个月前才明白指针和引用区别的初学者简单易懂么?
  2. 更重要的是,要代码能够“看得懂”,主要是靠足够多的注释吗?

我觉得这两点都是扯淡。

关于第1点,造就了一些自我感觉过度良好的人,习惯性地把前人写的代码批得体无完肤。在他们眼中,这段代码巨烂,那段代码是屎,更有甚者,在评审别人代码的时候,一样说出这样的话来(请参见这篇文章里的“一坨屎型评审”)。

反对我的人会说,软件公司做产品赚钱,它们希望你的代码让不熟悉项目的新员工快速阅读、上手。这确实是个矛盾。说白了,你写的代码要和一个团队的能力匹配。在一个鱼龙混杂的团队,甚至一个糟糕的团队,你写出的代码也许很难和大伙儿产生共鸣,他们希望你写最普通最易懂的代码,没有精巧的设计(我指的是,“某一些精巧的设计,恰恰是以降低代码的可维护性为代价的”),没有语言高级特性,看着只有顺序、循环、分支判断的基本代码。如果大家都是JavaEE的初学者,那么就从JSP+Servlet开始吧,这样你们才在一个圈子里,要不然,没有人能真正和你一起讨论设计和代码的问题。

所以许多上进的程序员,会希望在一个牛人的团队里工作。这就像足球运动员一样,因为足球是集体运动,一个足球运动员能达到的高度,是和他所在的团队息息相关的。在一个优秀的团队里,大家个性各有千秋,擅长领域不甚相同,但是都学习迅速,能力不差太远,大家阅读代码都能够很快理解和领会,而且讨论问题可以用一些程序员才明白的隐喻(比如有人说“我觉得这里应该用一个builder来实现”,大家都明白builder指的是什么),氛围和效率显而易见。

如果你恰好对当前需要用到的业务和技术特别熟悉,领先团队里其他人一大截怎么办?那你就该在做设计编码的时候先行一步,你是那个最该去做架构设计、写骨架代码的人,完成一个架子以后再来给大家讲解,并和大家讨论,改进现有的设计。也就是说,你要多做一些更重要的事,而不是和大家一起分析、一起讨论,甚至一人负责一个模块,最后的结果就是大家根本和你讨论不到一块儿去。

关于第2点,要代码“看得懂”,是设计出来的,而不是注释加出来的。这和产品质量一样,产品质量是设计出来的,而不是测试测出来的。注释的意义在于对当前代码自解释做不到的地方进行补充。

所以,你的代码要易于理解,首先要保持简洁和清晰,这既包括良好的设计,也包括良好的编码习惯,也就是说,代码是自解释的,其次才通过注释的补充,让代码更易懂。注意,我不是说注释不重要和不必要,而是说,注释应该完成它自己的功用,它远不能代替代码本身自我解释的价值。

举一个简单的例子,你可以这样写代码:

/**  


 * 图表模型  


 */ 



class Chart{  




    /**  


     * 图表长度  


     */ 



    private int length;  



 



    /**  


     * 获取图表长度  


     * @return 图表长度  


     */ 



    public int getLength(){  




        return this.length;  



    }  


 



    /**  


     * 设置图表长度  


     * @param length 图表长度  


     */ 



    public void setLength(int length){  




        this.length = length;  



    }  


} 

好,那么你告诉我,这段代码和下面这段代码相比,你获取了什么更多的有用信息?

class Chart{  



    private int length;  



 



    public int getLength(){  




        return this.length;  



    }  



    public void setLength(int length){  




        this.length = length;  



    }  


} 

我想你懂我的意思,两段代码,代码本身意思已经够明确了,再加上这些无聊的注释,只是浪费资源、浪费生命。很多编程语言,利用语法糖,连简单 get、set方法都可以省了(比如Objective C),而加这种注释的做法却依然在反软件、反人类而行。也许你和我一样,曾经为了公司某些扯淡的规定,为了规避某些扯淡的代码静态检查工具(比如 CheckStyle这样的,甚至自己开发这种无聊的检查工具)检查结果中的警告信息,加上了(包括IDE自动生成)这些毫无意义的注释,于是领导看了: “好,没有警告信息,代码质量好”。虽然至始都痛恨这样的做法,但我还是做了,至今后悔。最让人痛恨自己的事情就是不得不去做那些自己痛恨的事情。

设计文档要详细,细化到方法定义

持这个观点的大有人在。对于这个观点我并不是完全反对,如果你要说设计文档需要“详细到可以指导编码”我还能同意,但是我确实非常不喜欢详详细细的设计文档。肯定设计文档的价值这没有错,但是过于详细的设计文档撰写,往往容易造成纸上谈兵的窘境

有人说设计文档太过粗略了做不好设计,事实上,文档只是呈现设计的其中一种形式而已,做得好设计的人,可以一边编码一边思考,可能辅助草稿纸上写写划划,就可以完成优秀的软件;不会做设计的人,设计文档写多少页都没用。这让我想起了今天和同事关于TDD的讨论,会做设计的人,不让他用TDD也能写好代码;不会做设计的人,TDD又有何用?所以让TDD成为设计的主要工具,那就是扯淡。

再一个,在设计文档中,不可能做完设计,不知你是否有这样的体会,设计文档思考得再缜密细致,等落到代码上的时候,还会和开始的思考有许多不同,至少有很多细小的不同。这是因为设计思考本身就是贯穿整个设计编码过程的,一人只做设计、另一人只写代码这样的理想模式是不可能达成的。

我了解一些对日外包公司,程序员拿到手的设计文档就是细化到方法定义了的,如果你有能力有志气,在中国最好就不要做外包,尤其是对日外包,这样的公司拒绝你的一切思考,就是在摧残人才

另外一个原因,是针对一些阐明“设计文档可以传承业务和技术知识”观点的人,详细的设计文档并不能够传承什么业务和技术,原因很简单,详细的文档初始撰写成本高,维护的成本更高。我不相信程序员在修改了代码逻辑以后,会去经常保持设计文档的同步性。这不合理,只有代码才是保持最新的,其它一切都会过时。而相对简要或粗略的文档,稳定性就要强得多。

让项目组各个角色去评审代码设计

下面我要驳斥的这个观点来源于我的一些经历,也许并不能算是主流观点。

对于设计文档的评审,如果是设计原理、实现原理,正到了程序员才关心的层面上,如果不写代码的测试跑来一起讨论,这就成了浪费时间、制造矛盾的做法。我经历过这样的事情,觉得很幽默。专职测试人员的定位各有千秋,许多人经验丰富、无可替代,但是至少,我接触的测试人员中,他们几乎是不阅读代码的,也就是说,对于代码设计(注意,是代码设计,不是产品设计)的讨论,他们不该参与进来。

另外,不要说“我几年前也是写代码的”,毛主席都讲了,“不了解情况,就没有发言权”,如果你对当前的代码实现不了解,就不要来碍手碍脚地评审代码层面的设计了。

我也经历过这样的场景,每一个产品都要组织一些有经验的程序员,去给别的产品的代码挑刺儿。我的看法是,这很难挑出特别有价值的毛病来,原因也是一样的,你对别的产品业务不了解,那么要花大量精力去阅读代码,更要去熟悉业务,否则只能从代码层面上抠抠细节。

所以,谁来把关实现层面的设计和代码的质量最卓有成效呢?正是熟悉项目的程序员们,尤其是项目组骨干,或者一起参与设计、编码和测试的架构师(其实架构师还是一名优秀的程序员,我从来不认为“只懂业务的架构师”有什么资格去做软件架构)。

为代码设置量化的限制指标

统计指标是有价值的,但是如果设置这些量化指标给程序员套限,则是违背客观规律的行为。这一观点我有必要举例说明一下:

  • 测试代码覆盖率不得低于95%(比如工具EMMA);
  • 方法圈复杂度不能超过15(你也许知道圈复杂度的检查工具SourceMonitor);
  • 单个类的行数不能超过500,单个方法的行数不能超过200;
  • 任意两个类之间重复代码行数不能超过10行(你可能知道重复代码检测工具Simian);
  • ……

这些硬生生限制,都是反软件、反人类的。你可以说圈复杂度高的方法也许过于复杂,你可以说重复比率高的代码往往可以优化,但是这些都只是一个辅助的指标。这些工具都是用来帮助程序员改善他们的设计和代码质量的,如今它们却被用来做反程序员的事。

对于测试代码覆盖率的要求,而且有许许多多公司拿来作为代码质量衡量的重要指标,我认为更是骇人听闻。我写代码也做单元测试,但是会有选择地写UT 用例,不会去追求测试覆盖率,而且测试再全面也不可能保证结果的绝对正确,好钢要用在刀刃上,时间的投入要换取划算的回报,而不是不计代价地补充测试用例。而且,在这里我要说的是,保证软件质量的方式有很多,测试验证的方式也有很多。即便覆盖率达到100%,也不能说明质量高到哪儿去,追求覆盖率始终太过功利。另外,有许多代码本身就没有多大被UT测试的价值,这也是不容忽视的。

优秀的程序员,应该难以容忍自己产出糟糕的代码,也许对代码有一点洁癖,对代码之美有不懈的追求。对这样的软件的使用动机,也应该来源于程序员,而相关数据的采集,最终一定要为程序员服务。

相关推荐