java代码规范

Java后台代码编写规范前言

以 Java 开发者为中心视角,划分为:编程规约、异常日志、单元测试、安全规约、工程结构五个维度,再根据内容特征,细分成若干二级子目录。另外,依据约束力强弱及故障敏感性,规约依次分为强制、推荐、参考三大类。在延伸信息中,“说明”对规约做了适当扩展和解释;“正例”提倡什么样的编码和实现方式;“反例”说明需要提防的雷区,以及真实的错误案例,本文主要参考阿里巴巴编码规范

  • 编程规约
  • 命名风格
  1. 【强制】代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束

反例: _name/__name/$Object/name_/name$/Object$

  1. 【强制】代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。

说明:正确的英文拼写和语法可以让阅读者易于理解,避免歧义。

注意,即使纯拼音命名方式也要避免采用。

正例:szkingdom/youku/shenzhen/chengdu 等国际通用的名称,可视同英文。

反例:DaZhePromotion[打折]/getPingfenByName()[评分]/int 某变量 = 3

  1. 【强制】类名使用UpperCamelCase风格,必须遵从驼峰形式,但以下情形例外:(领域模型的相关命名):DO/BO/DTO/VO/AO/PO/UID等

正例:JavaServerlessPlatform/UserDO/XmlService/TcpUdpDeal/TaPromotion

反例:javaserverlessplatform/UserDo/XMLService/TCPUDPDeal/TAPromotion

  1. 【强制】方法名、参数名、成员变量、局部变量都统一使用lowerCamelCase风格,必须遵从驼峰形式。

正例: localValue / getHttpMessage() / inputUserId

  1. 【强制】常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。

  正例:localValue/getHttpMessage()/inputUserId

  1. 【强制】抽象类命名使用Abstract或Base开头;异常类命名使用Exception结尾;测试类命名以它要测试的类的名称开始,单元测试类以Test结尾,集成测试类以IT结尾。

  正例:MAX_STOCK_COUNT 反例:MAX_COUNT

  1. 【强制】中括号是数组类型的一部分。

正例:定义整形数组int[] arrayDemo;

反例:在main参数中,使用String args[]来定义。

说明:String[] args是Java风格的数组定义,String args[]是C风格的数组定义。

  1. 【强制】POJO类中布尔类型变量都不要加is前缀,否则部分框架解析会引起序列化错误。

  说明:在本文MySQL规约中的建表约定第一条,表达是与否的值采用is_xxx的命名方式,所以,需要在<resultMap>设置从is_xxx到xxx的映射关系。

反例:定义为基本数据类型Boolean isDeleted的属性,它的方法也是isDeleted(),RPC框架在反向解析的时候,“误以为”对应的属性名称是deleted,导致属性获取不到,进而抛出异常。

  1. 【强制】包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式。

  正例:应用工具类包名为com.szkingdom.open.util、类名为MessageUtils(此规则参考spring的框架结构)

  1. 【强制】避免在子父类的成员变量之间、或者不同代码块的局部变量之间采用完全相同的命名,使可读性降低。

  说明:子类、父类成员变量名相同,即使是public类型的变量也是能够通过编译,而局部变量在同一方法内的不同代码块中同名也是合法的,但是要避免使用。对于非setter/getter的参数名称也要避免与成员变量名称相同。

  1. 【强制】杜绝完全不规范的缩写,避免望文不知义。

反例:AbstractClass“缩写”命名成AbsClass;condition“缩写”命名成 condi,此类随意缩写严重降低了代码的可阅读性。

  1. 【推荐】为了达到代码自解释的目标,任何自定义编程元素在命名时,使用尽量完整的单词组合来表达其意。

正例:在JDK中,表达原子更新的类名为:AtomicReferenceFieldUpdater。

反例:int a的随意命名方式。

  1. 【推荐】在常量与变量的命名时,表示类型的名词放在词尾,以提升辨识度。

正例:startTime/workQueue/nameList/TERMINATED_THREAD_COUNT

反例:startedAt/QueueOfWork/listName/COUNT_TERMINATED_THREAD

  1. 【推荐】如果使用到了设计模式,建议在类名中体现出具体模式。

  说明:将设计模式体现在名字中,有利于阅读者快速理解架构设计思想。

正例:public class OrderFactory;

public class LoginProxy;

public class ResourceObserver;

  1. 【推荐】接口类中的方法和属性不要加任何修饰符号(public 也不要加),保持代码的简洁性,并加上有效的Javadoc注释。尽量不要在接口里定义变量,如果一定要定义变量,肯定是与接口方法相关,并且是整个应用的基础常量。

正例:接口方法签名:void commit();

接口基础常量表示:String COMPANY = "szkingdom";

反例:接口方法定义:public abstract void f();

说明:JDK8中接口允许有默认实现,那么这个default方法,是对所有实现类都有价值的默认实现。

  1. 接口和实现类的命名有两套规则:
  1. 【强制】对于Service和DAO类,基于SOA的理念,暴露出来的服务一定是接口,内部的实现类用Impl的后缀与接口区别。
  2. 【推荐】 如果是形容能力的接口名称,取对应的形容词做接口名(通常是–able的形式)。

  1. 【参考】枚举类名建议带上Enum后缀,枚举成员名称需要全大写,单词间用下划线隔开。

