一、 命名约束
1. [强制] 代码中的命名均不能以下划线或美元符号开始,也不能一下划线或美元符号结束。
反例:_name / __name / $name / name_ / name$ / name__
2. [强制]代码中的命名不允许直接使用中文的方式,更不允许使用拼音与英文混合的方式。
反例:DaZhePromotion [打折] / getPingfenByName() / int 某变量 = 3
3. [强制]类名使用UpperCamelCase风格,但以下情形例外:DO / BO / DTO / VO 等
正例:MarcoPolo / UserDO / XmlService / TcpUdpDeal / TaPromotion
反例:macroPolo / UserDo / XMLService / TCPUDPDeal / TAPromotion
4. 方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格,必须遵循驼峰形式。
正例: localValue / getHttpMessage() / inputUserId
5. [强制]常量命名全部大写,单词键用下划线隔开,力求语义表达完整。
正例:MAX_STOCK_COUNT
反例:MAX_COUNT
6. [强制] POJO类中布尔类型的标量,都不要加is前缀。
反例:Boolean isDeleted;
7. [强制]包名统一是用小写,点分隔符之间有且仅有一个自然语义的英语单词。
8. [强制]杜绝完全不规范的缩写,避免忘文不知义。
反例:AbstractClass –-> AbsClass
9. [强制]不允许任何未经预先定义的常量直接出现在代码中。
反例:String key = "levovo_Id_" + id;
10. [推荐]抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类命名以它要测试的类名开始,以Test结尾。
11. [推荐]为了达到代码自己自解释的目标,使用精良完整的单词组合来表达其意。若单词组合过长,可适当精简或缩写,不要望文不知义就可以。
正例:Object pullCodeFromRemoteRepository;
反例:int a;
12. [推荐]接口类中的方法和属性不要加任何修饰符号(public 也不要加),保持代码的简洁性,并加上有效的javadoc注释。
13. [推荐]枚举类名带上Enum后缀,枚举成员名称需要全部大写,单词间用下划线隔开。
14. [推荐]如果变量值在一个固定范围内变化用enum类型来定义。
15. [参考]各层命名规约:
A) Service/DAO层方法命名规约:
i. 获取单个对象的方法用get作前缀。
ii. 获取多个对象的方法用list作前缀。
iii. 获取统计值的方法用count作前缀。
iv. 插入的方法用save/insert作前缀。
v. 删除的方法用remove/delete作前缀。
vi. 修改的方法用update作前缀。
B) 领域模型命名规约
i. 数据对象xxxDO,xxx即为数据表名。
ii. 数据传输对象:xxxDTO,xxx为业务领域相关的名称。
iii. 展示对象:xxxVO,xxx一般为网页名称。
iv. POJO是DO/DTO/BO/VO 的统称,禁止命名成xxxPOJO
二、 代码约束
1. [强制]生产环境下代码禁止使用system.out.println()输出日志。推荐使用log4j、slf4j等日志框架。说明:system.out.println()是同步日志控制台输出,在日志量较大时会对性能产生一定影响。
2. [强制]大括号的使用约定。如果是大括号内为空,则简介地写成{}即可,不需要换行;如果是非空代码块则:
a) 左大括号前不换行。
b) 左大括号后换行。
c) 右大括号前换行。
d) 右大括号后还有 else 等代码则不换行;表示终止的右大括号后必须换行。
3. [强制]左小括号和字符之间不出现空格;同样的,有小括号和字符之间也不出现空格。
4. [强制] if/for/while/switch/do等保留字与括号之间都必须加空格。
5. [强制]在 if/else/for/while/do 语句中必须使用大括号。即使只有一行代码,避免采用单行的编码方式。
6. [强制]在高并发场景中,避免使用”等于”判断作为中断或退出的条件。说明:如果并发控制没有处理好,容易产生等值判断被“击穿”的情况,使用大于或小于的区间判断条件来代替。
7. [强制]任何二目、三木运算符的左右两边都需要加一个空格。
正例:
/**
* Title: Test.java
*
* @author "chenpeng"
* @date 2018年5月10日
* @Email:[email protected]
* @Description: TODO
*/
public class Test {
public static void main(String[] args) {
String say = "hello";
// 运算符的左右必须有一个空格
int flag = 0;
// 关键词 if 与括号之间必须有一个空格,括号内的 f 与左括号,0 与右括号不需要空格 if (flag == 0) {
System.out.println(say);
// 左大括号前加空格且不换行;左大括号后换行
if (flag == 1) {
System.out.println("world");
// 右大括号前换行,右大括号后有 else,不用换行
} else {
System.out.println("ok");
// 在右大括号后直接结束,则必须换行
}
}
}
8. [推荐]不要在条件判断中执行其它复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性。
9. [推荐]循环体中的语句要考量性能,如定义对象、变量、获取数据库连接,进行不必要的 try-catch 操作,尽量移至循环体外处理。
10. [参考]下列情形,需要进行参数校验
a) 调用频次低的方法。
b) 执行时间开销很大的方法。此情形中,参数校验时间几乎可以忽略不计,但如果因为参数错误导致中间执行回退,或者错误,那得不偿失。
c) 需要极高稳定性和可用性的方法。
d) 对外提供的开放接口,不管是 RPC/API/HTTP 接口。
e) 敏感权限入口
11. [参考]避免出现重复的代码(Don’t Repeat Yourself),即 DRY 原则。必要时抽取共性方法,或者抽象公共类,甚至是组件化。
三、 OOP约束
1. [强制]避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可。
2. [强制]所有的覆写方法,必须加@Override注解。
3. [强制]相同参数类型,相同业务含义,才可以使用Java的可变参数,可变参数必须放置在参数列表的最后(提倡尽量不用可变参数编程)
4. [强制]外部正在调用或者二方库依赖的接口,不允许修改方法签名,避免对接口调用方法产生影响。接口过时必须加@Deprecated注解,并清洗地说明采用的新接口或者新服务是什么。
5. [强制]不使用过时的类和方法。
6. [强制] Object的equals方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals。
正例:“test”.equals(object);
反例:object.equals(“test”);
7. [强制]所有的相同类型的包装类对象之间值的比较,全部使用equals方法比较。
说明:对于 Integer var = ? 在-128 至 127 范围内的赋值,Integer 对象是在 IntegerCache.cache 产生,会复用已有对象,这个区间内的 Integer 值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用 equals 方法进行判断。
8. [强制] 所有的 POJO 类属性必须使用包装数据类型;RPC 方法的返回值和参数必须使用包装数据类型。
9. [强制]定义 DO/DTO/VO 等 POJO 类时,不要设定任何属性默认值。避免引起对象赋值部分属性更新时引起的默认值覆盖更新情况。
10. [强制]序列化类新增属性时,请不要修改 serialVersionUID 字段,避免反序列化失败;如果完全不兼容升级,避免反序列化混乱,那么请修改 serialVersionUID 值。
11. [强制]构造方法里面禁止加入任何业务逻辑。如有需要增加的业务逻辑,放在init()。
12. [推荐] POJO类写toString方法。在方法执行抛出异常时,可以直接调用 POJO 的 toString()方法打印其属性值,便于排查问题。
13. [推荐]使用索引访问String的split方法得到的数组时,需做最后一个分隔符后有无内容的检查,否则会有抛 IndexOutOfBoundsException 的风险。
14. [推荐]当一个类有多个构造方法,或者多个同名方法,这些方法应该按顺序放置在一起,便于阅读。
15. [推荐]类内方法定义的顺序推荐:公有方法或保护方法>私有方法>getter/setter方法。
16. [推荐] 在getter/setter方法中,不要增加业务逻辑,增加排查问题的难度。
17. [推荐]循环体内,字符串的连接方法,使用StringBuilder的append方法进行扩展。
18. [推荐] final可以声明类、成员变量、方法、以及本地变量,下列情况使用final关键字:
a) 不允许被继承的类,如:String 类。
b) 不允许修改引用的域对象,如:POJO 类的域变量。
c) 不允许被重写的方法,如:POJO 类的 setter 方法。
d) 不允许运行过程中重新赋值的局部变量。
e) 避免上下文重复使用一个变量,使用 final 描述可以强制重新定义一个变量,方便更好地进行重构。
19. [推荐]慎用Object的clone方法来拷贝对象。推荐使用序列化的方式来实现对象的深克隆。
public class Outer implements Serializable{
private static final long serialVersionUID = 369285298572941L; //最好是显式声明ID
public Inner inner;
// [深度复制方法,需要对象及对象所有的对象属性都实现序列化]
public Outer myclone() {
Outer outer = null;
try {
// 将该对象序列化成流,写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。利用这个特性可以实现对象的深拷贝
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
// 将流序列化成对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
outer = (Outer) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return outer;
}
}
20. [推荐]类成员与方法访问控制从严:
a) 如果不允许外部直接通过 new 来创建对象,那么构造方法必须是 private。
b) 工具类不允许有 public 或 default 构造方法。
c) 类非 static 成员变量并且与子类共享,必须是 protected。
d) 类非 static 成员变量并且仅在本类使用,必须是 private。
e) 类 static 成员变量如果仅在本类使用,必须是 private。
f) 若是 static 成员变量,必须考虑是否为 final。
g) 类成员方法只供类内部调用,必须是 private。
h) 类成员方法只对继承类公开,那么限制为 protected。
21. [推荐]工具类二方库已经提供的,尽量不要在本应用中编程实现。
md5操作:commons-codec
工具集合:Guava包
数组操作:ArrayUtils(org.apache.commons.lang3.ArrayUtils)
集合操作:CollectionUtils(org.apache.commons.collections4.CollectionUtils)
除上面以外还有NumberUtils、DateFormatUtils、DateUtils等优先使用 org.apache.commons.lang3这个包下的,不要使用org.apache.commons.lang包下面的。
22. [推荐]所有pom文件中的依赖声明放在
23. [推荐]给JVM设置-XX:+HeapDumpOnOutOfMemoryError参数,让JVM碰到OOM场景时输出dump 信息。
四、 集合约束
1. [强制]关于hsahCode和equals的处理,遵循如下规则:
a) 只要重写 equals,就必须重写 hashCode。
b) 因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须重写这两个方法。
c) 如果自定义对象作为 Map 的键,那么必须重写 hashCode 和 equals。
2. [强制]使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一样的数组,大小就是list.size()。
3. [强制]使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的 add/remove/clear 方法抛出UnsupportedOperationException 异常。说明:asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。
4. [强制]不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁。
正例
List list = new ArrayList();
Iterator
while (iterator.hasNext()) {
String item = iterator.next();
if ("ok".equals(item)) {
iterator.remove();
}
}
反例
List
list.add("1");
list.add("2");
for (String item : list) {
if ("1".equals(item)) {
list.remove(item);
}
}
5. [推荐]使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历。说明:keySet 其实是遍历了 2 次,一次是转为 Iterator 对象,另一次是从 hashMap 中取出key 所对应的 value。而 entrySet 只是遍历了一次就把 key 和 value 都放到了 entry 中,效率更高。如果是 JDK8,使用 Map.foreach 方法。
6. [推荐]高度注意 Map 类集合 K/V 能不能存储 null 值的情况,如下表格:
集合类 Key Value Super 说明
Hashtable 不允许为 null 不允许为 null Dictionary 线程安全
ConcurrentHashMap 不允许为 null 不允许为 null AbstractMap 锁分段技术(JDK8:CAS)
TreeMap 不允许为 null 允许为 null AbstractMap 线程不安全
五、 并发约束
1. [强制]创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
2. [强制]线程资源必须通过线程池提供,不允许在应用中自行显示创建线程。
3. [强制]线程池不允许使用Executors去创建,而是通过 ThreadPoolExecutor的方法,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
4. [强制] SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为static,必须加锁,或者使用 DateUtils 工具类。
正例:注意线程安全,使用 DateUtils。亦推荐如下处理:
private static final ThreadLocal
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
如果是 JDK8 的应用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar,DateTimeFormatter 代替 SimpleDateFormat。
5. [强制]高并发时同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。
6. [强制]对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁。
7. [强制]并发修改同一记录时,避免更新丢失,需要枷锁。要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观锁,使用version作为更新依据。
8. [强制]多线程并发处理定时任务时,Timer运行多个TimeTask时,只要其中之一没有捕获抛出的异常,其他任务便会自动终止运行,使用ScheduledExecutorService 则没有这个问题。
9. [推荐]使用CountDownLatch 进行异步转同步操作,每个线程退出前必须调用 countDown 方法,线程执行代码注意 catch 异常,确保 countDown 方法被执行到,避免主线程无法执行至 await 方法,直到超时才返回结果。
10. [推荐]在并发场景下,通过双重检查锁(double-checked locking)实现延迟初始化的优化问题隐患,可以用将目标属性声明为 volatile 型的方式解决。
反例
class Singleton {
private Helper helper = null;
public Helper getHelper() {
if (helper == null)
synchronized (this) {
if (helper == null)
helper = new Helper();
}
return helper;
}
// other methods and fields...
}
正例
class Singleton {
volatile Helper helper = null;
public Helper getHelper() {
if (helper == null){
helper = new Helper();
}
return helper;
}
}
六、 注释约束
1. [强制]类、类属性、类方法的注释必须使用Javadoc规约,使用/*内容/格式,不得使用//xxx 方式。
2. [强制]所有的抽象方法(包括接口中的方法)必须要用javadoc注释、除了返回值、参数、异常说明外,还必须指出该方法做什么事情,实现什么功能。 说明:对子类的实现要求,或者调用注意实现,请一并说明。
3. [强制]所有类都必须添加创建者和创建日期。
4. [强制]方法内部单行注释,在被注释语句上方另起一行,使用//珠海。方法内部多行注释使用/**/注释,注意与代码对齐。
5. [推荐]如果害怕英语注释表达不清,不如用中文注释把问题说清楚。专有名词与关键字保持英文原文即可。
6. [推荐]代码修改的同时,注释也要进行相应的修改,尤其是参数、返回值、异常、核心逻辑等的修改。
7. [推荐]谨慎注释掉代码。在上方详细说明,而不是简单地注释掉。如果无用,则删除。
8. [参考]好的命名、代码结构是自解释的,注释力求精简准确、表达到位。避免出现增加过多注释后代码逻辑修改导致修改注释的工作量加大甚至代码逻辑修改后不维护注释导致其他同事的理解产生偏差。
9. [参考]特殊注释标记,请注明标记人与标记时间。注意及时处理这些标记,通过标记扫描,经常清理此类标记。
例如:待办事宜(TODO):( 标记人,标记时间,[预计处理时间])
错误,不能工作(FIXME):(标记人,标记时间,[预计处理时间])
七、 异常约束
1. [强制] Java 类库中定义的可以通过预检查方式规避的 RuntimeException 异常不应该通过catch 的方式来处理。
正例:if (obj != null) {…}
反例:try { obj.method() } catch (NullPointerException e) {…}
2. [强制]异常不要用来做流程控制,条件控制。说明:异常设计的初中是解决程序运行中的各种意外情况,且异常的处理效率比条件判断方式要低很多。
3. [强制] try-catch时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的catch尽可能进行区分异常类型,再做对应的异常处理。
4. [强制]捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。
5. [强制]有 try 块放到了事务代码中,catch 异常后,如果需要回滚事务,一定要注意手动回滚事务。
6. [强制] finally 块必须对资源对象、流对象进行关闭,有异常也要做 try-catch。说明:如果 JDK7 及以上,可以使用 try-with-resources 方式。
7. [强制]不要在 finally 块中使用 return。 说明:finally 块中的 return 返回后方法结束执行,不会再执行 try 块中的 return 语句。
8. [推荐]方法的返回值可以为 null,不强制返回空集合,或者空对象等,必须添加注释充分说明什么情况下会返回 null 值。
9. [推荐]防止 NPE,是程序员的基本修养,注意 NPE 产生的场景:
a) 返回类型为基本数据类型,return 包装数据类型的对象时,可能产生 NPE。
eg:public int f() { return Integer 对象}, 如果为 null,则会抛出 NPE。
b) 数据库的查询结果可能为 null。
c) 集合里的元素即使 isNotEmpty,取出的数据元素也可能为 null。
d) 远程调用返回对象时,一律要求进行空指针判断,防止 NPE。
e) 对于 Session 中获取的数据,建议 NPE 检查,避免空指针。
f) 级联调用 obj.getA().getB().getC();一连串调用,易产生 NPE。
10. [推荐] 使用 JDK8 的 Optional 类来防止 NPE 问题。
正例
public static String getName(User u) {
if (u == null)
return "Unknown";
return u.name;
}
public static String getName(User u) {
return Optional.ofNullable(u).map(user->user.name)
.orElse("Unknown");
}
11. [推荐]定义时区分 unchecked / checked 异常,避免直接抛出 new RuntimeException(),更不允许抛出 Exception 或者 Throwable,应使用有业务含义的自定义异常。推荐业界已定义过的自定义异常,如:DAOException / ServiceException 等。
12. [参考]对于公司外的 http/api 开放接口必须使用“错误码”;而应用内部推荐异常抛出;跨应用间 RPC 调用优先考虑使用 Result 方式,封装 isSuccess()方法、“错误码”、“错误简短信息”。
八、 日志约束
1. [强制]应用应依赖使用日志框架SLF4J中的API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
2. [强制]日志文件推荐至少保存15天,因为有些异常具备以“周”为频次发生的特点。
3. [强制]应用中的扩展日志(如打点、临时监控、访问日志等)命名方式:appName_logType_logName.log。
logType:日志类型,推荐分类有stats/desc/monitor/visit 等;logName:日志描述。
说明:推荐对日志进行分类,错误日志和业务日志尽量分开存放,便于开发人员查看,也便于通过日志对系统进行及时监控。
4. [强制]对trace/debug/info级别的日志输出,必须使用条件输出形式或者使用占位符的方式。说明:
logger.debug("Processingtrade withid: " + id + " symbol: " + symbol);
如果日志级别是warn,上述日志不会打印,但是会执行字符串拼接操作,如果symbol是对象,会执行toString()方法,浪费了系统资源,执行了上述操作,最终日志却没有打印。
正例:(条件+占位符)
if (logger.isDebugEnabled()){
logger.debug("Processing trade with id:{} andsymbol : {} ", id, symbol);
}
5. [强制]避免重复打印日志,浪费磁盘空间,务必在log4j.xml中设置additivity=false。
正例:
6. [强制]异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么往上抛。
正例:
logger.error(各类参数或者对象toString +"_" + e.getMessage(), e);
7. [推荐]可以使用warn日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适从。注意日志输出的级别,error级别只记录系统逻辑出错、异常、或者重要的错误信息。如非必要,请不要在此场景打出error级别,避免频繁报警。
8. [推荐]谨慎地记录日志。生产环境禁止输出debug日志;有选择地输出info日志;如果使用warn来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑爆,并记得及时删除这些观察日志。
九、 版本约束
1. [强制]版本号命名方式:主版本号.次版本号.修订号
a) 主版本号:当做了不兼容的API 修改,或者增加了能改变产品方向的新功能。
b) 次版本号:当做了向下兼容的功能性新增(新增类、接口等)。
c) 修订号:修复bug,没有修改方法签名的功能加强,保持 API 兼容性。
2. [强制]线上应用不要依赖SNAPSHOT版本(安全包除外);正式发布的类库必须使用RELEASE 版本号升级+1的方式,且版本号不允许覆盖升级,必须去中央仓库进行查证。
十、 安全约束
1. 【强制】可被用户直接访问的功能必须进行权限控制校验。
2. 【强制】用户敏感数据禁止直接展示,必须对展示数据脱敏。
3. 【强制】用户输入的SQL参数严格使用参数绑定或者METADATA字段值限定,防止SQL注入,禁止字符串拼接SQL访问数据库。
4. 【强制】用户请求传入的任何参数必须做有效性验证。说明:忽略参数校验可能导致:page size过大导致内存溢出、恶意order by导致数据库慢查询、正则输入源串拒绝服务ReDOS、任意重定向、SQL注入、Shell注入、反序列化注入
5. 【强制】禁止向HTML页面输出未经安全过滤或未正确转义的用户数据。
参考:
阿里巴巴Java开发手册(纪念版)
地址:https://github.com/alibaba/p3c/
eclipse安装插件地址:https://github.com/alibaba/p3c/tree/master/eclipse-plugin
IDEA:https://github.com/alibaba/p3c/tree/master/idea-plugin