Java开发规范

下载链接:Java开发规范

编程规约:
①不能以下划线或美元符号开始和结束
②严禁使用拼音与英文混合的方式
③类名使用 UpperCamelCase,但以下情形例外:DO / DTO / VO 等ForceCode / UserDO / HtmlDTO
④方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase localValue / getHttpMessage()
⑤常量命名全部大写,语义表达完整清楚
⑥抽象类命名使用 AbstractBase 开头;异常类命名使用 Exception 结尾;测试类命名以它要测试的类的名称开始,以 Test 结尾
⑦类型与中括号紧挨相连来表示数组:int[] arrayDemo;
⑧任何布尔类型的变量,都不要加 is 前缀,否则部分框架解析会引起序列化错误
⑨包名统一使用小写,包名统一使用单数形式,类名可以使用复数形式com.alibaba.ei.kunlun.aap.util、类名MessageUtils
参考:
A) Service/DAO 层方法命名规约
1) 获取单个对象的方法用 get 做前缀。
2) 获取多个对象的方法用 list 做前缀,复数结尾,如:listObjects。 
3) 获取统计值的方法用 count 做前缀。 
4) 插入的方法用 save/insert 做前缀。
5) 删除的方法用 remove/delete 做前缀。
6) 修改的方法用 update 做前缀。
B) 领域模型命名规约
1) 数据对象:xxxDO,xxx 即为数据表名。
2) 数据传输对象:xxxDTO,xxx 为业务领域相关的名称。
3) 展示对象:xxxVO,xxx 一般为网页名称。
4) POJO 是 DO/DTO/BO/VO 的统称,禁止命名成 xxxPOJO。

1.long 或者 Long 赋值时,数值后使用大写的 L,小写的l容易被误解为数字1

2.注释的双斜线与注释内容之间有且仅有一个空格
// 这是示例注释,请注意在双斜线之后有一个空格

3.外部正在调用或者二方库依赖的接口,过时必须加@Deprecated 注解,并清晰地说明采用的新接口或者新服务是什么

4.equals比较使用:Objects.equals("a", "b");所有整型包装类对象之间值的比较,全部使用 equals 方法比较
Integer var = ?-128127时使用==可比较,但是这个区间之外的所有数据,都会在堆上产生

5.任何货币金额,均以最小货币单位且整型类型来进行存储

6.浮点数之间的等值判断
正例:
(1) 指定一个误差范围,两个浮点数的差值在此范围之内,则认为是相等的。
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");
}
(2) 使用 BigDecimal 来定义值,再进行浮点数的运算操作。
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
BigDecimal c = new BigDecimal("0.8");
BigDecimal x = a.subtract(b);
BigDecimal y = b.subtract(c);
if (x.equals(y)) {
	System.out.println("true");
}

7.定义数据对象 DO 类时,属性类型要与数据库字段类型相匹配
数据库字段的 bigint 必须与类属性的 Long 类型相对应

8.禁止使用 new BigDecimal(0.1f)double 值转化为 BigDecimal 对象,实际的存储值为:0.10000000149
推荐:BigDecimal.valueOf(0.1);

9.局部变量使用基本数据类型,所有的 POJO 类属性必须使用包装数据类型,RPC 方法的返回值和参数必须使用包装数据类型
x%,x 为基本数据类型,调用的 RPC 服务,调用不成功时,返回的是默认值,页面显示为 0%(不合理)

10.定义 DO/DTO/VO 等 POJO 类时,不要设定任何属性默认值

11.new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
yyyy 表示当天所在的年,YYYY 代表是 week in which year,只要本周跨年,返回的 YYYY 就是下一年
1) 表示月份是大写的 M;表示分钟则是小写的 m; 
224 小时制的是大写的 H12 小时制的则是小写的 h。

12.获取当前毫秒数:System.currentTimeMillis();
在 JDK8 中,针对统计时间等场景,推荐使用 InstantInstant startTime = Instant.now();
Instant endTime = Instant.now();
long seconds = Duration.between(startTime, endTime).getSeconds();