说明:枚举其实就是特殊的常量类,且构造方法被默认强制是私有。

正例:枚举名字:DealStatusEnum,成员名称:SUCCESS/UNKOWN_REASON。

  1. 【参考】各层命名规约:

  A) Service/DAO层方法命名规约

1) 获取单个对象的方法用get做前缀。

2) 获取多个对象的方法用list做前缀。

3) 获取统计值的方法用count做前缀。

4) 插入的方法用save(推荐)或insert做前缀。

5) 删除的方法用remove(推荐)或delete做前缀。

6) 修改的方法用update做前缀。

 B) 领域模型命名规约

1) 数据对象:xxxDO,xxx即为数据表名。

2) 数据传输对象:xxxDTO,xxx为业务领域相关的名称。

3) 展示对象:xxxVO,xxx一般为网页名称。

4) POJO是DO/DTO/BO/VO的统称,禁止命名成xxxPOJO。

  • 常量定义

   1、【强制】不允许出现任何魔法值(即未经定义的常量)直接出现在代码中。

反例:String key = "Id#szkingdom_" + tradeId;

cache.put(key, value);

2、【强制】long或者Long初始赋值时,必须使用大写的L,不能是小写的l,小写容易跟数字1混淆,造成误解。

说明:Long a = 2l; 写的是数字的21,还是Long型的2?

3、【推荐】不要使用一个常量类维护所有常量,要按常量功能进行归类,分开维护。

说明:大而全的常量类,杂乱无章,使用查找功能才能定位到修改的常量,不利于理解和维护。

正例:缓存相关常量放在类CacheConsts下;系统配置相关常量放在类ConfigConsts下。

4、【推荐】常量的复用层次有五层:跨应用共享常量、应用内共享常量、子工程内共享常量、包内共享常量、类内共享常量。

1)跨应用共享常量:放置在二方库中,通常是client.jar中的constant目录下。

2)应用内共享常量:放置在一方库的modules中的constant目录下。

  反例:易懂变量也要统一定义成应用内共享常量,两位攻城师在两个类中分别定义了表示“是”的变量:

   类A中:public static final String YES = "yes";

   类B中:public static final String YES = "y";

   A.YES.equals(B.YES),预期是true,但实际返回为false,导致产生线上问题。

3)子工程内部共享常量:即在当前子工程的constant目录下。

4)包内共享常量:即在当前包下单独的constant目录下。

5)类内共享常量:直接在类内部private static final定义。

5、【推荐】如果变量值仅在一个范围内变化用Enum类。

说明:如果存在名称之外的延伸属性应使用enum类型,下面正例中的数字就是延伸信息,表示一年中的第几个季节。

正例:

public enum SeasonEnum {

SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4);

private int seq;

SeasonEnum(int seq) {

this.seq = seq;

}

public int getSeq() {

return seq;

}

}

  • 代码格式
  1. 【强制】如果是大括号内为空,则简洁地写成{}即可,大括号中间无需换行和空格;如果是非空代码块则:

1)左大括号前不换行。

2)左大括号后换行。

3)右大括号前换行。

4)右大括号后还有else等代码则不换行;表示终止的右大括号后必须换行。

  1. 【强制】左小括号和字符之间不出现空格;同样,右小括号和字符之间也不出现空格;而左大括号前需要空格。详见第5条下方正例提示。

反例:if (空格a == b空格)

  1. 【强制】if/for/while/switch/do等保留字与括号之间都必须加空格。
  2. 【强制】任何二目、三目运算符的左右两边都需要加一个空格。

说明:运算符包括赋值运算符=、逻辑运算符&&、加减乘除符号等。

  1. 【强制】采用4个空格缩进,禁止使用tab字符。

如果使用tab缩进,必须设置1个tab为4个空格。IDEA设置tab为4个空格时,请勿勾选Use tab character;而在eclipse中,必须勾选insert spaces for tabs。

  1. 【强制】注释的双斜线与注释内容之间有且仅有一个空格,禁止行尾注释 。
  2. 【强制】在进行类型强制转换时,右括号与强制转换值之间不需要任何空格隔开。
  3. 【强制】单行字符数限制不超过120个,超出需要换行,换行时遵循如下原则:

1)第二行相对第一行缩进4个空格,从第三行开始,不再继续缩进,参考示例。

2)运算符与下文一起换行。

3)方法调用的点符号与下文一起换行。

4)在多个参数超长,逗号后进行换行。

5)在括号前不要换行,见反例。

  1. 【强制】方法参数在定义和传入时,多个参数逗号后边必须加空格。

正例:下例中实参的"a",后边必须要有一个空格。

method("a", "b", "c");  

  1. 【强制】IDE的text file encoding设置为UTF-8;IDE中文件的换行符使用Unix格式,不要使用windows格式。

  1. 【强制】单个方法的总行数不超过80行。

说明:除注释之外的方法签名、左右大括号、方法内代码、空行、回车及任何不可见字符的总行数不超过80行。

