编程规约
2025年3月30日大约 10 分钟
编程规约
编程规约是保证代码质量的基础,规范的代码更易于阅读、维护和协作。本文将介绍阿里巴巴Java开发规范中的编程规约部分,包括命名规范、常量定义、格式规约等核心内容。
命名规范
命名风格
强制
- 代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。
// 反例:_name / __name / $name / name_ / name$ / name__
强制
- 代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。
// 反例:DaZhongTransfer / getPingfenByName() / 某某服务 / getCurrentTime()
// 正例:alibaba / taobao / youku / hangzhou 等国际通用的名称,可视为英文
强制
- 类名使用UpperCamelCase风格,必须遵从驼峰形式。
// 正例:MallService / UserDao / LoginController
// 反例:mallService / UserDAO / LoginController
强制
- 方法名、参数名、成员变量、局部变量都统一使用lowerCamelCase风格,必须遵从驼峰形式。
// 正例:localValue / getHttpMessage() / inputUserId
强制
- 常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。
// 正例:MAX_STOCK_COUNT / CACHE_EXPIRED_TIME
// 反例:MAX_COUNT / EXPIRED_TIME
强制
- 抽象类命名使用Abstract或Base开头;异常类命名使用Exception结尾;测试类命名以它要测试的类的名称开始,以Test结尾。
// 正例:AbstractDAO / BaseHttpClient / BusinessException / UserServiceTest
强制
- 类型与中括号紧挨相连来表示数组。
// 正例:定义整形数组 int[] arrayDemo;
// 反例:在main参数中,使用String args[]来定义
强制
- POJO类中布尔类型变量都不要加is前缀,否则部分框架解析会引起序列化错误。
// 正例:定义为boolean success
// 反例:定义为boolean isSuccess
推荐
- 包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式。
// 正例:应用工具类包名为com.alibaba.ai.util、类名为MessageUtils(此规则参考spring的框架结构)
推荐
- 避免在子父类的成员变量之间、或者不同代码块的局部变量之间采用完全相同的命名,使可读性降低。
// 反例:
public class ConfusingName {
public int stock;
// 子类中的成员变量与父类的命名相同
public void method() {
int stock = 3;
// 局部变量如果与成员变量同名,更加令人迷惑
if (condition) {
int money = 5;
// 套娃局部变量
}
// 变量的作用域不要太大,否则影响可读性
}
}
推荐
- 杜绝完全不规范的缩写,避免望文不知义。
// 反例:DaoUser / BeanUser 即使用户模块的DAO层和POJO类,也不允许使用此类缩写
常量定义
强制
- 不允许任何魔法值(即未经预先定义的常量)直接出现在代码中。
// 反例:
String key = "Id#taobao_" + tradeId;
cache.put(key, value);
强制
- 在long或者Long赋值时,数值后使用大写字母L,不能是小写字母l,小写容易跟数字1混淆,造成误解。
// 正例:Long a = 2L;
// 反例:Long a = 2l;
推荐
- 不要使用一个常量类维护所有常量,应该按常量功能进行归类,分开维护。
// 正例:缓存相关常量放在类CacheConsts下;系统配置相关常量放在类ConfigConsts下。
推荐
- 常量的复用层次有五层:跨应用共享常量、应用内共享常量、子工程内共享常量、包内共享常量、类内共享常量。
- 跨应用共享常量:放置在二方库中,通常是client.jar中的constant目录下。
- 应用内共享常量:放置在一方库中,通常是子模块中的constant目录下。
- 子工程内部共享常量:即在当前子工程的constant目录下。
- 包内共享常量:即在当前包下单独的constant目录下。
- 类内共享常量:直接在类内部private static final定义。
参考
- 如果变量值仅在一个固定范围内变化,且带有名称含义,建议使用枚举。
// 正例:
public enum SeasonEnum {
SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4);
private int code;
private SeasonEnum(int code) {
this.code = code;
}
public int getCode() {
return code;
}
}
代码格式
强制
- 大括号的使用约定。如果是大括号内为空,则简洁地写成{}即可,不需要换行;如果是非空代码块则:
- 左大括号前不换行。
- 左大括号后换行。
- 右大括号前换行。
- 右大括号后还有else等代码则不换行;表示终止的右大括号后必须换行。
强制
- 左小括号和字符之间不出现空格;右小括号和字符之间不出现空格;左大括号前不出现空格,左大括号后必须换行。
// 正例:if (condition) {
// 反例:if ( condition ){
强制
- if/for/while/switch/do等保留字与括号之间都必须加空格。
强制
- 任何二目、三目运算符的左右两边都需要加一个空格。
// 正例:2 + 2 = 4
// 正例:x > 0 ? 1 : 0
强制
- 采用4个空格缩进,禁止使用tab字符。
强制
- 注释的双斜线与注释内容之间有且仅有一个空格。
// 正例:// 注释内容
强制
- 单行字符数限制不超过120个,超出需要换行,换行时遵循如下原则:
- 第二行相对第一行缩进4个空格,从第三行开始,不再继续缩进。
- 运算符与下文一起换行。
- 方法调用的点符号与下文一起换行。
- 方法调用中的多个参数需要换行时,在逗号后进行。
- 在括号前不要换行。
强制
- 方法参数在定义和传入时,多个参数逗号后边必须加空格。
// 正例:method(args1, args2, args3);
推荐
- 没有必要增加若干空格来使变量的赋值等号与上一行对应位置的等号对齐。
// 反例:
int a = 1;
String s = "abc";
float f = 3.14f;
推荐
- 不同逻辑、不同语义、不同业务的代码之间插入一个空行分隔开来以提升可读性。
// 正例:
if (condition) {
doSomething();
}
// 这里故意插入一个空行
if (otherCondition) {
doSomethingElse();
}
OOP规约
强制
- 避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可。
// 正例:ClassName.staticMethod();
// 反例:instance.staticMethod();
强制
- 所有的覆写方法,必须加@Override注解。
强制
- 相同参数类型,相同业务含义,才可以使用Java的可变参数,避免使用Object...
// 正例:
public void saveUsers(User... users);
public void saveUsers(List<User> users);
强制
- 外部调用的方法必须使用接口封装,并按需暴露。
强制
- 构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在init方法中。
强制
- POJO类必须写toString方法。
推荐
- 使用索引访问用String的split方法得到的数组时,需做最后一个分隔符后有无内容的检查,否则会有抛IndexOutOfBoundsException的风险。
// 正例:
String str = "a,b,c,,";
String[] ary = str.split(",");
// 预期大于3,结果是4
int len = ary.length;
推荐
- 类内方法定义的顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter方法。
推荐
- setter方法中,参数名称与类成员变量名称一致,this.成员名 = 参数名。在getter/setter方法中,尽量不要增加业务逻辑,增加排查问题的难度。
// 正例:
public void setName(String name) {
this.name = name;
}
集合处理
强制
- 关于hashCode和equals的处理,遵循如下规则:
- 只要重写equals,就必须重写hashCode。
- 因为Set存储的是不重复的对象,依据hashCode和equals进行判断,所以Set存储的对象必须重写这两个方法。
- 如果自定义对象作为Map的键,那么必须重写hashCode和equals。
强制
- ArrayList的subList结果不可强转成ArrayList,否则会抛出ClassCastException异常。
// 正例:
List<Integer> subList = list.subList(1, 4);
强制
- 在使用Collection接口任何实现类的addAll()方法时,要对输入的集合参数进行NPE判断。
强制
- 使用集合转数组的方法,必须使用集合的toArray(T[] array),传入的是类型完全一致、长度为0的空数组。
// 正例:
List<String> list = new ArrayList<>(2);
list.add("guan");
list.add("bao");
String[] array = list.toArray(new String[0]);
强制
- 使用工具类Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的add/remove/clear方法会抛出UnsupportedOperationException异常。
强制
- 泛型通配符<? extends T>来接收返回的数据,此写法的泛型集合不能使用add方法,而<? super T>不能使用get方法,因为不能确定容器里面放的是什么类型。
推荐
- 不要在foreach循环里进行元素的remove/add操作。remove元素请使用Iterator方式,如果是并发操作,需要对Iterator对象加锁。
// 正例:
List<String> list = new ArrayList<>();
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (condition) {
iterator.remove();
}
}
推荐
- 集合初始化时,指定集合初始值大小。
// 正例:
HashMap<String, String> map = new HashMap<>(16);
ArrayList<String> list = new ArrayList<>(8);
推荐
- 使用entrySet遍历Map类集合KV,而不是keySet方式进行遍历。
// 正例:
Map<String, String> map = new HashMap<>(16);
for (Map.Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
}
参考
- 合理利用好集合的有序性(sort)和稳定性(order),避免集合的无序性(unsort)和不稳定性(unorder)带来的负面影响。
并发处理
强制
- 获取单例对象需要保证线程安全,其中的方法也要保证线程安全。
// 正例:
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
强制
- 创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
// 正例:
public class TimerTaskThread extends Thread {
public TimerTaskThread() {
super.setName("TimerTaskThread");
}
}
强制
- 线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
// 正例:
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
ExecutorService pool = new ThreadPoolExecutor(
5, 200, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy()
);
强制
- 并发修改同一记录时,避免更新丢失,要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观锁,使用version作为更新依据。
// 正例:
boolean success = updateOrder(id, getVersion(id) + 1);
if (success) {
// 业务处理
} else {
// 更新失败的流程控制
}
强制
- 多线程并行处理定时任务时,Timer运行多个TimeTask时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,使用ScheduledExecutorService则没有这个问题。
推荐
- 使用CountDownLatch进行异步转同步操作,每个线程退出前必须调用countDown方法,线程执行代码注意catch异常,确保countDown方法被执行到。
推荐
- 避免Random实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一seed而导致的性能下降。
// 正例:
ThreadLocalRandom.current().nextInt();
参考
- volatile解决多线程内存不可见问题,对于一写多读,是可以解决变量同步问题的,但如果多写,同样无法解决线程安全问题。
参考
- 注意HashMap在容量不够进行resize时由于高并发可能出现死链,导致CPU飙升,在开发过程中注意规避此风险。
以上就是阿里巴巴Java开发规范中编程规约的核心内容,遵循这些规范可以帮助您编写更加清晰、易维护、高质量的Java代码。