《驯服烂代码》第一章

【按】这是本人正在撰写的《驯服烂代码》第一章的草稿,请各位网友审阅,望各位不吝赐教,多谢!

 出自:http://blog.csdn.net/oychw/article/details/8921553

第1章 何谓烂代码

 

光阴如梭,从1993年大学计算机专业本科毕业,至今一晃就过了近20年。在这近20年的IT职业生涯中,做了11年的程序员。这期间,自己写了不少代码,也看到别人写的不少代码。现在回想起来,这些代码竟然80%以上可以算作“烂代码”。

那么,究竟什么是烂代码?国内外程序员是如何定义烂代码的?为什么说要想驯服烂代码,首先要影响中国本土软件开发团队(以下简称:中国团队)的观念?影响观念的有效工具是什么?本书对“烂代码”的定义是什么?让我们一一道来。

1.1         国内程序员对烂代码的描述

从目前所掌握的资料来看,国内出版物中,尚无专门论述“烂代码”,并给出明确定义的书籍。“烂代码”一词经常出现在国内程序员的口头交流、微博和博客中,比如在撰写本书时,如果用关键词“烂代码”在新浪微博中进行搜索,可以搜到近500条微博。

通过下面笔者对编程操练社区“北京设计模式学习组(BJDP)”[1]中的程序员和新浪微博的网友(以下简称:微博网友)对“烂代码”的主要评论的归纳可以看出,国内程序员一般认为,“烂代码”就是那些难以理解和难以维护的代码。

1.1.1   难以理解

烂代码难以理解的问题,一般表现在下面3个方面:命名不清、多层嵌套和滥用模式。

q  命名不清。微博网友@荣老说:“烂代码从烂命名开始。”微博网友@哦小印说:“一堆烂代码看着头大,命名能再不靠谱一点吗?调用关系能再混乱一点吗?别乱用继承好吗?”命名不清导致的直接后果就是看不懂代码,北京的Java程序员杜金秀说:“看不懂的就是烂代码。”

q  多层嵌套。怀化的C程序员英界尔说:“烂代码一般都具备多层嵌套的特性。我最不喜欢的就是类似for循环下嵌套着另一个for循环,接着再嵌套一个if语句,然后再嵌套另一个if语句这样的代码。”

q  滥用模式。设计模式是蕴含在代码的内在逻辑里面的,而不是人为给加上的。微博网友@放翁_文初说:“好代码是容易看懂,易于维护,便于扩展,兼顾细节的。烂代码是模式一堆,嵌套层出,看不清楚,搞不明白。有人入行就听说代码不要超过多少行,要记得多用模式。这些是为了什么?是为了便于维护,是为了逻辑易于复用和扩展。死记硬背最后写出来的不是一坨X,就是一堆老鼠X了!”设计模式的运用应该是发乎自然,而不是扭捏作态的。微博网友@feuvan说:“‘大家之作,其言情也必沁人心脾,其写景也必豁人耳目,其辞脱口而出无矫揉装束之态。以其所见者真,所知者深也。’[2]其实对码农也是一样,别扭造作的都是烂代码。”

代码写得难以理解的极端但是很常见的现象,就是看到眼前的烂代码烂得可以,正要发作,结果却发现这烂代码正是自己前不久写的。北京的Java程序员李小波说:“有时候骂完娘,一查版本历史才知道是自己的,然后都不敢相信。”北京的C#程序员二奎说:“平时我们写的烂代码,我看见,都不想承认那是我写的代码,哈哈。”

1.1.2   难以维护

代码难以维护的问题,一般表现在下面3个方面:代码冗余、难以扩展和不如重写。

q  代码冗余。微博网友@灵感之源说:“……想到的是公司那些大量冗余烂代码,几万行的一个代码文件,编译起来是不是要比高质量、重构得当的代码要时间长很多呢?”微博网友@thinkinlamp说:“……现场分析一些典型的烂代码,例如传递信息的粒度太大、代码冗余、方法的定义不精准等。”源代码拷贝和粘贴是造成代码冗余的最主要的祸根。微博网友@-李江华-说:“在烂代码中遨游时间久了,再新开始一个项目时,会暗暗谋划,一定把代码写好,但是也难以善始善终。拷贝粘贴是万恶之源,为了快速完成任务,拷贝代码成了权宜之计。真的是不得已去做烂?”微博网友@Denny_Wang_NJ说:“烂代码更容易被Copy,Paste后成为更烂的代码,可变更性更差,导致更高的Bug率,陷入万劫不复之地。”

q  难以扩展。北京Java程序员Jeff说:“烂代码就是耦合度高的代码。”北京软件开发咨询师王洪亮说:“烂代码就是代码质量不高、难以扩展、难以维护、出Bug概率高的代码。”

q  不如重写。很多程序员由于缺乏驯服烂代码的心法和手法,在看到难以维护的烂代码时,往往产生“不如重写”的念头。北京C#程序员二奎说:“我理解的烂代码,就是不想再碰,也不敢碰的代码。如果实在要改,不如重新来过。”微博网友@自律则强说:“重写比驯服也许更经济。”

维护那些难以维护的代码的确是一个苦活累活。微博网友@不亮说:“维护烂代码就好似钻到一跎超级大X的最中心——想吐。”微博网友@WasabiHu说:“改別人留下的爛代碼,改到我嘔血啦。”微博网友@兔乃伊说:“在叫我改那些烂代码,我就马上辞职。”

1.2         国外程序员对烂代码的看法