正例:代码逻辑分清红花和绿叶,个性和共性,绿叶逻辑单独出来成为额外方法,使主干代码更加清晰;共性逻辑抽取成为共性方法,便于复用和维护。

  1. 【推荐】没有必要增加若干空格来使某一行的字符与上一行的相应字符对齐。

正例:int a = 3;

long b = 4L;

float c = 5F;

StringBuffer sb = new StringBuffer();

说明:增加sb这个变量,如果需要对齐,则给a、b、c都要增加几个空格,在变量比较多的情况下,是一种累赘的事情。

  1. 【推荐】不同逻辑、不同语义、不同业务的代码之间插入一个空行分隔开来以提升可读性。

说明:任何情形,没有必要插入多个空行进行隔开。

  1. 【强制】注解样式使用compact模式。关于格式请参考:

https://checkstyle.sourceforge.io/config_annotation.html#AnnotationUseStyle

  1. 【强制】每个注解应单独一行。

反例:if (a == b) { //判断a b是否相等

  1. 【强制】空的代码块应添加注释说明。

  1. 【强制】避免使用嵌套的块语句。

  1. 【强制】嵌套(内部)类/接口声明在类的底部(在所有方法和字段声明之后)。

  1. 【强制】避免使用静态导入,导入应按JDK/JAVA EE/三方类/当前项目类的顺序导入。

20、【推荐】关于静态导入,使用标准如下:

1) 避免静态导入静态变量。

2) 允许静态导入静态方法简化代码,如静态方法有重名,禁止静态导入。

  • OOP约束
  1. 强制】避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可。

  1. 【强制】所有的覆写方法,必须加@Override注解

反例:getObject()与get0bject()的问题。一个是字母的O,一个是数字的0,加@Override可以准确判断是否覆盖成功。另外,如果在抽象类中对方法签名进行修改,其实现类会马上编译报错。

  1. 【强制】相同参数类型,相同业务含义,才可以使用Java的可变参数,避免使用Object。

说明:可变参数必须放置在参数列表的最后。(提倡同学们尽量不用可变参数编程)。

正例:public List<User> listUsers(String type, Long... ids) {...}

  1. 【强制】外部正在调用或者二方库依赖的接口,不允许修改方法签名,避免对接口调用方产生影响。接口过时必须加@Deprecated注解,并清晰地说明采用的新接口或者新服务是什么。

  1. 【强制】不能使用过时的类或方法。

说明:java.net.URLDecoder中的方法decode(String encodeStr)这个方法已经过时,应该使用双参数decode(String source, String encode)。接口提供方既然明确是过时接口,那么有义务同时提供新的接口;作为调用方来说,有义务去考证过时方法的新实现是什么。

  1. 【强制】Object的equals方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals。

正例:"test".equals(object);

反例:object.equals("test");

说明:推荐使用java.util.Objects#equals (JDK7引入的工具类)

  1. 【强制】所有整型包装类对象之间值的比较,全部使用equals方法比较。

说明:对于Integer var = ?在-128至127范围内的赋值,Integer对象是在IntegerCache.cache产生,会复用已有对象,这个区间内的Integer值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用equals方法进行判断。

  1. 【强制】任何货币金额,均以最小货币单位且整型类型来进行存储。

  1. 【强制】浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用equals来判断。

说明:浮点数采用“尾数+阶码”的编码方式,类似于科学计数法的“有效数字+指数”的表示方式。二进制无法精确表示大部分的十进制小数,具体原理参考《码出高效》

  1. 【强制】定义数据对象DO类时,属性类型要与数据库字段类型相匹配。

正例:数据库字段的bigint必须与类属性的Long类型相对应。

反例:某个案例的数据库表id字段定义类型bigint unsigned,实际类对象属性为Integer,随着id越来越大,超过Integer的表示范围而溢出成为负数。

  1. 【强制】为了防止精度损失,禁止使用构造方法BigDecimal(double)的方式把double值转化为BigDecimal对象。

说明:BigDecimal(double)存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。

如: BigDecimal g = new BigDecimal(0.1f); 实际的存储值为:0.10000000149

正例:优先推荐入参为String的构造方法,或使用BigDecimal的valueOf方法,此方法内部其实执行了Double的toString,而Double的toString按double的实际能表达的精度对尾数进行了截断。

BigDecimal recommend1 = new BigDecimal("0.1");

BigDecimal recommend2 = BigDecimal.valueOf(0.1);

  1. 关于基本数据类型与包装数据类型的使用标准如下:

1)【强制】所有的POJO类属性必须使用包装数据类型。

2)【强制】RPC方法的返回值和参数必须使用包装数据类型。

3)【推荐】所有的局部变量使用基本数据类型。

说明:POJO类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何NPE问题,或者入库检查,都由使用者来保证。

正例:数据库的查询结果可能是null,因为自动拆箱,用基本数据类型接收有NPE风险。

反例:比如显示成交总额涨跌情况,即正负x%,x为基本数据类型,调用的RPC服务,调用不成功时,返回的是默认值,页面显示为0%,这是不合理的,应该显示成中划线。所以包装数据类型的null值,能够表示额外的信息,如:远程调用失败,异常退出。

  1. 【强制】定义DO/DTO/VO等POJO类时,不要设定任何属性默认值。

