现在的软件系统开发难度主要在于其复杂度和规模,客户需求也不再像Winston Royce瀑布模型期望那样在系统编码前完成所有的设计满足用户软件需求。在这个信息爆炸技术日新月异的时代,需求总是在不停的变化,随之在2001年业界17位大牛聚集在美国犹他州的滑雪胜地雪鸟(Snowbird)雪场,提出了“Agile”(敏捷)软件开发价值观,并在他们的努力推动下,开始在业界流行起来。在《代码整洁之道》(Clean Code),提出一种软件质量,可持续开发不仅在于项目架构设计,还与代码质量密切相关,代码的整洁度和质量成正比,一份整洁的代码在质量上是可靠的,为团队开发,后期维护,重构奠定了良好的基础。在这本书中作者提出了注重实际开发实践的细节,而不是站在空洞的理论来谈论整洁之道。
什么是整洁代码?不同的人会站在不同的角度阐述不同的说法。而我最喜欢的是Grady Booch(《面向对象分析与设计》作者)阐述:
“整洁的代码简单直接。整洁的代码如同优美的散文。整洁的代码从不隐藏设计者的意图,充满了干净利落的抽象和直截了当的控制语句。”
整洁的代码就是一种简约(简单而不过于太简单)的设计,阅读代码的人能很清晰的明白这里在干什么,而不是隐涩难懂,整洁的代码读起来让人感觉到就像阅读散文-艺术的沉淀,作者是精心在意缔造出来。
一:命名
命名包括变量、函数、参数,类等,一个好的命名能够很好的表述其所承载的业务,从命名上就已经很好的答复了为什么存在,做了什么事,应该怎么用等的大部分的问题,阅读者看到它的时候不必去深究其实现细节,一切都在命名上一目了然。一个好的命名必须是名副其实,不存在歧义(双关语或常见属于冲突),直接了当(否定语句或者误导性命名)。
二:函数:
从汇编/C时代开始的到现在函数一直都存在与我们开发中不可或缺的一部分,结构化组织,重用.作为函数式语言的一等公民,所有程序的第一组代码。
三:注释、格式:
并不是写出完备的注释就是好的开发人员,如果代码清晰的表述自己意图,那么注释反而多余。在《重构-改善现有代码之道》中Martin Fowler指出多余的注释是一种代码坏味道。就是好的注释随着项目的维护不断的重构很多时候也会变得不那么适应,而我们很少会去主动维护。再则误导性的注释更为使用者所憎恨。当然有时我们也得使用注释,注释并不是万恶的,当我们没法用代码来描述自己的时候,我们需要注释去描述意图;多余有副作用的代码给使用者提供警告注释。TODO开发时进度控制,比如你在进行较大规模领域重构,目前有些逻辑不再适应,不那么自然,而对它的重构还在任务列表最后,你可以选择标注在TODO中,最后完成从ToDoList中去掉每一个TODO任务。
良好的代码格式,会使得我们阅读更容易,一套共同的格式会让我们查找理解更快速。每个团队都应该遵循一套固定的代码格式规范,整个软件系统的统风格统一,而不是各自为政各成一体。
四:对象和数据结构:
数据结构指的就是数据的载体,暴露数据,而几乎没有有意义的行为的贫血类。最常见的应用在分布式服务,以wcf,webservice,reset之类的分布式服务中不可或缺的数据传输对象(DTO)模式,DTO(Request/Response)就是一个很典型的数据载体,只存在简单的get,set属性,并且更倾向于作为值对象存在。而对象则刚好相反作为面向对象的产物,必须封装隐藏数据,而暴露出行为接口,DDD中领域模型倾向于对象不仅在数据更多暴露行为操作自己或者关联状态。
数据结构和对象之间看是细微的差别却导致了不同的本质区别:使用数据结构的代码便于在不改动现在数据结构的前提下添加新的行为(函数),面向对象代码则便于不改动现 有函数的前提下添加新的类。换句话说就是数据结构难以添加新的的数据类型,因为需要改动所有函数,面向对象的代码则难以添加新的函数,因为需要修改所有的类。在任何一个复杂的系统都会同时存在数据结构和对象,我们需要判断的是我们需要的是需要添加的新的数据类型还是新的行为函数。
隐藏作为面向对象主要特性中的最重要特性,封装隐藏是面向对象中最重要的特性,一个好的面向对象代码肯定是对对象的内部细节做到很好的隐藏封装,封装过后才有是多态,委派之类的。一个好的面向对象的代码一定是具有很好的隐藏封装,易于测试,不稳定因素往往集中在一处很小或者固定的位置,不稳定因素的变更不会导致更大面积的修改扩散。
对象的隐藏要求:方法不应和任何调用方法返回的对象操作,换句话之和朋友说话,不和陌生人说话(迪米特法则,或被译为最小知识原则),比如:ctxt.getOptions().getSearchDir().getAbsolutePath(),就是迪米特法则的反例模式。
五:异常处理:
每个软件系统都避不开异常处理,需要防止它搞乱我们的逻辑。
六:边界:
在系统开发中不可能一切都得从零开始,自己写所有的代码,更好的方案是需要整合一些开源或者第三方的项目,为我所用。但是不能让这些非自己的代码渗侵中我们的代码各处,有一些所以功能很强大的第三方产品,但不一定具有很好的抽象。很多时候我更宁愿花些时间抽象出我们自己所需要的接口在第三方类库上外覆一层自己的抽象,这样不仅便于TDD,因为我们能够很好的创建伪对象,使的测试独立不依赖外部资源,得到快速反馈;而且在设计上得到很好的扩展,当由于某些原因如第三方类库不再能满足业务需求,或者权益收费等等,我们可以很好的切换底层而使得修改不会扩散到系统各处。外覆类也是处理遗留代码带入测试容器的一种很好实践。
七:单元测试:
TDD中测试代码在往往和产品代码差不多,在系统中占据一半的代码量,不好的测试代码也可能拖累项目的开发。整洁的测试代码应该是遵循first原则的:
八:类:
面向对象的相似行为的抽象,函数代码块的组织形式,在面向对象中我们的软件系统是由众多的类和类之间的交互协作完成了。面向对象特征:封装,继承,多态度,委派。一个设计良好的类该是具有良好的封装,站在使用者的调度考虑那些是使用接口,那些是内部细节;这是面向对象最主要的特征,但是有时会与测试冲突,可以适当的放开并仅限于于测试调用。继承和多态在面向对象中可以实现重用,但我更倾向于继承不是为了重用,而是隔离变化;大量的滥用继承不干净的继承体系将会导致庞大的继承体系,继承体系中众多职责重复在各个同级派生类,理想的继承应该是满足里氏替换原则(LSP:每个父类出现的地方都应该可以被派生类所替换,并且能正确的工作);面oo第二原则组合优先。而委派则是一个类把部分功能委派给其他类来完成,体现类之间的协作,类似组合。
九:并发编程:
并发是一种时间(When)和目的(What)的解耦,提供应用程序的吞吐量,提高cpu利用率;但是并发编码不是那么容易,再加上临界资源竞争死锁。在并发编程的时候我们必须尽量遵守一些原则:
更多关于简单设计,迭进,逐步编程代码,坏味道,并发示例请参见代码整洁之道。