国内程序员认为那些“难以理解”和“难以维护”的代码,就是烂代码,那么,国外程序员是如何看待烂代码的呢?在国外有关提高软件内在质量的书籍中,描述上述“烂代码”概念的英文词汇有若干个,包括“Legacy Code” (遗留代码)、“Bad Code[3]”(糟糕的代码)、“A Big Ball of Mud[4]”(大泥球)、“Crap”(垃圾)、“A Mess”(一个烂摊子)和“Impossible to Work with[5]”(不可雕之朽木代码)。

1.2.1   遗留代码

上述描述“烂代码”的英文词汇中,在国外影响力最大的当属Michael C. Feathers在他的书Working Effectively with Legacy Code中描述的“遗留代码”。遗留代码的严格定义“就是指从其他人那儿得来的代码。”“在业内人士的口中,‘遗留代码’一词常常是‘无法理解的、难以修改的代码’的代名词。”而难以修改就是难以维护,由此可见,“遗留代码”在国外软件开发业界,同样具有“难以理解”和“难以维护”的问题。Michael C. Feathers接着在书中写道:“然而,在多年来与形形色色的开发团队共事,并帮助他们解决重大的编码问题的过程中,我总结出了一个不同的定义。对我来说,遗留代码就是那些没有编写相应测试的代码。明白这一点是很痛苦的。”“没有编写测试的代码是糟糕的代码(Bad Code)。不管我们有多细心地去编写它们,不管它们有多漂亮、面向对象或封装良好,只要没有编写测试,我们实际上就不知道修改后的代码是变得更好了还是更糟了。反之,有了测试,我们就能够迅速、可验证地修改代码的行为。[6]”Michael C. Feathers为“遗留代码”下的“没写测试”定义在国外影响如此之大,以至于“没写测试”超越“无法理解和难以修改”,在国外成为“遗留代码”“事实上的标准定义[7]”。但由于中国团队大部分程序员几乎很少写单元测试,所以上面“没写测试”的定义,在国内似乎影响力不及国外。这就解释了为什么很少有中国团队的程序员,把“烂代码”定义为“没写测试”的代码。

1.2.2   大泥球

上述描述“烂代码”的英文词汇中,把烂代码描述得最为生动形象的,当属“大泥球”。该英文词汇的发明人Brian Foote和Joseph Yoder是这样形象地描述“大泥球”的:“大泥球代码就是一个结构混乱、肆意蔓延、轻浮草率、贴满补丁、私搭乱建、一团乱麻的丛林,这类系统明确无误地显现出无序增长、反复修补和权宜修复的迹象,信息在系统的互不相干的部分之间杂乱地被共享,在这类系统中,经常几乎所有的重要信息都会变为全局的或重复的。系统的总体结构可能从未被很好地定义过,就算以前定义过,也被侵蚀得面目全非,只要有一点点软件架构意识的程序员都会躲避这样的泥潭,只有那些不关心软件架构的程序员,和那些或许乐于在一个即将垮掉的堤防上每天做修修补补差事的程序员,才愿意工作在这样的系统上。[8]”从这里可以看出,那些结构混乱、重复冗余和依赖全局信息的系统,可以称之为“大泥球”。

综上所述,国外程序员一般认为烂代码就是那些没写测试、无法理解、难以修改、结构混乱、重复冗余和依赖全局信息的代码。

1.3         借助比喻的力量

本书的主旨是驯服烂代码,其中,烂代码可以认为外界的事物,程序员在“烂代码”面前,可以有各种各样的反应或行为。那么,如何才能让程序员在烂代码面前做出正确的“驯服烂代码”的行为呢?让我们先看一看美国心理学家Albert Ellis提出的ABC理论[9]

1.3.1   什么能影响人的行为?

中国式管理之父曾仕强教授在讲述ABC理论时曾举了一个有趣的例子:“让三个人坐在窗前,让他们描述外面所看到的东西,结果是不一样的:有人会告诉你,他看到远远的地方有电线杆,电线上面有三只鸟;有人会告诉你,一大早车子就那么多,这个交通实在拥挤;还有一个人只看到外边有一位年轻的姑娘,长得很漂亮。[10]”为什么三个人从同一扇窗户往外看,看到不同的东西?说出不同的话?这个例子就体现出ABC理论的价值。这里A(ActivatingEvent,激活事件)代表外界发生的事物,如窗外的风景;C(EmotionalConsequence,情感后果)代表人对外界事物的反应,如看到风景后说出的话。一般人认为人一旦看到外界的事物,不会有中间过程,就会直接做出反应。但是ABC理论认为,从外界的事物A到人的反应C之间有一个中间状态,就是B(Belief,观念),代表观念。外界的事物A,只有经过了中间的观念B进行处理,才能转化为反应C。

ABC理论的价值在于,如果我们能够让人们在头脑中形成某些观念,就能影响他们的行为。

根据这个理论,如果我们能够让中国团队的每一个人,在头脑中形成驯服烂代码的观念,我们就能影响他们的行为,进而有可能让他们在烂代码面前做出正确的行为。而在头脑中形成观念,就是人通过思维进行认知的能力。如果我们能够找到某种通过思维进行认知的好工具,我们就能利用这个工具,有效地在人们的头脑中形成观念。那么,这种工具是什么呢?

1.3.2   比喻是影响和指导人们行为的利器

比喻的运用在人们的生活中司空见惯,比如在像“金蝉脱壳”、“调虎离山”和“釜底抽薪”等等这些中国古代“三十六计”的四字格的成语中,就用到了大量的比喻。这些比喻,“既是一种修辞方式,又是一种思维方式和认知方式。”“对于任何一个概念,都可以从不同角度下定义,都可有多个定义。”让我们看看比喻在下面三个不同角度下的定义。在修辞角度,“比喻是用同甲对象本质不同而又有相似之点的乙对象来描写、说明、灵化、强化甲对象的一种修辞手法。”在思维和认知角度,“比喻是通过与甲对象本质不同而又有相似之点的乙对象来认知甲对象的一种思维模式、认知方式。[11]”这里,比喻的“语言表达方式、思维方式(或模式)和认知方式三者是不能断然分开的。”其中,比喻的表达方式(即修辞手法),“指出了比喻对认知结果的负载性。”比喻的思维方式,“具有工具性意义。”而比喻的认知方式,“指出了比喻的目的性——本质属性。[12]

