目录
前言
一.命名风格
1.规范变量的命名【强制】
2. 规范类的命名【强制】
3. 规范方法的命名【强制】
二. 代码风格
1.提升代码的可读性【推荐】
2.尽量避免魔法值【推荐】
3.避免大段代码【推荐】
三. 控制语句
1.控制语句的大括号【强制】
2.善于使用卫语句【推荐】
3.循环内避免重复性操作【强制】
4.循环内的try catch【参考】
四. 注释规范
1.类、属性、方法的注释【强制】
2.方法内的注释【推荐】
3.注释同样需要维护【强制】
4.谨慎注释代码【强制】
五. OOP规范
1.善于使用@Override【强制】
2.安全的调用equals方法【强制】
3.注意包装类型的比较【强制】
4.注意包装类型与基本类型的选择【强制】
5.高效的拼接字符串【强制】
6.类如何禁止被new【参考】
7.控制工具类的构造方法【参考】
六. 集合处理
1.集合的初始化【强制】
2.Map类集合能不能存null值【强制】
3.Java8中对集合的优雅操作【参考】
七. 并发处理
1.善于使用线程池【强制】
2.线程安全的数据结构【强制】
八. 异常处理
1.RuntimeException的处理【强制】
2.防止NPE【强制】
3.区分unchecked(Runtime)/checked异常【强制】
4.注意finally的使用【强制】
5.注意catch中的异常类型【强制】
九. 日志规范
1.注意使用占位符【强制】
2.精简日志内容【推荐】
3.规范error的使用【强制】
前言
高质量的代码应该同时具备以下特性:
- 准确性:能够准确无误的返回业务需要的结果;
- 健壮性:健壮的程序应该对于数据异常、网络异常、系统故障等特殊场景有良好的适应能力;
- 高效性:高效的程序才会有良好的用户体验;
- 可读性:代码除了自己看就是给同事或同行看,可读性与规范程度直接决定你在对方心中的地位。你的垃圾代码有可能会成为反面教材,在你不知道的地方被展览;
- 可扩展性:扩展别人代码的时候有没有吐槽过?扩展还不如重新写?懂得都懂。
以下内容参考于《阿里巴巴Java开发手册》等权威资料。
一.命名风格
1.规范变量的命名【强制】
所有变量必须使用驼峰式风格,不能以特殊字符开头。常量命名全部大写,单词下划线隔开,语义尽量完整,不要嫌名字长。
正例:lowerCamelCase
反例:ObjId、_name、WFROLE_TYPE
2. 规范类的命名【强制】
类名首字母大写,遵循驼峰式,命名要体现类的业务用途。
正例:WorkFlowService、WorkFlowUtils、WorkFlowTest、WorkFlowException
反例:Test、Common
- 领域模型命名:数据对象:xxxDO(xxxEntity),xxx为数据库表名;
- 数据传输对象:xxxDTO;
- 展示对象:xxxVO;
- POJO是DO/DTO/VO的统称,禁止命名成xxxPOJO。
3. 规范方法的命名【强制】
方法名首字母小写,遵循驼峰式,命名要体现方法的业务用途(方法突出动作,一般都是动词)。
- 获取单个对象的方法用get作为前缀;
- 获取多个对象的方法用list作为前缀;
- 获取统计值的方法用count作为前缀;
- 插入方法用save/insert作为前缀;
- 删除方法用remove/delete作为前缀。
二. 代码风格
1.提升代码的可读性【推荐】
- if/for/while等关键字与括号之间加一个空格;
- =、逻辑运算符、加减乘除等符号左右两边加一个空格;
- 双斜线注释与内容之间有且仅有一个空格;
- 单行字符数不超过120个,超出需要换行;
- 方法参数在定义和传入时,多个参数逗号后面必须加空格;
- 不同逻辑、不同语义、不同业务的代码之间加一个空行分隔,提升可读性;
- 删除类中无用的jar包。
2.尽量避免魔法值【推荐】
方法内应尽量避免魔法值,用常量代替。
3.避免大段代码【推荐】
不要把一个功能的所有内容都集中在一个方法中。
最外层的入口方法往往用来体现整体的业务流程,尽量让人一目了然,具体实现细节可以抽到单独的方法来实现,同时要有明确的注释,这样的代码可以大大提升可读性与可扩展性。
正例:
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 1、调用容器准备刷新的方法,获取容器的当时时间,同时给容器设置同步标识
prepareRefresh();
// 2、告诉子类启动refreshBeanFactory()方法,启动内部的bean factory然后对bean进行定位、加载、注册,最后bean会被组装成BeanDifinition对象保存在beanDefinitionMap中。
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 3、为BeanFactory 配置容器特性,例如类加载器、事件处理器等
prepareBeanFactory(beanFactory);
try {
// 4、允许子类做beanFactory的后置处理
postProcessBeanFactory(beanFactory);
// 5、在容器中调用注册为bean的工厂处理器
invokeBeanFactoryPostProcessors(beanFactory);
// 6、注册Bean的后置处理器,用于监听容器触发的事件
registerBeanPostProcessors(beanFactory);
// 7、初始化信息源,和国际化相关
initMessageSource();
// 8、初始化容器事件传播器
initApplicationEventMulticaster();
// 9、调用子类的某些特殊Bean初始化方法
onRefresh();
// 10、检查并注册实践监听器
registerListeners();
// 11、初始化所以剩余(非懒加载)的单例bean,根据beanName循环递归调用getBean方法,包括对循环引用的解决
finishBeanFactoryInitialization(beanFactory);
// 12、初始化容器的生命周期事件处理器,并发布容器的生命周期事件
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// 13、销毁已创建的Bean
destroyBeans();
// 14、取消refresh 操作,重置容器的同步标识
cancelRefresh(ex);
throw ex;
}
finally {
// 15、重置公共缓存
resetCommonCaches();
}
}
}
三. 控制语句
1.控制语句的大括号【强制】
在if/else/for/while语句中,必须使用大括号,即使只有一行,使作用范围更清晰。
2.善于使用卫语句【推荐】
考虑使用卫语句代替if else。
正例:
if (todoOrderPastDTO == null) {
throw new BizException(-1, "参数不能为空");
}
if (StringUtils.isBlank(todoOrderPastDTO.getOneLevelCode())) {
throw new BizException(-1, "一级编码不能为空");
}
if (StringUtils.isBlank(todoOrderPastDTO.getTwoLevelCode())) {
throw new BizException(-1, "二级编码不能为空");
}
if (StringUtils.isBlank(todoOrderPastDTO.getBizCode())) {
throw new BizException(-1, "业务单号不能为空");
}
if (StringUtils.isBlank(todoOrderPastDTO.getTodoHandlerCode())) {
throw new BizException(-1, "处理人系统号不能为空");
}
反例:
if (...) {
if (...) {
if (...) {
...
} else {
...
}
} else {
...
}
}
3.循环内避免重复性操作【强制】
Map<String, String> map = new HashMap<>(2);
map.put("a", "ok");
// String a = map.get("a");
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++) {
// 这里要执行100次map的get方法
sb.append(map.get("a"));
}
4.循环内的try catch【参考】
try catch对程序性能有一定影响,请根据业务场景考虑try catch是否可以提到循环外。
四. 注释规范
1.类、属性、方法的注释【强制】
必须使用Javadoc规范,/** xxx **/,不得使用 // xxx,尤其是利用工具生成的类,请补充注释说明。
2.方法内的注释【推荐】
方法内部单行注释使用// xxx,多行使用/* xxx */
3.注释同样需要维护【强制】
修改代码的同时,要对注释进行相应的修改,尤其是参数、返回值、异常、核心逻辑等。
4.谨慎注释代码【强制】
注释代码要说明理由,而不是简单的注释掉,如果后续可能恢复,统一使用三个斜杠 ///,并注明理由。如果不恢复,则直接删除,历史记录可以在git上找到。
五. OOP规范
1.善于使用@Override【强制】
所有覆写的方法必须加@Override,例如getObject()与get0bject(),通过@Override可以准确判断是否覆盖成功。
2.安全的调用equals方法【强制】
用常量或确定的值来调用equals方法。
3.注意包装类型的比较【强制】
所有相同类型的包装类对象之间的比较全部使用equals方法。
注:jdk1.7之后推荐使用Objects工具类。
Integer a1 = 1;
Integer a2 = 1;
Integer b1 = 128;
Integer b2 = 128;
System.out.println(a1 == a2); // -128 ~ 127 true
System.out.println(b1 == b2); // false
System.out.println(b1.equals(b2)); // true
4.注意包装类型与基本类型的选择【强制】
所有实体类必须使用包装数据类型,所有局部变量使用基本数据类型。
例如数据库的返回值可能是null,如果用基本数据类型接收,自动拆箱有NPE风险。
5.高效的拼接字符串【强制】
涉及多个字符串拼接的场景使用StringBuilder。
String str = "";
for (int i = 0; i < 100; i++) {
// 编译后编译器会对代码优化,实际上每次循环都会new出一个StringBuilder,浪费资源
str += "a";
}
System.out.println(str);
6.类如何禁止被new【参考】
如果类不允许外界直接new对象,构造方法必须是private,参考单例模式。
7.控制工具类的构造方法【参考】
工具类的构造方法建议是private或protected(可被继承)。
六. 集合处理
1.集合的初始化【强制】
集合初始化时,如果元素数量确定或可以预估,为了避免没必要的扩容,请指定集合初始值。
正例:
// 初始化一个需要承载20个元素的ArrayList(默认容量10,每次扩容1.5倍)
List<Object> list = new ArrayList<>(20);
// 初始化一个需要承载20个元素的HashMap(默认容量16,扩容因子0.75,每次扩容2倍)
Map<String, Object> map = new HashMap<>(32);
2.Map类集合能不能存null值【强制】
由于 HashMap 的干扰,很多人认为 ConcurrentHashMap 是可以置入 null 值,而事实上, 存储 null 值时会抛出 NPE 异常。
3.Java8中对集合的优雅操作【参考】
Demo:
- List按某属性分组;
- List转Map;
- List按条件过滤;
- List去重;
- List升序;
- List降序;
- List先升序再降序;
- List求和;
- List求最大值;
- List求最小值。
import lombok.Data;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
/**
* Java8 集合操作
*
* @Author hujy
* @Date Created in 2019-07-16 11:38
* @Version 1.0
*/
@Data
public class Fruit {
private Integer id;
private String name;
private BigDecimal money;
private Integer num;
public Fruit(Integer id, String name, BigDecimal money, Integer num) {
this.id = id;
this.name = name;
this.money = money;
this.num = num;
}
/**
* 分组
* @author hujy
* @date 2019-07-18 15:03
* @param fruitList
* @return void
*/
private static void groupBy(List<Fruit> fruitList) {
Map<Integer, List<Fruit>> groupBy =
fruitList.stream().collect(Collectors.groupingBy(Fruit::getId));
System.out.println("groupBy:" + groupBy);
}
/**
* List转Map
* @author hujy
* @date 2019-07-18 15:04
* @param fruitList
* @return void
*/
private static void listToMap(List<Fruit> fruitList) {
// 注:如果集合对象有重复的key,会报Duplicate key错误,可以用 (k1, k2) -> k1 来设置,如果有重复的key,则保留key1,舍弃key2
Map<Integer, Fruit> fruitMap =
fruitList.stream().collect(Collectors.toMap(Fruit::getId, a -> a, (k1, k2) -> k1));
System.out.println("toMap:" + fruitMap);
}
/**
* 按条件过滤
* @author hujy
* @date 2019-07-18 15:07
* @param fruitList
* @return void
*/
private static void filter(List<Fruit> fruitList) {
List<Fruit> filterList =
fruitList.stream().filter(a -> a.getName().equals("香蕉")).collect(Collectors.toList());
System.out.println("filter:" + filterList);
}
/**
* 去重
* @author hujy
* @date 2019-07-18 15:08
* @param fruitList
* @return void
*/
private static void toUnique(List<Fruit> fruitList) {
// 根据id去重
List<Fruit> uniqueList =
fruitList.stream().collect(
Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(o -> o.getId()))),
ArrayList::new));
System.out.println("unique:" + uniqueList);
}
/**
* 升序
* @author hujy
* @date 2019-07-18 15:09
* @param fruitList
* @return void
*/
private static void sortAsc(List<Fruit> fruitList) {
// 按id升序排序
List<Fruit> sortAsc=
fruitList.stream().sorted(Comparator.comparing(Fruit::getId)).collect(Collectors.toList());
System.out.println("sortAsc:" + sortAsc);
}
/**
* 降序
* @author hujy
* @date 2019-07-18 15:09
* @param fruitList
* @return void
*/
private static void sortDesc(List<Fruit> fruitList) {
// 按id降序排序
List<Fruit> sortDesc =
fruitList.stream().sorted(Comparator.comparing(Fruit::getId).reversed()).collect(Collectors.toList());
System.out.println("sortDesc:" + sortDesc);
}
/**
* 先升序再降序
* @author hujy
* @date 2019-07-18 15:09
* @param fruitList
* @return void
*/
private static void sortAscDesc(List<Fruit> fruitList) {
// 按id升序排序,id一样的按num降序
List<Fruit> sortAscDesc = fruitList.stream().sorted(Comparator.comparing(Fruit::getId).reversed()
.thenComparing(Fruit::getNum).reversed()).collect(Collectors.toList());
System.out.println("sortAscDesc:" + sortAscDesc);
}
/**
* 求和
* @author hujy
* @date 2019-07-18 15:10
* @param fruitList
* @return void
*/
private static void sum(List<Fruit> fruitList) {
// 按money求和
BigDecimal totalMoney = fruitList.stream().map(Fruit::getMoney).reduce(BigDecimal.ZERO, BigDecimal::add);
System.out.println("totalMoney:" + totalMoney);
}
/**
* 求最大值
* @author hujy
* @date 2019-07-18 15:10
* @param fruitList
* @return void
*/
private static void getMax(List<Fruit> fruitList) {
// 求money最大值
Optional<BigDecimal> max = fruitList.stream().map(Fruit::getMoney).max(BigDecimal::compareTo);
System.out.println("max:" + max.get());
}
/**
* 求最小值
* @author hujy
* @date 2019-07-18 15:10
* @param fruitList
* @return void
*/
private static void getMin(List<Fruit> fruitList) {
// 求money最小值
Optional<BigDecimal> min = fruitList.stream().map(Fruit::getMoney).min(BigDecimal::compareTo);
System.out.println("min:" + min.get());
}
public static void main(String[] args) {
Fruit fruit1 = new Fruit(1, "苹果1", new BigDecimal("2.25"), 10);
Fruit fruit2 = new Fruit(1, "苹果2", new BigDecimal("1.85"), 20);
Fruit fruit3 = new Fruit(2, "香蕉", new BigDecimal("1.89"), 30);
Fruit fruit4 = new Fruit(3, "樱桃", new BigDecimal("8.99"), 40);
List<Fruit> fruitList = Arrays.asList(fruit1, fruit2, fruit3, fruit4);
// 1.分组
groupBy(fruitList);
// groupBy:{1=[Fruit(id=1, name=苹果1, money=2.25, num=10), Fruit(id=1, name=苹果2, money=1.85, num=20)], 2=[Fruit(id=2, name=香蕉, money=1.89, num=30)], 3=[Fruit(id=3, name=樱桃, money=8.99, num=40)]}
// 2.List转Map
listToMap(fruitList);
// toMap:{1=Fruit(id=1, name=苹果1, money=2.25, num=10), 2=Fruit(id=2, name=香蕉, money=1.89, num=30), 3=Fruit(id=3, name=樱桃, money=8.99, num=40)}
// 3.按条件过滤
filter(fruitList);
// filter:[Fruit(id=2, name=香蕉, money=1.89, num=30)]
// 4.根据id去重
toUnique(fruitList);
// unique:[Fruit(id=1, name=苹果1, money=2.25, num=10), Fruit(id=2, name=香蕉, money=1.89, num=30), Fruit(id=3, name=樱桃, money=8.99, num=40)]
// 5.按id升序
sortAsc(fruitList);
// sortAsc:[Fruit(id=1, name=苹果1, money=2.25, num=10), Fruit(id=1, name=苹果2, money=1.85, num=20), Fruit(id=2, name=香蕉, money=1.89, num=30), Fruit(id=3, name=樱桃, money=8.99, num=40)]
// 6.按id降序
sortDesc(fruitList);
// sortDesc:[Fruit(id=3, name=樱桃, money=8.99, num=40), Fruit(id=2, name=香蕉, money=1.89, num=30), Fruit(id=1, name=苹果1, money=2.25, num=10), Fruit(id=1, name=苹果2, money=1.85, num=20)]
// 7.按id升序排序,id一样的按num降序
sortAscDesc(fruitList);
// sortAscDesc:[Fruit(id=1, name=苹果2, money=1.85, num=20), Fruit(id=1, name=苹果1, money=2.25, num=10), Fruit(id=2, name=香蕉, money=1.89, num=30), Fruit(id=3, name=樱桃, money=8.99, num=40)]
// 8.money求和
sum(fruitList);
// totalMoney:14.98
// 9.求money最大值
getMax(fruitList);
// max:8.99
// 10.求money最小值
getMin(fruitList);
// min:1.85
}
}
七. 并发处理
1.善于使用线程池【强制】
创建线程要使用线程池,建议不要自行显式创建。
// 定义固定数量线程池
ThreadPoolExecutor pool = new ThreadPoolExecutor(
threadNum,
threadNum,
0L,
TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(500),
new DefaultThreadFactory("dataMove-pool"));
2.线程安全的数据结构【强制】
HashMap是非线程安全的,在并发写操作情况可能导致数据丢失,极端情况下产生死循环,占满CPU,因此禁止将HashMap等非线程安全的数据结构当做成员变量使用,特别是在Spring管理的单例中。
可使用ConcurrentHashMap代替HashMap来满足多线程并发操作的场景,ConcurrentHashMap在Java8中做了大量优化,建议了解。
ArrayList → CopyOnWriteArrayList
StringBuilder → StringBuffer
SimpleDateFormat是非线程安全的,禁止将SimpleDateFormat对象作为成员变量或公共静态工具使用,不建议加锁使用,会有效率问题。
八. 异常处理
1.RuntimeException的处理【强制】
可以通过预检查方式规避的RuntimeException不应该通过try/catch方式处理,例如NullPointerException(NPE)、IndexOutOfBoundsException。
2.防止NPE【强制】
防止NPE是程序员的基本素养,以下场景需要注意NPE校验:
- 当使用基本数据类型接收包装数据类型对象时,自动拆箱可能产生NPE,例如数据库查询返回null;
- 远程调用返回对象时,如果不进行非空校验,易产生NPE;
- 集合即使是isNotEmpty,取出的元素也可能是null,若不对元素进行非空校验直接使用,易产生NPE;
- 级联调用obj.getA().getB().getC()易产生NPE。
3.区分unchecked(Runtime)/checked异常【强制】
例如使用Spring进行事务管理时,默认会对运行时异常与error进行回滚,如果还想对非运行时异常回滚,需要通过rollbackFor具体指定回滚异常类型,这样会对该类型以及其子类异常进行回滚。
基于AOP动态代理的实现原理,如果在方法内部通过try/catch捕获异常,事务不会回滚。
4.注意finally的使用【强制】
在使用try/catch/finally时,不能在finally进行return,这样会覆盖try或catch中的return的结果,finally中一般用来执行对于资源释放的操作,如锁释放、流关闭等。
5.注意catch中的异常类型【强制】
在使用catch捕获异常时,应该注意捕获的异常与抛出的异常完全匹配,或者捕获的异常是抛出异常的父类。
九. 日志规范
1.注意使用占位符【强制】
debug/info级别的日志输出必须使用占位符方式,避免字符串拼接产生无用对象。
2.精简日志内容【推荐】
日志内容应该是一些唯一性强、辨识度高的关键字,方便快速定位分析问题,例如业务主键、流水号、接口入参、返回值、函数计算结果等。
日志并不是越详细越好,应该尽量做到精简,日志过多会影响系统性能。
3.规范error的使用【强制】
正例:
catch (Exception e) {
logger.error("xxx接口执行异常," + e.getMessage(), e);
}
反例:
catch (Exception e) {
logger.error("保存失败");
}