反例:POJO类的createTime默认值为new Date(),但是这个属性在数据提取时并没有置入具体值,在更新其它字段时又附带更新了此字段,导致创建时间被修改成当前时间。

  1. 【强制】序列化类新增属性时,请不要修改serialVersionUID字段,避免反序列失败;如果完全不兼容升级,避免反序列化混乱,那么请修改serialVersionUID值。

说明:注意serialVersionUID不一致会抛出序列化运行时异常。

  1. 【强制】构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在init方法中。

  1. 【强制】POJO类必须写toString方法。使用IDE中的工具:source > generate toString时,如果继承了另一个POJO类,注意在前面加一下super.toString。

说明:在方法执行抛出异常时,可以直接调用POJO的toString()方法打印其属性值,便于排查问题。

  1. 【强制】禁止在POJO类中,同时存在对应属性xxx的isXxx()和getXxx()方法。

说明:框架在调用属性xxx的提取方法时,并不能确定哪个方法一定是被优先调用到。

  1. 【推荐】使用索引访问用String的split方法得到的数组时,需做最后一个分隔符后有无内容的检查,否则会有抛IndexOutOfBoundsException的风险。

说明:

String str = "a,b,c,,";

String[] ary = str.split(",");

//预期大于3,结果是3

System.out.println(ary.length);

  1. 【推荐】当一个类有多个构造方法,或者多个同名方法,这些方法应该按顺序放置在一起,便于阅读,此条规则优先于下一条。

  1. 【推荐】 类内方法定义顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter方法。

说明:公有方法是类的调用者和维护者最关心的方法,首屏展示最好;保护方法虽然只是子类关心,也可能是“模板设计模式”下的核心方法;而私有方法外部一般不需要特别关心,是一个黑盒实现;因为方法信息价值较低,所有Service和DAO的getter/setter方法放在类体最后。

  1. 【推荐】setter方法中,参数名称与类成员变量名称一致,this.成员名 = 参数名。在getter/setter方法中,不要增加业务逻辑,增加排查问题的难度。

反例:

public Integer getData() {

if (condition) {

return this.data + 100;

} else {

return this.data - 100;

}

}

  1. 【推荐】循环体内,字符串的联接方式,使用StringBuilder的append方法进行扩展。

反例:

String str = "start";

for(int i = 0; i < 100; i++){

str = str + "hello";

}

说明:反编译出的字节码文件显示每次循环都会new出一个StringBuilder对象,然后进行append操作,最后通过toString方法返回String对象,造成内存资源浪费。

  1. 【推荐】final可以声明类、成员变量、方法、以及本地变量,下列情况使用final关键字:

1)不允许被继承的类,如:String类。

2)不允许修改引用的域对象。

3)不允许被覆写的方法,如:POJO类的setter方法。

4)不允许运行过程中重新赋值的局部变量。

5)避免上下文重复使用一个变量,使用final可以强制重新定义一个变量,方便更好地进行重构。

  1. 【推荐】慎用Object的clone方法来拷贝对象。

说明:对象的clone方法默认是浅拷贝,若想实现深拷贝需要重写clone方法实现属性对象的拷贝。

  1. 【推荐】类成员与方法访问控制从严:

1)如果不允许外部直接通过new来创建对象,那么构造方法必须是private。

2)工具类不允许有public或default构造方法。

3)类非static成员变量并且与子类共享,必须是protected。

4)类非static成员变量并且仅在本类使用,必须是private。

5)类static成员变量如果仅在本类使用,必须是private。

6)若是static成员变量,必须考虑是否为final。

7)类成员方法只供类内部调用,必须是private。

8)类成员方法只对继承类公开,那么限制为protected。

说明:任何类、方法、参数、变量,严控访问范围。过于宽泛的访问范围,不利于模块解耦。思考:如果是一个private的方法,想删除就删除,可是一个public的service成员方法或成员变量,删除一下,不得手心冒点汗吗?变量像自己的小孩,尽量在自己的视线内,变量作用域太大,无限制的到处跑,那么你会担心的。

  1. 【强制】只有私有构造器的类应该声明为final。

  1. 【强制】工具类应隐藏public构造器。

  1. 【强制】确保异常类是不可变的,也就是说,它们仅具有final字段。

  1. 【强制】成员变量应定义为 private 的,并配置访问方法。

  1. 【推荐】根据Java编程语言的代码约定,类或接口声明的各部分应按以下顺序出现:

1)类(静态)变量。首先是公共类变量,然后是受保护的,然后是程序包级别(无访问修饰符),然后是私有。

2)实例变量。首先是公共类变量,然后是受保护的,然后是程序包级别(无访问修饰符),然后是私有。

3)构造方法

4)成员方法