比喻的上述三种角度就是比喻的三种属性,即修辞属性、思维属性和认知属性,本书将重点关注比喻的后两种属性:思维属性和认知属性,因为前者揭示了比喻是思维工具,后者揭示了比喻的认知本质。这两种属性结合起来,说明“比喻”正是我们要寻找的那种通过思维进行认知并有效地在人们的头脑中形成某种观念的有力工具。利用这个工具,我们就能有效地在人们的头脑中形成相关观念,进而影响人们的行为,所以,比喻是影响人们行为的利器。

软件匠艺(Software Craftsmanship)运动的领袖[13],人称Bob大叔的美国软件开发顾问和作家Robert C. Martin也看到了比喻在软件开发中的重要性,他说:“在某种意义上,比喻是最重要的极限编程实践之一。”他用拼图游戏来打比方。为了把拼图拼起来,需要知道每一块拼图本身的形状,以及能与它们无缝拼接的其他相邻拼图的形状,“但是,为了把拼图拼起来,有一个比拼图形状更重要的东西——那就是拼图上印的图案。图案是真正的指南。拼图上的图案是如此强大,以至于如果两块拼图,虽然能在图案上相匹配,但若拼图本身的形状不能无缝拼接的话,你就会知道拼图厂家把拼图弄错了。而这正揭示了比喻的意义。比喻是将整个系统连接在一起的一幅大图案,它是系统的愿景,使得每一个模块的位置和形状明晰可见。如果一个模块的形状与比喻不一致,你就能知道这个模块出现了错误。[14]

由上述Bob大叔对比喻在软件开发中的作用的描述能够看出,比喻还可以起到检验人们的行为是否正确的作用,进而指导人们的行为。比喻也是指导人们行为的利器。

综上所述,比喻既是影响人们行为的利器,同时也是指导人们行为的利器。本书将根据这两点,大量使用比喻,来促使中国团队成员通过思维认知驯服烂代码的概念,并形成相关观念,影响和指导他们的编码行为,进而使得他们有效地驯服烂代码。

在本节结束前,不妨让我们体验一下比喻的力量。相信中国团队的程序员们都体验过在一个长期加班的项目中工作的滋味。“这类项目有很多种定义,但基本上都可以归结为,‘在太少的时间内要做太多事’。因此,你被要求在一段很长的时期内,每天工作很长的时间,以消除两者之间的矛盾。[15]”那么,西方软件开发者是用什么来比喻上述项目呢?《代码之殇》的作者,EricBrechner用到的比喻是“死亡行军”。在人类战争史上,有过很多次“死亡行军”,让我们看看发生在第二次世界大战中的那次“巴丹死亡行军”。这次死亡行军从1942年4月9日开始,从持续了3个月的菲律宾巴丹战役中投降的6~8万菲律宾和美军战俘,被日军强迫徒步行军128公里。最后,约2,500~1万菲律宾战俘和100~650名美军战俘在此次行军中丧生。这128公里的行军的特点是大范围的身体虐待和杀戮,导致战俘和平民在日军手中遭受重大伤亡,后来这次行军被盟军军事委员会判定为日本战争犯罪。[16]有了上述“死亡行军”触目惊心的“漫长、艰辛和伤亡惨重”的比喻,你还愿意仅仅用轻描淡写的“长期加班”来形容一个这样的项目吗?

1.4         什么是烂代码

在上文中,我们已经讨论了国内外程序员对烂代码的定义。国内程序员一般认为,难以理解和维护的代码就是烂代码,而国外程序员一般认为,没写测试、无法理解、难以修改、结构混乱、重复冗余和依赖全局信息的代码就是烂代码。这些对烂代码的描述,都没有用到比喻这个人们进行思维和认知的利器,其后果就是中国团队成员一般都没有形成足够强的驯服烂代码的观念,进而削弱了他们驯服烂代码的行为,使得驯服烂代码的效果差强人意。微博网友@自律则强说:“‘烂代码’(形成)的主因是程序员能力不足。编码知识体系可细分两类:一类是高水平软件需要的图论、算法分析设计等知识,这些虽难,但建构在规范的人类公共知识体系基础上,还是在可控范围内的;另一类是完全建立在自己的一套私有符号体系中,‘烂代码’往往是漏洞百出的私有符号系统,这是真正问题。”这里,“程序员能力不足”是外在表现,导致这个外在表现的原因,就是程序员的观念不足。前文讲过,要加强驯服烂代码的观念,比喻是有力武器。

下面先用比喻来说明两种控制代码的方式,然后给出本书对“烂代码”的定义。

1.4.1   农耕与游牧的比喻

本书有关驯服烂代码的大部分比喻都是基于下面两个基本比喻。

比喻1:“开发团队里的代码和人员”好比“国家里的土地和居民”。

这里,“开发团队”好比“国家”,“代码”好比“土地”,“人员”好比“居民”。注意,这里的代码指产品代码,而不是测试产品代码的测试代码。测试代码在下文中另有比喻。

比喻2:“每一位中国团队成员”好比“农耕民族的皇帝”或“游牧民族的首领”。

