前言
为了利于项目维护以及规范开发,促进成员之间Code Review的效率,故提出以下开发规范
根据约束力强弱, 规约依次分为强制、推荐、参考三大类:
【强制】必须遵守,违反本约定或将会引起严重的后果;
【推荐】尽量遵守,长期遵守有助于系统稳定性和合作效率的提升;
【参考】充分理解,技术意识的引导,是个人学习、团队沟通、项目合作的方向。
一、命名规范
1.【强制】代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。
// 错误示例:
name / name / $name / name / name$ / name
2.【强制】代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式(即使纯拼音命名方式也要避免采用)。
// 正确示例:
alibaba / taobao / youku / hangzhou 等国际通用的名称,可视同英文。
// 错误示例:
DaZhePromotion
getPingfenByName()
int 某变量 = 3
3.【强制】类名使用UpperCamelCase风格。
// 正确示例:
MarcoPolo / UserDO / XmlService
// 错误示例:
macroPolo / UserDo / XMLService
4.【强制】方法名、参数名、成员变量、局部变量都统一使用lowerCamelCase风格,必须遵从驼峰形式。
// 正确示例:
localValue / getHttpMessage() / inputUserId
5.【强制】常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。
// 正确示例:
MAX_STOCK_COUNT
// 错误示例:
MAX_COUNT
6.【强制】类型与中括号紧挨相连来定义数组。
// 正确示例:
int[] arrayDemo
// 错误示例:
String args[]
7.【推荐】为了达到代码自解释的目标,任何自定义编程元素在命名时,使用尽量完整的单词组合来表达其意。杜绝完全不规范的缩写。
//正确示例:
类命名为PullCodeFromRemoteRepository
// 错误示例:
变量: int a;
AbstractClass“缩写”命名成AbsClass;
8.【参考】枚举类名建议带上Enum后缀,枚举成员名称需要全大写,单词间用下划线隔开。
// 正确示例:
// 枚举类型:
ProcessStatusEnum
// 成员:
SUCCESS / UNKNOWN_REASON
二、常量定义
1.【强制】不允许任何魔法值(即未经预先定义的常量)直接出现在代码中。
// 错误示例:
String key = "Id#taobao_" + x;
2.【强制】long或者Long初始赋值时,使用大写的L,不能是小写的l,小写容易跟数字1混淆,造成误解。
// 正确示例:
Long a = 2L;
// 错误示例:
Long a = 2l;
3.【推荐】如果变量值仅在一个固定范围内变化用enum类型来定义。
三、代码格式
1.【强制】如果是大括号内为空,则简洁地写成{}即可,大括号中间无需换行和空格;如果是非空代码块则:左大括号前不换行;左大括号后换行;右大括号前换行;右大括号后还有 else 等代码则不换行;表示终止的右大括号后必须换行。
2.【强制】左小括号和右边相邻字符之间不出现空格;右小括号和左边相邻字符之间也不出现空格;而左大括号前需要加空格。
3.【强制】if/for/while/switch/do 等保留字与括号之间都必须加空格。
// 正确示例:
if (a == b) {
method(args1, args2, args3);
} else if (a > b) {
method(args1, args2, args3);
} else {}
4.【强制】任何二目、三目运算符的左右两边都需要加一个空格。
// 正确示例:
int y = 0;
int b = 1;
int x = (y > b) ? b : y;
5.【强制】注释的双斜线与注释内容之间有且仅有一个空格。
// 正确示例:
// 这是示例注释,请注意在双斜线之后有一个空格
6.【强制】在进行类型强制转换时,右括号与强制转换值之间不需要任何空格隔开。
// 正确示例:
double first = 3.2d;
int second = (int)first + 2;
7.【强制】方法参数在定义和传入时,多个参数逗号后面必须加空格。
// 正确示例:
method(args1, args2, args3);
8.【推荐】不同逻辑、不同语义、不同业务的代码之间插入一个空行分隔开来以提升可读性。
四、集合处理
1.【强制】判断所有集合内部的元素是否为空,使用 isEmpty()方法,而不是 size()==0 的方式。
// 正确示例:
Map<String, Object> map = new HashMap<>(16);
if(map.isEmpty()) {
System.out.println("no element in this map.");
}
2.【强制】ArrayList 的 subList 结果不可强转成 ArrayList,否则会抛出 ClassCastException 异常。
3.【强制】使用 Map 的方法 keySet()/values()/entrySet()返回集合对象时,不可以对其进行添加元素操作,否则会抛出 UnsupportedOperationException 异常。
4.【强制】Collections 类返回的对象,如:emptyList()/singletonList()等都是 immutable list,不可对其进行添加或者删除元素的操作。
5.【强制】使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一致、长度为 0 的空数组。
// 正确示例:
List<String> list = new ArrayList<>(2);
list.add("guan");
list.add("bao");
String[] array = list.toArray(new String[0]);
6.【强制】在无泛型限制定义的集合赋值给泛型限制的集合时,在使用集合元素时,需要进行instanceof 判断,避免抛出 ClassCastException 异常。
// 正确示例:
List<String> generics = null;
List notGenerics = new ArrayList(10);
notGenerics.add(new Object());
notGenerics.add(new Integer(1));
generics = notGenerics;
// 错误示例:
String string = generics.get(0);
7.【强制】不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator方式,如果并发操作,需要对 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 (删除元素的条件) {
iterator.remove();
}
}
// 错误示例:
for (String item : list) {
if ("1".equals(item)) {
list.remove(item);
}
}
8.【推荐】集合初始化时,指定集合初始值大小。
9.【推荐】使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历。
10.【参考】利用 Set 元素唯一的特性,可以快速对一个集合进行去重操作,避免使用 List 的contains()进行遍历去重或者判断包含操作。
五、并发处理
1.【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
2.【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
3.【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
4.【强制】SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为 static,必须加锁,或者使用 DateUtils 工具类。
正确示例:
private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
}
5.【强制】高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。
6.【强制】在使用尝试机制来获取锁的方式中,进入业务代码块之前,必须先判断当前线程是否持有锁。锁的释放规则与锁的阻塞等待方式相同。
// 正确示例:
Lock lock = new XxxLock();
boolean isLocked = lock.tryLock();
if (isLocked) {
try {
doSomething();
doOthers();
} finally {
lock.unlock();
}
}
7.【强制】多线程并行处理定时任务时,Timer 运行多个 TimeTask 时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,使用 ScheduledExecutorService 则没有这个问题。
六、控制语句
1.【强制】在一个 switch 块内,每个 case 要么通过 continue/break/return 等来终止,要么注释说明程序将继续执行到哪一个 case 为止;在一个 switch 块内,都必须包含一个 default语句并且放在最后,即使它什么代码也没有。
2.【强制】当 switch 括号内的变量类型为 String 并且此变量为外部参数时,必须先进行 null判断。
// 错误示例:
public class SwitchString {
public static void main(String[] args) {
method(null);
}
public static void method(String param) {
switch (param) {
// 肯定不是进入这里
case "sth":
System.out.println("it's sth");
break;
// 也不是进入这里
case "null":
System.out.println("it's null");
break;
// 也不是进入这里
default:
System.out.println("default");
}
}
}
3.【强制】在 if/else/for/while/do 语句中必须使用大括号。即使只有一行代码,也禁止不采用大括号的编码方式:if (condition) statements。
4.【强制】三目运算符 condition? 表达式 1 : 表达式 2 中,高度注意表达式 1 和 2 在类型对齐时,可能抛出因自动拆箱导致的 NPE 异常。
// 错误示例:
Integer a = 1;
Integer b = 2;
Integer c = null;
Boolean flag = false;
// a*b 的结果是 int 类型,那么 c 会强制拆箱成 int 类型,抛出 NPE 异常
Integer result=(flag ? a*b : c);
5.【推荐】表达异常的分支时,少用 if-else 方式,如果非使用 if()…else if()…else…方式表达逻辑,避免后续代码维护困难,请勿超过 3 层。
// 正确示例:
public void findBoyfriend (Man man) {
if (man.isUgly()) {
System.out.println("本姑娘是外貌协会的资深会员");
return;
}
if (man.isPoor()) {
System.out.println("贫贱夫妻百事哀");
return;
}
if (man.isBadTemper()) {
System.out.println("银河有多远,你就给我滚多远");
return;
}
System.out.println("可以先交往一段时间看看");
}
6.【推荐】除常用方法(如 getXxx/isXxx)等外,不要在条件判断中执行其它复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性。
7.【推荐】不要在其它表达式(尤其是条件表达式)中,插入赋值语句。
8.【推荐】避免采用取反逻辑运算符。
七、注释
1.【强制】类、类属性、类方法的注释必须使用 Javadoc 规范,使用/*内容/格式,不得使用// xxx 方式。
2.【强制】所有的抽象方法(包括接口中的方法)必须要用 Javadoc 注释、除了返回值、参数、异常说明外,还必须指出该方法做什么事情,实现什么功能。
3.【强制】所有的类都必须添加创建者和创建日期。
4.【强制】方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释使用/* */注释,注意与代码对齐。
5.【强制】所有的枚举类型字段必须要有注释,说明每个数据项的用途。
6.【推荐】代码修改的同时,注释也要进行相应的修改,尤其是参数、返回值、异常、核心逻辑等的修改。
7.【推荐】在类中删除未使用的任何字段、方法、内部类;在方法中删除未使用的任何参数声明与内部变量。
参考文档(阿里java开发手册):Java开发手册(嵩山版).pdf
开源项目路径:https://github.com/alibaba/p3c