在阅读完《阿里巴巴Java开发手册》和《Effective java》这两本书之后,渐渐对编程规范有了清晰的认识,也让我感觉在代码规范问题上,我们应该做到遵守开发的规范,这样不但对现在开发是有利的,对后期代码维护也铺垫好了基础。这样编写出的代码,易懂且高效。也从中学习到了很多之前在学校没有接触到的很多编程规范,下面就让我来罗列一下:
《阿里巴巴Java开发手册》
编程规约
命名风格
- 如果模块、接口、类、方法使用设计模式,在命名时体现出具体模式;
- 接口类中的方法和属性不要加任何修饰符号,保持代码的简介性,并加上有效的javadoc注释。尽量不要在接口里定义变量,如果一定要定义变量,肯定是与接口方法相关,并且是整个引用的基础常量;
- 枚举类名建议带上Enum后缀,枚举成员名称需要全大写,单词间用下划线隔开;
- Service/DAO层方法命名规约
get做前缀表示获取单个对象的方法
list做前缀表示获取多个对象的方法
count做前缀表示获取统计值的方法
save/insert做前缀表示插入的方法
remove/delete做前缀表示删除的方法
update做前缀表示修改的方法 - 领域模型命名规约
数据对象:xxxDO,xxx即为数据表名
数据传输对象:xxxDTO,xxx为业务领域相关的名称
展示对象:xxxVO,xxx一般为网页名称
POJO是DO/DTO/BO/VO的统称,禁止命名成xxxPOJO
常量定义
- Long或者long初始赋值时,使用大写的L,不能是小写的l,小写很容易和数字1混淆,造成误会
- 不要使用一个常量类维护素所有常量,按常量功能进行归类,分开维护
- 如果变量值尽在一个范围内变化,且带有名称外的延伸属性,定义为枚举类
代码格式
- If/for/while/switch/do保留字与括号间都必须加空格
- 方法体内的执行语句组、变量的定义语句组、不同的业务逻辑之间或者不同的语义之间插入一个空行。相同业务逻辑和语义之间不需要插入空行。
OOP规约
- 避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可。
- 外部正在调用或者二方库依赖的接口,不允许修改方法签名,避免对接口调用方产生影响。接口过时必须加@Deprecated注解,并清晰地说明采用的新接口或者新服务是什么。
- 不可以使用过时的类或方法
- Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。
- 所有的相同类型的包装类对象之间值的比较,全部使用 equals 方法比较。
- 定义 DO/DTO/VO 等 POJO 类时,不要设定任何属性默认值。
- 构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init 方法中。
- 使用索引访问用 String 的 split 方法得到的数组时,需做最后一个分隔符后有无内容的检查,否则会有抛 IndexOutOfBoundsException 的风险。
- 循环体内,字符串的连接方式,使用 StringBuilder 的 append 方法进行扩展。
- 慎用 Object 的 clone 方法来拷贝对象。
集合处理
- 关于 hashCode 和 equals 的处理,遵循如下规则:
1) 只要重写 equals,就必须重写 hashCode。
2) 因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的
对象必须重写这两个方法。
3) 如果自定义对象做为 Map 的键,那么必须重写 hashCode 和 equals。
ArrayList的sublist结果不可强转成ArrayList,否则会抛出classCastException异常 - 使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一样的数组,大小就是 list.size()。
- 使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常。
- 不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁。
并发处理
- 获取单例对象需要保证线程安全,其中的方法也要保证线程安全。
- 创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
- 线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
- SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为 static,必须加锁,或者使用 DateUtils 工具类。
- 高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能 锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。
- 对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁。
- 并发修改统一记录时,避免更新丢失,需要加锁。要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观锁,使用version作为更新依据。
- 多线程并行处理定时任务时,Timer 运行多个 TimeTask 时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,使用ScheduledExecutorService 则没有这个问题。
控制语句
表达异常的分支时,少用 if-else 方式,这种方式可以改写成:if(){ return;}
注释规约
- 所有的枚举类型字段必须要有注释,说明每个数据项的用途。
- 合理处理注释掉的代码。在上方详细说明,而不是简单的注释掉。如果无用,则删除。
异常日志
异常处理
- Java 类库中定义的一类 RuntimeException 可以通过预先检查进行规避,而不应该 通过 catch 来处理,比如:IndexOutOfBoundsException,NullPointerException 等等。
- 异常不要用来做流程控制,条件控制,因为异常的处理效率比条件分支低。
- 捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请 将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。
- 有 try 块放到了事务代码中,catch 异常后,如果需要回滚事务,一定要注意手动回 滚事务。
- finally 块必须对资源对象、流对象进行关闭,有异常也要做 try-catch。
- 不能在 finally 块中使用 return,finally 块中的 return 返回后方法结束执行,不 会再执行 try 块中的 return 语句。
- 捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类。
日志规约
- 对 trace/debug/info 级别的日志输出,必须使用条件输出形式或者使用占位符的方式。
- 避免重复打印日志,浪费磁盘空间。
- 异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过关键字 throws 往上抛出。
Effective Java
- 考虑使用静态工厂方法代替构造方法(通常,静态工厂更可取,因此避免在没有考虑静态工厂的情况下提供公共构造方法。)
- 当构造方法参数过多时使用 builder 模式(当设计类的构造方法或静态工厂的参数超过几个时,Builder模式是一个不错的选择,特别是如果许多参数是可选的或相同类型的。)
- 使用私有构造方法或枚类实现 Singleton 属性 (单一元素枚举类通常是实现单例的最佳方式。)
- 使用私有构造方法执行实例化
- 依赖注入优于硬连接资源(依赖注入的实践将极大地增强类的灵活性、可重用性和可测试性。)
- 避免创建不必要的对象
- 消除过期的对象引用(因为内存泄漏通常不会表现为明显的故障,所以它们可能会在系统中保持多年)
- 避免使用Finalizer和Cleaner机制(除了作为一个安全网或者终止非关键的本地资源,不要使用 Cleaner 机制)
- 使用try-with-resources语句代替try-finally语句(在处理必须关闭的资源时,使用 try-with-resources 语句替代 try-finally 语句。)
- 重写equals方法时遵守通用约定(在很多情况下,不要重写equals方法,从Object继承的实现完全是你想要的。)
- 重写equals方法时同时也要重写hashcode方法(每次重写equals方法时都必须重写hashCode方法,否则程序将无法正常运行。)
- 始终重写toString方法
- 慎重地重写clone方法(复制构造方法或复制工厂可以接受类型为该类实现的接口的参数。)
- 考虑实现Comparable接口(无论何时实现具有合理排序的值类,你都应该让该类实现 Comparable 接口,以便在基于比较的集合中轻松对其实例进行排序,搜索和使用。)
- 使类和成员的可访问性最小化(应该尽可能地减少程序元素的可访问性。)
- 在公共类中使用访问方法而不是公共属性(公共类不应该暴露可变属性。)
- 最小化可变性
- 组合优于继承(继承是强大的,但它是有问题的,因为它违反封装。)
- 要么设计继承并提供文档说明,要么禁用继承(最好是通过声明你的类为 final 禁止继承,或者确保没有可访问的构造方法。)
- 接口优于抽象类(一个接口通常是定义允许多个实现的类型的最佳方式。)
- 为后代设计接口(编写多个使用每个新接口的实例来执行各种任务的客户端程序同样重要。)
- 接口仅用来定义类型(接口只能用于定义类型。 它们不应该仅用于导出常量。)
- 类层次结构优于标签类(标签类很少有适用的情况。)
- 支持使用静态成员类而不是非静态类
- 将源文件限制为单个顶级类(永远不要将多个顶级类或接口放在一个源文件中。)
- 不要使用原始类型(可能导致运行时异常。)
- 消除非检查警告(每个未经检查的警告代表在运行时出现 ClassCastException 异常的可能性。)
- 列表优于数组(数组是协变和具体化的; 泛型是不变的,类型擦除的。)
- 优先考虑泛型(泛型类型比需要在客户端代码中强制转换的类型更安全,更易于使用。)
- 优先使用泛型方法(编译时不会生成任何警告,并提供类型安全性和易用性。)
- 使用限定通配符来增加API的灵活性(相对于提供的不可变的类型,此方法增加更多的灵活性。 )
- 合理地结合泛型和可变参数(可变参数机制是在数组上面构建的脆弱的抽象,并且数组具有与泛型不同的类型规则。)
- 优先考虑类型安全的异构容器(泛型 API 的通常用法限制了每个容器的固定数量的类型参数。)
- 使用枚举类型代替整形常量(枚举更具可读性,更安全,更强大。)
- 使用实例属性代替序数
- 使用EnumSet代替位属性(EnumSet 类具有位属性的简洁性和性能)
- 使用EnumMap代替序数索引(使用序数来索引数组很不合适)
- 使用接口模拟可扩展的枚举(允许客户端编写自己的各种类型来实现接口。)
- 注解优于命名(可以提高源代码的可读性。)
- 始终使用Override注解(编译器可以保护免受很多错误的影响。)
- 使用标记接口定义类型(可以更精确地定位目标。)
- lambda表达式优于匿名类(更容易地创建函数对象。)
- 方法引用优于lambda表达式(比lambda 提供一个更简洁的选择)
- 优先使用标准的函数式接口
- 明智审慎地使用Stream
- 优先考虑流中无副作用的函数(编程流管道的本质是无副作用的函数对象。)
- 优先使用Collection而不是Stream来作为方法的返回类型(Collection 或适当的子类型通常是公共序列返回方法的最佳返回类型。)
- 谨慎使用流并行(不仅会导致糟糕的性能,包括活性失败;它会导致安全故障)
- 检查参数有效性
- 必要时进行防御性拷贝(如果一个类有从它的客户端获取或返回的可变组件,那么这个类必须防御性地拷贝这些组件。)
- 仔细设计方法签名
- 明智审慎地使用重载(最好避免重载具有相同数量参数的多个签名的方法。)
- 明智审慎地使用可变参数
- 返回空的数组或集合,不要返回null(使 API 更难以使用,更容易出错,并且没有性能优势)
- 明智审慎地返回Optional(除了作为返回值之外,不应该在任何其他地方中使用 Optional。)
- 为所有已公开的API元素编写文档注释(文档注释是记录 API 的佳、有效的方法。)
- 最小化局部变量的作用域(保持方法小而集中)
- for-each循环优于传统for循环(for-each 循环在清晰度,灵活性和错误预防方面提供了超越传统 for 循环的令人注目的优势,而且没有性能损失。)
- 了解并使用库(可以少做很多无用功)
- 若需要精确答案就应避免使用float和double类型(不超过9位,使用int,不超过18位,使用long,超过18位,使用BigDecimal)
- 基本数据类型优于包装类(基本类型更简单、更快。)
- 当使用其他类型更合适时应避免使用字符串(使用不当,字符串比其他类型更麻烦、灵活性更差、速度更慢、更容易出错。)
- 当心字符串连接引起的性能问题(使用StringBuilder的append方法连接字符串,除非不考虑性能问题)
- 通过接口引用对象(使程序将更加灵活)
- 接口优于反射(反射失去了编译时类型检查的所有好处,访问所需的代码既笨拙又冗长,性能降低)
- 明智审慎地本地方法(本地代码中的一个错误就可以破坏整个应用程序)
- 明智审慎地进行优化
- 遵守被广泛认可的命名约定(方便了解是那种性征)
- 只针对异常的情况下才使用异常(异常是为了在异常情况下被设计和使用的)
- 对可恢复的情况使用受检异常,对编程错误使用运行时异常
- 避免不必要的使用受检异常(过度使用,将会使 API 使用起来非常痛苦。)
- 优先使用标准的异常
- 抛出与抽象对应的异常
- 每个方法抛出的异常都需要创建文档(要为你编写的每个方法所能抛出的每个异常建立文档。)
- 在细节消息中包含失败-捕捉消息(异常的细节信息应该包含“对该异常有贡献”的所有参数和字段的值)
- 保持失败原子性(失败的方法调用应该使对象保持在被调用之前的状态)
- 不要忽略异常(用空的catch 块忽略它,都将导致程序在遇到错误的情况下悄然地执行下去)
- 同步访问共享的可变数据(当多个线程共享可变数据的时候,每个读或者写数据的线程都必须执行同步。)
- 避免过度同步(为了避免死锁和数据破坏)
- executor、task和stream优先于线程
- 并发工具优于wait和notify
- 文档应包含线程安全属性(要启用安全的并发使用,类必须清楚地记录它支持的线程安全级别。)
- 明智审慎地使用延迟初始化
- 不要依赖线程调度器(任何依赖线程调度器来保证正确性或性能的程序都可能是不可移植的。)
- 优先选择Java序列化的代替方案
- 非常谨慎地实现Serializable
- 考虑使用自定义的序列化形式
- 保护性的编写readObject方法
- 对于实例控制,枚举类型优于readResolve(应该尽可能的使用枚举类型来实施实例控制的约束条件)
- 考虑用序列化代理代替序列化实例(必须在一个不能被客户端拓展的类上面编写 readObject 或者 writeObject 方法)