自从秦始皇在2,234年前统一六国称帝、冒顿单于(mòdú chán yú)于2,222年前自立为匈奴部落联盟首领以来,大凡每个中国人,都或多或少有一个皇帝梦或首领梦。中国团队中的每个成员也是如此,所以我们不妨把“每一位中国团队成员”比作“农耕民族的皇帝”或“游牧民族的首领”。

无论是皇帝,还是首领,统治天下,最要紧的事情是控制天下的土地和居民。由于农耕民族和游牧民族的经济形态和生活方式的不同,造成了皇帝和首领对于控制天下的土地和居民的方式也迥然不同。

1. 农耕民族的皇帝

对于农耕民族的国家,“都是在一定的领土范围,对一定的居民行驶国家权力。为了把国家权力落实到国家领土范围内的所有地方和一切人,统治阶级就必须把国家划分为若干分层分类的行政区域,并分别在这些行政区域内设置若干国家机关,这便形成了国家的结构。[17]”由此可见,农耕民族的皇帝,是以土地为根本,用建立政府的方式对土地及土地上的人员进行控制。

中国古代农耕民族的皇帝是以什么形式和方法来控制土地和人员的呢?“自秦以后的两千年的中国历史,尽管历代封建王朝对国家机构的设置、地方政府体制层级或类型的安排不尽相同,但秦王朝确立的中央集权单一制国家结构形式和以县为基础的地方政府体制结构,却始终没有大的变化。[18]”从这里可以看出,中国古代皇帝们用“中央集权单一制国家结构”来进行宏观控制,并用“以县为基础的地方政府体制结构”来进行微观控制。

在皇帝们用“以县为基础的地方政府体制结构”的方式来进行微观控制的基础上,可以有以下一系列比喻。

比喻3:“代码中面向对象的类”好比“行政县”。

在目前占主流的各种面向对象的软件开发语言中,类是最小的代码模块。而行政县是政府体制结构中最小的单元,基于这个相似性,“代码中面向对象的类”可以比作“行政县”。

比喻4:“对软件进行测试”好比“对土地进行控制”。

对软件进行测试,本质上就是对软件的质量进行控制。而软件由代码组成,在比喻1中,代码好比土地,所以“对软件进行测试”可以比作“对土地进行控制”。

比喻5: “代码中面向对象的类的单元测试”好比“县级政府”。

根据比喻4“对软件进行测试”好比“对土地进行控制”,而单元测试是软件测试中的一种测试,所以单元测试也是“对土地进行控制”的一种方式;而前面说到,皇帝对土地进行控制的手段,是建立政府,所以单元测试也可以比作政府;另外根据比喻3“代码中面向对象的类”好比“行政县”,由此可以推导出,“代码中面向对象的类的单元测试”可以比作“县级政府”。

比喻6:“为代码写单元测试的软件开发”好比“为土地建立地方政府的农耕文化”。

根据比喻1,“代码”可以比作“土地”;根据比喻5,“单元测试”可以比作“县级地方政府”;而前面提到,农耕民族的皇帝,是以土地为根本,用建立政府的方式对土地及土地上的人员进行控制,同时皇帝的意志,代表了农耕文化,所以,“为代码写单元测试的软件开发”可以比作“为土地建立地方政府的农耕文化”。

比喻7:“测试工程师进行手工测试”好比“皇帝出巡”。

根据比喻2,“每一位中国团队成员”好比“农耕民族的皇帝”,而进行手工测试的测试工程师是团队成员,所以“测试工程师”可以比作“皇帝”;测试工程师搭建测试环境,编写测试用例,进行手工测试,和皇帝安排出巡,选择出巡线路,进行视察,同样都是通过“亲身体验”的方式了解系统或国家的情况,所以“测试工程师进行手工测试”可以比作“皇帝出巡”。

比喻8: “自动化单元测试”好比“县级政府基层官员给皇帝上疏”。

相对于比喻7中皇帝需要亲自出巡,在比喻8中,皇帝可以把了解国家情况的任务委派给县级官员,要求他们给自己汇报,这样,“自动化单元测试”可以比作“县级政府基层官员给皇帝上疏”。

比喻9:“自动化集成测试”好比“县级以上高层官员给皇帝上疏”。

在比喻8中,如何皇帝觉得县级官员数量太多,照顾不过来,可以委派一些高层官员帮自己处理县级官员的上疏,然后高层官员再向自己汇报。而这里的高层官员所负责的工作,类似于集成测试所做的工作,都是需要了解或调用最底层的县级或单元测试的信息或功能,所以“自动化集成测试”可以比作“县级以上高层官员给皇帝上疏”。

比喻10:“自动化测试”好比“官员给皇帝上疏”。

综合比喻8和比喻9,“自动化测试”可以比作“官员给皇帝上疏”。

2. 游牧民族的首领

了解完农耕民族皇帝的情况,再来看看游牧民族的首领。游牧民族首领所控制的部落,“依赖于土地上的水草, ‘依天地自然之利, 养天地自然之物’ , 他们不能效仿农耕民族的样子, 建造永久性的居所, 而必须‘庐帐而居, 随水草畜牧。’, 于是, 草原上水草长势的优劣, 就规定了游牧生产与生活的流动性。游牧民族生产与生活的流动性, 就使得国家不能按地域划分和管理居民, 而只能以血缘为纽带, 按部族划分居民, 组成部落, 组成社会。[19]”由此可见,游牧民族的首领,以有血缘关系的人为根本,用建立部落的方式对人及人所活动的土地进行控制。

前面讲到,农耕民族的皇帝用“中央集权单一制国家结构”来进行宏观控制,并用“以县为基础的地方政府体制结构”来进行微观控制。那么,游牧民族的首领,又是怎样进行宏观和微观控制的呢?