13.【强制】关于 hashCode 和 equals 的处理,遵循如下规则:
1) 只要重写 equals,就必须重写 hashCode。 
2) 因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须重写这两个方法。
3) 如果自定义对象作为 Map 的键,那么必须覆写 hashCode 和 equals。
说明:String 因为重写了 hashCode 和 equals 方法,所以我们可以愉快地使用 String 对象作为 key 来使用。

14.【强制】判断所有集合内部的元素是否为空,使用 isEmpty()方法,而不是 size()==0 的方式

15.【强制】在使用 java.util.stream.Collectors 类的 toMap()方法转为 Map 集合时,一定要使
用含有参数类型为 BinaryOperator,参数名为 mergeFunction 的方法,否则当出现相同 key值时会抛出 IllegalStateException 异常。
说明:参数 mergeFunction 的作用是当出现 key 重复时,自定义对 value 的处理策略。
正例:
List<Pair<String, Double>> pairArrayList = new ArrayList<>(3);
pairArrayList.add(new Pair<>("version", 6.19));
pairArrayList.add(new Pair<>("version", 10.24));
pairArrayList.add(new Pair<>("version", 13.14));
Map<String, Double> map = pairArrayList.stream().collect(
// 生成的 map 集合中只有一个键值对:{version=13.14}
Collectors.toMap(Pair::getKey, Pair::getValue, (v1, v2) -> v2));

反例:
String[] departments = new String[] {"iERP", "iERP", "EIBU"};
// 抛出 IllegalStateException 异常
Map<Integer, String> map = Arrays.stream(departments) .collect(Collectors.toMap(String::hashCode, str -> str));

16.【强制】泛型通配符<? extends T>来接收返回的数据,此写法的泛型集合不能使用 add 方法, 而<? super T>不能使用 get 方法,两者在接口调用赋值的场景中容易出错。
说明:扩展说一下 PECS(Producer Extends Consumer Super)原则:第一、频繁往外读取内容的,适合用
<? extends T>。第二、经常往里插入的,适合用<? super T>

17.【推荐】高度注意 Map 类集合 K/V 能不能存储 null 值的情况,如下表格:
集合类               Key              Value     Super       说明
Hashtable          不允许为null    不允许为null Dictionary   线程安全
ConcurrentHashMap  不允许为null    不允许为null AbstractMap  锁分段技术(JDK8:CAS)
TreeMap            不允许为null    允许为null   AbstractMap  线程不安全
HashMap            允许为null      允许为null   AbstractMap  线程不安全

18.【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下: 
1FixedThreadPoolSingleThreadPool:允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。 
2CachedThreadPool:允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

19.【强制】必须回收自定义的 ThreadLocal 变量,尤其在线程池场景下,线程经常会被复用,
如果不清理自定义的 ThreadLocal 变量,可能会影响后续业务逻辑和造成内存泄露等问题。尽量在代理中使用 try-finally 块进行回收。
正例:
objectThreadLocal.set(userInfo);
try {
	// ...
} finally {
	objectThreadLocal.remove();
}

20.在一个 switch 块内,都必须包含一个 default语句并且放在最后,即使它什么代码也没有。

21.【强制】在 if/else/for/while/do 语句中必须使用大括号。
说明:即使只有一行代码,禁止不采用大括号的编码方式:if (condition) statements;

22.【强制】类、类属性、类方法的注释必须使用 Javadoc 规范,使用/**内容*/格式,不得使用
// xxx 方式

23.【强制】所有的类都必须添加创建者和创建日期。设置模板时,IDEA 的@author 为`${USER}`,日期统一为 yyyy/MM/dd 的格式

24.【强制】方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释使用/* */注释

