第2章 有意义的命名
- 名副其实
- 避免误导
- 有意义的区分。不要有废话,不要有仅靠数字的标识,名称之间有区分度。
- 名称长端应与其作用域大小相对应。
- 给每个抽象概念选一个词,并且一以贯之。
- 只要短名称足够清楚,就比长名称好。
第3章 函数
- 短小,更短小。
- 只做一件事。
- 语句要在同一抽象层上。
- 函数参数尽量不超过2个;不要有标识参数。
- 无副作用。
- 使用异常替代返回错误码。
- 最好把try catch代码的主题部分抽离形成另一个函数。错误处理就是一件事。
- 不要重复。
第4章 注释
- 用代码来阐述而不是用注释
- 好注释:提供信息、对意图的解释、警示、TODO
- 坏注释:多余、误导、不必都包含javadoc、日志式、废话、信息过多、包含的联系并不明显
- 能用函数或变量表达含义就别用注释
- 注释掉的代码,不应该存在。
第5章 格式
- 垂直格式:概念间垂直方向上间隔、变量声明应尽可能靠近其使用位置、函数间调用则最好将函数放在相近位置
- 横向格式:使用空格将紧密事物连接到一起,也将相关性较弱的事物区隔开。
第6章 对象和数据结构
public interface Vehicle {
double getFuelTankCapacityInGallons();
double getGallonsOfGasoline();
}
public interface Vehicle {
double getPercentFuelRemaining();
}
- 过程式代码便于在不改动既有数据结构的前提下添加新函数;面向对象代码便于在不改动既有函数的前提下添加新类。
- 模块不应了解它所操作对象的内部情形,不过如果是数据结构,可能会了解。
- 对象暴露行为,隐藏数据,便于添加新对象类型而无须修改既有行为,同时难以在既有对象中添加新行为;数据结构暴露数据,没有明显行为,便于向既有数据结构添加新行为,同时难以向既有函数添加新数据结构。
第7章 错误处理
- 某种意义try代码块就像是事务,catch代码块将程序维持在一种持续状态。
- 已检查的代价就是违反开闭原则,使用未检异常RuntimeException
- 不返回null值,也不传递null值
- 在业务逻辑和错误处理代码之间有良好的区隔
第8章 边界
- 避免从公共API中返回边界接口,或将边界接口作为参数传递给公共API
- 例如在系统中不受限制地传递Map<String, Sensor>的实体,以为着当Map接口被修改时,有许多地方都要跟着改。
// 这样能稍微好一点
publc class Sensors {
private Map sensors = new HashMap();
public Sensor getById(String id) {return (Sensor) sensors.get(id);}
}
- 编写测试来遍览和理解第三方代码。
- 边界上的代码需要清晰地分割和定义了期望的测试。应该避免我们的代码过多地了解第三方代码中的特定信息。
第9章 单元测试
- 测试直达,只用到那些真正需要的数据类型和函数。
- 每个测试函数中只测试一个概念。
- FIRST原则,快、独立、可重复、自足验证(有布尔值输出,不应该通过查看日志文件来确认是否通过)、及时
第10章 类
- 单一权责,类或模块应该有且只有一条加以修改的理由
- 如果一个类中的每个变量都被每个方法所使用,则该类具有最大的内聚性
- 保持内聚性就会得到许多短小的类
- 为了修改而组织,对类加以组织,降低修改的风险。依赖接口而不是依赖实现。
第11章 系统
- 将系统的构造与使用分开,每个应用程序都应该留意起始过程 (工厂)
- 扩充,提到了代理、AOP
- 最佳的系统架构由模块化的关注面领域组成,每个关注面均用纯Java对象实现。不同的领域之间用最不具有侵害性的方面或类方面工具整合起来。
- 延迟决策至最后一刻也是好手段。
第12章 迭进
- 通过迭进设计达到整洁目的。
- 运行所有测试
- 递增式地重构代码
- 不可重复
- 表达了程序员的意图
- 尽可能减少类和方法的数量。
第13章
并发防御原则
- 分离并发相关代码与其他代码
- 限制临界区数量
- 使用数据副本避免共享数据
- 线程应尽可能地独立。每个线程处理一个客户端请求,从不共享的源头接纳所有请求数据,存储为本地变量。
警惕同步方法之间的依赖
保持同步区域微小
很难编写正确的关闭代码
测试线程代码
- 偶发失败都需要注意,最好假设偶发错误根本不应该存在
- 不要同时追踪非线程缺陷和线程缺陷。
- 编写可插拔的线程代码,在不同的配置环境下运行
- 允许线程数量可调整
- 运行多余处理器或处理器核心数量的线程
- 装置代码,增加对Object.wait() Object.sleep()等方法调用,改变代码执行顺序测试并发代码中的缺陷。
第14章
- 毁坏程序的最好方法之一就是以改进之名大动其结构。
- 每次修改都必须保证系统能像以前一样工作。
第17章 味道与启发
注释
环境
函数
一般性问题
- 重复
- 不正确的边界行为。别依赖直觉,追索每种边界条件,并编写测试。
- 在错误的抽象层级上的代码。创建分离较高层级一般性概念与较低层级细节概念的抽象模型。
- 基类依赖派生类
- 信息过多。限制类或模块中暴露的接口数量。
- 垂直分隔。 变量和函数应该靠近被使用的地方定义。
- 前后不一致。命名、约定保持一致。
- 人为耦合。 不互相依赖的东西不该耦合。
- 特性依恋。类的方法只应对其所属类中的变量和函数感兴趣,不该垂青其他类中的变量和函数。
- 晦涩的意图。
- 位置错误的权责。
- 不恰当的静态方法。静态方法应当只在当个实体上操作,不在任何特定对象上操作。
- 理解算法,而不是使用大量的if else
- 把逻辑依赖改为物理依赖,依赖者模块不应该对被依赖者模块有假定,它应当明确地询问后者全部信息。
public class HourlyReporter {
private HourlyReportFormatter formatter;
private List<LineItem> page;
private final int PAGE_SIZE = 55; // 这是假定知道页面尺寸,是一种逻辑依赖。可以改成getMaxPageSize()的新方法来物理化依赖。
public HourlyRepoter(HourlyReportFormatter formatter) {}
}
- 用多态替代if else
- 用命名常量替代魔术数
- 准确。确认自己足够准确。明确自己为何要这么做。
- 封装条件。把解释了条件意图的函数抽离出来。
- 避免否定性条件。
- 函数值该做一件事。
- 掩蔽时序耦合。有必要使用时序耦合时,不应该掩蔽它。
- 封装边界条件。 把处理边界条件的代码集中到一处。
- 函数应该只在一个抽象层级上。
public String render() throws Exception {
StringBuffer html = new StringBuffer("<hr");
if (size > 0) html.append(" size=\").append(size + 1).append("\");
return html.toString();
}
public String render() throws Exception {
HtmlTag hr = new HtmlTag("hr");
if(extraDashes > 0) hr.addAttribtue("size", hrSize(extraDashes));
return hr.html();
}
- 在较高抽象层级的默认常量或配置值,不要将它埋藏到较低层级的函数中。
- 避免传递浏览。
a.getB().getC()
除非里面有数据结构类,否则不应该传递。
名称
- 采用描述性名称
- 名称应与抽象层级相符
- 无歧义名称
- 为较大作用范围选用较长名称
- 避免编码
测试