游牧民族对中国历史的影响可谓深远,若按照与中原农耕民族对峙的时间、程度和影响力来看,匈奴首当其冲,因为匈奴早在“公元前3世纪初在北方大漠草原上首次建立了与西汉对峙的强大游牧政权。[20]”“匈奴人在蒙古地区留下了长久不灭的痕迹,匈奴的某些制度和习俗在蒙古高原 各民族中世代流传。左右翼和十进制的军事行政划分,一直延续到明清时期,今天的内蒙古自治区还使用着左、中、右旗的名称。[21]

“所谓两翼制度,又称左右翼制度,简称两翼制、左右翼制,为中国古代北方游牧民族实行的军事行政组织制度之一,是指在分封制基础上,最高首领居中控制,两翼长官侧翼拱卫的一种军政合一的地方统治制度。其实质是中国古代北方游牧民族的统治者为了维护特权家族的统治,而实行的一种控制手段,即最高统治者通过这种制度控制所辖地区和民众。此制在中国古代北方游牧民族中首见于匈奴,之后,几乎所有兴起于蒙古草原上的游牧民族均沿用不辍,成为“行国体制”的重要组成部分,而且还影响了周围其他民族。”“‘行国’一词最初见于《史记》卷一百二十三《大宛列传》中,泛指与‘土著’定居的‘城国’相对而言的游牧民族建立的国家政权。”“因为游牧民族的势力大小不同,因此,有时两翼的一翼中包括许多部落,有时一翼就是一个部落。在通常情况下,翼是部落之上的军政区划。[22]

从上面对游牧民族军事行政组织制度的描述可以看出,游牧民族的首领“居中”进行宏观控制,并用“两翼长官侧翼拱卫”的两翼及两翼之下的部落来进行微观控制。如此看来,有血缘关系的人所结成的部落,是游牧民族最小的组织单位,其根本是“人”,而“团队”是“通过一个共同的目标而联系起来的一群人或动物,[23]”其根本也是“人”。既然“部落”和“团队”的根本都是“人”,由此得出如下比喻。

比喻11:“团队”好比“游牧民族的部落”。

因为“游牧民族不能生产其生活和生产上需要的全部物资”,所以“游牧民族为了获得他们所需要的物资,只有两条途径:一是掠夺;二是交易。在古代世界史上,游牧民族对农业民族的掠夺,是一种普遍存在的现象。[24]”这里的“掠夺”,仅仅指抢夺,而不会在“掠夺”后建立地方政府进行“占领”。如果在软件开发中,不写单元测试进行控制,根据比喻5,“代码中面向对象的类的单元测试”好比“县级政府”,就可以推导出“不写单元测试”就好比“不建县级地方政府”,这一点与上面谈到的游牧民族“掠夺”后,不建地方政府有相似之处,故得出如下比喻。

比喻12:“不写单元测试的软件开发”好比“对土地进行掠夺、攻而不占的游牧文化”。

前面讲过,农耕民族的皇帝,是以土地为根本,用建立政府的方式对土地及土地上的人员进行控制;而游牧民族的首领,以有血缘关系的人为根本,用建立部落的方式对人及人所活动的土地进行控制;再综合上面的12条比喻,不难得出下面两个比喻。

比喻13:“以建立政府般的自动化测试来控制代码为根本,进而管理与代码相关人员的团队”好比“农耕民族”。

比喻14:“以建立团队来控制人员为根本,进而管理团队成员涉及的代码的团队”好比“游牧民族”。

在结束本节“农耕与游牧”的比喻之前,有必要说明一点:将开发方式比喻为农耕民族与游牧民族本身并无褒贬之意。两种方式均有适用场景:若代码规模较小,手工测试反馈速度快,且基本能实现手工测试完全覆盖的情况下,游牧民族方式的开发就比较适合;若代码规模较大,手工测试反馈速度慢,且无法实现手工测试完全覆盖的情况下,就需要建立政府般的自动化测试来协助团队控制代码,这时农耕民族方式的开发就比较适合。

具体选择哪种开发方式,除了要考虑上述手工测试反馈的速度和覆盖率,还需要考虑团队的观念和技能。一般说来,中国团队过去长期使用瀑布式软件开发方式,产品代码一般没有写自动化单元测试,而是依赖产品代码编写完成后的手工测试,来验证新的产品代码是否正确,所以这样的团队,都没有建立政府般的自动化测试来控制代码,而是以建立团队来控制人员为根本,进而管理团队成员涉及的代码,这基本符合比喻14的描述,表明中国团队一般采用“游牧民族”的开发方式。

使用“游牧民族”的开发方式,当团队的代码规模越来越大,造成手工测试的环境搭建越来越复杂,进而造成手工测试反馈速度越来越慢,进而造成时间花费越来越多,进而造成可用的测试时间越来越少,进而造成运行的手工测试用例越来越少,进而造成手工测试覆盖率越来越低,就会出现这种近乎荒谬的现象:产品代码明明完好无损地保存在公司的版本服务器里,但是这些产品代码由于手工测试反馈太慢、覆盖率太低,而渐渐地失去了控制。这样保存在版本控制服务器中的产品代码,仿佛渐渐地变成了一座不受自己控制的“城池”,团队成员开始渐渐地用与游牧民族领袖一样的眼神,望着这样的“城池”,心里盘算着:“我如何又得重新杀进城去,胡乱地“抢掠”一番,才能完成手里新的产品功能,或者修复一个新的bug。要是城里能有我们的人,里应外合一下就好了。”

要想“里应外合”地控制城池,就有必要让团队成员树立“农耕民族”开发方式的观念,并掌握相关技能,建立政府般的自动化测试来协助团队控制代码,使得保存在公司版本服务器中的产品代码,真正被团队所控制。

1.4.2   烂代码就是失控的代码

