阿里巴巴 Java 开发手册总结
第1章:编程规约
这一章是对传统意义上的代码规范,包括变量命名、代码风格、控制语句、代码注释等基本的变成习惯,以及从高并发场景中提炼出来的集合处理技巧与并发多线程的注意事项。
1.1 命名风格
第一条:【强制】代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。
- 反例:
_name
/$name
/name_
/name$
尽管 $
可以作为标识符使用,然而我们应该尽量避免对其使用。
-
原因:
$
通常在编译器生成的标识符名称中使用,如果我们也使用这个符号,可能会有一些意想不到的错误发生.... - 意想不到的错误示例:
package test;
public class User$VIP {
public static void main(String[] args) {
User user = new User();
User.VIP vip = user.new VIP();
vip.print();
}
}
class User{
class VIP{
void print(){
System.out.println("成员类");
}
}
}
仔细阅读以下,似乎并没有什么问题,代码也比较简单,但正在我们编译的时候,IDEA提示我们:
image.png定义了重复的代码?归根到底,都是 $
惹的祸!因为 $
被编译器所使用,在源文件(.java 文件)编译成字节码(.class 文件)后,会称为顶层类型与嵌套类型之间的连接符。例如,如果存在一个顶层类 A,在其内声明了一个成员类 B,那么编译之后就会产生两个 class 文件,分别为 A.class
与 A$B.class
。
就本程序来说,会生成 3 个 class 文件(如果可以编译的话),分别是 User$VIP.class
(顶层类)、User.class
与 User$VIP.class
(User 类的成员类,也就是类 VIP)。由于试图存在两个 User$VIP.class
所以才会报错!
第三至第六条:【强制】
- 类名使用 UpperCamelCase 风格,方法名、参数名、成员变量、局部变量都同意使用 lowerCamelCase 风格,必须遵从驼峰形式。
- 变量命名全部大写,单词兼用下划线隔开,力求予以表达完整清楚,不要嫌名字太长。
正例:MAX_STOCK_COUNT / PRIZE_NUMBER_EVERYDAY
反例:MAX_COUNT / PRIZE_NUMBER
- 抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类命名以它要测试的类名开始,以 Test 结尾。
第八条:【强制】 POJO 类中布尔类型的变量都不要加 is 前缀,否则部分框架解析会引起序列化错误。
反例:定义为基本数据类型
Boolen isDeleted;
的属性,它的方法名称也是isDeleted()
,RPC 框架在反向解析的时候,“误以为” 对应的属性名称是deleted
,导致属性获取不到抛出异常。
第十二条:【推荐】 如果模块、类、方法使用了设计模式,应在命名时体现出具体模式
- 说明: 将设计模式体现在名字中,有利于阅读者快速理解架构设计理念。
正例:
public class OrderFactory;
public class LoginProxy;
public class ResourceObserver;
第十三条:【推荐】 接口类中的方法和属性不要加任何修饰符号(public 也不要加),保持代码的间接性,并加上有效的 Javadoc 注释。尽量不要在接口里定义变量,如果一定要定义变量,必须是与接口方法相关的,并且是整个应用的基础变量。
正例:
接口方法签名:void commit();
接口基础变量:String COMPANY = "alibaba";
反例:
接口定义方法:public abstract void commit();
- 说明: 如果 JDK8 中接口允许有默认实现,那么这个 default 方法,是对所有实现类都有价值的默认实现。
第十四条:接口和实现类的命名规则
- 1):【强制】 对于 Service 和 DAO 类,基于 SOA 的理念,暴露出来的服务一定是接口,内部的实现类用 Impl 后缀与接口区别。
正例: CacheServiceImpl 实现 CacheServcie 接口
- 2):【推荐】 如果是形容能力的接口名称,取对应的形容词为接口名(通常是 -able 的形式)。
正例: AbstractTranslator 实现 Translatable。
1.2 常量定义
第二条:【强制】 long 或者 Long 初始赋值时,使用大写的 L,不能是小写的 l。小写的 l 容易跟数字 1 混淆,造成误解。
-
说明:
Long a = 2l;
写得是数字的 21 还是 Long 型的 2?
第三条:【推荐】 不要使用一个常量类维护所有变量,要按常量功能进行归类,分开维护。
- 说明: 大而全的变量类,非得使用查找功能才能定位到修改的常量,不利于理解和维护。
正例:缓存相关常量放在类 CacheConsts 下;系统配置相关常量放在 ConfigConsts 下。
1.3 代码格式
public static void main(String[] args){
// 注释的双斜线与注释内容之间有且仅有一个空格
// 缩进 4 个空格
String say = "hello";
// 运算符的左右必须有 1 个空格
int flag = 0;
// 关键字 if 与括号之间必须有 1 个空格,括号内的 f与左括号、
// 0 与右括号之间不需要空格
if (flag == 0) {
System.out.println(say);
}
// 左大括号前加空格且不换行;左大括号后换行
if (flag == 1) {
System.out.println("world");
// 右大括号前换行,右大括号后有 else,不用换行
} else {
System.out.println("ok");
// 在右大括号后直接结束,则必须换行
}
}
第八条:【强制】 方法参数在定义和传入时,多个参数逗号后边必须加空格。
正例:下例中实参的“one”,后边必须要有一个空格。
method("one", "two", "three");
1.4 OOP 规约
第二条:【强制】 所有的复写方法,必须加 @Override 注解。
-
说明: getObject() 与 get0bject() 的问题。一个是字母 O,一个是数字 0,
加 @Override 注解可以准确判断是否覆盖成功。另外,如果在抽象类中对方法签名进行修改,其实现类会马上编译报错。
第七条:【强制】 所有相同类型的包装类对象之间值得比较,全部使用 equals 方法
-
说明: 对于
Intergre var = ?
在 -128~127 范围内的赋值, Integer 对象是在 IntegerCache.cache 中产生的,会复用已有的对象,这个区间内的 Integer 值可以直接使用 == 进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象。这是一个大坑,推荐使用 equals 方法进行判断。
第十二条:【强制】 POJO 类必须写 toString 方法。在使用 IDE 中的工具 source>generate toString 时,如果继承了另一个 POJO 类,注意在前面加一下 super.toString。
- 说明: 在方法执行抛出异常时,可以直接调用 POJO 的 toString() 方法打印其属性值,便于排查问题。
1.5 集合处理
第七条:【强制】 不要在 foreach 循环里进行元素的 remove / add 操作。remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁。
// 正例
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (删除元素的条件) {
iterator.remove();
}
}
// 反例
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
for (String item : list) {
// 如果把 1 改为 2 再试一下看看是否相同
if ("1".equals(item)) {
list.remove(item);
}
}
image.png
第十一条:【推荐】 高度注意 Map 类集合 K/V 能不能存储 null 值得情况
image.png1.6 并发处理
第三条:【强制】 线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
- 说明: 使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源,解决资源不足的问题。如果不适用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者 “过渡切换” 的问题。
1.7 控制语句
第二条:【强制】 在 if / else / for / while / do 语句中,必须使用大括号。即使只有一行代码,也应该避免采用单行的编码方式:if (condition) statements;
第三条:【强制】 在高并发场景中,避免使用 “等于” 判断作为终端或退出的条件
- 说明: 如果并发控制没有处理好,容易产生等值判断被 “击穿” 的情况,应使用大于或小于的区间判断条件来代替。
反例:判断剩余奖品数量等于 0 时,终止发放奖品,但因为并发处理错误导致奖品数量瞬间变成了负数,这样的话,活动无法终止。
第四条:【推荐】 在表达异常的分支时,尽量少用 if-else 方式
- 说明: 如果不得不使用 if()...else if()...else... 方式表达逻辑,【强制】 避免后续代码维护困难,请勿超过 3 层。
// 正例:超过 3 层的 if-else 逻辑判断代码可以使用卫语句、策略模式
// 状态模式等来实现,其中卫语句实例如下:
public void today() {
if (isBusy()) {
System.out.println("change time,");
return;
}
if (isFree()) {
System.out.println("go to travel.");
return;
}
System.out.println("stay at home to learn Java");
return;
}
1.8 注释规约
第一条:【强制】 类、类属性、类方法的注释必须使用 Javadoc 规范,使用 /**内容*/
格式,不得使用 //xxx
方式
image.png
第二条:【强制】 所有的抽象方法(包括接口中的方法)必须要用 Javadoc 注释,除了返回值、参数、异常说明外,还必须指出该方法做什么事情,实现什么功能。
- 说明: 对子类的实现要求,或者调用注意事项,请一并说明。
第三条:【强制】 所有的类都必须添加创建者和创建日期。
image.png1.9 其他
第三条:【强制】 后台输送给网页的变量必须加$!{var}——中间是感叹号
- 说明: 如果 var=null 或者不存在,那么 ${var} 会直接显示在页面上。
第四条:【强制】 注意 Math.random() 这个方法返回的是 double 类型,取值的范围 0≤x<1(能够取到零值,注意除零异常),如果向获取整数类型的随机数,不要将 x 放大 10 的若干倍然后取整,直接使用 Random 对象的 nextInt 或者 nextLong 方法。
第六条:【推荐】 不要在视图模板中加入任何复杂的逻辑。
- 说明: 根据 MVC 理论,视图的职责是展示,不要抢模型和控制器的工作。
第4章:安全规约
“安全生产,责任重于泰山。” 这句话同样适用于软件生产,本章主要说明编程中需要注意的比较基础的安全准则。
第一条:【强制】 隶属于用户个人的页面或者功能必须进行权限控制校验
- 说明: 放置皆有做水平权限校验就可以随意访问、修改、删除别人的数据,比如查看他人的私信内容、修改他人的订单。
第二条:【强制】 用户敏感数据禁止直接展示,必须对展示数据进行脱敏。
- 说明: 个人手机号码会显示为 158****9119,隐藏中间 4 位,防止个人隐私泄露。
第三条:【强制】 用户输入的 SQL 参数严格使用参数绑定或者 METADATA 字段值限定,防止 SQL 注入,禁止字符串拼接 SQL 访问数据库。
image.png第四条:【强制】 用户请求传入的任何参数必须做有效性验证
-
说明: 忽略参数校验可能导致如下情况。
1)page size 过大导致内存溢出
2)恶意 order by 导致数据库慢查询
3)任意重定向
4)SQL 注入
5)反序列化注入
6)正则输入源串拒绝服务 ReDoS
Java 代码用正则来验证客户端的输入,有些正则写法验证普通用户输入没有问题,但是如果攻击人员使用的是特殊构造的字符串来验证,则有可能导致死循环。
第五条:【强制】 禁止向 HTML 页面输出未经安全过滤或未正确转义的用户数据。
第六条:【强制】 表单、AJAX 提交必须执行 CSRF 安全过滤
[图片上传失败...(image-c3804-1533370102283)]
第七条:【强制】 在使用平台资源,譬如短信、邮件、电话、下单、支付,必须实现正确的防重放限制,如数量限制、疲劳度控制、验证码校验,避免被滥刷、资损。
- 说明:如注册时发送验证码到手机,如果没有限制次数和频率,那么可以利用此功能骚扰到其它用户,并造成短信平台资源浪费。
第5章:MySQL 数据库
5.1 建表规约
第二条:【强制】 表名、字段名必须使用小写字母或数字 , 禁止出现数字开头,禁止两个下划线中间只出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。
- 说明: MySQL 在 Windows 下不区分大小写,但在 Linux 下默认区分大小写。因此,数据库名、表明、字段名都不允许出现任何大写字母,避免节外生枝。
正例: getter _ admin , task _ config , level 3_ name
反例: GetterAdmin , taskConfig , level 3 name
第四条:【强制】禁用保留字,如 desc 、 range 、 match 、 delayed 等,请参考 MySQL 官方保留字。
第五条: 【强制】主键索引名为 pk_ 字段名;唯一索引名为 uk _字段名 ; 普通索引名则为 idx _字段名。
- 说明: pk_ 即 primary key;uk _ 即 unique key;idx _ 即 index 的简称。
第六条:【强制】小数类型为 decimal ,禁止使用 float 和 double 。
- 说明:float 和 double 在存储的时候,存在精度损失的问题,很可能在值的比较时,得到不正确的结果。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数分开存储。
第八条:【强制】 varchar 是可变长字符串,不预先分配存储空间,长度不要超过 5000,如果存储长度大于此值,定义字段类型为 text ,独立出来一张表,用主键来对应,避免影响其它字段索引效率。
第九条:【强制】表必备三字段: id , gmt _ create , gmt _ modified
- 说明:其中 id 必为主键,类型为 unsigned bigint 、单表时自增、步长为 1。 gmt _ create ,gmt _ modified 的类型均为 date _ time 类型。
第十条: 【推荐】表的命名最好是加上“业务名称_表的作用”。
正例: tiger _ task / tiger _ reader / mpp _ config
第十五条:【参考】合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度。
正例:如下表,其中无符号值可以避免误存负数,且扩大了表示范围。
对象 | 年龄区间 | 类型 | 表示范围 |
---|---|---|---|
人 | 150 岁之内 | unsigned tinyint | 无符号值:0 到 255 |
龟 | 数百岁 | unsigned smallint | 无符号值:0 到 65535 |
恐龙化石 | 数千万年 | unsigned int | 无符号值:0 到约 42.9 亿 |
太阳 | 约 50 亿年 | unsigned bigint | 无符号值:0 到约 10 的 19 次方 |
5.2 索引规约
第五条: 【推荐】如果有 order by 的场景,请注意利用索引的有序性。 order by 最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现 file _ sort 的情况,影响查询性能。
正例: where a =? and b =? order by c; 索引: a _ b _ c
反例:索引中有范围查找,那么索引有序性无法利用,如: WHERE a >10 ORDER BY b; 索引 a _ b 无法排序。
第九条: 【推荐】建组合索引的时候,区分度最高的在最左边。
正例:如果 where a =? and b =? , a 列的几乎接近于唯一值,那么只需要单建 idx _ a 索引即可。
- 说明: 存在非等号和等号混合判断条件时,在建索引时,请把等号条件的列前置。如: where a >? and b = ? 那么即使 a 的区分度更高,也必须把 b 放在索引的最前列。
5.3 SQL 语句
第一条:【强制】不要使用 count( 列名 ) 或 count( 常量 ) 来替代 count( * ) , count( * ) 是 SQL 92 定义的标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。
- 说明: count( * ) 会统计值为 NULL 的行,而 count( 列名 ) 不会统计此列为 NULL 值的行。
第六条: 【强制】不得使用外键与级联,一切外键概念必须在应用层解决。
-
说明: ( 概念解释 ) 学生表中的 student _ id 是主键,那么成绩表中的 student _ id 则为外键。如果更新学生表中的 student _ id ,同时触发成绩表中的 student _ id 更新,则为级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群 ; 级联更新是强阻塞,存在数据库更新风暴的风险 ; 外键影响数据库的插入速度。
image.png
第八条: 【强制】数据订正时,删除和修改记录时,要先 select ,避免出现误删除,确认无误才能执行更新语句。
image.png5.4 ORM 映射
整个规约对自己来说都挺有用的,因为正好涉及到这方面,幸好感觉脸不怎么疼。
第一条:【强制】在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。
- 说明: 1 ) 增加查询分析器解析成本。2 ) 增减字段容易与 resultMap 配置不一致。
第二条:【强制】 POJO 类的 布尔 属性不能加 is ,而数据库字段必须加 is _,要求在 resultMap 中进行字段与属性之间的映射。
- 说明: 参见定义 POJO 类以及数据库字段定义规定,在 <resultMap>中 增加映射,是必须的。在 MyBatis Generator 生成的代码中,需要进行对应的修改。
第三条:【强制】不要用 resultClass 当返回参数,即使所有类属性名与数据库字段一一对应,也需要定义 ; 反过来,每一个表也必然有一个与之对应。
- 说明: 配置映射关系,使字段与 DO 类解耦,方便维护。
第七条:【强制】更新数据表记录时,必须同时更新记录对应的 gmt _ modified 字段值为当前时间。
第九条:【参考】@ Transactional 事务不要滥用。事务会影响数据库的 QPS ,另外使用事务的地方需要考虑各方面的回滚方案,包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等。
总结
浏览了一遍,还是学习到了很多东西吧,上面也仅仅只是总结了对我自己比较收益,现阶段我能吸收能实际感受得到的规约,如果想要 PDF 版的可以在这里下载:戳这里