文章目录
阿里巴巴Java开发手册泰山版(整理)
一、编程规约
(一)命名风格
-
【强制】代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。
-
【强制】常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。
正例:MAX_STOCK_COUNT / CACHE_EXPIRED_TIME
反例:MAX_COUNT / EXPIRED_TIME
-
【强制】抽象类命名使用
Abstract
或Base
开头; 异常类命名使用
Exception
结尾; 测试类命名以它要测试的类的名称开始,以
Test
结尾 -
【强制】
POJO
类中的任何布尔类型的变量,都不要加is
前缀,否则部分框架解析会引起序列化错误。说明:在本文 MySQL 规约中的建表约定第一条,表达是与否的值采用 is_xxx 的命名方式,所以,需要在
<resultMap>
设置从 is_xxx 到 xxx 的映射关系。反例:定义为基本数据类型 Boolean isDeleted 的属性,它的方法也是 isDeleted(),框架在反向解析的时
候,“误以为”对应的属性名称是 deleted,导致属性获取不到,进而抛出异常。
-
【强制】包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。
包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式。
正例:应用工具类包名为 com.alibaba.ei.kunlun.aap.util、类名为 MessageUtils
(此规则参考 spring 的框架结构)
-
【强制】杜绝完全不规范的缩写,避免望文不知义。
反例:AbstractClass“缩写”命名成 AbsClass; condition“缩写”命名成 condi,
此类随意缩写严重降低了代码的可阅读性。
-
【推荐】为了达到代码自解释的目标,任何自定义编程元素在命名时,使用尽量完整的单词组合来表达。
正例:在 JDK 中,对某个对象引用的 volatile 字段进行原子更新的类名为:AtomicReferenceFieldUpdater。
反例:常见的方法内变量为 int a;的定义方式。
-
【推荐】在常量与变量的命名时,表示类型的名词放在词尾,以提升辨识度。
正例:startTime / workQueue / nameList / TERMINATED_THREAD_COUNT
反例:startedAt / QueueOfWork / listName / COUNT_TERMINATED_THREAD
-
【推荐】如果模块、接口、类、方法使用了设计模式,在命名时需体现出具体模式。
说明:将设计模式体现在名字中,有利于阅读者快速理解架构设计理念。
正例: public class OrderFactory;
public class LoginProxy;
public class ResourceObserver; -
【推荐】接口类中的方法和属性不要加任何修饰符号(public 也不要加),保持代码的简洁性,并加上有效的
Javadoc 注释
.尽量不要在接口里定义变量,如果一定要定义变量,确定与接口方法相关,并且是整个应用的基础常量。
正例:接口方法签名 void commit();
接口基础常量 String COMPANY = “alibaba”;
反例:接口方法定义 public abstract void f();
说明:JDK8 中接口允许有默认实现,那么这个 default 方法,是对所有实现类都有价值的默认实现。
-
接口和实现类的命名有两套规则:
1)【强制】对于
Service 和 DAO类
,基于 SOA 的理念,暴露出来的服务一定是接口,内部的实现类用 Impl 的后缀与接口区别。正例:CacheServiceImpl 实现 CacheService 接口。
2)【推荐】如果是形容能力的接口名称,取对应的形容词为接口名(通常是–able 的形容词)。
正例:AbstractTranslator 实现 Translatable 接口。
-
【参考】枚举类名带上 Enum 后缀,枚举成员名称需要全大写,单词间用下划线隔开。
说明:枚举其实就是特殊的常量类,且构造方法被默认强制是私有。
正例:枚举名字为 ProcessStatusEnum 的成员名称:SUCCESS / UNKNOWN_REASON。
-
【参考】各层命名规约:
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。
(二)常量定义
-
【强制】不允许任何魔法值(即未经预先定义的常量)直接出现在代码中
-
【强制】在 long 或者 Long 赋值时,数值后使用大写的 L,不能是小写的 l,小写容易跟数字混淆,造成误解。
-
【推荐】不要使用一个常量类维护所有常量,要按常量功能进行归类,分开维护。
说明:大而全的常量类,杂乱无章,使用查找功能才能定位到修改的常量,不利于理解,也不利于维护。
正例:缓存相关常量放在类 CacheConsts 下;系统配置相关常量放在类 ConfigConsts 下。
-
【推荐】常量的复用层次有五层:跨应用共享常量、应用内共享常量、子工程内共享常量、包内共享常量、类内共享常量。
1)跨应用共享常量:放置在二方库中,通常是 client.jar 中的 constant 目录下。
2)应用内共享常量:放置在一方库中,通常是子模块中的 constant 目录下。反例:易懂变量也要统一定义成应用内共享常量,两位工程师在两个类中分别定义了“YES”的变量:
类 A 中:public static final String YES = “yes”;
类 B 中:public static final String YES = “y”;
A.YES.equals(B.YES),预期是 true,但实际返回为 false,导致线上问题。 3)子工程内部共享常量:即在当前子工程的 constant 目录下。
4)包内共享常量:即在当前包下单独的 constant 目录下。
5)类内共享常量:直接在类内部 private static final 定义。 -
【推荐】如果变量值仅在一个固定范围内变化用 enum 类型来定义。
说明:如果存在名称之外的延伸属性应使用 enum 类型,下面正例中的数字就是延伸信息,表示一年中的第几个季节。
正例:
public enum SeasonEnum { SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4); private int seq; SeasonEnum(int seq) { this.seq = seq; } public int getSeq() { return seq; } }
(三)代码格式
-
【强制】如果是大括号内为空,则简洁地写成{}即可,大括号中间无需换行和空格;
-
【强制】左小括号和右边相邻字符之间不出现空格;右小括号和左边相邻字符之间也不出现空格;而左大括号前需要加空格。
-
【强制】任何二目、三目运算符的左右两边都需要加一个空格。
说明:包括赋值运算符=、逻辑运算符&&、加减乘除符号等。
-
【强制】
if/for/while/switch/do 等保留字与括号之间都必须加空格
-
【强制】采用 4 个空格缩进,禁止使用 tab 字符。
说明:如果使用 tab 缩进,必须设置 1 个 tab 为 4 个空格。 IDEA 设置 tab 为 4 个空格时,请勿勾选 Use tab
character;而在 eclipse 中,必须勾选 insert spaces for tabs。
-
【强制】注释的双斜线与注释内容之间有且仅有一个空格。
-
【强制】在进行类型强制转换时,右括号与强制转换值之间不需要任何空格隔开。
-
【强制】单行字符数限制不超过
120
个,超出需要换行,换行时遵循如下原则: 1)第二行相对第一行缩进 4 个空格,从第三行开始,不再继续缩进,参考示例。
2)运算符与下文一起换行。
3)方法调用的点符号与下文一起换行。
4)方法调用中的多个参数需要换行时,在逗号后进行。
5)在括号前不要换行,见反例。
正例:
StringBuilder sb = new StringBuilder(); // 超过 120 个字符的情况下,换行缩进 4 个空格,并且方法前的点号一起换行 sb.append("zi").append("xin")... .append("huang")... .append("huang")... .append("huang");
反例:
StringBuilder sb = new StringBuilder(); // 超过 120 个字符的情况下,不要在括号前换行 sb.append("you").append("are")...append ("lucky"); // 参数很多的方法调用可能超过 120 个字符,逗号后才是换行处 method(args1, args2, args3, ... , argsX);
-
【强制】方法参数在定义和传入时,多个参数逗号后边必须加空格。
-
【强制】IDE 的 text file encoding 设置为 UTF-8; IDE 中文件的换行符使用 Unix 格式,不要使用 Windows 格式。
-
【推荐】单个方法的总行数不超过
80
行。说明:除注释之外的方法签名、左右大括号、方法内代码、空行、回车及任何不可见字符的总行数不超过
80
行。正例:代码逻辑分清红花和绿叶,个性和共性,绿叶逻辑单独出来成为额外方法,使主干代码更加清晰;共性逻辑抽取成为共性方法,便于复用和维护。
-
【推荐】没有必要增加若干空格来使变量的赋值等号与上一行对应位置的等号对齐。
正例:
int one = 1; long two = 2L; float three = 3F; StringBuilder sb = new StringBuilder();
说明:增加 sb 这个变量,如果需要对齐,则给 one、two、three 都要增加几个空格,在变量比较多的情
况下,是非常累赘的事情。
-
【推荐】不同逻辑、不同语义、不同业务的代码之间插入一个空行分隔开来以提升可读性。
说明:任何情形,没有必要插入多个空行进行隔开。
(四)OOP 规约 2021.10.28
-
【强制】避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可。
-
【强制】所有的覆写方法,必须加 @Override 注解。
说明:getObject()与get0bject()的问题。一个是字母的O,一个是数字的0,加@Override可以准确判断是否覆盖成功。另外,如果在抽象类中对方法签名进行修改,其实现类会马上编译报错。
-
【强制】相同参数类型,相同业务含义,才可以使用Java的可变参数,避免使用 Object。
说明:可变参数必须放置在参数列表的最后。(提倡同学们尽量不用可变参数编程)
正例:public List listUsers(String type, Long… ids) {…} -
【强制】外部正在调用或者二方库依赖的接口,不允许修改方法签名,避免对接口调用方产生影响。接口过时必须加@Deprecated注解,并清晰地说明采用的新接口或者新服务是什么。
-
【强制】不能使用过时的类或方法。
说明:java.net.URLDecoder 中的方法decode(String encodeStr) 这个方法已经过时,应该使用双参数decode(String source, String encode)。
接口提供方既然明确是过时接口,那么有义务同时提供新的接口;
作为调用方来说,有义务去考证过时方法的新实现是什么 -
【强制】Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。
正例:“test”.equals(object);
反例:object.equals(“test”);
说明:推荐使用java.util.Objects#equals(JDK7引入的工具类)。 -
【强制】所有
整型包装类对象之间值的比较
,全部使用equals 方法
比较。说明:对于Integer var = ? 在-128至127之间的赋值,Integer对象是在 IntegerCache.cache产生,会复用已有对象,这个区间内的Integer值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用equals方法进行判断。
-
【强制】任何货币金额,均以最小货币单位且整型类型来进行存储。
-
【强制】浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用equals来判断。
说明:浮点数采用“尾数+阶码”的编码方式,类似于科学计数法的“有效数字+指数”的表示方式。二进制无法精确表示大部分的十进制小数,具体原理参考《码出高效》。
反例:
float a = 1.0f - 0.9f; float b = 0.9f - 0.8f; if (a == b) { // 预期进入此代码快,执行其它业务逻辑 // 但事实上a==b的结果为false } Float x = Float.valueOf(a); Float y = Float.valueOf(b); if (x.equals(y)) { // 预期进入此代码快,执行其它业务逻辑 // 但事实上equals的结果为false }
正例:
(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"); }
附2: 专有名词解释 2021.10.25
- CAS(Compare And Swap)
阿里巴巴专指数据库表一一对应的
POJO类
。
解决多线程并行情况下使用锁造成性能损耗的一种机制,这是硬件实现的原子操作。
CAS操作包含三个操作数:内存位置、预期原值和新值。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。 - DO(Data Object)
阿里巴巴专指数据库表一一对应的
POJO类
。 - GAV(GroupId、ArtifactId、Version)
Maven坐标,是用来唯一标识jar包。
- AQS(AbstractQueuedSynchronizer)
利用先进先出队列实现的底层同步工具类,它是很多上层同步实现类的基础,比如:
ReentrantLock、CountDownLatch、Semaphore
等,它们通过继承AQS实现其模版方法,然后将AQS子类作为同步组件的内部类,通常命名为Sync。 - ORM(Object Relation Mapping)
对象关系映射,对象领域模型与底层数据之间的转换,本文泛指
iBATIS, mybatis
等框架。 - POJO(Plain Ordinary Java Object):
在本规约中,POJO专指只有setter/getter/toString的简单类,包括
DO/DTO/BO/VO
等。 - AO(Application Object):
阿里巴巴专指Application Object,即在Service层上,极为贴近业务的复用代码。
- NPE(java.lang.NullPointerException):
空指针异常。
- OOM(Out Of Memory):
源于 java.lang.OutOfMemoryError,当 JVM 没有足够的内存来为对象分配空间并且垃圾回收器也无法回收空间时,系统出现的严重状况。
- 一方库:
本工程内部子项目模块依赖的库(jar 包)。
- 二方库:
公司内部发布到中央仓库,可供公司内部其它应用依赖的库(jar 包)。
- 三方库:
公司之外的开源库(jar 包)。
附3: 错误码列表 2021.10.28
错误码 | 中文描述 | 说明 |
---|---|---|
00000 | 一切ok | 正确执行后的返回 |
A0001 | 用户端错误 | 一级宏观错误码 |
A0100 | 用户注册错误 | 二级宏观错误码 |
A0101 | 用户未同意隐私协议 | |
A0102 | 注册国家或地区受限 | |
A0110 | 用户名校验失败 | |
A0111 | 用户名已存在 | |
A0112 | 用户名包含敏感词 | |
A0113 | 用户名包含特殊字符 | |
A0120 | 密码校验失败 | |
A0121 | 密码长度不够 | |
A0122 | 密码强度不够 | |
A0130 | 校验码输入错误 | |
A0131 | 短信校验码输入错误 | |
A0132 | 邮件校验码输入错误 | |
A0133 | 语音校验码输入错误 | |
A0140 | 用户证件异常 | |
A0141 | 用户证件类型未选择 | |
A0142 | 大陆身份证编号校验非法 | |
A0143 | 护照编号校验非法 | |
A0144 | 军官证编号校验非法 | |
A0150 | 用户基本信息校验失败 | |
A0151 | 手机格式校验失败 | |
A0152 | 地址格式校验失败 | |
A0153 | 邮箱格式校验失败 | |
A0200 | 用户登陆异常 | 二级宏观错误码 |
A0201 | 用户账户不存在 | |
A0202 | 用户账户被冻结 | |
A0203 | 用户账户已作废 | |
A0210 | 用户密码错误 | |
A0211 | 用户输入密码次数超限 | |
A0220 | 用户身份校验失败 | |
A0221 | 用户指纹识别失败 | |
A0222 | 用户面容识别失败 | |
A0223 | 用户未获得第三方登陆授权 | |
A0230 | 用户登陆已过期 | |
A0240 | 用户验证码错误 | |
A0241 | 用户验证码尝试次数超限 | |
A0300 | 访问权限异常 | 二级宏观错误码 |
A0301 | 访问未授权 | |
A0302 | 正在授权中 | |
A0303 | 用户授权申请被拒绝 | |
A0310 | 因访问对象隐私设置被拦截 | |
A0311 | 授权已过期 | |
A0312 | 无权限使用API | |
A0320 | 用户访问被拦截 | |
A0321 | 黑名单用户 | |
A0322 | 账号被冻结 | |
A0323 | 非法IP地址 | |
A0324 | 网关访问受限 | |
A0325 | 地域黑名单 | |
A0330 | 服务已欠费 | |
A0340 | 用户签名异常 | |
A0341 | RSA签名错误 | |
A0400 | 用户请求参数错误 | 二级宏观错误码 |
A0401 | 包含非法恶意跳转链接 | |
A0402 | 无效的用户输入 | |
A0410 | 请求必填参数为空 | |
A0411 | 用户订单号为空 | |
A0412 | 订购数量为空 | |
A0413 | 缺少时间戳参数 | |
A0414 | 非法的时间戳参数 | |
A0420 | 请求参数值超出允许的范围 | |
A0421 | 参数格式不匹配 | |
A0422 | 地址不在服务范围 | |
A0423 | 时间不在服务范围 | |
A0424 | 金额超出限制 | |
A0425 | 数量超出限制 | |
A0426 | 请求批量处理总个数超出限制 | |
A0427 | 请求JSON解析失败 | |
A0430 | 用户输入内容非法 | |
A0431 | 包含违禁敏感词 | |
A0432 | 图片包含违禁信息 | |
A0433 | 文件侵犯版权 | |
A0440 | 用户操作异常 | |
A0441 | 用户支付超时 | |
A0442 | 确认订单超时 | |
A0443 | 订单已关闭 | |
A0500 | 用户请求服务异常 | 二级宏观错误码 |
A0501 | 请求次数超出限制 | |
A0502 | 请求并发数超出限制 | |
A0503 | 用户操作请等待 | |
A0504 | WebSocket连接异常 | |
A0505 | WebSocket连接断开 | |
A0506 | 用户重复请求 | |
A0600 | 用户资源异常 | 二级宏观错误码 |
A0601 | 账户余额不足 | |
A0602 | 用户磁盘空间不足 | |
A0603 | 用户内存空间不足 | |
A0604 | 用户OSS容量不足 | |
A0605 | 用户配额已用光 | 蚂蚁森林浇水数或每天抽奖数 |
A0700 | 用户上传文件异常 | 二级宏观错误码 |
A0701 | 用户上传文件类型不匹配 | |
A0702 | 用户上传文件太大 | |
A0703 | 用户上传图片太大 | |
A0704 | 用户上传视频太大 | |
A0705 | 用户上传压缩文件太大 | |
A0800 | 用户当前版本异常 | 二级宏观错误码 |
A0801 | 用户安装版本与系统不匹配 | |
A0802 | 用户安装版本过低 | |
A0803 | 用户安装版本过高 | |
A0804 | 用户安装版本已过期 | |
A0805 | 用户API请求版本不匹配 | |
A0806 | 用户API请求版本过高 | |
A0807 | 用户API请求版本过低 | |
A0900 | 用户隐私未授权 | 二级宏观错误码 |
A0901 | 用户隐私未签署 | |
A0902 | 用户摄像头未授权 | |
A0903 | 用户相机未授权 | |
A0904 | 用户图片库未授权 | |
A0905 | 用户文件未授权 | |
A0906 | 用户位置信息未授权 | |
A0907 | 用户通讯录未授权 | |
A1000 | 用户设备异常 | 二级宏观错误码 |
A1001 | 用户相机异常 | |
A1002 | 用户麦克风异常 | |
A1003 | 用户听筒异常 | |
A1004 | 用户扬声器异常 | |
A1005 | 用户GPS定位异常 | |
B0001 | 系统执行出错 | 一级宏观错误码 |
B0100 | 系统执行超时 | 二级宏观错误码 |
B0101 | 系统订单处理超时 | |
B0200 | 系统容灾功能被触发 | 二级宏观错误码 |
B0210 | 系统限流 | |
B0220 | 系统功能降级 | |
B0300 | 系统资源异常 | 二级宏观错误码 |
B0310 | 系统资源耗尽 | |
B0311 | 系统磁盘空间耗尽 | |
B0312 | 系统内存耗尽 | |
B0313 | 文件句柄耗尽 | |
B0314 | 系统连接池耗尽 | |
B0315 | 系统线程池耗尽 | |
B0320 | 系统资源访问异常 | |
B0321 | 系统读取磁盘文件失败 | |
C0001 | 调用第三方服务出错 | 一级宏观错误码 |
C0100 | 中间件服务出错 | 二级宏观错误码 |
C0110 | RPC服务出错 | |
C0111 | RPC服务未找到 | |
C0112 | RPC服务未注册 | |
C0113 | 接口不存在 | |
C0120 | 消息服务出错 | |
C0121 | 消息投递出错 | |
C0122 | 消息消费出错 | |
C0123 | 消息订阅出错 | |
C0124 | 消息分组未查到 | |
C0130 | 缓存服务出错 | |
C0131 | key长度超过限制 | |
C0132 | value长度超过限制 | |
C0133 | 存储容量已满 | |
C0134 | 不支持的数据格式 | |
C0140 | 配置服务出错 | |
C0150 | 网络资源服务出错 | |
C0151 | VPN服务出错 | |
C0152 | CDN服务出错 | |
C0153 | 域名解析服务出错 | |
C0154 | 网关服务出错 | |
C0200 | 第三方系统执行超时 | 二级宏观错误码 |
C0210 | RPC执行超时 | |
C0220 | 消息投递超时 | |
C0230 | 缓存服务超时 | |
C0240 | 配置服务超时 | |
C0250 | 数据库服务超时 | |
C0300 | 数据库服务出错 | 二级宏观错误码 |
C0311 | 表不存在 | |
C0312 | 列不存在 | |
C0321 | 多表关联中存在多个相同名称的列 | |
C0331 | 数据库死锁 | |
C0341 | 主键冲突 | |
C0400 | 第三方容灾系统被触发 | 二级宏观错误码 |
C0401 | 第三方系统限流 | |
C0402 | 第三方功能降级 | |
C0500 | 通知服务出错 | 二级宏观错误码 |
C0501 | 短信提醒服务失败 | |
C0502 | 语音提醒服务失败 | |
C0503 | 邮件提醒服务失败 |