5)内部类

  • 日期与时间
  1. 【强制】日期格式化时,传入pattern中表示年份统一使用小写的y。
  2. 【强制】在日期格式中分清楚大写的M和小写的m,大写的H和小写的h分别指代的意义。
  3. 【强制】获取当前毫秒数:System.currentTimeMillis();而不是new Date().getTime()
  4. 【强制】不允许在程序任何地方中使用1)java.sql.Date 2)java.sql.Time 3)java.sql.Timestamp。
  5. 【强制】不要在程序中写死一年为365天,避免在公历闰年时出现日期转换错误或程序逻辑错误。
  6. 【推荐】避免公历闰年2月问题。闰年的2月份有29天,一年后的那一天不可能是2月29 日
  7. 【推荐】使用枚举值来指代月份。如果使用数字,注意Date,Calendar等日期相关类的月份month取值在0-11 之间。

  • 集合处理
  1. 【强制】关于hashCode和equals的处理,遵循如下规则:

1) 只要覆写equals,就必须覆写hashCode。

2) 因为Set存储的是不重复的对象,依据hashCode和equals进行判断,所以Set存储的对象必须覆写这两个方法。

3) 如果自定义对象作为Map的键,那么必须覆写hashCode和equals。

说明:String已覆写hashCode和equals方法,所以我们可以愉快地使用String对象作为key来使用。

  1. 【强制】判断所有集合内部的元素是否为空,使用isEmpty()方法,而不是size()==0的方式

  1. 【强制】在使用java.util.stream.Collectors类的toMap()方法转为Map集合时,一定要使用含有参数类型为BinaryOperator,参数名为mergeFunction的方法,否则当出现相同key值时会抛出IllegalStateException异常

  1. 【强制】在使用java.util.stream.Collectors类的toMap()方法转为Map集合时,一定要注意当 value 为null时会抛NPE异常。

  1. 【强制】ArrayList的subList结果不可强转成ArrayList,否则会抛出ClassCastException异常:java.util.RandomAccessSubList cannot be cast to java.util.ArrayList;

说明:subList返回的是ArrayList的内部类SubList,并不是 ArrayList,而是ArrayList的一个视图,对于SubList子列表的所有操作最终会反映到原列表上。

  1. 【强制】使用Map的方法keySet()/values()/entrySet()返回集合对象时,不可以对其进行添加元素操作,否则会抛出UnsupportedOperationException异常

  1. 【强制】Collections类返回的对象,如:emptyList()/singletonList()等都是immutable list,不可对其进行添加或者删除元素的操作

  反例:如果查询无结果,返回Collections.emptyList()空集合对象,调用方一旦进行了添加元素的操作,就会触发UnsupportedOperationException异常。

  1. 【强制】 在subList场景中,高度注意对原集合元素个数的修改,会导致子列表的遍历、增加、删除均产生ConcurrentModificationException 异常。

  1. 【强制】使用集合转数组的方法,必须使用集合的toArray(T[] array),传入的是类型完全一致、长度为0的空数组。

  1. 【强制】在使用Collection接口任何实现类的addAll()方法时,都要对输入的集合参数进行NPE判断。

说明:在ArrayList#addAll方法的第一行代码即Object[] a = c.toArray(); 其中c为输入集合参数,如果为null,则直接抛出异常。

  1. 【强制】使用工具类Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的add/remove/clear方法会抛出UnsupportedOperationException异常。

  1. 【强制】泛型通配符<? extends T>来接收返回的数据,此写法的泛型集合不能使用add方法,而<? super T>不能使用get方法,作为接口调用赋值时易出错。

  1. 【强制】在无泛型限制定义的集合赋值给泛型限制的集合时,在使用集合元素时,需要进行instanceof判断,避免抛出ClassCastException异常。

  1. 【强制】不要在foreach循环里进行元素的remove/add操作。remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁。

  1. 【强制】在JDK7版本及以上,Comparator实现类要满足如下三个条件,不然Arrays.sort,Collections.sort会抛IllegalArgumentException异常。

  1. 【推荐】集合泛型定义时,在JDK7及以上,使用diamond语法或全省略。

  1. 推荐】集合初始化时,指定集合初始值大小。

  1. 【推荐】使用entrySet遍历Map类集合KV,而不是keySet方式进行遍历。

  1. 【推荐】高度注意Map类集合K/V能不能存储null值的情况,如下表格:

  1. 【参考】合理利用好集合的有序性(sort)和稳定性(order),避免集合的无序性(unsort)和不稳定性(unorder)带来的负面影响

  1. 【参考】利用Set元素唯一的特性,可以快速对一个集合进行去重操作,避免使用List的contains方法进行遍历、对比、去重操作。

  • 控制语句
  1. 【强制】在一个switch块内,每个case要么通过break/return等来终止,要么注释说明程序将继续执行到哪一个case为止;在一个switch块内,都必须包含一个default语句并且放在最后,即使它什么代码也没有。

说明:注意break是退出switch语句块,而return是退出方法体。

  1. 【强制】当switch括号内的变量类型为String并且此变量为外部参数时,必须先进行null判断。

  1. 【强制】在if/else/for/while/do语句中必须使用大括号。

  1. 【强制】三目运算符“condition?表达式1:表达式2中”,高度注意表达式1和2在类型对齐时,可能抛出因自动拆箱导致的NPE异常

  1. 【推荐】当某个方法的代码行数超过10行时,return/throw 等中断逻辑的右大括号后加一个空行。

  1. 【推荐】推荐尽量少用else, if-else的方式可以改写成:

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("可以先交往一段时间看看");

}

  1. 【推荐】除常用方法(如getXxx/isXxx)等外,不要在条件判断中执行其它复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性。

  1. 【推荐】不要在其它表达式(尤其是条件表达式)中,插入赋值语句。

