基本规范
-
禁止使用SUN包下的类,替换成公开的API,否则Maven不能打包
-
保持代码 google 风格
-
移除无用的代码和注释
-
【强制】类、类属性、类方法的注释必须使用 Javadoc 规范,使用/*内容/格式,不得使用// xxx 方式。
-
【强制】方法内部单行注释,在被注释语句上方另起一行。方法内部多行注释使用/* */注释,注意与代码对齐。
-
禁止JavaBean中的setter和getter里有逻辑代码
-
定时任务时间间隔不能小于5分钟,特殊情况需要告知技术经理,并经过技术经理同意
-
产品需求里的批量导入最多支持20条
-
【建议】POJO 类必须写 toString 方法,方便问题查询
-
【强制】所有的相同类型的包装类对象之间值的比较,全部使用 equals 方法比较
-
【推荐】类成员、方法、构造函数访问控制从严,正确使用 public、private、protected、final
-
【推荐】 表达异常的分支时, 少用 if-else 方式, 这种方式可以改写成:
说明: 如果非得使用 if()...else if()...else...方式表达逻辑, 避免后续代码维护困难, 请勿超过 3 层。
正例: 超过 3 层的 if-else 的逻辑判断代码可以使用卫语句、策略模式、状态模式等来实现。if (condition) { return obj; } // 接着写 else 的业务逻辑代码;
说明: 如果非得使用
if()...else if()...else...
方式表达逻辑, 避免后续代码维护困难, 请勿超过 3 层。
正例: 超过 3 层的 if-else 的逻辑判断代码可以使用卫语句、策略模式、状态模式等来实现。
Service/DAO层方法命名规范
- 获取单个对象的方法用 select 作前缀。
- 获取多个对象的方法用 selectList 作前缀。
- 获取统计值的方法用 selectCount 作前缀。
- 插入的方法用 insert 作前缀。
- 删除的方法用 delete 作前缀。
- 修改的方法用 update 作前缀。
变量命名
- 力求语义表达完整清楚,不要嫌名字长。
包名
- 统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词
- 统一使用单数形式
常量定义
【推荐】常量的复用层次有五层:跨应用共享常量、应用内共享常量、子工程内共享常量、包内共享常量、类内共享常量。
- 跨应用共享常量:放置在二方库中,通常是 client.jar 中的 constant 目录下。
- 应用内共享常量:放置在一方库中, 通常是子模块中的 constant 目录下。
反例: 易懂变量也要统一定义成应用内共享常量,两位攻城师在两个类中分别定义了表示“是”的变量:
类 A 中:public static final String YES = "yes"
;
类 B 中:public static final String YES = "y"
;
A.YES.equals(B.YES)
,预期是 true,但实际返回为 false,导致线上问题。 - 子工程内部共享常量:即在当前子工程的 constant 目录下。
- 包内共享常量:即在当前包下单独的 constant 目录下。
- 类内共享常量:直接在类内部
private static final
定义。
变量类型
- 【强制】 所有的 POJO 类属性必须使用包装数据类型。
- 【强制】 RPC 方法的返回值和参数必须使用包装数据类型。
- 【推荐】 所有的局部变量使用基本数据类型。
final关键字
【推荐】 final 可以声明类、成员变量、方法、以及本地变量,下列情况使用 final 关键字:
- 不允许被继承的类,如: String 类。
- 不允许修改引用的域对象,如: POJO 类的域变量。
- 不允许被重写的方法,如: POJO 类的 setter 方法。
- 不允许运行过程中重新赋值的局部变量。
- 避免上下文重复使用一个变量,使用 final 描述可以强制重新定义一个变量,方便更好地进行重构。
hashCode 和 equals的处理
- 只要重写 equals,就必须重写 hashCode。
- 因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须重写这两个方法。
- 如果自定义对象作为 Map 的键,那么必须重写 hashCode 和 equals。(比如:String类)
集合处理
-
【强制】使用集合转数组的方法,必须使用集合的
toArray(T[] array)
,传入的是类型完全一样的数组,大小就是 list.size()。反例: 直接使用toArray ()
存在问题,此方法返回值只能是 Object[]类,若强转其它类型数组将出现 ClassCastException 错误。 -
【强制】使用工具类
Arrays.asList()
把数组转换成集合时,不能使用其修改集合相关的方法,它的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常。
说明: asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。Arrays.asList体现的是适配器模式,只是转换接口,后台的数据仍是数组。第一种情况: list.add("yangguanbao"); 运行时异常。
第二种情况: str[0] = "gujin"; 那么 list.get(0)也会随之修改。String[] str = new String[] { "you", "wu" }; List list = Arrays.asList(str);
第一种情况: list.add("yangguanbao"); 运行时异常。
第二种情况: str[0] = "gujin"; 那么 list.get(0)也会随之修改。 -
【强制】不要在 foreach 循环里进行元素的 remove/add 操作。 remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁。
-
【强制】HashMap 初始化,initialCapacity = (需要存储的元素个数 / 负载因子0.75) + 1。
-
【推荐】使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历。如果是 JDK8,使用 Map.foreach 方法。
日期处理
- 如果是 JDK8 的应用,可以使用 Instant 代替 Date, LocalDateTime 代替 Calendar,DateTimeFormatter 代替 SimpleDateFormat。 JDK8日期详解
并发处理
-
【强制】高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁; 能锁区块,就不要锁整个方法体; 能用对象锁,就不要用类锁。
-
【强制】多线程并行处理定时任务时, Timer 运行多个 TimeTask 时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,使用 ScheduledExecutorService 则没有这个问题。
-
【推荐】避免 Random 实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一seed 导致的性能下降。
说明: Random 实例包括 java.util.Random 的实例或者 Math.random()的方式。
正例: 在 JDK7 之后,可以直接使用 API ThreadLocalRandom, 而在 JDK7 之前, 需要编码保证每个线程持有一个实例。 -
【参考】 ThreadLocal 无法解决共享对象的更新问题, ThreadLocal 对象建议使用 static 修饰。这个变量是针对一个线程内所有操作共享的,所以设置为静态变量,
所有此类实例共享此静态变量 ,也就是说在类第一次被使用时装载,只分配一块存储空间,所有此类的对象(只要是这个线程内定义的)都可以操控这个变量。 -
【强制】在高并发场景中,避免使用”等于”判断作为中断或退出的条件。
说明: 如果并发控制没有处理好,容易产生等值判断被“击穿”的情况,使用大于或小于的区间判断条件来代替。
反例: 判断剩余奖品数量等于 0 时,终止发放奖品,但因为并发处理错误导致奖品数量瞬间变成了负数, 这样的话,活动无法终止。 -
【参考】 volatile 解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题,但是如果多写,同样无法解决线程安全问题。如果是 count++操作,使用如下类实现:
AtomicInteger count = new AtomicInteger(); count.addAndGet(1);
如果是 JDK8,推荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数) 。
异常日志处理
- 【强制】 finally 块必须对资源对象、流对象进行关闭,有异常也要做 try-catch。
说明: 如果 JDK7 及以上,可以使用 try-with-resources 方式。 - 【推荐】方法的返回值可以为 null,不强制返回空集合,或者空对象等,必须添加注释充分说明什么情况下会返回 null 值。
- 【推荐】防止 NPE,是程序员的基本修养,注意 NPE 产生的场景:
1)返回类型为基本数据类型, return 包装数据类型的对象时,自动拆箱有可能产生 NPE。
反例:public int f() { return Integer 对象}
, 如果为 null,自动解箱抛 NPE。
2) 数据库的查询结果可能为 null。
3) 集合即使 isNotEmpty,取出的数据元素也可能为 null。
4) 远程调用返回对象时,一律要求进行空指针判断,防止 NPE。
5) 对于 Session 中获取的数据, 建议 NPE 检查,避免空指针。
6) 级联调用 obj.getA().getB().getC(); 一连串调用,易产生 NPE。
正例: 使用 JDK8 的 Optional 类来防止 NPE 问题。
日志规范
-
【强制】应用中不可直接使用日志系统(Log4j、 Logback) 中的 API,而应依赖使用日志框架SLF4J 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
import org.slf4j.Logger; import org.slf4j.LoggerFactory; private static final Logger logger = LoggerFactory.getLogger(Abc.class);
-
【强制】日志文件推荐至少保存 15 天,因为有些异常具备以“周”为频次发生的特点。
-
【强制】应用中的扩展日志(如打点、临时监控、访问日志等) 命名方式:appName_logType_logName.log。logType:日志类型,推荐分类有 stats/monitor/visit 等;logName:日志描述。这种命名的好处:通过文件名就可知道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找。
正例: mppserver 应用中单独监控时区转换异常,如:
mppserver_monitor_timeZoneConvert.log
说明: 推荐对日志进行分类, 如将错误日志和业务日志分开存放,便于开发人员查看,也便于通过日志对系统进行及时监控。 -
【强制】对 trace/debug/info 级别的日志输出,必须使用条件输出形式或者使用占位符的方式。
说明:logger.debug("Processing trade with id: " + id + " and symbol: " + symbol);
如果日志级别是 warn,上述日志不会打印,但是会执行字符串拼接操作,如果 symbol 是对象,会执行 toString()方法,浪费了系统资源,执行了上述操作,最终日志却没有打印。
正例: (条件)if (logger.isDebugEnabled()) { logger.debug("Processing trade with id: " + id + " and symbol: " + symbol); }
正例: (占位符)
logger.debug("Processing trade with id: {} and symbol : {} ", id, symbol);
-
【强制】避免重复打印日志,浪费磁盘空间,务必在 log4j.xml 中设置 additivity=false。
正例:<logger name="com.taobao.dubbo.config" additivity="false">
-
【强制】异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过关键字 throws 往上抛出。
正例:logger.error(各类参数或者对象 toString + "_" + e.getMessage(), e);
-
【推荐】谨慎地记录日志。生产环境禁止输出 debug 日志; 有选择地输出 info 日志; 如果使用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑爆,并记得及时删除这些观察日志。
说明: 大量地输出无效日志,不利于系统性能提升,也不利于快速定位错误点。 记录日志时请思考:这些日志真的有人看吗?看到这条日志你能做什么?能不能给问题排查带来好处? -
【推荐】可以使用 warn 日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适从。如非必要,请不要在此场景打出 error 级别,避免频繁报警。
说明: 注意日志输出的级别, error 级别只记录系统逻辑出错、异常或者重要的错误信息。 -
【强制】日志级别对应的关系
- trace 用来跟踪线上问题,问题解决后删除
- Warning 用来警告不符合业务规范等相关日志
- Info 用来输出业务日志,用于排查和恢复数据
- Error 只记录线上异常日志,方便监控报警
单元测试
-
单元测试中不准使用 System.out 来进行人肉验证,必须使用 assert 来验证
-
单元测试用例之间决不能互相调用,也不能依赖执行的先后次序
-
编写测试代码遵守 BCDE 原则
- B:Border,边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等
- C:Correct,正确的输入,得到预期的结果
- D:Design,与设计文档相结合,来编写单元测试
- E:Error,强制错误信息输入(如:非法数据、异常流程、非业务允许输入等),并得到预期的结果
-
对于不可测的代码,建议做必要的重构,使代码变得可测,避免为了达到测试要求而书写不规范的代码
网友评论