从上节的描述能够看出,在中国历史上,无论农耕民族皇帝的“中央集权单一制”,还是游牧民族首领的“两翼制”,都在强调对“土地”或“人”的控制。也就是说,以皇帝和首领为代表的中国人,都很重视“控制”。有了这些铺垫,我们下面给出本书对“烂代码”的定义。

定义:烂代码就是“失控”的代码。

烂代码的“失控”,体现在下面3个方面:轻视代码命名造成代码可理解性的失控、裸奔代码导致软件功能稳定性的失控、和重复代码导致代码可维护性的失控。

1. 代码命名不当造成代码可理解性的失控

因为代码命名不当,而造成代码可理解性失控,主要表现在下面4个方面。

q  片面依赖文档和注释,同时轻视代码命名

记得20多年前,笔者在大学修计算机专业,上“软件工程”课的时候,老师讲过:“软件就是程序,加数据,再加文档。”而代码注释也被认为起到与文档一致的作用,即二者都是“开发、使用和维护程序所需要的图文资料[25]”。当时老师强调文档和注释对于软件的重要性的场景,现在依然历历在目。这样的强调文档作用的传统软件工程的教育,被国内院校一直延续了下来。翻阅近几年出版的软件工程类的大学教材,软件的定义与20年前笔者学到的定义一模一样:“软件是程序、数据及相关文档的完整集合。[26]

上述关于“软件”和“文档”的定义本身看起来似乎没有问题,但是这给了国内程序员一个负面暗示:开发、使用和维护程序所需要的图文资料,都专门放到文档和注释里来保存,程序(即:代码)本身似乎不必包含这些开发和维护程序所需要的资料,反正有文档和注释保存着呢。这样就导致下面一系列问题:国内程序员写出代码时不注意命名,而是把解释代码的资料放到注释或文档里;按理说,注释和文档应该时刻保持与代码的一致性,但是当程序员在进度的压力下修改代码时,往往只修改了代码本身,而忘记了同时修改相关的注释和文档;在同样的进度压力下,代码评审只重视代码本身,不重视文档和注释,这样与代码本身不一致的注释和文档就保留了下来,成为误导维护这段代码的其他程序员的错误的“指路牌”。

对于上述关于文档和注释的负面暗示,国外程序员也有如下类似的评论。

“我已经忘记有多少次,有人给我一份长长的有关系统的手册,然后补上一句警告:‘这本手册不完全正确。’就像廉价的葡萄酒一样,打印在纸上的长长的文档,会快速地过期。如果你想在1年之后使用这份文档,只会让你头疼。[27]

“我为什么要极力贬低注释?因为注释会撒谎。也不是说总是如此或有意如此,但出现得实在太频繁。注释存在的时间越久,就离其所描述的代码越远,越来越变得全然错误。原因很简单,程序员不能坚持维护注释。代码在变动,在演化。从这里移到那里。彼此分离、重造又合到一处。很不幸,注释并不总是随之变动——不能总是跟着走。注释常常会与其所描述的代码分隔开来,孑然飘零, 越来越不准确。[28]

《程序员》杂志(新浪微博:@程序员杂志)于2013年4月23日,在新浪微博上发了这样一条微博:“好代码本身就是最好的文档。当你需要添加一个注释时,你应该考虑如何修改代码才能不需要加注释。”引来微博网友的上百条评论和转发。

其中,微博网友@ edwardnorton说:“说的极端了。好代码 + 好注释,最好再有个好文档,齐活了。”微博网友@胖Sir熊说:“坚决不认同!不需要注释就能轻松读懂的代码一定比helloworld复杂不了多少,涉及到一些复杂的算法和交互的地方,有注释会好维护得多。”在该微博的31条回复中,有13条回复持与上述两位网友相似的观点,这说明上文讲的文档和注释对程序员的负面暗示,还是很有影响的。

微博网友@灭火鸡说:“项目管理中,怎么做到需求文档和代码实际实现,注释和代码相吻合的?目前来看比较难的,这么多年,没有看到有人或者团队能做的比较出色的。”这从另一个角度,说明片面依赖文档和注释,同时轻视代码命名,会让代码可理解性失控。

q  中国程序员写英文代码的语言障碍

世界上绝大部分编程语言都用的是英语。不仅是中国程序员写英文代码有语言障碍,其他母语不是英语的程序员,也有同样问题。在这种情况下,要想把英文代码的命名写好,“每一位软件开发人员不应该懂英语吗?[29]”但遗憾的是,笔者在过去20年的时间里,接触的程序员,大部分英文都没有足够好到能自如地命名的程度。比如,北京的C#程序员汪刚问:“‘某一项’的英文命名该如何写?”有的程序员建议写成“oneItem”,有的建议“oneIssue”,有的建议“someItem”,有的建议直接用“item”。“oneItem”和“oneIssue”都表示“一个项目”,而不表示“某一项”;“someItem”若表示“很重要的一项”,不是“某一项”;“item”并没有强调“某一项”中“某一”的概念,也不合适。在查阅了Bob大叔(即美国软件艺匠运动发起人Robert C. Martin)参与编写的ATDD开源项目FitNesse的源代码的命名习惯后,笔者认为,“某一项”比较好的英文命名是“anItem”。由此可见,一个简单的“某一项”的命名,国内程序员由于语言障碍,且阅读国外程序员的源代码机会较少,造成命名不当,引起代码可理解性失控。

q  缺乏统一的问题领域概念的中英文对照词汇表

