好文章系列——代码整洁之道

Posted 小金乌会发光-Z&M

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了好文章系列——代码整洁之道相关的知识,希望对你有一定的参考价值。

注:初入职场,作为一个程序员,要融入项目组的编程风格,渐渐地觉得系统地研究下如何写出整洁而高效的代码还是很有必要的。与在学校时写代码的情况不同,实现某个功能是不难的,需要下功夫的地方在于如何做一些防御性的代码设计等,以使得自己写出的代码能够应对各种意外的情况。

 BTW,通过阅读发现了“整洁代码之道”相关的几篇写得还不错的文章,挑出一些感兴趣的地方以提醒自己,提升代码的健壮性、高效性等。

(1)整洁代码之道——重构

(文章来源:http://www.infoq.com/cn/articles/clean-code-refactor  作者 南志文

写在前面

现在的软件系统开发难度主要在于其复杂度和规模,客户需求也不再像Winston Royce瀑布模型期望那样在系统编码前完成所有的设计满足用户软件需求。在这个信息爆炸技术日新月异的时代,需求总是在不断的变化,随之在2001年业界17位大牛聚集在美国犹他州的滑雪胜地雪鸟(Snowbird)雪场,提出了“Agile”(敏捷)软件开发价值观,并在他们的努力推动下,开始在业界流行起来。在《代码整洁之道》一书中提出:一种软件质量,可持续开发不仅在于项目架构设计,还与代码质量密切相关,代码的整洁度和质量成正比,一份整洁的代码在质量上是可靠的,为团队开发,后期维护,重构奠定了良好的基础。

接下来笔者将结合自己之前的重构实践经验,来探讨平时实际开发过程中我们注重代码优化实践细节之道,而不是站在纯空洞的理论来谈论代码整洁之道。

在具体探讨如何进行代码优化之前,我们首先需要去探讨和明确下何谓是“代码的坏味道”,何谓是“整洁优秀代码”。因为一切优化的根源都是来自于我们平时开发过程中而且是开发人员自己产生的“代码坏味道”。

代码的坏味道

如果尿布臭了,就换掉它。”-语出Beck奶奶,论抚养小孩的哲学。同样,代码如果有坏味道了,那么我们就需要去重构它使其成为优秀的整洁代码。

 谈论到何谓代码的坏味道,重复代码(Duplicated Code)首当其冲。重复在软件系统是万恶的,我们熟悉的分离关注点,面向对象设计原则等都是为了减少重复提高重用,Don’t repeat yourself(DRY)。关于DRY原则,我们在平时开发过程中必须要严格遵守。

其次还有其他坏味道:过长函数(Long Method)、过大的类(Large Class)、过长参数列表(Long Parameter List)、冗余类(Lazy Class)、冗余函数(Lazy Function)无用函数参数(Unused Function Parameter)、函数圈复杂度超过10(The Complexity is over 10)、依恋情结(Feature Envy)、Switch过多使用(Switch Abuse)、过度扩展设计(Over-extend design)、不可读或者可读性差的变量名和函数名(unread variable or function name)、异曲同工类(Alternative Classes with Different Interfaces)、过度耦合的消息链(Message Chains)、令人迷惑的临时字段(Temporary Field)、过多注释(Too Many Comments)等坏味道。

整洁代码

什么是整洁代码?不同的人会站在不同的角度阐述不同的说法。而我最喜欢的是Grady Booch(《面向对象分析与设计》作者)阐述:

整洁的代码简单直接。整洁的代码如同优美的散文。整洁的代码从不隐藏设计者的意图,充满了干净利落的抽象和直截了当的控制语句。

整洁的代码就是一种简约(简单而不过于太简单)的设计,阅读代码的人能很清晰的明白这里在干什么,而不是隐涩难懂,整洁的代码读起来让人感觉到就像阅读散文-艺术的沉淀,作者是精心在意缔造出来。

整洁代码是相对于代码坏味道的,如何将坏味道代码优化成整洁代码,正是笔者本文所探讨的重点内容:整洁代码之道-重构,接下来笔者将从几个角度重点描述如何对软件进行有效有技巧的重构。

重构 — Why

在软件开发过程中往往开发者不经意间就能产生代码的坏味道,特别是团队人员水平参差不齐每个人的经验和技术能力不同的情况下更容易产生不同阶段的代码坏味道。并且随着需求的迭代和时间推移,代码的坏味道越来越严重,甚至影响到团队的开发效率,那么遇到这个问题该如何去解决。

在软件开发Coding之前我们不可能事先了解所有的需求,软件设计肯定会有考虑不周到不全面的地方,而且随着项目需求的Change,很有可能原来的代码设计结构已经不能满足当前需求。

更何况,我们很少有机会从头到尾参与并且最终完成一个项目,基本上都是接手别人的代码,即使这个项目是从头参与的,也有可能接手团队其他成员的代码。我们都有过这样的类似的抱怨经历,看到别人的代码时感觉就像垃圾一样特别差劲,有一种强烈的完全想重写的冲动,但一定要压制住这种冲动,你完全重写,可能比原来的好一点,但浪费时间不说,还有可能引入原来不存在的Bug,而且,你不一定比原来设计得好,也许原来的设计考虑到了一些你没考虑到的分支或者异常情况。

我们写的代码,终有一天也会被别人接手,很可能到时别人会有和我们现在一样的冲动,所以开发者在看别人代码时候,要怀着一颗学习和敬畏之心,去发现别人的代码之美,在这个过程中挑出写的比较好的优秀代码,吸取精华,去其糟粕,在这个基础上,我们再去谈重构,那么你的重构会是一个好的开端。

总之,我们要做的是重构不是重写,要先从小范围的局部重构开始,然后逐步扩展到整个模块

重构 — 作用

重构,绝对是软件开发写程序过程中最重要的事之一。那么什么是重构,如何解释重构。名词:对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。动词:使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。

重构不只可以改善既有的设计结构,还可以帮助我们理解原来很难理解的流程。比如一个复杂的条件表达式,我们可能需要很久才能看明白这个表达式的作用,还可能看了好久终于看明白了,过了没多长时间又忘了,现在还要从头看,如果我们把这个表达式运用Extract Method抽象出来,并起一个易于理解的名字,如果函数名字起得好,下次当我们再看到这段代码时,不用看逻辑我们就知道这个函数是做什么的。

如果对这个函数内所有难于理解的地方我们做了适当的重构,把每个细小的逻辑抽象成一个小函数并起一个容易理解的名字,当我们看代码时就有可能像看注释一样,不用再像以前一样通过看代码的实现来猜测这段代码到底是做什么的,我一直坚持和秉持这个观点:好的代码胜过注释,毕竟注释还是有可能更新不及时的,不及时最新的注释容易更其他人带来更多的理解上的困惑。

此外重构可以使我们增加对代码和业务逻辑功能的理解,从而帮助我们找到Bug;重构可以帮助我们提高编程速度,即重构改善了程序结构设计,并且因为重构的可扩展性使添加新功能变得更快更容易。

重构 — 时机

理解了重构的意义和作用,那么我们何时开始重构呢?笔者一直坚持这种观点:重构是一个持续的系统性的工程,它是贯穿于整个软件开发过程中,我们无需专门的挑出时间进行重构,重构应该随时随地的进行,即遵循三次法则:事不过三,三则重构。这个准则表达的意思是:第一次去实现一个功能尽管去做,但是第二次做类似的功能设计时会产生反感,但是还是会去做,第三次还是实现类似的功能做同样的事情,那你就应该去重构。三次准则比较抽象,那么对应到我们具体的软件开发流程中,一般可以在这三个时机去进行:

(1) 当添加新功能时如果不是特别容易,可以通过重构使添加特性和新功能变得更容易。在添加新功能的时候,我们就先清理这个功能所需要的代码。花一点时间,用滴水穿石的方法逐渐清理代码,随着时间的推移,我们的代码就会越来越干净,开发速度也会越来越快。

(2) 修改Bug的时候去重构,比如你在查找定位Bug的过程中,发现以前自己的代码或者别人的代码因为设计缺陷比如可扩展性、健壮性比较差造成的,那么此时就是一个比较好的重构时机。可能这个时候很多同学就有疑问了,认为我开发要赶进度,没有时间去重构,或者认为我打过补丁把Bug解决不就行了,不需要去重构。根据笔者之前多年的经验得出的结论:遇到即要解决即那就是每遇到一个问题,就马上解决它,而不是选择绕过它。完善当前正在使用的代码,那些还没有遇到的问题,就先不要理它。在当前前进的道路上,清除所有障碍,以后你肯定还会再一次走这条路,下次来到这里的时候你会发现路上不再有障碍。

软件开发就是这样。或许解决这个问题需要你多花一点时间。但是从长远来看,它会帮你节省下更多的时间。也就是重构是个循序渐进的过程,经过一段时间之后,你会发现之前所有的技术债务会逐步都不见了,所有的坑相继都被填平了。这种循序渐进的代码重构的好处开始显现,编程的速度明显会加快。

(3)Code Review时去重构,很多公司研发团队都会有定期的Code Review,这种活动的好处多多,比如有助于在开发团队中传播知识进行技术分享,有助于让较有经验的开发者把知识传递给欠缺经验的人,并帮助更多的人对软件的其他业务模块更加熟悉从而实现跨模块的迭代开发。Code Review可以让更多的人有机会对自己提出更多优秀好的建议。同时重构可以帮助审查别人的代码,因为在重构前,你需要先阅读代码得到一定程度的理解和熟悉,从而提出一些建议和好的idea,并考虑是否可以通过重构快速实现自己的好想法,最终通过重构实践你会得到更多的成就感满足感。为了使审查代码的工作变得高效有作用,据我以前的经验,我建议一个审查者和一个原作者进行合作,审查者提出修改建议,然后两人共同判断这些修改是否能够通过重构轻松实现,如果修改成本比较低,就在Review的过程中一起着手修改。

如果是比较大型比较复杂的设计复查审核工作,建议原作者使用UML类序列图、时间序列图、流程图去向审查者展现设计的具体实现细节,在整个Code Review中,审查者可以提出自己的建议或者修改意见。在这种情景下,审查者一般由团队里面比较资深的工程师、架构师、技术专家等成员组成。

关于Code Review的形式,还可以采取极限编程中的“结对编程”形式。这种形式可以采取两个人位置坐在一起去审查代码,可以采取两个平台比如iosandroid 的开发人员一起去审查,或者经验资深的和经验不资深的人员一起搭配去审查。

重构的这三个时机要把握好原则,即什么时候不应该重构,比如有时候既有代码实现太混乱啦,重构它还不如重新写一个来得简;此外,如果你的项目已经进入了尾期,此时也应该避免重构,这时机应该尽可能以保持软件的稳定性为主。

理解了重构是做什么,重构的作用,为什么要重构,以及重构的时机,我们对重构有了初步认识,接下来笔者重点篇幅来讲解如何使用重构技巧去优化代码质量达成Clean Code .

重构技巧 — 函数重构

重构的源头一切从重构函数开始,掌握函数重构技巧是重构过程中很关键的一步,接下来我们来探讨下函数重构有那些实用技巧。

  • 重命名函数(Rename Function Name) : Clean Code要求定义的变量和函数名可读性要强,从名字就可以知道这个变量和函数去做什么事情,所以好的可读性强的函数名称很重要,特别是有助于理解比较复杂的业务逻辑。

  • 移除参数(Remove Parameter): 当函数不再需要某个参数时,要果断移除,不要为了某个未知需求预留参数,过多的参数会给使用者带来参数困扰。

  • 将查询函数和修改函数分离:如果某个函数既返回对象值,又修改对象状态。这时候应该建立两个不同的函数,其中一个负责查询,另一个负责修改。如果查询函数只是简单的返回一个值而没有副作用,就可以无限次的调用查询函数。对于复杂的计算也可以缓存结果。

  • 令函数携带参数:如果若干函数做了类似的工作,只是少数几个值不同导致行为略有不同,合并这些函数,以参数来表达不同的值。

  • 以明确函数取代参数:有一个函数其中的逻辑完全取决于参数值而采取不同行为,针对该参数的每一个可能值建立一个单独的函数。

  • 保持对象完整性:如果你需要从某个对象取若干值,作为函数的多个参数传进去,特别是需要传入较多参数比如5个参数或者更多参数时,这种情况建议直接将这个对象直接传入作为函数参数,这样既可以减少参数的个数,增加了对象间的信赖性,而且这样被调用者需要这个对象的其他属性时可以不用人为的再去修改函数参数。

  • 以函数取代参数:对象调用某个函数,并将所得结果作为参数传递给另外一个函数,而那个函数本身也能够调用前一个函数,直接让那个函数调用就行,可以直接去除那个参数,从而减少参数个数。

  • 引入参数对象:某些参数总是同时出现,新建一个对象取代这些参数,不但可以减少参数个数,而且也许还有一些参数可以迁移到新建的参数类中,增加类的参数扩展性。

  • 移除设值函数(Setting Method):如果类中的某个字段应该在对象创建时赋值,此后就不再改变,这种情景下就不需要添加Setting method。

  • 隐藏函数:如果有一个函数从来没有被其他类有用到,或者是本来被用到,但随着类动态添加接口或者需求变更,之后就使用不到了,那么需要隐藏这个函数,也就是减小作用域。

  • 以工厂函数取代构造函数:如果你希望创建对象时候不仅仅做简单的构建动作,最显而易见的动机就是派生子类时根据类型码创建不同的子类,或者控制类的实例个数。

重构技巧 — 条件表达式

  • 分解条件表达式:如果有一个复杂的条件语句,if/else语句的段落逻辑提取成一个函数。

  • 合并条件表达式:一系列条件测试,都得到相同的测试结果,可以将这些测试表达式合并成成一个,并将合并后的表达式提炼成一个独立函数,如果这些条件测试是相互独立不相关的,就不要合并。

  • 合并重复的条件片段:在条件表达式的每个分支上有着相同的一段代码,把这段代码迁移到表达式之外。

  • 移除控制标记:不必遵循单一出口的原则,不用通过控制标记来决定是否退出循环或者跳过函数剩下的操作,直接break或者return。

  • 以卫语句替代嵌套条件表达式:条件表达式通常有两种表现形式,一:所有分支都属于正常行为;二:只有一种是正常行为,其他都是不常见的情况。对于一的情况,应该使用if/else条件表达式;对于二这种情况,如果某个条件不常见,应该单独检查条件并在该条件为真时立即从函数返回,这样的单独检查常常被称为卫语句。

  • 以多态取代条件表达式:如果有个条件表达式根据对象类型的不同选择而选择不同的行为,将条件表达式的每个分支放进一个子类内的覆写函数中,将原始函数声明为抽象函数。

  • 引入Null对象:当执行一些操作时,需要再三检查某对象是否为NULL,可以专门新建一个NULL对象,让相应函数执行原来检查条件为NULL时要执行的动作,除NULL对象外,对特殊情况还可以有Special对象,这类对象一般是Singleton.

  • 引入断言:程序状态的一种假设

  • 以MAP取代条件表达式:通过HashMap的Key-Value键值对优化条件表达式,条件表达式的判断条件作为key值,value值存储条件表达式的返回值。

  • 通过反射取代条件表达式:通过动态反射原理

重构技巧 — 案例

Map去除if条件表达式

关于该技巧的实现方法,上章节有讲述,我们直接看代码案例如下代码所示:

原始的条件表达式代码如下图1所示:

public static int getServiceCode(String str){
     int code = 0;
     if(str.equals("Age")){
         code = 1;
     }else if(str.equals("Address")){
         code = 2;
     }else  if(str.equals("Name")){
         code = 3;
     }else if(str.equals("No")){
         code = 4;
     }
     return  code;
 }

重构后的代码如下所示:

public static void initialMap(){
     map.put("Age",1);
     map.put("Address",2);
     map.put("Name",3);
     map.put("No",4);
 }

上述代码是直接通过Map结构,将条件表达式分解, Key 是条件变量,Value是条件表达式返回值。取值很方便,显然高效率O(1)时间复杂度取值。这种重构技巧适合于比较简单的条件表达式场景,下面是比较复杂的没有返回值的条件表达式场景,我们去看看如何处理。

反射去除分支结构

原始的条件表达式代码如下图1所示:

图1 条件表达式示范

图2 通过Map和反射重构示范

如上图2所示,通过Map和反射去分解条件表达式,将条件表达式分支的逻辑抽取到子类中的覆写函数中,提取了共同的抽象类,里面包含抽象接口 handleBusinessData,子类继承实现它。

多态取代条件表达式

图3 重构后的案例结果图

图4 重构后的案例-多态如何使用

图5 重构后的代码结构图

(点击放大图像)

图6 重构-抽象类、简单工厂模式思想去实现条件表达式的分解

如上图6所示,在原始的条件表达式中,有两个条件表达式分支(分支逻辑):

  • 中文入住人操作HotelCNPasserngerOperaton类

  • 英文入住人操作HotelEnPassengerOperation 类

共同抽取了基类抽象类:AbstractPassengerOperation,其两个分支子类去继承抽象类。

为了分解条件表达式,笔者采取了多态的重构技巧去实现,具体有两种实现方式,第一种实现方式是采用抽象类去实现多态,代码结构图如图5 passenger文件夹,UML类图如上图6所示。第二种实现方式是采用接口去实现多态,代码结构如图5 passenger2 文件夹,UML类图如上图7所示。

图7重构-接口状态者模式思想去实现条件表达式的分解

如上图7所示,在原始的条件表达式中,有两个条件表达式分支(分支逻辑),其分支逻辑分别放在了子类HotelCNPassengerState 和 HotelENPassengerState中,统一提取了接口类 PassengerState类,里面包含子类都需要实现的两个基础接口。从图7,可以看出,是使用了状态者模式。

经过了上述重构之后,我们达成了什么效果:

  • 逻辑清晰

  • 主逻辑代码行数减少

  • 业务逻辑,更好的封装解藕,无需关注具体的业务细节

  • 采用了多态、抽象、状态模式、工厂模式、Build模式的等不同的思想和方法,很多不同的重构技巧去重构一个功能,值得推广和借签;

 

(2)【读书笔记】--代码整洁之道

 (文章来源:http://www.cnblogs.com/stoneniqiu/p/4815259.html  作者 stoneniqiu

 

 “相对于任何宏伟景愿,对细节的关注甚至是更为关键的专业性基础。首先,开发者通过小型实践获得可用于大型实践的技能和信用度。其次,宏伟建筑中最细小的部分,比如关不紧的门,有点儿没有铺平的地板,甚至是凌乱的桌面,都会将整个大局的魅力毁灭殆尽。这就是整洁代码之所系”----没有比书中的这段话更能说明这本书的意义了。

     

      《代码整洁之道》是第1期书山有路活动选出的读本。相对于记住那些如何写出整洁代码的那些法则,养成保持代码整洁、提高代码质量的习惯和思维更为重要。全书大致分为三个部分,第一部分1-10章都是介绍如函数、类、命名、单元测试等保持整洁的建议。第二部分11-13章从系统设计的层面提倡用AOP、IOC等方式保持整洁,或者合适的时候使用并发编程。第三部分14-17章以及后面的附录,作者以JAVA源码(全书都是以JAVA代码示例)来实际讲解如何保持整洁。

later equals never

 我们都曾经瞟一眼自己亲手造成的混乱,决定弃之不顾,走向新的一天。我们都曾经看到自己的烂程序居然能运行,然后断言能运行的烂程序总比什么都没有强,我们都曾经说过有朝一日再回头清理。当然,在那些日子里。我们都没有听过布朗法则:later equals never 稍后等于永不.

       ps:看到这句话确实有点惭愧。印象最深的感觉就是,我们去看一两年前自己的代码。那是写的什么玩意,真的是自己都看不下去。要让我改,我情愿再实现一个。所以时刻保持好的习惯是多么重要。不要想着以后再解决。就像领导说,这个事情以后再考虑,然后就没有然后了。

第一章 整洁代码    

 怎样是整洁的代码?
Bjarne Stroustrup(C++发明者) 说:
  “我喜欢优雅和高效的代码,代码逻辑应当直接了当,叫缺陷难以隐藏;尽量减少依赖关系,使之便于维护;依据某种分层战略完善错误处理代码;性能调至最优,省得引诱别人做没有必要的优化,搞出一堆混乱来,整洁的代码之做好一件事。
  
Ron Jeffries 对整洁代码的理解:
  1.能通过所有的测试。
  2.没有重复代码。
  3.体现系统中的全部设计理念。
  4.包含尽量少的实体、比如类、方法、函数等。
 “在以上诸项中,我最在意的是代码重复。如果一段代码重复出现,就表示某种想法未在代码中得到良好的提现。我会尽力去找出到底那是什偶么,然后在尽力的更清晰的表达出来。”
 
[---PS:总结下就是整洁的代码1.职责明确,没有多余,2.减少依赖,便于维护。3.高效。---]
 
第二章  有意义的命名 
 1.名副其实。 说起来很简单。选个好名字需要花时间,但省下的时间比花掉的多。注意命名,一旦有好的命名,就换掉旧的。
 int d;// 消失的时间,以日计。
 int elapsedTimeInDays;

2.避免误导。比如不是List类型,就不要用个accountList来命名,这样形成误导。

3.做有意的区分。 
Public static void copyChars(char a1[],char a2[]){
for(int i=0;i<a1.length;i++)
{
a2[i]=a1[i]; } }

如果参数名称改为source和destination ,这个函数就会像样很多。废话都是冗余的,Variable一词 永远不应当出现在变量名中。Table一词永远不应当出现在表名中。NameString 会比 Name好吗,难道Name 会是一个浮点数不成?如有一个Customer的类,有又一个CustomerObject的类。是不是就凌乱了。

4.使用便于搜索的的名称

单个字母或者数字常量是很难在一大堆文章中找出来。比如字母e,它是英文中最常用的字母。长名胜于短名称,搜得到的名称胜于自编的名称。 窃以为单字母的名称仅用于短方法中的本地变量。名称长短应与其作用域大小相对应。

5.类名应该是名词或短语,像Customer,Account,避免使用Manager,Processor,Data或者Info这样的类名。类名不应当是动词。方法名应该是动词或动词短语,如postPayment ,deletePage或Save,属性访问、修改和断言应该根据其值来命名,并加上get,set,is这些前缀。
6.别扮可爱,耍宝,比如谁知道HolyHandGrenada 函数是干什么的,没错这个名字挺伶俐,但是不过DeleteItems或许是更好的名字。
7.每个概念对应一个词。并且一以贯之。 
 在一堆代码中有Controller,又有manager,driver。就会令人困惑。比如DeviceManager和Protal-Controller之间又什么本质区别?
 
第三章 函数
 
1.函数的第一规则是要短小,第二条规则是还要更短小。
2.函数应该做一件事。做好这件事。只做这一件事。
3.尽量少的函数参数。有两个参数的函数要比一元函数的难懂。如果需要三个或者三个以上的参数应该封装成类了。
4.不要重复自己。
 
第四章 注释
 
注释的恰当用法是弥补我们在用代码表达意图时遭遇的失败。作者认为注释是一种失败,我们总无法找到不用注释就能表达自我的方法,所以总要有注释,这并不值得庆贺。写注释的常见动机之一是糟糕代码的存在。带有少量注释的整洁而有表达力的代码,要比带有大量注释的零碎而复杂的代码像样的多。与其花时间编写解释你搞出的糟糕的代码注释,不如花时间清洁那堆糟糕的代码。
 
[---PS:这段话看起来可能有些过激。我们确实可以通过好的编码习惯减少不必要的注释。不过现在自动生成文档的技术都是从代码的注释中提取的。如果是这种情况,上司肯定是要求你写完备的注释的。而且注释还是更有助于增强理解,避免偏差,前提是有好的注释归纳能力。]
 
好的注释:
 1. 法律信息。有时,公司代码规范要求编写与法律有关的注释。例如版权和著作申明。
 2.提供信息的注释。   
// returen an instance of the Responder being tested
protected abstract Responder responderInstance();

 不过作者认为将函数名重新命名为 responderBeingTested,注释就是多余的。

 3.对意图的解释。 有时注释不仅提供了有关实现的有用信息,而且还提供了某个决定后面的意图。
 4.阐释。有时注释把某种晦涩难明的参数或返回值的意义翻译为某种可读形式。也会是有用的。特别是参数或者返回值是某个标准库的一部分,或者你不能修改代码,那帮助阐释其含义的代码就会有用,例如:
assertTrue(bb.compareTo(ba)==1);//bb>aa
assertTrue(a.compareTo(b)==-1);//a<b

直接看方法可能不明确,但有注释就明白多了。我看这2,3,4都是一个意思。就是说明是干嘛的。

5.警示,告诉别人要注意这个方法之类的。

6.放大。有的代码可能看着有点多余,但编码者当时是有他自己的考虑,这个时候需要注释下这个代码的重要性。避免后面被优化掉。

第五章 格式

纵向格式:
1. 函数与函数之间留空行。
2.变量声明:变量声明应该尽可能靠近其使用位置。因为函数很短,本地变量应该在函数的顶部出现。
3.实体变量 应该在内的顶部,相当于我们的field 字段,会被使用的多。
4.相关函数,如果某个函数调用另外一个,就应该把他们放在一起,而且调用者应该尽可能放在被调用者的上面。这样这个程序就会自然有序。(之前我喜欢把private的方法 放到一起。当然这确实没有什么实际的意义)
5.“相关概念的代码放在一起。相关性越强,比如一个大功能逻辑靠在一起。” (更多的时候我喜欢用 region 来收起来。)
 横向格式:

1.一行的长度,作者建议是上限是120个字符

PS 平时我们都是按照自己的屏幕大小来决定,当然太长了,自己也不便阅读,又不是压缩的js文件

2.赋值语句两端留空。
a = b ; 
3.不在函数名和左括号间加空格。因为函数与其参数密切相关。
4.缩进。源文件是一种继承结构,而不是一种大纲结构,继承结构中的每一层级都圈出一个范围, 也就是代码块,其中有声明语句和执行语句。要体现这种继承结构,就要对源代码进行缩进处理。但有时候我们会把if语句,while循环,或小函数写成一行,但这样没有层级的概念,不便阅读,还是缩进的好。

第六章 对象和数据结构

1.过程式代码(函数编程)便于在不改动既有数据结构的前提下添加新函数,面向对象代码便于在不改动既有函数的前提下添加新类。反过来讲也说的通,过程式代码难以添加新的数据结构,因为必须修改所有函数,面向对象代码难以添加新函数,因为必须修改所有类。所以在设计的时候要分析好是以后是要添加新函数还是要添加新的数据结构。

2.德墨忒尔律:模块不应该了解它所操作对象内部情形。比如C的方法f只能调用以下对象的方法。

  • C
  • 由f创建的对象
  • 作为参数传递给f的对象
  • C的实体变量持有的变量
var outpath=cxt.getOptions().getScart().getAbsolutePath();

这个代码就违反了上面的德墨忒尔律,调用了返回值的方法。这样就是暴露了内部结构。

第七章 异常处理

 1.try代码就像是事务,catch代码块将程序维持在一种持续状态。在编写可能抛出异常的代码时,最好先写出try-catch-finally 语句。

 2.根据需要定义异常类。对错误分类的方式有多种,可以依据来源,是组件还是其他地方,或者依据类型,是设备错误还是网络错误。不过在我们定义异常类的时候,最重要的考虑是如何捕获它们。

 3.别返回null值。程序中不断的看到检测null值的代码,一处漏掉检测就可能会失控。返回Null,作者认为这种代码很糟糕。建议抛出异常 或者返回特定对象(默认值)。更早的发现问题。同理,也应该避免传递Null值给其他的方法。

PS:在大多数的编程语言中,没有良好的方法能对付由调用者意外传入的null值。我们发布产品应该有容错的机制,程序不能轻易的就崩掉,有异常应该及时记录下来或给出提示。

第八章 边界

 有时候我们在使用第三方程序包或者开源代码的时候,或者依靠公司其他团队的代码,我们都得干净利落的的整合进自己的代码中。这一章就是介绍保持保持软件边界整洁的实践手段和技巧。

1.对第三方进行学习性测试,当第三方程序包发布了新的版本,我们可以允许学习性测试,看看程序包的行为有没有发生改变。

2.使用尚不存在的代码,有时候我们的第三方,还没有开发好API,但又不能影响到我们的开发进度,所以我们先可以定义好自己想要的接口。如果第三方ok了,我们再对接起来。
3.通过接口管理第三方边界,可以使用ADApter模式将我的接口转换为第三方提供的接口。这个是要注意,第三方的代码和自己的代码混合太多,这样不便管理。
 
第九章  单元测试 
 敏捷和TDD运动鼓舞了许多程序员编写自动化单元测试,单元测试是确保代码中的每个犄角旮旯都如我们所愿的工作。
 TDD三定律
  1. 除非这能让失败的单元测试通过,否则不允许去编写任何的生产代码。
  2. 只允许编写刚好能够导致失败的单元测试。 (编译失败也属于一种失败)
  3. 只允许编写刚好能够导致一个失败的单元测试通过的产品代码。
 PS:什么是生产代码,这里有点迷惑。
  测试代码和生产代码一样重要,它可不是二等公民,它需要被思考、被设计和北照料。它该像生产代码一样保持整洁。单元测试让你的代码可扩展,可维护,可复用。原因很简单,有了测试,你就不担心对代码的修改,没有单元测试,每次修改可能带来缺陷,一个测试,一个断言。一个测试,对应一个概念。我们不想要超长的测试函数。
 
测试还应遵守以下5条规则。
1.快速 测试应该能快速运行,太慢了你就不会频繁的运行,就不会尽早的发现问题。
2.独立。测试应该相互独立,某个测试不应该为下个测试设定条件。当测试相互依赖,一个没通过导致一连串的测试失败,使问题诊断变的困难。
3.可重复。测试应该可以在任何环境中重复通过。
4.自足验证 测试应该有布尔值输出,无论通过或失败,不应该是查看日志文件去确认 
5.及时。单元测试应该恰好在使其通过的生产代码之前编写。
 
 
第十章  类 
 1.类应该短小 
 2.单一权责原则(SRP):类或模块应有且只有一条加以修改的理由。系统应该有许多短小的类而不是巨大的类组成。
 PS:每个达到一定规模的系统都会包括大量逻辑和复杂性。管理这种复杂性的首要目标就是加以组织,以便开发者在哪儿能找到东西,反之,拥有巨大、多目的的类的系统,总是让我们在目前并不需要了解的一大堆东西中艰难的跋涉。
 3.内聚:如果一个类中的每个变量都被每个方法所使用,则该类具有最大的内聚性。内聚性高,意味着类中的方法和变量相互依赖,相互结合成一个逻辑整体。
 4.为了修改而组织。开放闭合原则(OCP):类应当对扩展开放,对修改封闭。我们可以借助接口和抽象类来隔离这些细节带来的影响。
 
第十一章:系统
 将系统的构造和使用分开:构造和使用是不一样的过程。
 PS:修建一栋大楼的时候,起重机和升降机在外面,工人们穿着安全服在忙碌。当大楼建设完成,建筑物变得整洁,覆盖着玻璃幕墙和漂亮的漆色。在其中工作的人,看完完全不同的景象。软件也是如此,将关注的方面分离。
 1.工厂,有时候应用程序需要确定何时创建对象,我们可以使用抽象工厂模式。将构造的细节隔离于应用程序之外。
 2.依赖注入(DI/IOC)。在依赖管理情景中,对象不应该负责实例化对自身的依赖,反之,它应该将这份权责移交给其他有权利的机制,从而实现控制的反转。
PS 现在的依赖注入组件比较多了,Autofac,Ninject等。
 3.扩容:“一开始就做对的系统”纯属神话,反之,我们应该只实现今天的用户的需求。然后重构,明天再扩容系统,实现新用户的需求。这就是迭代和增量敏捷的精髓所在。 就像城市不断的再拆掉,再建设。
 4.面向切面编程。AOP中,被称为方面(aspect)的模块构造指明了系统中哪些点的行为会以某种一致的方式被修改,从而支持某种特定的场景。这种说明是用某种简洁的声明(Attribute)或编程机制来实现的。
PS:MVC的Filter是个很好的AOP,可以从权限验证,方法进入前,方法进入后,返回结果前,返回结果后等这几个横切面进行编程。更好的组织代码。第十,十一章讲的设计只是一少部分。更多的可能要去参考专门讲设计模式之类的书。
 
 第十二章 迭进
 
1.简单设计规则
(1)运行所有测试。
紧耦合的代码难以编写测试。同样编写测试越多,就会越遵循DIP之类的原则,使用依赖注入,接口和抽象等工具尽可能减少耦合。如此一来设计就会有长足进步。遵循有关编写测试并持续运行测试的、明确的规则,系统就会更贴近OO低耦合度、高内聚的目标。
(2) 重构:
 在重构过程中,可以应用有关优秀软件设计的一切知识,提升内聚性,降低耦合度。换句话说:消除重复,保证表达力,尽可能的减少类和方法的数量。
2.不可重复。重复是良好设计系统的大敌。它代表着额外的工作、额外的风险和额外不必要的复杂度。重复有多种表现。雷同的代码行是一种。另外的比如:
int size();
bool isEmpty();

 这两个方法可以分别实现,但可以在isEmpty中使用size消除重复。

bool isEmpty(){
 return size()==0;
}

不但是从代码行的角度,也要从功能上消除重复。

第十三章: 并发编程

并发是一种解耦策略,它帮助我们把做什么(目的)和何时(时机)做分解开在单线程应用中,目的与时机紧密耦合,很多时候只要查看堆栈追踪即可断定应用程序的状态。而解耦目的与时机能明显地改进应用程序的吞吐量和结构。从结构的角度看,应用程序看起来更像是许多台协同工作的计算机,而不是一个大循环。单线程程序许多时间花在等待Web套接字I/O结束上面。

  • 并发会在性能和编写额外代码上增加一些开销。
  • 正确的并发是复杂的,即使对于简单的问题也是如此。
  • 并发缺陷并非总能重现,所以常被看做偶发事件而忽略,而未被当做真的缺陷看待。
  • 并发常常需要对设计策略的根本性修改。

 一些基础定义:

 

在并发编程中用到的几种执行模型:

1)生产者-消费者模型
     一个或多个生产者线程创建某些工作,并置于缓存或者队列中。一个或者多个消费者线程从队列中获取并完成这些工作。生产者和消费者之间的队列是一种限定资源。
2)读者-作者模型。
     当存在一个主要为读者线程提供信息源,但只是偶尔被作者线程更新的共享资源,吞吐量就会是个问题。增加吞吐量,会导致线程饥饿和过时信息的积累。协调读者线程不去读取正在更新的信息,而作者线程倾向于长期锁定读者线程。
