第 1 章 编程规约
1.1 命名风格
【强制】 代码中的命名均不能以 _ 或 $ 开始和结束
【强制】 包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词,包名统一使用单数形式
com.alibaba.ai.util
【强制】 类名可以使用复数形式。类名使用 UpperCamelCase 风格,但以下情形例外:
- DO:Data Object,数据对象。与数据库表结构一一对应,通过 DAO 层向上传输
- BO:Business Object,业务对象。BO 包括了业务逻辑,常常封装了对 DAO、RPC 等的调用
- DTO:Data Transfer Object,数据传输对象。通常用于不同服务或服务不同分层之间的数据传输
- VO:Value Object,与前端进行交互的 Java 对象
- AO: Application Object,应用对象。 在 Web 层与 Service 层之间抽象的复用对象模型
- PO:Persistant Object,持久对象。表示数据库中的一条记录映射成的 Java 对象
JavaServerlessPlatform / UserDO / XmlService
【强制】 常量命名应该全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长
MAX_STOCK_COUNT / CACHE_EXPIRED_TIME
【强制】 方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格,必须遵从驼峰形式
localValue / getHttpMessage() / inputUserId
【强制】抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类命名以它要测试的类的名称开始,以 Test 结尾。
【强制】POJO 类中布尔类型变量都不要加 is 前缀,否则部分框架解析会引起序列化错误
【推荐】在常量与变量的命名时,表示类型的名词放在词尾
startTime / workQueue / nameList / TERMINATED_THREAD_COUNT
【推荐】如果模块、接口、类、方法使用了设计模式,在命名时需体现出具体模式
OrderFactory / LoginProxy / ResourceObserver
【推荐】 接口类中的方法和属性不要加任何修饰符号,并加上有效的注释。尽量不要在接口里定义变量,如果一定要定义变量,肯定是与接口方法相关,并且是整个应用的基础常量
【推荐】 如果是形容能力的接口名称,取对应的形容词为接口名(通常是 –able 的形容词)
AbstractTranslator 实现 Translatable 接口
【推荐】 枚举类名带上 Enum 后缀,枚举成员名称需要全大写,单词间用下划线隔开
枚举名字为 ProcessStatusEnum 的成员名称:SUCCESS / UNKNOWN_REASON
【推荐】 Service / DAO 层方法命名规约:
- 获取单个对象的方法用 get 做前缀
- 获取多个对象的方法用 list 做前缀,复数形式结尾。如 listObjects
- 获取统计值的方法用 count 做前缀
- 插入的方法用 save / insert 做前缀
- 删除的方法用 remove / delete 做前缀
- 修改的方法用 update 做前缀
1.2 常量定义
【强制】不允许任何魔法值(即未经预先定义的常量)直接出现在代码中
【强制】在给 Long 类型变量赋值时,数值后使用大写的 L,小写容易跟数字 1 混淆,造成误解
【推荐】不要使用一个常量类维护所有常量,要按常量功能归类,如缓存相关常量放在类 CacheConsts 下,系统配置相关常量放在类 SystemConfigConsts 下
【推荐】常量的复用层次有五层:跨应用共享常量、应用内共享常量、子工程内共享常量、包内共享常量、类内共享常量。
跨应用共享常量放置在二方库中,通常是 client.jar 中的 constant 目录下。应用内共享常量放置在一方库中,通常是子模块中的 constant 目录下。
【推荐】如果变量值仅在一个固定范围内变化用 enum 类型来定义
1.3 代码格式
【强制】if / for / while / switch / do 等保留字与括号之间都必须加空格
【强制】注释的双斜线与注释内容之间有且仅有一个空格
【强制】单行字符限制不超过 120 个,超过需要换行:
- 第二行相对第一行缩进 4 个空格,从第三行开始不再继续缩进
- 运算符和下行一起换行
- 方法调用的点符号与下行一起换行
- 方法调用的多个参数需要换行时,在逗号后面进行
- 括号前不换行
【强制】IDE 的 text file encoding 设置为 UTF-8; IDE 中文件的换行符使用 Unix 格式,不要使用 Windows 格式
【推荐】不同逻辑、不同语义、不同业务的代码插入一个空行分隔开来提升可读性
1.4 OOP 规约
【强制】所有的重写方法,必须加 @Override 注解
【强制】相同参数类型,相同业务逻辑才可以使用可变参数,避免使用 Object。可变参数必须放在参数列表最后
【强制】不能使用过时的类或方法
【强制】接口过时必须加 @Deprecated 注解,并清晰地注释说明采用的新接口
【强制】所有的 POJO 类属性、RPC 方法的返回值和参数必须使用包装数据类型
【强制】所有的 POJO 类属性、RPC 方法的返回值和参数必须使用包装数据类型定义 DO / DTO / VO 等 POJO 类时,不要设定任何属性默认值
【强制】序列化新增属性时,不要修改 serialVersionUID 字段
【强制】POJO 类必须写 toString 方法。如果继承了另一个 POJO 类,注意在前面加一下 super.toString。在方法执行抛出异常时,可以直接调用 POJO 的 toString()方法打印其属性值,便于排查问题。
【强制】比较对象使用 Objects 的 equals 方法
Objects.equals(“test”, object)
【强制】所有整型包装类对象之间值的比较,全部使用 equals 方法比较
【强制】浮点数之间的等值判断,基本数据类型不能用 == 来比较,包装数据类型不能用 equals 来判断,应该指定一个误差范围,两个浮点数的差值在此范围之内,则认为是相等的:
float a = 1.0f - 0.9f;
float b = 0.9f - 0.8f;
float diff = 1e-6f;
if (Math.abs(a - b) < diff) {
System.out.println("true");
}
或者使用 BigDecimal 来定义值,再进行浮点数的运算操作:
BigDecimal a = BigDecimal.valueOf(1.0);
BigDecimal b = BigDecimal.valueOf(0.9);
BigDecimal c = BigDecimal.valueOf(0.8);
BigDecimal x = a.subtract(b);
BigDecimal y = b.subtract(c);
if (x.equals(y)) {
System.out.println("true");
}
【推荐】getter / setter 方法中不要增加业务逻辑,setter 方法中 this.成员名=参数名 二者名称一致
【推荐】循环体内,字符串的连接方式,使用 StringBuilder 的 append 方法进行扩展
1.5 集合处理
【强制】只要重写 equals,就必须重写 hashCode。尤其是 Set 集合存储不重复对象,必须重写这两种方法。String 已覆写 hashCode 和 equals 方法,所以我们可以愉快地使用 String 对象作为 key 来使用
【强制】集合判空使用 isEmpty() 方法
【强制】ArrayList 的 subList 不可强转成 ArrayList,因为 subList 返回的并不是 ArrayList,而是 ArrayList 的一个视图
【强制】使用 Map 的方法 keySet() / values() / entrySet() 返回集合对象时,不可以对其进行添加元素操作,否则会抛出 UnsupportedOperationException 异常
【强制】使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一致、长度为 0 的空数组:
List list = new ArrayList<>(2);
list.add(“guan”);
list.add(“bao”);
String[] array = list.toArray(new String[0]);
【强制】使用工具类 Arrays.asList() 把数组转换成集合时,不能使用其修改集合相关的方法。因为 Arrays.asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。Arrays.asList 体现的是适配器模式,只是转换接口,后台的数据仍是数组。
【强制】不要在 foreach 循环里进行元素的 remove / add 操作。请使用 Iterator 方式来 remove 元素,如果并发操作,需要对 Iterator 对象加锁:
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (Objects.equals(item, "1")) {
iterator.remove();
}
}
【强制】HashMap 使用 HashMap(int initialCapacity) 初始化。initialCapacity = (需要存储的元素个数 / 负载因子) + 1,负载因子默认为 0.75。如果暂时无法确定初始值大小,请设置为 16(即默认值)。
1.6 并发处理
【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程
【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式
【强制】必须回收自定义的 ThreadLocal 变量,尤其在线程池场景下,线程经常会被复用,如果不清理自定义的 ThreadLocal 变量,可能会影响后续业务逻辑和造成内存泄露等问题。尽量在代理中使用 try-finally 块进行回收:
ThreadLocal.set(userInfo);
try {
// ...
} finally {
objectThreadLocal.remove();
}
【强制】高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁
【强制】对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁
【强制】资金相关的金融敏感信息,使用悲观锁策略
1.7 日期事件
【强制】日期格式化年份统一使用小写 y
【强制】获取当前毫秒数使用 System.currentTimeMillis()