说明:赋值点类似于人体的穴位,对于代码的理解至关重要,所以赋值语句需要清晰地单独成为一行。

反例:

public Lock getLock(boolean fair) {

// 算术表达式中出现赋值操作,容易忽略count值已经被改变

threshold = (count = Integer.MAX_VALUE) - 1;

// 条件表达式中出现赋值操作,容易误认为是sync==fair

return (sync = fair) ? new FairSync() : new NonfairSync();

}

  1. 【推荐】循环体中的语句要考量性能,以下操作尽量移至循环体外处理,如定义对象、变量、获取数据库连接,进行不必要的try-catch操作(这个try-catch是否可以移至循环体外)

  1. 【推荐】避免采用取反逻辑运算符。

说明:取反逻辑不利于快速理解,并且取反逻辑写法必然存在对应的正向逻辑写法。

正例:使用if (x < 628) 来表达 x 小于628。

反例:使用if (!(x >= 628)) 来表达 x 小于628。

  1. 【推荐】接口入参保护,这种场景常见的是用作批量操作的接口

  • 注释约束
  1. 【强制】类、类属性、类方法的注释必须使用Javadoc规范,使用/*内容/格式,不得使用//xxx方式。

说明:在IDE编辑窗口中,Javadoc方式会提示相关注释,生成Javadoc可以正确输出相应注释;在IDE中,工程调用方法时,不进入方法即可悬浮提示方法、参数、返回值的意义,提高阅读效率。

  1. 【强制】所有的抽象方法(包括接口中的方法)必须要用Javadoc注释、除了返回值、参数、异常说明外,还必须指出该方法做什么事情,实现什么功能。

  1. 【强制】所有的类都必须添加创建者信息。

  1. 【强制】方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释使用/* */注释,注意与代码对齐。

  1. 【强制】所有的枚举类型字段必须要有注释,说明每个数据项的用途。

  1. 【推荐】与其“半吊子”英文来注释,不如用中文注释把问题说清楚。专有名词与关键字保持英文原文即可。

  1. 【推荐】代码修改的同时,注释也要进行相应的修改,尤其是参数、返回值、异常、核心逻辑等的修改。

  1. 【推荐】在类中删除未使用的任何字段和方法;在方法中删除未使用的任何参数声明与内部变量。

  1. 【参考】谨慎注释掉代码。在上方详细说明,而不是简单地注释掉。如果无用,则删除。

  1. 【参考】好的命名、代码结构是自解释的,注释力求精简准确、表达到位。避免出现注释的一个极端:过多过滥的注释,代码的逻辑一旦修改,修改注释是相当大的负担。

  1. 【强制】java源文件必须添加统一的文件头信息。

  1. 【强制】类、接口、枚举的注释标签出现顺序:@param, @author, @since, @see, @version, @serial, @deprecated。方法、构造器、变量的注释标签出现顺序:@param, @return, @throws, @since, @deprecated, @see。

  1. 【强制】public修改的变量必须添加注释。
  • 异常日志
  • 错误码
  1. 【强制】错误码的定制原则:快速回溯、简单易记、沟通标准化。

  说明:错误码的回答问题是错误对象和错误位置,错误码必须能够快速的知晓错误的来源,可以快速判断是谁的问题;错误码要易于记忆与对比;错误码能够脱离文档和系统平台达到线下轻量化底自由沟通的目的。

  1. 【强制】错误码不体现版本号和错误等级信息

  1. 【强制】全部都正常,但不得不填充错误码时统一返回特定错误码,例如:00000或0。

  1. 【强制】错误码使用者避免随意定义新的错误码。