25.【参考】特殊注释标记,请注明标记人与标记时间
1) 待办事宜(TODO):(标记人,标记时间,[预计处理时间]2) 错误,不能工作(FIXME):(标记人,标记时间,[预计处理时间],FIXME 标记某代码是错误的,而且不能工作,需要及时纠正的情况

26.对于暂时被注释掉,后续可能恢复使用的代码片断,在注释代码上方,统一规定使用三个斜杠(///)
public static void hello() {
	/// 业务方通知活动暂停
	// Business business = new Business();
	// business.active();
	System.out.println("it's finished");
}
异常日志:
1.错误码
【强制】全部正常,但不得不填充错误码时返回五个零:00000。
【强制】错误码为字符串类型,共 5 位,分成两个部分:错误产生来源+四位数字编号。
	说明:错误产生来源分为 A/B/CA 表示错误来源于用户,比如参数错误,用户安装版本过低,用户支付
	超时等问题;B 表示错误来源于当前系统,往往是业务逻辑出错,或程序健壮性差等问题;C 表示错误来源
	于第三方服务,比如 CDN 服务出错,消息投递超时等问题;四位数字编号从 00019999,大类之间的步长间距预留 100

2.异常处理
【强制】不要在 finally 块中使用 return。
说明:try 块中的 return 语句执行成功后,并不马上返回,而是继续执行 finally 块中的语句,如果此处存在 return 语句,则在此直接返回,无情丢弃掉 try 块中的返回点。
	反例:
	private int x = 0;
	public int checkReturn() {
	try {
		// x 等于 1,此处不返回
		return ++x; 
	} finally {
		// 返回的结果是 2
		return ++x; 
	} 
}

3.日志规约
【强制】应用中不可直接使用日志系统(Log4jLogback)中的 API,而应依赖使用日志框架
(SLF4J、JCL--Jakarta Commons Logging)中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
推荐使用 SLF4J
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Test.class);

【强制】所有日志文件至少保存 15 天,因为有些异常具备以“周”为频次发生的特点。
对于当天日志,以“应用名.log”来保存,保存在/home/admin/应用名/logs/</font>目录下,过往日志格式为: {logname}.log.{保存日期},日期格式:yyyy-MM-dd
说明:以 mppserver 应用为例,日志保存在/home/admin/mppserver/logs/mppserver.log,历史日志名称为 mppserver.log.2016-08-01

【强制】在日志输出时,字符串变量之间的拼接使用占位符的方式。
说明:因为 String 字符串的拼接会使用 StringBuilderappend()方式,有一定的性能损耗。使用占位符仅是替换动作,可以有效提升性能。
正例:logger.debug("Processing trade with id: {} and symbol: {}", id, symbol);

【强制】日志打印时禁止直接用 JSON 工具将对象转换成 String。
说明:如果对象里某些 get 方法被重写,存在抛出异常的情况,则可能会因为打印日志而影响正常业务流程的执行。
MySQL数据库:
【强制】表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint(1 表示是,0 表示否)。
说明:任何字段如果为非负数,必须是 unsigned。
注意:POJO 类中的任何布尔类型的变量,都不要加 is 前缀,所以,需要在<resultMap>设置从 is_xxx 到Xxx 的映射关系。
数据库表示是与否的值,使用 tinyint 类型,坚持 is_xxx 的命名方式是为了明确其取值含义与取值范围。
正例:表达逻辑删除的字段名 is_deleted,1 表示删除,0 表示未删除。

【强制】数据库名、表名、
字段名,都不允许出现任何大写字母,

【强制】主键索引名为 pk_字段名;唯一索引名为 uk_字段名;普通索引名则为 idx_字段名。
说明:pk_ 即 primary key;uk_ 即 unique key;idx_ 即 index 的简称。

【强制】小数类型为 decimal,禁止使用 floatdouble。
说明:在存储的时候,floatdouble 都存在精度损失的问题,很可能在比较值的时候,得到不正确的
结果。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数并分开存储。

【强制】表必备三字段:id, gmt_create, gmt_modified。
说明:其中 id 必为主键,类型为 bigint unsigned、单表时自增、步长为 1。gmt_create, gmt_modified的类型均为 datetime 类型,前者现在时表示主动式创建,后者过去分词表示被动式更新。

【推荐】如果有 order by 的场景,请注意利用索引的有序性。order by 最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现 file_sort 的情况,影响查询性能。
正例:where a=? and b=? order by c; 索引:a_b_c
反例:索引如果存在范围查询,那么索引有序性无法利用,如:WHERE a>10 ORDER BY b; 索引 a_b 无法排序。

【推荐】SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,如果可以是 consts最好。
说明:
1) consts 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。
2) ref 指的是使用普通的索引(normal index)。 
3) range 对索引进行范围检索。
反例:explain 表的结果,type=index,索引物理文件全扫描,速度非常慢,这个 index 级别比较 range还低,与全表扫描是小巫见大巫
EXPLAIN执行计划中type字段: system > const > eq_ref > ref > range > index > ALL
ALL:全表扫描       
INDEX:遍历整个索引来查找匹配的行。explain select title from film  (虽然where条件中没有用到索引,但是要取出的列title是索引包含的列,所以只要全表扫描索引即可,直接使用索引树查找数据)      
RANGE:索引范围扫描,常见于<<=>>=、in、between等操作符。 explain select * from payment where customer_id > 300 and customer_id < 350 (customer_id是索引,范围查找索引找到具体的数据)        
REF:使用非唯一性索引或者唯一索引的前缀扫描。explain select * from payment where customer_id = 350 (customer_id为非唯一性索引) explain select b.*, a.* from payment a ,customer b where a.customer_id = b.customer_id   
EQ_REF:相对于ref来说就是在联表查询中使用primary key或者unique key作为关联条件,explain select b.*, a.* from payment a ,customer b where a.customer_id = b.customer_id
CONST,SYSTEM:explain select * from film  where film_id = 1 ,file_id是主键或唯一索引,(如果上表中film表中只有一行数据,那么type就是system)        
NULL:MYSQL不用访问表或者索引就直接能到结果, explain select 1 from dual where 1