代码的可理解性依赖代码的命名,而代码的命名又必须正确地反映软件所解决的问题领域(Domain)的概念,而问题领域建模(DomainModeling)正是用来解决代码命名与问题领域概念的对应关系的。“问题领域建模是一项创建项目词汇表(Project Glossary),或者项目专用名词词典(Dictionary of Terms)的工作,如一个网上书店项目,应该包括Book、Customer、Order和Order Item这样的问题领域对象。其目的是让项目中的每个人用无二义性的名词,来理解问题空间。[30]”由于中国团队理解问题领域的概念时使用的是中文,编写代码时使用英文,所以在上述词汇表和词典中描述问题领域的概念,需要用中英文对照的形式,以保证代码中的英文代码,能够准确映射到中文相对应的问题领域概念,以便于中国团队成员理解代码。

通过上述分析可以看出,在中国团队中建立一个统一的问题领域概念的中英文对照词汇表,是提高代码可理解性和统一代码命名的必要手段。但遗憾的是,在笔者20年的工作经验中,几乎没有见到中国团队中有过这样的中英文对照词汇表。究其原因,笔者认为,主要原因是高校软件开发课程,几乎没有照顾到国内实际软件开发所面临的上述代码命名的问题。中国的年轻程序员主要来自国内高等院校,高校在讲授“面向对象的程序设计”、“软件工程”和“面向对象需求分析与建模教程”的相关课程时,都没有涉及如何创建项目词汇表和专有名词词典的内容,这导致国内程序员普遍缺乏建立统一的问题领域概念的中英文对照词汇表的观念,造成代码可理解性失控。

q  问题领域的概念重复

就像在编程时,重复代码是最严重的代码腐臭一样,重复的问题领域概念也会带来同样的问题。重复代码的后果是代码难以维护,重复概念的后果是代码的可理解性失控,造成沟通混乱。“软件行业还太年轻,实际上还未提出或建立关于可复用组件的正确标准。”“每个项目中重复创建的软件组件,它们也被重新命名了。软件行业里对现有的概念发明新的名字是很常见的,即使用的名字相同,这些名字也以新的方式被重用。行业里有一个心照不宣的秘密:对于如何选择最好的开发方式已经有不少的讨论,这些参与讨论的人使用不同命名,他们之间的沟通完全是一头雾水。[31]”可以看出,重复代码能够造成重复组件,而重复组件又被重复命名,重复命名的组件又对应着问题领域的同一个概念,这就导致了同一个问题领域,会出现多个重复命名的概念,造成团队成员沟通混乱,进而使代码可理解性失控。

2. 裸奔代码导致软件功能稳定性的失控

这里的“裸奔”代码指的是没有写单元测试的代码,其内涵与前文描述的Michael C. Feathers定义的“遗留代码”相同。笔者第一次见到裸奔与代码相关的描述,是在微博网友、天津敏捷教练和软件艺匠@申导回复笔者的微博中:“后写单元测试最主要的(问题)还是:难以入手写测试。需要先读懂(即使是自己写的代码),然后解依赖增加可测性。这个过程中有时不得不在没有任何保护的情况下裸奔重构,很容易引入新bug。”

这里,申导把“单元测试”比作保护代码的“外衣”,没有单元测试保护的代码,就是裸奔代码。代码“只要没有编写测试,我们实际上就不知道修改后的代码是变得更好了还是更糟了。[32]”而“不知道修改后的代码是变得更好了还是更糟了”,就是软件功能稳定性失控的表现。

3. 重复代码导致代码可维护性的失控

导致代码可维护性失控的因素有很多,如果要把这些因素按照恶劣程度从高往低排序的话,排在首位的,当属“重复代码”。

“坏味道行列中首当其冲的就是Duplicated Code(重复代码)[33]”这里的“坏味道”,是测试驱动开发的开创者、美国程序员Kent Beck用来“形容重构时机[34]”的,而软件重构(Refactoring),是代码维护的一种手段。

“最常见的设计问题都出自这样的代码:重复、不清晰和复杂。[35]”这里,重复代码排在了最常见设计问题的首位。

“重复代码是软件中最司空见惯、最刺鼻的坏味。它可能很明显,也可能微妙难寻。完全相同的代码中当然存在明显的重复,而微妙的重复会出现在表面不同但是本质相同的结构或处理步骤中。[36]

从上面的论述能够看出,重复代码是导致代码可维护性失控的首要因素。

1.5         本章小结

国内程序员一般认为,难以理解和难以维护的代码,就是烂代码。国外程序员一般认为,烂代码就是那些没写测试、无法理解、难以修改、结构混乱、重复冗余和依赖全局信息的代码。国外程序员还给烂代码起了两个很有影响力的名字:遗留代码和大泥球。

根据ABC理论,影响一个人的观念,就能影响一个人的行为。而比喻,除了修辞之外,更是人们思维和认知的有力武器,从而帮助我们影响观念,进而影响行为。本书将大量运用比喻的手法,来说明驯服烂代码的观念。从中国人对“控制”的重视,和农耕和游牧的比喻,我们得出了本书对烂代码的定义:失控的代码就是烂代码。而烂代码失控的表现,体现在三个方面:代码可理解性失控、软件功能稳定性失控和代码可维护性失控。

好好的代码,为什么会失控?我们在下一章详细讨论。



[1] 北京设计模式学习组(BeijingDesign Patterns Study Group, BJDP), http://bjdp.yolasite.com/

[2] 王国维原著,施议对译注,《人间词话译注(增订本)》,岳麓书社,2003年9月第1版,第94页

[3] “Legacy Code”和”Bad Code”的出处参见:Michael C. Feathers, Working Effectively with Legacy Code, PrenticeHall PTR, 2004年9月22日, Preface

[4] Brian Foote, Joseph Yoder, Big Ball of Mud Fourth Conference onPatterns Languages of Programs (PLoP ’97/EuroPLoP ’97) Monticello, Illinois,September 1997

