6.避免使用编码
把类型或作用域编进名称里面,徒然增加了解码的负担。带编码的名称通常也不便发音,容易打错。 人们会很快学会无视前缀(或后缀),只看到名称中有意义的部分 7. 避免思维映射 不应当让读者在脑中把你的名称翻译为他们熟知的名称。 读者必须在脑中将变量名映射为真实概念。 明确是王道,编写其他人能理解的代码 。 8.类名 类名和对象名应该是名词或名词短语,如Customer、WikiPage、Account和AddressParser。 避免使用Manager、Processor、Data或Info这样的类名。 类名不应当是动词。 9.方法名 方法名应当是动词或动词短语,如postPayment、deletePage或save。 属性访问器、修改器和断言应该根据其值命名,并依Javabean标准 加上get、set和is前缀。 10.别扮可爱 名称解释够准确无误;别用花哨的名字。 言到意到。意到言到。 11.每个概念对应一个词 给每个抽象概念对应一个词,并且一以贯之。 切记同时使用多个词对应一个概念(例:切记同时出现controller、manager、driver等) 12.别用双关语 避免将同一单词用于不同目的。同一术语用于不同概念,基本上就是双关语了。 13. 使用解决方案领域名称 只有程序员才会读你的代码。所以,尽管用那些计算机科学(Computer Science,CS)术语、算法名、模式名、数学术语。 14. 使用源自所涉问题领域的名称 如果不能用程序员熟悉的术语来给手头的工作命名,就采用从所涉问题领域而来的名称吧。 优秀的程序员和设计师,其工作之一就是分离 解决方案领域 和 问题领域 的概念。 与所涉问题领域更为贴近的代码,应当采用源自问题领域的名称。 15. 添加有意义的语境 你需要用有良好命名的类、函数或名称空间来放置名称,给读者提供语境。 如果没这么做,给名称添加前缀 就是最后一招了。 16. 不要添加没用的语境 只要短名称足够清楚,就要比长名称好。别给名称添加不必要的语境。 正确是命名的要点。 设若有一个名为"加油站豪华版"(Gas Station Deluxe)的应用,在其中给每个类添加GSD前缀就不是什么好点子。 17.最后的话 取好名字最难的地方在于需要良好的描述技巧和共有文化背景。 心得: 通过看代码的名称,就能知道代码具体要干什么 状态量(STATUS_VALUE, FLAGGED)大写 第三章 函数 1.短小 函数的第一规则是要短小。第二条规则是还要更短小。 2.只做一件事 函数应该做一件事。做好这件事。只做这一件事。 3.每个函数一个抽象层级 自顶向下读代码:向下规则 程序就像是一系列TO起头的段落,每一段都描述当前抽象层级,并引用位于下一抽象层级的后续TO起头段落。 4.使用描述性的名称 命名方式要保持一致。使用与模块名一脉相承的短语、名词和动词给函数命名。 5.函数参数 最理想的参数数量是零(零参数函数),其次是一(单参数函数),再次是二(双参数函数),应尽量避免三(三参数函数)。 有足够特殊的理由才能用三个以上参数(多参数函数)-所以无论如何也不要这么做。 如果函数看来需要两个、三个或三个以上参数,就说明其中一些参数应该封装为类了。 Circle makeCircle(double x, double y, double radius); Circle makeCircle(Point center, double radius);6.使用异常替代返回错误码
Try/catch代码块丑陋不堪。它们搞乱了代码结构,把错误处理与正常流程混为一谈。最好把try和catch代码块的主体部分抽离出来,另外形成函数。这意味着如果关键字try在某个函数中存在,它就该是这个函数的第一个单词,而且在catch/finally代码块后面也不该有其他内容。 public void delete(Page page) { try { deletePageAndAllReferences(page); } catch (Exception e) { logError(e); } } 使用异常替代错误码,新异常就可以从异常类派生出来,无需重新编译或重新部署。 7.如何写出这样的函数 写代码和写别的东西很像。在写论文或文章时,你先想什么就写什么,然后再打磨它。初稿也许粗陋无序,你就斟酌推敲,直至达到你心目中的样子。 我写函数时,一开始都冗长而复杂。有太多缩进和嵌套循环。有过长的参数列表。名称是随意取的,也会有重复的代码。不过我会配上一套单元测试,覆盖每行丑陋的代码。 然后我打磨这些代码,分解函数、修改名称、消除重复。我缩短和重新安置方法。有时我还拆散类。同时保持测试通过。 最后,遵循本章列出的规则,我组装好这些函数。 我并不从一开始就按照规则写函数。我想没人做得到。 不过永远别忘记,真正的目标在于讲述系统的故事,而你编写的函数必须干净利落地拼装到一起,形成一种精确而清晰的语言,帮助你讲故事。 第四章 注释 优秀的代码不需要注释,本身就能说明问题。 唯一真正好的注释是想办法不去写注释。 好的注释比不上干净的代码。 // Check to see if the employee is eligible for full benefits if (( employee.flags & HOURLY_FLAG ) && ( employee.age > 65 )) 修改后: if ( employee.isEligibleForFullBenefits() ) 很多时候,简单到只需要创建一个描述与注释所言同一事物的函数即可,用代码解释大部分的意图。 如果决定写注释,就要花必要的时间确保写出最好的注释。 第五章 格式 选用一套管理代码格式的简单规则,然后贯彻这些规则 源文件要像报纸那样。名称应当简单且一目了然,名称本身应该足够告诉我们是否在正确的模块中,源文件最顶部应该给出最高层次概念和算法,细节应该往下渐次展开,直至找到源文件中最底层的函数和细节。 紧密相关的代码应该相互靠近。 变量声明应该尽可能靠近其使用位置。 实体变量应该在类的顶部声明。 相关函数,应该把它们放到一起,并且调用函数应该尽可能放在被调用函数上面。 概念相关的代码应该放到一起,相关性越强,彼此之间的距离就该越短,相关性可能来自执行相似操作的一组函数。 根据运算符优先级格式化代码: 优先级高的不加空格,优先级低的添加空格。 private static double determinant(double a, double b, double c) { return b*b - 4*a*c } 保持循环体为空时的格式缩进。 第六章 对象和数据结构 过程式代码(使用数据结构的代码)便于在不该懂既有数据结构的前提下添加新函数;面向对象代码便于在不改动既有函数的前提下添加新类。 过程式代码难以添加新数据结构,因为必须修改所有函数;面向对象代码难以添加新函数,因为必须修改所有类。 得墨忒耳律:模块不应该了解它所操作对象的内部情形。 类C的方法f只应该调用以下对象的方法: C;由f创建的对象;作为参数传递给f的对象;由C的实体变量持有的对象。方法不应调用由任何函数返回的对象的方法。即:只和朋友谈话,不与陌生人谈话。 第七章 错误处理 如果错误处理搞乱了代码逻辑,就是错误的做法。 遇到错误时,最好抛出一个异常。 尝试编写强行抛出异常的测试,再往处理器中添加行为,使之满足测试要求。结果就是要先构造try代码块的事务范围,维护该范围的事务特征。 在应用程序中定义异常类时,最重要考虑的时它们 如何被捕获。 特例模式:创建一个类或配置一个对象,用来处理特例。客户代码就不用应付异常行为了,异常行为被封装到特例对象中。 别返回null值。更别传递null值。 List<Employee> employees = getEmployees(); if(employees != null) { // employees可能为null for(Employee e : employees) { totalPay += e.getPay(); } } 修改后: public List<Employee> getEmployees(){ ..... return Collections.emptyList(); // Java collection.empty() 返回一个预定义不可变列表,避免NullPointException出现 } List<Employee> employees = getEmployees(); for(Employee e : employees) { totalPay += e.getPay(); } 如果在调用第三方API中可能返回null值的方法,可以用新方法打包返回null值的方法,在新方法中抛出异常或返回特例对象。 第八章 边界 将外来的代码干净利落的整合进自己的代码中。 如果使用类似Map这样的边界接口,就把它保留在类或近亲类中。避免从公共API中返回边界接口,或将边界接口作为参数传递给公共API。 Map sensors = new HashMap(); Sensors s = (Sensor)sensors.get(sensorId); // 强制类型转换 修改为: Map<Sensor> sensors = new HashMap<Sensor>(); // 泛型 Sensor s = sensor.get(sensorId); 修改为: public class Sensors{ private Map sensors = new HashMap(); public Sensor getById(String id){ return (Sensor) sensors.get(id); // 转换与类型管理在Sensors类内部处理 } } 边界上的代码需要清晰的分割和定义了期望的测试。 第九章 单元测试 TDD三定律: 在编写不能通过的单元测试前,不可编写生产代码。 只可编写刚好无法通过的单元测试,不能编译也算不通过。 只可编写刚好足以通过当前失败测试的生产代码。 测试代码和生产代码一样重要。它需要被思考、被设计和被照料,该像生产代码一般保持整洁。 整洁的测试三要素:可读性、可读性、可读性。 每个测试函数只测试一个概念。 整洁的测试遵循5条规则: 快速,测试应该够快。 独立,测试应该相互独立。应该可以单独运行每个测试,以及任何顺序运行测试。 可重复,测试应当可在任何环境中重复通过。应该能够在生产环境、测试环境中运行测试。 自足验证,测试应该有布尔值输出。 及时,测试应及时编写,单元测试应该恰好在使其通过的生产代码之前编写。 第十章 类 类应该短小; 类的名称应该描述其权责(方法的数量); 类长短的标准:能否为某个类命以精确的名称;(包括含义模糊的词:processor、manager、super) 单一权责原则:类或模块应该有且只有一条加以修改的理由。 系统应该由许多短小的类而不是少量巨大的类组成。每个小类封装一个权责,只有一个修改的原因,并与少数其他类一起协同达成期望的系统行为。 保持内聚性就会得到许多短小的类: 当把含有许多变量的大函数拆解成单独的函数,要拆解的代码使用了该函数声明中的多个变量,是否必须将这多个变量都作为参数传到新函数中? 完全没必要!只要将多个变量提升为类的实体变量,完全无需传递任何变量就能拆解代码,应该很容易将函数拆分成为小块。 通过降低连接度,类就遵循了依赖倒置原则:即类应当依赖于抽象(接口)而不是依赖于具体细节。 第十一章 系统 软件系统应将启动过程和启动过程之后的运行时逻辑分离开,在启动过程中构建应用对象,也会存在相互缠结的依赖关系。 将构造与使用分开的方法之一就将全面构造过程搬迁到main或main的模块中。 使用抽象工厂模式让应用自行控制并创建对象,但构造的细节却隔离于应用程序代码之外。 依赖注入,控制反转实现分流构造与使用。 第十二章 跌进 跌进设计: 运行所有测试; 不可重复; 表达了程序员的意图; 尽可能减少类和方法的数量。 以上规则按其重要程度排列。 测试消除了对清理代码就会破坏代码的恐惧。 应用简单设计后三条规则: 消除重复、保证表达力、尽可能减少类和方法的数量。 第十三章 并发编程 并发会在性能和编写额外代码上增加一些开销; 正确的并发是复杂的,即便对于简单的问题也是如此; 并发缺陷并非总能重视,所以常被看作偶发事件而忽略,未被当作真的缺陷看待; 并发常常需要对设计策略的根本性修改。 并发防御原则 单一权责原则:建议:分离并发相关代码与其他代码。 限制数据作用域:(防止多线程共享对象的同一字段互相干扰,synchronized保护一块使用共享对象的临界区)谨记数据封装;严格限制对可能被共享的数据的访问。 使用数据复本:避免共享数据的方法之一就是开始就避免共享数据。 线程尽可能独立:尝试将数据分解到可被独立线程(可能在不同处理器上)操作的独立子集(不与其他线程共享数据)。 建议:检读可用的类。对于Java掌握java.util.concurrent, java.util.concurrent.atomic 和 java.util.concuttent.locks。 执行模型:学习这些基础算法,理解其解决方案。 生产者-消费者模型; 读者-作者模型; 宴席哲学家模型; 测试线程代码: 编写有潜力暴露问题的测试,在不同的编程配置、系统配置和负载条件下频繁运行。 建议: 将伪失败看作可能的线程问题;-- 不要将系统错误归咎于偶发事件。先是非线程代码可工作;-- 不要同事追踪非线程缺陷和线程缺陷。确保代码在线程之外可工作。编写可插拔的线程代码;-- 编写可在数个配置环境下运行的线程代码。编写可调整的线程代码;-- 在不同配置环境下检测系统性能,允许线程数量可调整与线程变动,允许线程根据吞吐量和系统使用率自我调整。运行多于处理器数量的线程;-- 防止频繁切换任务导致死锁。在不同的平台上运行;-- 今早并经常的在所有目标平台上允许线程代码。调整代码并强迫错误发生;-- 捕捉线程中罕见的错误,采用硬编码与自动化的方法装置代码,改变代码执行顺序。如果花点时间装置代码,能极大的提升发现错误代码的机会。只要采用了整洁的做法,做对的可能性就有翻天覆地的提高。 第十四章 逐步改进 要编写整洁代码,必须先写肮脏代码,然后清理它。 避免程序在改进时无法恢复,采用测试驱动开发的规程,保证系统始终能运行。 解决之道就说保持代码持续整洁和简单,用不让腐坏有机会开始。