【强制】不要使用 count(列名)count(常量)来替代 count(*)count(*)是 SQL92 定义的标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。
说明:count(*)会统计值为 NULL 的行,而 count(列名)不会统计此列为 NULL 值的行。

【强制】禁止使用存储过程,存储过程难以调试和扩展,更没有移植性

【推荐】SQL 语句中表的别名前加 as,并且以 t1、t2、t3、...的顺序依次命名。
说明:
1)别名可以是表的简称,或者是根据表出现的顺序,以 t1、t2、t3 的方式命名。
2)别名前加 as
使别名更容易识别。
正例:select t1.name from table_first as t1, table_second as t2 where t1.id=t2.id;

【强制】在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。
说明:
1)增加查询分析器解析成本。
2)增减字段容易与 resultMap 配置不一致。
3)无用字段增加网络消耗,尤其是 text 类型的字段。

【强制】不要用 resultClass 当返回参数,即使所有类属性名与数据库字段一一对应,也需要
定义<resultMap>;反过来,每一个表也必然有一个<resultMap>与之对应。
说明:配置映射关系,使字段与 DO 类解耦,方便维护

【强制】sql.xml 配置参数使用:#{},不要使用${} 此种方式容易出现 SQL 注入。

【强制】不允许直接拿 HashMapHashtable 作为查询结果集的输出

【强制】更新数据表记录时,必须同时更新记录对应的 gmt_modified 字段值为当前时间。

【参考】@Transactional 事务不要滥用。事务会影响数据库的 QPS,另外使用事务的地方需
要考虑各方面的回滚方案,包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等。
工程结构:
【强制】二方库版本号命名方式:主版本号.次版本号.修订号
1)主版本号:产品方向改变,或者大规模 API 不兼容,或者架构不兼容升级。 
2) 次版本号:保持相对兼容性,增加主要功能特性,影响范围极小的 API 不兼容修改。
3) 修订号:保持完全兼容性,修复 BUG、新增次要功能特性等。
说明:注意起始版本号必须为:1.0.0,而不是 0.0.1。

【强制】线上应用不要依赖 SNAPSHOT 版本(安全包除外);正式发布的类库必须先去中央仓
库进行查证,使 RELEASE 版本号有延续性,且版本号不允许覆盖升级。

版权声明:本文为weixin_44171910原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。