diff --git a/clean-code/Clean Code Book.md b/clean-code/Clean Code Book.md index be7afad08679a0bd6a227f2dc6a4a76b42736cd4..841a69ad67125f6a90c8ffba9c04c021e958216c 100644 --- a/clean-code/Clean Code Book.md +++ b/clean-code/Clean Code Book.md @@ -131,28 +131,182 @@ Comments Do Not Make Up for Bad Code. ## 第 6 章 对象和数据结构 +- 数据抽象 + - 隐藏实现关乎抽象,类并不简单地取值器和赋值器将变量推向外间,而是暴露抽象接口,以便用户无需了解数据的实现就能擦做数据本体。 +- 数据、对象的反对称性 + - 对象把数据隐藏于抽象之后,暴露操作数据的函数。数据结构暴露其数据,没有提供有意义的函数。回头再读一遍,留意这两种定义的本质。他们是对立的,这种差异貌似渺小,但却有深远的意义。对象与数据结构的二分原理: + - 过程式代码(使用数据结构的代码)便于在不改动既有数据结构的前提下添加新函数。面向对象代码便于在不改动既有函数的前提下添加新类。 + - 反过来讲也说得通: 过程式代码难以添加新的数据结构,因为必须修改所有函数。面向对象代码难以添加新函数,因为必须修改所有类。 +- 迪米特法则 + - 模块不应了解它所操作对象的内部情形。对象隐藏数据,暴露操作。者意味着对象不应通过存取其暴露其内部结构,因为这样更像是暴露而非隐藏其内部结构。 + +- 数据传送对象 + - 最为精练的数据结构,是一个只有公共变量,没有函数的类。这种数据结构有时被称为数据传送对象,或 DTO。 + ## 第 7 章 错误处理 -- try catch语句块的范围不要太大,这样不利于对异常的分析 -- 别返回null值,这样可以减少调用者的防御性检测。与其返回null,不如抛出异常,或是返回特例对象(特例对象详见 p101) -- 别传递null值,传递null就要求被调用函数需要一系列防御性检测,也就意味着程序有更大可能出错 +- 使用异常而非返回码。 +- 在编写可能抛出异常的代码时,先写 `try-Catch-Finally`语句。 +- 使用不可控异常:C 使用可控异常的代价是:违反“开放/闭合原则”。对于一般性应用开发,其依赖成本要高于收益。 +- 给出异常发生的环境说明:应创建信息充分的错误消息,并和异常一起传递出去,以便判断错误的来源和处所。 +- 依调用者需要定义异常类(看异常如何被捕获):如打包调用 API 确保返回通用异常类型。 +- 定义常规流程:创建一个类或配置一个对象,用来处理特例。 +- try catch 语句块的范围不要太大,这样不利于对异常的分析。 +- 别返回 null 值,这样可以减少调用者的防御性检测。 + - 与其返回 null,不如抛出异常,或是返回特例对象。 +- 别传递 null 值,传递 null 就要求被调用函数需要一系列防御性检测,也就意味着程序有更大可能出错。 ## 第 8 章 边界 +- 使用第三方代码 + - 使用第三方代码时,如果有边界接口,可将其保留在类或近亲中,避免从公共 API 中返回边界接口,或者将其边界接口作为参数传给公共 API。 + +- 浏览和学习边界 + - 在利用第三方程序包时,没有测试第三方代码的职责,但为要使用的第三方代码编写测试,可能最符合我们的利益。不要在生成代码中实验新东西,而是编写测试来遍览和理解第三方代码。 + +- 学习性测试的好处不只是免费 + - 学习性测试是一种精确试验,帮助我们增进对 api 的理解。当第三方程序包发布了新版本,我们可以运行学习性测验,看看程序包的行为有没有改变。学习性测试确保第三方程序包按照我们想要的方式工作。一旦整合进来,就不能保证第三方代码总与我们的需要兼容。如果第三方程序包的修改与测试不兼容,我们也能马上发现。 +- 整洁的边界 + - 在使用我们控制不了的代码时,必须加倍小心,确保未来的修改代价不会太大。边界上的代码需要清晰的分隔和定义了期望的测试。 + ## 第 9 章 单元测试 +- TDD 三定律 + - 定律一,在编写不能通过的单元测试前,不可编写生成代码。 + - 定律二,只可编写刚好无法通过的单元测试,不能编译也不算通过。 + - 定律三,只可编写刚好足以通过当前失败测试的生成代码。 +- 保持测试整洁 + - 可读性,可读性,可读性。 + - 如何做到可读,那就是要保证测试代码同其他代码一样,明确,简洁,足具表达力,这也是这本书一直强调的事情。 + - 测试代码和生产代码一样重要。需要被思考,设计和照料。应该和生产代码一样整洁。 +- 每个测试一个断言 + - 在尽可能减少每个概念的断言数量的同时,最好能做到每个测试函数中只测试一个概念。 +- F.I.R.S.T 原则 + - 快速 (Fast) 测试应该够快。 + - 独立 (Independent) 测试应该相互独立。 + - 可重复 (Repeatable) 测试应当可在任何环境中重复通过。 + - 自足验证 (Self-Validating) 测试应该有布尔值输出。 + - 及时 (Timely) 测试应及时编写。 + ## 第 10 章 类 +- 类的组织 + - 遵循标准的 JAVA 约定,类应该从一组变量列表开始。 + - 变量顺序: + - 公共静态常量 + - 私有静态变量 + - 私有实体变量 + - 很少有公共变量,公共函数应该在变量列表后面。公共函数调用的私有工具函数紧随在该公共函数的后面。 +- 类应该短小 +- 单一权责原则 (SRP) + - 类或模块应有且只有一条加以修改的理由。系统应该有许多短小的类而不是巨大的类组成。 +- 内聚 + - 内聚性高:类中的方法和变量相互依赖、互相结合成一个逻辑整体。 + - 当类丧失了内聚性,就需要拆分它。将大函数拆分为许多小函数,往往也是将类拆分为许多个小类的时机。程序会更有组织,也会拥有更为透明的结构。 +- 为了修改而组织 + - 在整洁的系统中,对类进行组织,以降低修改的风险(通过扩展而非修改现有的代码来添加新的特性)。 + - 隔离修改 + - 遵循依赖倒置原则(DIP),应该依赖于抽象而非具体细节。 + ## 第 11 章 系统 Complexity kills. It sucks the life out of developers, it makes products difficult to plan, build and test. 复杂要人命,它消磨开发者的生命,让产品难于规划、构建和测试。 +- 将构造和使用分开的方法: + - 分解 main,将系统中的全部构造过程搬迁到 main 或者 main 模块中。 + - main 函数创建对象,再将对象传递给应用程序,应用程序只管使用,对构造一无所知。 + - 如果应用程序需要负责确定何时创建对象,可以创建抽象工厂,让应用程序控制实体创建的时机。 + - 依赖注入,控制反转 IoC 是依赖管理的手段,它将应用需要的依赖对象的创建权责从对象中拿出来,放在一个专注于此事的对象中,并通过依赖注入(赋值器)将依赖对象传递给应用。 +- 扩容: + + - 面向方面编程(AOP),Java 中三种方面和类似方面的机制:代理,纯 AOP 框架,AspectJ。 + + - java 代理:适用于简单情况,如在单独对象或类中包装方法调用。代码量和复杂度是代理的两大弱点。 + - 纯 Java AOP 框架,如 Spring AOP、JBoss AOP。 + - AspectJ:提供将方面作为模块构造处理支持的 Java 扩展。 +- 最佳系统架构由模块化的关注面领域组成,每个关注面均用纯 Java(或其他语言)对象实现。不同领域之间用最不具有侵害性的方面或类方面工具整合起来。这种架构能测试驱动,就像代码一样。 +- 拥有模块化关注面的 POJO 系统提供的敏捷能力,允许我们基于最新的知识做出优化的、时机刚好的决策。决策的复杂性也降低了。 +- 领域特定语言(DSL)允许所有抽象层级和应用程序中的所有领域,从高级策略到底层细节,使用 POJO 来表达。 + ## 第 12 章 迭进 +简单设计规则 + +- 运行所有测试 + - 不可测试的系统不可验证,不可验证的系统,绝不能部署。 +- 不可重复 + - 通过抽取或是模板方法整合重复代码。 + - 示例: + + ```java + int size(); + bool isEmpty(); + ``` + + 这两个方法可以分别实现,但可以在 `isEmpty` 中使用 `size` 消除重复。 + + ```java + bool isEmpty() { + return size() == 0; + } + ``` + +- 表达力 + - 选用好的名称来表达。 +- 尽可能少的类 + ## 第 13 章 并发编程 +- 为什么要并发 + - 并发是一种解耦策略,它帮助我们把做什么(目的)和何时(时机)做分解开。 + - 解耦目的与时机能明显地改进应用程序的吞吐量和结构。 + + - 单线程程序许多时间花在等待 web 套接字 I/O 结束上面,通过采用同时访问多个站点的多线程算法,就能改进性能。 +- 常见的迷思和误解 + - 并发总能改进性能 + - 只在多个线程或处理器之间能分享大量等待时间的时候管用。 + - 编写并发程序无需修改设计 + - 可能与单线程系统的设计极不相同。 + - 在采用 web 或 ejb 容器时,理解并发问题并不重要。 +- 有关编写并发软件的中肯的说法 + - 并发会在性能和编写额外代码上增加一些开销。 + - 正确的并发是复杂的,即使对于简单的问题也是如此。 + - 并发缺陷并非总能重现,所以常被看做偶发事件而忽略,未被当做真的缺陷看待。 + - 并发常常需要对设计策略的根本性修改。 +- 并发防御原则 + - 单一权责原则 + - 限制数据作用域 + - 使用数据副本 + - 线程应尽可能独立 +- 了解 Java 库 + - 使用类库提供的线程安全群集 + - 使用 executor 框架(executor framework)执行无关任务 + - 尽可能使用非锁定解决方案 + - 有几个类并不是线程安全的 +- 了解执行模型 +- 警惕同步方法之间的依赖 +- 保持同步区域微小 +- 很维编写正确的关闭代码 + - 平静关闭很难做到,常见问题与死锁有关,线程一直等待永远不会到来的信号。 + - 建议:尽早考虑关闭问题,尽早令其工作正常。 +- 测试线程代码 + - 建议:编写有潜力曝露问题的测试,在不同的编程配置、系统配置和负载条件下频繁运行。如果测试失败,跟踪错误。别因为后来测试通过了后来的运行就忽略失败。 + - 将伪失败看作可能的线程问题 + - 线程代码导致“不可能失败的”失败,不要将系统错误归咎于偶发事件。 + - 先使非线程代码可工作 + - 不要同时追踪非线程缺陷和线程缺陷,确保代码在线程之外可工作 + - 编写可插拔的线程代码,能在不同的配置环境下运行。 + - 编写可调整的线程代码 + - 允许线程依据吞吐量和系统使用率自我调整。 + - 运行多于处理器数量的线程 + - 任务交换越频繁,越有可能找到错过临界区域导致死锁的代码。 + - 在不同平台上运行 + - 尽早并经常地在所有目标平台上运行线程代码。 + - 装置试错代码 + - 增加对 `Object.wait()`、`Object.sleep()`、`Object.yield()`、`Object.priority()` 等方法的调用,改变代码执行顺序,硬编码或自动化。 + ## 第 14 章 逐步改进 ## 第 15 章 JUnit 内幕