说明:尽可能在原有的错误码中找到语义相近的错误码在代码中使用。

  1. 【强制】错误码不能直接输出给用户作为提示信息使用。

  1. 【推荐】错误码之外的业务独特信息由error_message来承载,而不是让错误码本身涵盖过多具体业务属性。

  • 异常处理
  1. 【强制】Java 类库中定义的可以通过预检查方式规避的RuntimeException异常不应该通过catch 的方式来处理,比如:NullPointerException,IndexOutOfBoundsException等等。

  1. 【强制】异常不要用来做流程控制,条件控制。

  1. 【强制】catch时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的catch尽可能进行区分异常类型,再做对应的异常处理。

  1. 【强制】捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,当然,不影响继续运行的异常除外。如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。

  1. 【强制】有try块放到了事务代码中,catch异常后,如果需要回滚事务,一定要注意手动回滚事务。

  1. 【强制】finally块必须对资源对象、流对象进行关闭,有异常也要做try-catch。

  1. 【强制】不要在finally块中使用return。

  1. 【强制】捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类。

  1. 【强制】在调用RPC、二方包、或动态生成类的相关方法时,捕捉异常必须使用Throwable类来进行拦截

  1. 【推荐】方法的返回值可以为null,不强制返回空集合,或者空对象等,必须添加注释充分说明什么情况下会返回null值

  1. 【推荐】防止NPE,是程序员的基本修养,注意NPE产生的场景

  1. 【推荐】定义时区分unchecked/checked 异常,避免直接抛出new RuntimeException(),更不允许抛出Exception或者Throwable,应使用有业务含义的自定义异常。推荐业界已定义过的自定义异常,如:DAOException/ServiceException等。

  • 日志约束
  1. 【强制】应用中不可直接使用日志系统(Log4j、Logback)中的API,而应依赖使用日志框架SLF4J中的API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一

  1. 【强制】所有日志文件至少保存15天,因为有些异常具备以“周”为频次发生的特点。对于当天日志,以“应用名.log”来保存,保存在/home/admin/应用名/logs/目录下, 过往日志格式为:{logname}.log.{保存日期},日期格式:yyyy-MM-dd

  1. 【强制】应用中的扩展日志(如打点、临时监控、访问日志等)命名方式:appName_logType_logName.log。logType:日志类型,如stats/monitor/access等;logName:日志描述。这种命名的好处:通过文件名就可知道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找

  1. 【强制】在日志输出时,字符串变量之间的拼接使用占位符的方式。

  1. 【强制】对于trace/debug/info级别的日志输出,必须进行日志级别的开关判断。
  2. 【强制】避免重复打印日志,浪费磁盘空间,务必在logger中设置additivity=false

  1. 【强制】生产环境禁止直接使用System.out或System.err输出日志或使用e.printStackTrace()打印异常堆栈。

  1. 【强制】异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过关键字throws往上抛出

  1. 【强制】日志打印时禁止直接用JSON工具将对象转换成String

  1. 【推荐】尽量用英文来描述日志错误信息,如果日志中的错误信息用英文描述不清楚的话使用中文描述即可,否则容易产生歧义
  • 单元测试
  1. 【强制】好的单元测试必须遵守AIR原则。 说明:单元测试在线上运行时,感觉像空气(AIR)一样并不存在,但在测试质量的保障上,却是非常关键的。好的单元测试宏观上来说,具有自动化、独立性、可重复执行的特点

  1. 【强制】单元测试应该是全自动执行的,并且非交互式的。测试用例通常是被定期执行的,执行过程必须完全自动化才有意义。输出结果需要人工检查的测试不是一个好的单元测试。单元测试中不准使用System.out来进行人肉验证,必须使用assert来验证

  1. 【强制】保持单元测试的独立性。为了保证单元测试稳定可靠且便于维护,单元测试用例之间决不能互相调用,也不能依赖执行的先后次序。

  1. 【强制】单元测试是可以重复执行的,不能受到外界环境的影响。

  1. 【强制】对于单元测试,要保证测试粒度足够小,有助于精确定位问题。单测粒度至多是类级别,一般是方法级别

  1. 【强制】核心业务、核心应用、核心模块的增量代码确保单元测试通过。

  1. 【强制】单元测试代码必须写在如下工程目录:src/test/java,不允许写在业务代码目录下。
  • 安全约束

1、【强制】隶属于用户个人的页面或者功能必须进行权限控制校验。

说明:防止没有做水平权限校验就可随意访问、修改、删除别人的数据,比如查看他人的私信内容、修改他人的订单。

2、【强制】用户敏感数据禁止直接展示,必须对展示数据进行脱敏。

说明:中国大陆个人手机号码显示为:137****0969,隐藏中间4位,防止隐私泄露。

3、【强制】用户输入的SQL参数严格使用参数绑定或者METADATA字段值限定,防止SQL注入,禁止字符串拼接SQL访问数据库。

说明:mybatis 中无非必要,均使用#符号,如需使用$符号,需严格限制并过滤用户输入参数,避免sql注入产生。

正例:

select user_name from user_info where user_code = #{code}

反例:在没有进严格输入限制的情况下,使用$绑定参数,如:

select user_name from user_info where user_code = ${code}。

4、【强制】用户请求传入的任何参数必须做有效性验证。

说明:忽略参数校验可能导致:

page size过大导致内存溢出

  恶意order by导致数据库慢查询

  任意重定向

  SQL注入

反序列化注入

  正则输入源串拒绝服务ReDoS

说明:Java代码用正则来验证客户端的输入,有些正则写法验证普通用户输入没有问题,但是如果攻击人员使用的是特殊构造的字符串来验证,有可能导致死循环的结果。

  1. 【强制】禁止向HTML页面输出未经安全过滤或未正确转义的用户数据。

6、【强制】表单、AJAX提交必须执行CSRF安全验证。

说明:CSRF(Cross-site request forgery)跨站请求伪造是一类常见编程漏洞。对于存在CSRF漏洞的应用/网站,攻击者可以事先构造好URL,只要受害者用户一访问,后台便在用户不知情的情况下对数据库中用户参数进行相应修改。

7、【强制】URL外部重定向传入的目标地址必须执行白名单过滤。

说明:可以参考以下策略:

当用户访问需要生成跳转URL的页面时,首先生成随机token,并放入cookie。

在显示连接的页面上生成URL,在URL参数中加入token。

应用程序在跳转前,判断token是否和cookie中的token一致,如果不一致,就判定为URL跳转攻击,并记录日志。

如果在中做页面跳转,需要判断域名白名单后,才能跳转。 

