* 整洁代码的意义?
可读性,可维护性。
* 如何写出整洁代码?
1.只做一件事
2.不重复
3.有表达力
* 整洁代码的态度要求,要遵守的军规?
专业 和责任。让营地比你来时更干净,拒绝破窗效应。
* 写出整洁代码的具体做法?
有意义的命名(表达力,可读性)
函数只做一件事,每个函数一个抽象层级,短小不重复。
注释是代码缺乏表达力时的弥补措施,好的代码自我注释。
格式要统一,有层次,易理解。
类,权责对应,内聚,只做一件事。得墨忒耳定律:模块不应该了解所操作的类的内部情况。
对象方便增加子类,数据结构方便增加新操作方法。
错误处理应该独立于主逻辑之外。
迭代、 逐步改进、重构。
下面是章节概要内容:
- 代码整洁之道
-
- 我加的前言
- 本书需要着重琢磨书中的案例才能深刻理解其原则,否则就如同是一本泛泛而谈的书。
- 简单原则
- 保持代码的整洁与可维护有一条很简单的规则:
- 10:包内的类不超过10个
- 50:方法的代码行数不超过50
- 500:类的代码行数不超过500
- 只做一件事
- 有表达力
- 不重复
- 保持代码的整洁与可维护有一条很简单的规则:
- 1整洁代码
- 混乱的代价
- 生产力的持续下降
- 态度
- 坚持整洁代码是一种专业的态度和责任
- 意义
- 什么是整洁代码
- 整洁的代码只做好一件事
- 直接简单如同优美的散文,从不隐藏设计者的意图,充满了干净利落的抽象和直截了当的控制语句
- Dave Thomas
- 便于其他开发人员阅读和增补
- 有单元测试和验收测试
- 有意义的命名
- 一种而非多种做一件的途径
- 尽量少的依赖
- 明确和尽量少的api
- 代码通过其字面表达含义
- Ron
- 通过所有测试
- 没有重复代码
- 体现系统的全部设计理念
- 包括尽量少的实体
- 减少重复代码、提高表达力、提早构建简单抽象。
- 我们是作者
- 其他程序员会阅读我们的代码,所以要写的易懂些。
- 童子军军规
- 让营地比你来时更干净
- 不论代码现状如何糟糕,都要进行改善,而不是任其腐烂。
- 不论代码现状如何良好,都不增加任何小的糟糕代码进行腐化。
- 拒绝破窗效应
- 让营地比你来时更干净
- 混乱的代价
- 2有意义的命名
- 这个一个严肃的问题
- 规则
- 名副其实
- 魔术数
- 避免误导
- 做有意义的区分
- 使用读得出来的名称
- 使用可搜索的名称
- 避免使用编码
- 避免使用各种前缀
- 接口I避免使用
- 干扰和废话
- 接口I避免使用
- 避免使用各种前缀
- 避免思维映射
- 不要用自己才懂的标记
- 明确是王道
- 类名应该是名词或名词短语。类名不应当是动词。
- 方法名应当是动词或动词短语。
- 别扮可爱
- 每个概念对应一个词
- 不要既有controller,又有manager
- 别用双关语
- 使用解决方案领域名称
- 使用源自所设计问题领域的名称
- 添加有意义的语境
- 不要添加没有意义的语境
- 名副其实
- 3函数
- 如何写好函数
- 短小
- 短小
- 行列不应该长于一个屏幕
- 20行最佳
- 行列不应该长于一个屏幕
- 只做一件事
- 每个函数一个抽象层级
- 不要让实现细节和基础概念混杂
- 向下规则
- 自顶向下读代码
- switch语句
- 根据代价判断是否使用多态来代替switch语句
- 使用描述性的名称
- 别怕长名称
- 命名方式要一脉相承
- 函数参数
- 尽量避免3个以上的参数
- 避免向参数传入布尔值
- 意外着函数要做不止一件事
- 函数名字和参数尽量形成动词/名称的结构
- 无副作用
- 避免时序耦合、顺序耦合
- 避免使用输出函数
- 使用更改对象的状态来替代
- 函数要么是做什么事,要么是回答什么事,两者不可兼得
- 指令与询问分隔开
- 使用异常替代返回错误码
- 错误码违反了指令与询问分隔开的原则
- 抽离try/catch代码块
- 错误处理就是一件事
- try开头,catch/finally代码后面不该有其他内容
- 别重复自己
- pmd
- 结构化编程
- 每个代码块只有一个入口一个出口
- 如何写出这样的函数
- 在单元测试通过的基础上不断重构打磨
- 如何写好函数
- 4注释
- 别给糟糕的代码加注释--重新写吧
- 用代码来阐述
- 好注释
- 想办法不去写注释
- 法律信息
- 使用链接
- 提供信息的注释
- 对意图的解释
- 阐述
- 警示
- TODO注释
- 要经常清理
- 公共API的JavaDoc
- 坏注释
- 喃喃自语
- 多余
- 误导
- 循规蹈矩的注释
- 日志式注释
- 废话
- 能用函数名和变量名时就不要用注释
- 位置标记
- 括号后面的注释
- 将函数写的短小就不需要这种注释了
- 归属与署名
- 注释掉的代码
- html注释
- 非本地信息
- 信息过多
- 不明显的联系
- 函数头注释
- 非公共代码的JavaDoc
- 5格式
- 团队公用一套管理代码格式的简单规则
- checkstyle
- 格式的目的
- 代码风格和可读性将会影响到可维护性和扩展性
- 垂直格式
- 使用大多数为200行,最长500行的单个文件是可以构建出优秀的系统的
- 向报纸学习
- 有层次,细节逐层展开
- 概念间垂直方向上的区隔
- 空白行分割,在每个函数之间有利于阅读
- 垂直方向的靠近
- 垂直距离
- 关系密切的放在一起,避免用户阅读时跳来跳去
- 变量声明
- 靠近使用位置
- 本地变量应该在函数的顶部
- 循环中的控制变量应该总是在循环语句中声明
- 实体变量
- 类的顶部
- 相关函数
- 调用关系的尽量在一起,并且调用者在被调用者上面。
- 概念相关
- 执行相似任务的函数放在一起
- 变量声明
- 关系密切的放在一起,避免用户阅读时跳来跳去
- 垂直顺序
- 自上而下展示函数调用顺序
- 横向格式
- 一行代码应该有多宽?
- 上限120个字符
- 水平方向的区隔与靠近
- 格式化工具自动完成
- 水平对齐
- 没什么用
- 缩进
- 按层级缩进,有助于快速了解文件结构
- 空范围
- 空函数也要有括号和缩进
- 一行代码应该有多宽?
- 团队规则
- 使用统一的风格以减少复杂度
- 团队公用一套管理代码格式的简单规则
- 6对象和数据结构
- 数据抽象
- 隐藏实现
- 用户无需了解数据的实现就能操作数据本体
- 隐藏实现
- 数据、对象的反对称性
- 对象和数据结构的差异
- 对象把数据隐藏于抽象之后,曝露操作数据的函数
- 数据结构曝露其数据,没有提供有意义的函数
- 对象与数据之间的二分原理
- 面向对象代码
- 便于在不改动既有函数的前提下添加新类
- 难以添加新函数,因为必须修改所有类
- 过程式代码
- 使用数据结构的代码
- 便于在不改动既有数据结构的前提下添加新函数
- 难以添加新数据结构,因为必须修改所有函数
- 面向对象代码
- 根据系统是需要添加新函数还是新数据类型选择合适的代码
- 对象和数据结构的差异
- 得墨忒耳定律
- The Law of Demeter
- 模块不应了解它所操作的类的内部情形
- 对象不应通过存取器暴露其内部结构
- 类C的方法f只应该调用以下对象的方法
- C
- 由f创建的对象
- 作为参数传递给f的对象
- 由C的实体变量持有的对象
- 方法不应调用由任何函数返回的对象的方法
- 只跟朋友谈话,不与陌生人谈话。
- 火车失事
- 传递链
- 违背了得墨忒尔定律,应该避免
- 混杂
- 一半是对象,一半是数据结构
- 一种混乱的设计,导致难以修改。应当避免。
- 一半是对象,一半是数据结构
- 隐藏结构
- 数据传送对象
- DTO
- Data Transfer Object
- 最精简的数据结构
- 只有公共变量、没有函数的类
- 在DTO中塞进业务处理方法是不智的行为,因为它导致了数据结构和对象的混杂体
- DTO
- 小结
- 对象
- 曝露行为
- 隐藏数据
- 便于新增数据类型而无需修改既有行为
- 难以在既有对象中添加行为
- 数据结构
- 曝露数据
- 没有明显的行为
- 便于向既有数据结构添加新行为
- 难以向既有函数添加新数据结构
- 对象
- 数据抽象
- 7错误处理
- 使用异常而非返回码
- 先写try-catch-finally语句
- 使用不可控异常
- 可控异常会导致从底层到每个调用该函数的函数都要捕获异常,违反了开闭原则破坏了封装
- 给出异常发生的环境说明
- 依调用者需要定义异常类
- 定义常规流程
- 别返回null值
- 空对象代替null
- 别传递null值
- 禁止传入null值
- 小结
- 将错误处理隔离看待,独立于主要逻辑之外
- 8边界
- 使用第三方代码时需要适当封装,不要要让边界细节传递到自己的系统中
- 使用第三方代码时要对其编写测试检测和加强自己的理解程度
- 学习性测试
- 示例:学习log4j
- 学习性测试的好处
- 边界测试能及时发现版本更新后的不兼容
- 使用尚不存在的代码
- 编写我们想得到的接口
- 整洁的边界
- 边界上的代码需要清晰的分割和定义了期望的测试
- 不要让我们的代码过多地了解第三方代码中的特定信息
- 使用少数的几处引用隔离和管理第三方边界
- 9单元测试
- TDD三定律
- 在编写不能通过的单元测试前,不可编写生产代码
- 只可编写刚好无法通过的单元测试,不能编译也算不通过
- 只可编写刚好足以通过当前失败测试的生产代码
- 保持测试整洁
- 测试代码和生产代码一样重要
- 坐视测试代码腐坏,那么生产代码也会跟着腐坏
- 测试带来一切好处
- 测试代码和生产代码一样重要
- 整洁的测试
- 可读性
- 面向特定领域的测试语言
- 测试api
- 双重标准
- 每个测试一个断言
- 每个测试一个概念
- F.I.R.S.T
- 整洁测试的5条规则
- 快速Fast
- 独立Independent
- 可重复Repeatable
- 自足验证Self-Validating
- 测试应该有布尔值输出,不要依赖于其他手段
- 及时Timely
- TDD三定律
- 10类
- 类的组织
- java约定
- 一组变量列表开头
- 公有静态常量量在上面
- 私有静态变量
- 私有实体变量
- 公共函数跟在变量列表之后
- 被公共函数调用的私有函数紧跟在该公有函数后面
- 一组变量列表开头
- 封装
- java约定
- 类应该短小
- 类名应当描述其权责
- 单一权责原则
- SRP
- 避免上帝类
- 维护了太多功能,职责较多的类
- 较难修改
- 类应当只有一条修改的理由
- 大量短小的类胜过少数臃肿的类
- 内聚
- 类应该只有少量实体变量
- 每个方法都应该操作一个或多个这种变量
- 类应该只有少量实体变量
- 保持内聚性就会得到很多短小的类
- 当类丧失了内聚性就拆分它
- SRP
- 为了修改而组织
- OCP
- 类应当对扩展开放,对修改封闭。
- 在理想系统中,我们通过扩展系统而非修改现有代码来添加特性
- 当出现了只与类的一小部分有关的私有方法时,意味着类存在改进空间
- 隔离修改
- 具体类中包含了太多的细节,细节改变将带来风险
- 可以借助接口和抽象类来隔离细节的影响
- 解耦
- 可以借助接口和抽象类来隔离细节的影响
- DIP
- 类应该依赖于抽象而非细节
- 依赖倒置原则
- 具体类中包含了太多的细节,细节改变将带来风险
- OCP
- 类的组织
- 11系统
- 如何建造一个城市
- 一些人负责全局,一些人负责细节
- 将系统的构造和使用分开
- 分解main
- 将构造过程搬迁到main或者main模块中
- 工厂
- 依赖注入
- 分解main
- 扩容
- 关注面切分,增量迭代
- 横贯式关注面
- AOP aspect
- Java代理
- 适用于简单的情况
- 原理
- 代理对象使用Java反射API将一般方法调用映射到被代理的实现类中对应的方法
- 缺点
- 代码量
- 复杂度
- 纯Java AOP框架
- Spring AOP
- 框架以对用户透明的方式处理Java代理或字节代码库。
- 用户只需要配置文件或调用API
- 框架声明驱动依赖注入容器实例化对象,并按需将对象连接起来
- AspectJ的方面
- 测试驱动系统架构
- 这种架构能测试驱动
- 最佳的系统架构由模块化的关注面领域组成,每个关注面均用纯Java(或其他语言)对象实现。
- 不同的领域之间用最不具有侵害性的方面或类方面工具整合起来
- 这种架构能测试驱动
- 优化决策
- 明智使用添加了可论证价值的标准
- 系统需要领域特定语言
- DSL
- 小结
- 保持敏捷性,避免侵害性的框架
- 所有的抽象层级上,意图都应该清晰可辨
- 编写POJO并无损地组合其他关注面
- 尽量使用大概可工作的最简单方案,避免脱离实际的过度设计
- 如何建造一个城市
- 12迭代
- 通过迭代设计达到整洁目的
- 简单设计四原则
- 运行所有测试
- 紧耦合的代码难以编写测试
- 测试编写越多,越促使系统符合OO设计原则
- 不可重复
- 重复意味着额外的工作和风险
- 表达了程序员的意图
- 别人读不懂就无法良好的维护
- 尽可能的减少类和方法的数量
- 最不重要的一条
- 以上四条重要性依次降低
- 运行所有测试
- 13并发编程
- 对象是过程的抽象,线程是调度的抽象
- 为什么要并发
- 并发是一种解耦策略
- 将目的和时机分解开
- 明显改进应用程序的吞吐量和结构
- 改进系统响应时间
- 大量数据的并行处理
- 并发是一种解耦策略
- 挑战
- 与编译器、jvm内存原型有关,要获得正确的结果并不容易
- 并发防御原则
- 单一权责原则
- 分离并发相关代码和其他代码
- 限制数据作用域
- 使用synchronized关键字在代码中保护一块使用共享对象的临界区
- 严格限制对可能被共享的数据的访问
- 使用数据复本
- 可以避免锁定
- 线程应该尽可能独立
- 尝试将数据分解到可被独立线程操作的独立子集。
- 单一权责原则
- 了解Java库
- java5编写线程代码时,要注意
- 使用类库提供的线程安全群集
- 使用executor框架执行无关任务
- 尽可能的使用非锁定方案
- 有几个类并不是线程安全的
- 建议检读可用的类,掌握java.util.concurrent、java.util.concurrent.atomic和java.util.concurrent.locks
- java5编写线程代码时,要注意
- 了解执行模型
- 基础定义
- 限定资源
- 固定
- 互斥
- 线程饥饿
- 死锁
- 活锁
- 限定资源
- 并发大多是这三种模型的变种
- 生产者消费者模型
- 限定资源
- 读者作者模型
- 线程饥饿
- 宴席哲学家模型
- 竞争性系统
- 生产者消费者模型
- 基础定义
- 警惕同步方法之间的依赖
- 建议:避免使用一个共享对象的多个方法。
- 这种情况下有3种写对代码的手段
- 基于客户端的锁定
- 客户端代码调用前锁定服务器,并确保锁的范围覆盖了最后一个方法
- 基于服务端的锁定
- 在服务端内创建锁定服务端的方法,调用所有方法然后解锁。
- 适配服务器
- 创建执行锁定的中间层。
- 基于客户端的锁定
- 保持同步区域微小
- 很难编写正确的关闭代码
- 尽早考虑关闭问题
- 测试线程代码
- 建议:编写有潜力暴露问题的测试,在不同的编程配置、系统配置和负载条件下频繁运行。如果失败,跟踪错误。
- 建议
- 将伪失败看作可能的线程问题
- 不要归因于偶发事件
- 先使非线程代码可工作
- 编写可插拔的线程代码
- 单线程与多个线程在执行时不同的情况
- 线程代码与实物或测试替身互动
- 用运行快速、缓慢和有变动的测试替身执行
- 将测试配置为能运行一定数量的迭代
- 编写(线程数量等)可调整的线程代码
- 运行多于处理器数量的线程
- 在不同平台上运行
- 装置试错代码
- 硬编码
- 手工插入wait、sleep、yield、priority等进行测试
- 自动化
- 将伪失败看作可能的线程问题
- 14逐步改进
- 一个逐步改进的案例
- 本章需要结合代码体会
- 实现
- 草稿
- 暂停
- 渐进
- 大量的最小规模改动
- TDD
- 每次改动必须保证系统能像以前一样工作
- 小结
- 清理清理糟糕代码的代价高昂,最好的解决之道就是保持代码持续整洁简单。
- 一个逐步改进的案例
- 15JUnit内幕
- 对JUnit的ComparisonCompactor模块的重构示例
- 本章需要结合代码体会
- 16重构SerialDate类
- 17味道与启发
- “代码味道”
- 注释
- 不恰当的信息
- 废弃的注释
- 冗余注释
- 糟糕的注释
- 注释掉的代码
- 环境
- 需多步才能实现的构建
- 需多步才能做到的测试
- 函数
- 过多的参数
- 输出参数
- 标识参数
- 死函数
- 一般性问题
- 一个源中存在多种语言
- 明显的行为未被实现
- 对函数直觉的期望未能满足将导致读者不得不阅读代码细节
- 不正确的边界行为
- 追索每种边界条件,并编写测试
- 忽视安全
- 重复
- DRY别重复自己
- 在错误的抽象层级上的代码
- 基类依赖于派生类
- 信息过多
- 死代码
- 垂直分割
- 垂直距离要短
- 前后不一致
- 混淆视听
- 没用的变量,从不调用的函数,无用的代码删掉
- 人为耦合
- 特性依恋
- 算子参数
- 晦涩的意图
- 位置错误的权责
- 不恰当的静态方法
- 使用解释性变量
- 函数名称应该表达其行为
- 理解算法
- 把逻辑依赖改为物理依赖
- 使用多态代替if/else
- 遵循标准约定
- 用命名常量代替魔术数
- 准确
- 结构胜于约定
- 封装条件
- 避免否定性条件
- 函数只做一件事
- 掩蔽时序耦合
- 别随意
- 封装边界条件
- 函数应该只在一个抽象层级上
- 在较高层级放置可配置数据
- 避免传递浏览
- java
- 使用通配符避免过长的清单
- 不要继承常量
- 常量vs枚举
- 名称
- 采用描述性名称
- 名称应与抽象层级相符
- 尽可能使用标准命名法
- 无歧义的名称
- 为较大范围选用校长名称
- 避免编码
- 名称应该说明副作用
- 测试
- 测试不足
- 使用覆盖率工具
- 别略过小测试
- 被忽略的测试就是不确定的疑问
- 测试边界条件
- 测试失败的模式有启发性
- 测试应该快速
- 小结
- 这份启发与味道的清单并不完备,重要的是它给出了一套价值体系,那才是目标。
- 专业性和技艺来自驱动规则的价值观
- 注释
- “代码味道”
- 我加的前言