《代码整洁之道》:细节之中自有天地,整洁成就卓越代码
概述
软件质量,不但依赖于架构及项目管理,而且与代码质量紧密相关。这一点,无论是敏捷开发流派还是传统开发流派,都不得不承认。《代码整洁之道》提出一种观念:代码质量与其整洁度成正比。干净的代码,既在质量上较为可靠,也为后期维护、升级奠定了良好基础。作为编程领域的佼佼者,这些实践在《代码整洁之道》中体现为一条条规则(或称“启示”),并辅以来自现实项目的正、反两面的范例。只要遵循这些规则,就能编写出干净的代码,从而有效提升代码质量。尽管糟糕的代码也能运行,但如果代码不整洁,会使整个开发团队泥足深陷,写得不好的代码每年都要耗费难以计数的时间和资源。然而这种情况并非无法避免。
本书阅读对象为一切有志于改善代码质量的程序员及技术经理。书中介绍的规则均来自作者多年的实践经验,涵盖从命名到重构的多个编程方面,虽为一“家”之言,然诚有可资借鉴的价值。
作者Robert C. Martin,(Bob大叔)自1970年进入软件行业,从1990年起成为国际软件咨询师。他是《敏捷软件开发:原则、模式与实践》、《敏捷软件开发:原则、模式与实践(C#版)》(邮电)、《极限编程实践》(邮电)等国内引进的畅销书的作者,其中第一本原著荣获美国《软件开发》第13届震憾(Jolt)大奖,Martin的敏捷系列书是软件工程界的权威书籍。本书是他的又一最新力作。
Martin在书中对代码具有革命性的解读阐述了整洁代码的最佳敏捷实践的方法书中介绍规则均来自Martin多年的经验,拥有很高的借鉴价值。
从《代码整洁之道》中可以学到:好代码和糟糕的代码之间的区别:如何编写好代码,如何将糟糕的代码转化为好代码:如何创建好名称、好函数、好对象和好类;如何格式化代码以实现其可读性的最大化:如何在不妨碍代码逻辑的前提下充分实现错误处理;如何进行单元测试和测试驱动开发。
现在的软件系统开发难度主要在于其复杂度和规模,客户需求也不再像Winston Royce瀑布模型期望那样在系统编码前完成所有的设计满足用户软件需求。在这个信息爆炸技术日新月异的时代,需求总是在不停的变化,随之在2001年业界17位大牛聚集在美国犹他州的滑雪胜地雪鸟(Snowbird)雪场,提出了“Agile”(敏捷)软件开发价值观,并在他们的努力推动下,开始在业界流行起来。在《代码整洁之道》(Clean Code),一份整洁的代码在质量上是可靠的,为团队开发,后期维护,重构奠定了良好的基础。在这本书中作者提出了注重实际开发实践的细节,而不是站在空洞的理论来谈论整洁之道。
第1章 整洁代码
糟糕的代码
当时赶着推出产品,代码写得乱七八糟。特性越加越多,代码也越来越烂,最后再也没法管理这些代码了。是糟糕的代码毁了很多公司。
勒布朗(LeBlanc)法则:稍后等于永不(Later equals never)。
混乱的代价
随着混乱的增加,团队生产力也持续下降,趋向于零。当生产力下降时,管理层就只有一件事可做了:增加更多人手到项目中,期望提升生产力。可是新人并不熟悉系统的设计。他们搞不清楚什么样的修改符合设计意图,什么样的修改违背设计意图。而且,他们以及团队中的其他人都背负着提升生产力的可怕压力。于是,他们制造更多的混乱,驱动生产力向零那端不断下降。
花时间保持代码整洁不但有关效率,还有关生存。
整洁代码的艺术
什么是整洁代码?不同的人会站在不同的角度阐述不同的说法。
Bjarne Stroustrup,C++语言发明者,C++ Programming Language(中译版《C++程序设计语言》)一书作者阐述:
我喜欢优雅和高效的代码。代码逻辑应当直截了当,叫缺陷难以隐藏;尽量减少依赖关系,使之便于维护;依据某种分层战略完善错误处理代码;性能调至最优,省得引诱别人做没规矩的优化,搞出一堆混乱来。整洁的代码只做好一件事。
Grady Booch(《面向对象分析与设计》作者)阐述:
“整洁的代码简单直接。整洁的代码如同优美的散文。整洁的代码从不隐藏设计者的意图,充满了干净利落的抽象和直截了当的控制语句。”
我认为整洁的代码:是意图明确、命名规范、符合所使用语言代码规范的;遵守原则、可复用、易扩展、便于维护的。整洁的代码应具有自我解释性,而不需要太多多余的注释。
美国“童子军军规”中有条说道“让营地比你来时更干净”,映射到我们编码中就是我们每日在提交代码的时候要确保代码时整洁的,否则就进行重构。好的代码是靠大家一点点积累出来的。问题发现的越早,修改的成本就越少。如果每次签入时,代码都比签出时干净,那么代码就不会腐坏。清理并不一定要花多少功夫。
整洁的代码就是一种简约(简单而不过于太简单)的设计,阅读代码的人能很清晰的明白这里在干什么,而不是隐涩难懂,整洁的代码读起来让人感觉到就像阅读散文-艺术的沉淀,作者是精心在意缔造出来。
第2章 有意义的命名
名副其实、见名知意
命名包括变量、函数、参数,类等,一个好的命名能够很好的表述其所承载的业务,从命名上就已经很好的答复了为什么存在,做了什么事,应该怎么用等的大部分的问题,阅读者看到它的时候不必去深究其实现细节,一切都在命名上一目了然。一个好的命名必须是名副其实,不存在歧义(双关语或常见属于冲突),直接了当(否定语句或者误导性命名)。
命名小结:
1、当避免使用与本意相悖的词。例如,hp、aix 和 sco 都不该用做变量名,因为它们都是 UNIX 平台或类 UNIX 平台的专有名称。
2、做有意义的区分。 ProductInfo 或 ProductData 类,那它们的名称虽然不同,意思却无区别。
3、使用读得出来的名称。人类长于记忆和使用单词。
4、使用可搜索的名称。单字母名称和数字常量有个问题,就是很难在一大篇文字中找出来。
5、避免使用编码。
6、类名和对象名应该是名词或名词短语,类名不应当是动词。
7、方法名应当是动词或动词短语。
8、命名别用双关语。
9、命名使用解决方案领域名称。
取好名字最难的地方在于需要良好的描述技巧和共有文化背景。与其说这是一种技术、商业或管理问题,还不如说是一种教学问题。其结果是,这个领域内的许多人都没能学会做得很好。
我们有时会怕其他开发者反对重命名。如果讨论一下就知道,如果名称改得更好,那大家真的会感激你。多数时候我们并不记忆类名和方法名。我们使用现代工具对付这些细节,好让自己集中精力于把代码写得就像词句篇章、至少像是表和数据结构(词句并非总是呈现数据的最佳手段)。改名可能会让某人吃惊,就像你做到其他代码改善工作一样。别让这种事阻碍你的前进步伐。
第3章 函数
从汇编/C时代开始的到现在函数一直都存在与我们开发中不可或缺的一部分,结构化组织,重用作为函数式语言的一等公民,所有程序的第一组代码。
函数小结:
1. 好的函数必须足够的小,其次还是足够的小。很容易想像阅读上千行的代码,是多么巨大的自我心理挑战,在实习的时候工作于毫无分层逻辑的WinForm平台下,完全依赖RAD模式带来后置cs页面上千行的代码,每次修改都令我恼怒,恨不得重写整个业务逻辑。
2. 一个函数在于短小精悍,只作一件事情,并做好这件事,只做一件事才能得到更好的利用函数名表述自己。
3. 每个函数一个抽象层级。
4. 好的函数还应该是CQS(查询命令分离)无副作用的(不存在隐藏歧义的背后逻辑),并对其他类型不存在“依恋情节(Feature Envy)“(类中的变量被所有的函数使用这是理想的高内聚,万物皆有其位,而后物尽归其位)。
5. 函数的参数应该足够的少,无最好,一次之,再次为二,尽量避免三个以及三个以上,对于太多的参数你可能该采用IntroduceParameterObject(引入参数对象)。
6. 使用异常替代返回错误码。
7. 函数应该只做一件事。
8. 不要重复的代码。重复在软件系统是万恶的,我们熟悉的分离关注点,面向对象,设计原则…都是为了减少重复提高重用,Don’t repeat yourself!(DRY)。远离重复,拒绝重复,方法有很多,抽象到基类或放到底层公共类库中。
写代码和写别的东西很像。在写论文或文章时,你先想什么就写什么,然后再打磨它。初稿也许粗陋无序,你就斟酌推敲,直至达到你心目中的样子。没有人能一次性就将函数写的很完美,好的函数是通过重构得到的。
我写函数时,一开始都冗长而复杂。有太多缩进和嵌套循环。有过长的参数列表。名称是随意取的,也会有重复的代码。不过我会配上一套单元测试,覆盖每行丑陋的代码。然后我打磨这些代码,分解函数、修改名称、消除重复。我缩短和重新安置方法。有时我还拆散类。
第4章 注释
注释是一把双刃剑,好的注释能够给我们好的指导,不好的注释只会将我们误导。注释是弥补代码子表述不足的一种手段,就像设计模式是用来弥补语言不足一样。
代码是我们获取信息的准确来源,注释随着项目人员的更替 反复的修改最终可能词不达意了,因为很多开发人员在整合代码,修改方法的时候并不是总是同步修改注释。
有时候看到一个函数的代码写的很糟糕,逻辑很混乱,有开发人员可能想,给这个函数加上几行注释,这样有可能起到适得其反的作用,这时要做的是将函数整理干净。
并不是写出完备的注释就是好的开发人员,如果代码清晰的表述自己意图,那么注释反而多余。在《重构-改善现有代码之道》中Martin Fowler指出多余的注释是一种代码坏味道。就是好的注释随着项目的维护不断的重构很多时候也会变得不那么适应,而我们很少会去主动维护。再则误导性的注释更为使用者所憎恨。当然有时我们也得使用注释,注释并不是万恶的,当我们没法用代码来描述自己的时候,我们需要注释去描述意图;多余有副作用的代码给使用者提供警告注释。TODO开发时进度控制,比如你在进行较大规模领域重构,目前有些逻辑不再适应,不那么自然,而对它的重构还在任务列表最后,你可以选择标注在TODO中,最后完成从ToDoList中去掉每一个TODO任务。
代码即注释,很多书和大师都这么讲,意思是我们要用代码本身来解释我们的意图,那就要求我们要控制好函数只做一件事,函数名和变量名要规范和可读。
当然也不是所有的注释都没有用,像下面几种类型的注释是有必要的:
· 具有警示性的注释;
· 描述一些负责业务场景;
· 有些函数现在还是一个空壳,但在将来可能有用,有必要写。
当我们不得不写一些注释的时候,要确保言简意赅,能够很好的表达意思,不要造成误解,也不要写多余的废话。"Comments Do Not Make Up for Bad Code."(注释不是对劣质代码的补救)。事实上好的代码即便没有注释也拥有良好的可读性,但恰当的注释会让代码变得更可读、可维护性更高。
日志式注释,一般出现在一个类的开始部分,记录每次修改时的时间、人员名称和修改内容。随着时间的推移这类注释会变得非常冗长。这类注释在一些项目中很普遍,而且有时会被严格要求写,但书中强调现在的源代码都会有源代码工具来进行管理,修改记录在源代码工具中有保存,这种日志式的注释应该全部删除。因为别人下载了你的源码之后,很可能会改成他的名称。
项目代码中经常会出现被注释掉的代码,这对后面的维护人员会造成困扰,也会使代码变得混乱,这种代码同样可以删掉。因为阅读者完全不知道被注释掉的代码,是什么意图、原作者为什么把它注释,也可能代码是被更改了很多次,但注释依旧保留在那,越积越多。因此作者的建议:果断删除。
第5章 格式
或许你认为“让代码能工作”才是专业开发者的第一优先级。你今天编写的功能,极有可能在下一版本中被修改,但代码的可读性却会对以后可能发生的修改行为产生深远影响。原始代码修改之后很久,其代码风格和可读性仍会影响到可维护性和扩展性。即便代码已不复存在,你的风格和律条仍存活下来。
良好的代码格式,会使得我们阅读更容易,一套共同的格式会让我们查找理解更快速。每个团队都应该遵循一套固定的代码格式规范,整个软件系统的统风格统一,而不是各自为政各成一体。
用什么样的代码风格不是关键,关键是整个项目组的成员应当使用相同的代码风格,让多个人编写的代码看起来像一个人书写的。我个在代码中使用的括号风格是1TBS(One True Bracing Style,也叫做K&R风格,这种风格是Kernighan和Ritchie两位老师在"The C Programming Language"一书中使用的代码风格),当然Allman风格(FreeBSD系统的作者之一使用的代码风格)也是很好的选择。
格式小结:
1、合理使用空白行,同一类的有关联代码行放在一起。
2、局部变量申明尽可能靠近使用的地方。
3、实体变量放在类的顶部,因为设计良好的类,实体变量会被大多数的方法使用。
4、相关函数(A函数调用了B函数,B函数应放在A函数的下面)放在一起,也包括的重载的函数。被调用的函数应该放在执行调用的函数下面,建立一种自顶向下贯穿的良好信息流。
5、一行代码的宽度:120字符之内,最好不要拖动横向滚动条。
6、横向空格,比如一个表达式中的符号左边和右边与符号之间加空格,这个在VS中的代码格式化会自动帮我们做了。
7、缩进,只有一行的也按缩进规则来。
8、向报纸学习:看看写得很好的报纸。从上到下阅读,在顶部,有头条,告诉你故事主题。然后第一段是大纲,接着细节渐次增加。源代码应当一样,名称简单且一目了然。源文件最顶部应该给出高层次概念和算法。细节应该往下渐次展开,直到最底层的函数和细节。报纸有许多篇文章,多数短小精悍。假若一份报纸只登一篇长篇故事,没人会去读它。
现代IDE(集成开发环境)几乎都有代码格式化代码的功能,你只需要设置好你使用的代码风格就可以了,其实不只是IDE,很多高级的文本编辑工具也能够按照指定的风格格式化你的代码。
大师级程序员把系统当作故事来讲,而不是当作程序来写。记住,编程是一种艺术。