[5] “Crap”, “A Mess”和”Impossible to Work with”的出处参见:Daniel Brolund, Ola Ellnestam, Behead Your Legacy Beast, 2012年2月25日,www.agical.com/mikmeth/mikadomethod.pdf

[6] Michael C. Feathers著,刘未鹏译,《修改代码的艺术》,人民邮电出版社,2007年11月第1版,前言

[7] Daniel Brolund, Ola Ellnestam, Behead Your Legacy Beast, 2012年2月25日,Preface,www.agical.com/mikmeth/mikadomethod.pdf

[8] Wikipedia, Big ball of mud, http://en.wikipedia.org/wiki/Big_ball_of_mud

[10] 曾仕强,《情绪管理》,鹭江出版社,2008年1月,第108页

[11] 聂炎,《比喻新论》,宁夏人民教育出版社,2009年11月第1版,第10页

[12]聂炎,《比喻新论》,宁夏人民教育出版社,2009年11月第1版,第198页

[13] Wikipedia, Robert Cecil Martin, http://en.wikipedia.org/wiki/Robert_C._Martin

[14] Robert C. Martin, Agile Principles, Patterns, and Practices in C#,Prentice Hall, 2007, 第21页

[15] Eric Brechner(美),《代码之殇(原书第2版)》,机械工业出版社,2013年5月第1版,第11页

[17] 陈小京,伏宁,黄福高著,《中国地方政府体制结构》,中国广播电视出版社,2001年1月,第3页

[18]陈小京,伏宁,黄福高著,《中国地方政府体制结构》,中国广播电视出版社,2001年1月,第53页

[19] 周作明,中国古代游牧文化与农耕文化之比较,《广西民族研究》1988年第2期,第71页

[20] 肖爱民,《中国古代北方游牧民族两翼制度研究》,人民出版社,2007年12月,第11页

[21] 亦邻真,《亦邻真蒙古学文集》,内蒙古人民出版社,2001年,第551页

[22] 肖爱民,《中国古代北方游牧民族两翼制度研究》,人民出版社,2007年12月,第2~3页

[24] 谷苞总编:《西北通史·序言》,第1页

[25] 张海藩,《软件工程导论(第5版)》,清华大学出版社,2008年2月,第5页

[26] 张海藩,《软件工程导论(第5版)》,清华大学出版社,2008年2月,第4页

[27] Gojko Adzic, Specification by Example, Manning Publications Co.,2011, p30

[28] [美]Robert C.Martin,韩磊译,《代码整洁之道》,人民邮电出版社,2010年1月第1版,第50页

[29] Jeff Atwood, The Ugly American Programmer, March 29, 2009,http://www.codinghorror.com/blog/2009/03/the-ugly-american-programmer.html

[30]  Doug Rosenberg and MattStephens, Use Case Driven Object Modeling with UML, Apress, 2007, p7

[31]  [美]Eric Brechner 著,林峰译,《代码之殇》,机械工业出版社,2013年5月第1版,Mike Zintel所写的第1版前言

[32] Michael C. Feathers著,刘未鹏译,《修改代码的艺术》,人民邮电出版社,2007年11月第1版,前言

[33] [美] MartinFowler著,熊节译, 《重构》,人民邮电出版社,2010年4月第1版,第76页

[34] [美] MartinFowler著,熊节译, 《重构》,人民邮电出版社,2010年4月第1版,第75页

[35] [美] JoshuaKerievsky著,杨光 刘基诚译,《重构与模式(修订版)》,人民邮电出版社,2013年1月第1版,第28页

[36] [美] JoshuaKerievsky著,杨光 刘基诚译,《重构与模式(修订版)》,人民邮电出版社,2013年1月第1版,第30页

 

 

---------------

 

原创文章,转载请注明出处, 谢谢。本文地址:http://blog.csdn.net/wubinben28/article/details/8934702

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Kent Beck、Martin Fowler、Michael C. Feathers、Robert C. Martin、Joshua Kerievsky、Gerard Meszaros等大师们的传世著作为如何提升编程技艺和代码质量提供了思想和原则上的指导,本书则为实践和融合这些思想、原则提供了过程和方法上指导。本书通过编程操练的方式讲述了如何用TDD(测试驱动开发)的方法来驯服代码,通过结对编程的对话形式展示了驯服代码的完整过程,将驯服代码所需要的思想、态度、习惯、方法和技艺完全融入编程操练实践中,是目前最具实践指导意义的驯服代码的著作。, 全书共20章,分为四个部分:第一部分(第1~10章)首先通过测试后行和测试先行两种方法完成了一个名为“码农酒店”(世界时钟)的编程操练题目,然后对这两种方法进行对比,引出了代码的概念,读者能非常直观看出哪一种方法更容易写出代码。第二部分(第11~15章)通过一个名为Trivia(答题闯关游戏)的经典编程操练题目详细讲述了驯服一段代码的过程,首先讲解了代码腐臭的识别和记录,然后讲解了如何通过一系列的重构来清除这些腐臭。第三部分(第16~18章)通过三个编程操练题目详述了编写真正的单元测试的过程,首先讲解了用提取接口的办法编写Stub来进行单元测试,以及用子类化并覆写方法的办法编写Mock来进行单元测试,然后讲解了如何将被测类与文件系统之间的这种不适用于单元测试的耦合,转化为被测类与字符串之间这种适合与单元测试的耦合。第四部分(第19~20章)总结了驯服代码的步骤及方法,首先讲解了TDD开发方法的一种实现:I-EPP-TR方法,该方法中的全面重构的概念对传统的重构概念进行了扩展,然后讨论了如何才能将前面所讨论的良好的编程方法形成习惯并固化下来。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值