3)宴席哲学家。
   许多企业级应用中会存在进程竞争资源的情形,如果没有用心设计,这种竞争会遭遇死锁,活锁,吞吐量和效率低等问题。
 
PS:这里对并发的讲解还不是那么的清晰,要掌握怎么正确使用并发,自己还是需要去专门看看这方面的书。
 
 小结:书十三章之后的部分是一些java源码的优化过程的讲解,这里先略过。本书最有价值的地方在于让我们程序员要有些整洁代码的习惯。从细微的变量命令,到函数、类的设计、以及整个系统的构造。不能忽略每一道工序。坏的代码就像沼泽,会让人越陷越深,很难改动,所以我们从一开始就要写整洁的代码。而至于设计模式或并发编程,从其他的书籍学习更全面。这本书满足不了我们的需求。
PS:书山有路活动是读书群的朋友共同选出来一起读的一本书。《代码整洁之道》是第一期。我是读书人,这本书一共读了七天。每天大概一个多小时。但是今天整理笔记,基本上全书又过了一遍。笔记内容也是依据我自己的判断。如果你想获得全面的了解,还是要请看原书。我们第二期正在读的书籍是《失控》,欢迎有兴趣的朋友加入。qq群452450927
      

以上是关于好文章系列——代码整洁之道的主要内容,如果未能解决你的问题,请参考以下文章

《代码整洁之道》精读与演绎之四 优秀代码的格式准则

代码整洁之道读书笔记

代码整洁之道- 函数

代码整洁之道- 函数

代码整洁之道-函数

好书推荐你想要的编码规范都在这里 | 《代码整洁之道》