8、【强制】在使用平台资源,譬如短信、邮件、电话、下单、支付,必须实现正确的防重放的机制,如数量限制、疲劳度控制、验证码校验,避免被滥刷而导致资损。

说明:如注册时发送验证码到手机,如果没有限制次数和频率,那么可以利用此功能骚扰到其它用户,并造成短信平台资源浪费。

9、【强制】对敏感信息进行加密传输。

正例:用户登录请求密码输入明文abc123EFG,请求传输过程中密码为密文fkGXT5DGmq8mm9ap7sLA5A==

10、【强制】禁止在数据库或文件系统中明文存储用户密码。

正例:使用摘要+盐,对密码进行加密,例如:

pwd:  396348c2ee1181633969373deae657ee15adf9b64c0c1c7b89458819cf10f0a6

反例:pwd:  123456abc

11、【强制】禁止使用GET方法传递敏感参数(会话标识、身份证号等)。

反例:curl -i http://127.0.0.1:8080/user/info?id_card=42xxxxxxxxxxx1922

1

12、【强制】对未能成功通过验证的请求,应当返回模糊的提示信息如“用户名或密码错误”。

13、【推荐】禁止长时间持续的登录状态,建议设置会话超时时间,超时后强制销毁会话。

14、【强制】文件操作应遵循以下规则:

上传操作应设计身份验证机制

必须在服务端使用白名单方式限制可上传文件类型,如果需求单一,可以不考虑上传文件的格式,直接在服务端根据时间生成(如jpg)文件名和目录名

限制允许上传的文件大小

引用上传的文件时,使用文件id(hash随机生成)进行调用,不要显示文件地址

禁止把上传的文件保存在Web环境中。如果有特殊需求,必须关闭在文件上传目录的执行限

权限检查:文件下载和引用时,应该检查该用户是否具有此文件的相关权限。

禁止使用直接传递文件路径的引用方式,如有特殊需要可设置路径白名单引用

  • 工程架构
  • 应用分层
  1. 【推荐】应用分层如下配置

    开放接口层:可直接封装Service方法暴露成RPC接口;通过Web封装成http接口;进行网关安全控制、流量控制等。

    终端显示层:各个端的模板渲染并执行显示的层。当前主要是velocity渲染,JS渲染,JSP渲染,移动端展示等。

Web层:主要是对访问控制进行转发,各类基本参数校验,或者不复用的业务简单处理等。

Service层:相对具体的业务逻辑服务层。

Manager层:通用业务处理层,它有如下特征:

1)对第三方平台封装的层,预处理返回结果及转化异常信息。

2)对Service层通用能力的下沉,如缓存方案、中间件通用处理。

3)与DAO层交互,对多个DAO的组合复用。

DAO层:数据访问层,与底层MySQL、Oracle、Hbase等进行数据交互。

外部接口或第三方平台:包括其它部门RPC开放接口,基础平台,其它公司的HTTP接口。

  1. 【参考】分层领域模型规约:

DO(Data Object):此对象与数据库表结构一一对应,通过DAO层向上传输数据源对象。

DTO(Data Transfer Object):数据传输对象,Service或Manager向外传输的对象。

BO(Business Object):业务对象,由Service层输出的封装业务逻辑的对象。

AO(Application Object):应用对象,在Web层与Service层之间抽象的复用对象模型,极为贴近展示层,复用度不高。

VO(View Object):显示层对象,通常是Web向模板渲染引擎层传输的对象。

Query:数据查询对象,各层接收上层的查询请求。注意超过2个参数的查询封装,禁止使用Map类来传输。

  • 服务器
  1. 【推荐】高并发服务器建议调小TCP协议的time_wait超时时间。

说明:操作系统默认240秒后,才会关闭处于time_wait状态的连接,在高并发访问下,服务器端会因为处于time_wait的连接数太多,可能无法建立新的连接,所以需要在服务器上调小此等待值。

正例:在linux服务器上请通过变更/etc/sysctl.conf文件去修改该缺省值(秒): net.ipv4.tcp_fin_timeout = 30

  1. 【推荐】调大服务器所支持的最大文件句柄数(File Descriptor,简写为fd)。

说明:主流操作系统的设计是将TCP/UDP连接采用与文件一样的方式去管理,即一个连接对应于一个fd。主流的linux服务器默认所支持最大fd数量为1024,当并发连接数很大时很容易因为fd不足而出现“open too many files”错误,导致新的连接无法建立。建议将linux服务器所支持的最大句柄数调高数倍(与服务器的内存数量相关)。

  1. 【推荐】给JVM环境参数设置-XX:+HeapDumpOnOutOfMemoryError参数,让JVM碰到OOM场景时输出dump信息。

说明:OOM的发生是有概率的,甚至相隔数月才出现一例,出错时的堆内信息对解决问题非常有帮助。

  1. 【推荐】在线上生产环境,JVM的Xms和Xmx设置一样大小的内存容量,避免在GC 后调整堆大小带来的压力。

5、【参考】服务器内部重定向使用forward;外部重定向地址使用URL拼装工具类来生成,否则会带来URL维护不一致的问题和潜在的安全风险。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值