Alibaba Java Coding Guidelines
目录
前言
我们很高兴向大家介绍阿里巴巴 Java 编码指南,它整合了阿里巴巴集团技术团队的最佳编程实践。大量的 Java 编程团队对跨项目的代码质量提出了苛刻的要求,因为我们鼓励重用和更好地理解彼此的程序。过去我们见过很多编程问题。例如,有缺陷的数据库表结构和索引设计可能会导致软件架构缺陷和性能风险。作为另一个例子,混乱的代码结构使其难以维护。此外,未经身份验证的易受攻击的代码很容易受到黑客的攻击。为了解决这些问题,我们为阿里巴巴的 Java 开发者开发了这个文档。
本文档由五个部分组成:编程规范、异常和日志、MySQL 规范、项目规范和安全规范。根据关注的严重程度,每个规范分为三个级别:强制、推荐和参考。进一步澄清表述为:
1、“说明”,对内容进行说明;
2、“正例”,描述推荐的编码和实现方法;
3、“反例”,其中描述了预防措施和实际错误情况。
本文档的主要目的是帮助开发人员提高代码质量。因此,开发人员可以最大限度地减少潜在和重复的代码错误。此外,规范对于实现有效协作的现代软件架构至关重要。打个比方,交通法规的目的是保护公共安全,而不是剥夺驾驶的权利。很容易想象没有限速和红绿灯的交通混乱。开发适当的软件规范和标准的目的不是破坏程序的创造力和优雅,而是通过限制过度个性化来提高协作效率。
我们将继续收集社区的反馈,以改进阿里巴巴 Java 编码指南。
一、编程规范
命名约定
1. [强制]名称不应以下划线或美元符号开头或结尾。
反例:_name / __name / $Object / name_ / name$ / Object$
2 、 【强制】严禁在命名中使用汉语、拼音、拼英混拼。准确的英语拼写和语法将使代码具有可读性、可理解性和可维护性。
正面例子:阿里巴巴/淘宝/优酷/杭州。在这些情况下,拼音中的中文专有名称是可以接受的。
3 、 【强制】类名除域模型外,应为大写形式的名词:DO、BO、DTO、VO等。
正面例子:MarcoPolo / UserDO / HtmlDTO / XmlService / TcpUdpDeal / TaPromotion
反例:
marcoPolo / UserDo / HTMLDto / XMLService / TCPUDPDeal / TAPromotion
4 、 【强制】方法名、参数名、成员变量名、局部变量名应采用小写驼峰式。
正面例子:localValue / getHttpMessage() / inputUserId
5. 【强制】常量变量名用大写字母,下划线隔开。这些名称在语义上应该是完整和清晰的。
正面例子:MAX_STOCK_COUNT 个
反例:
MAX_COUNT 个
6. 【强制】抽象类名必须以Abstract或Base开头。异常类名必须以Exception结尾。测试用例名称应以要测试的类名开头,以Test结尾。
7. 【强制】括号是Array类型的一部分。定义可以是:String[] args;
反例:字符串参数[];
8. 【强制】在定义布尔变量时不要添加'is'作为前缀,因为这可能会导致某些Java框架中的序列化异常。
反例:布尔值成功;方法名将是
isSuccess()
,然后 RPC 框架将变量名推断为“成功”,导致序列化错误,因为它找不到正确的属性。
9. 【强制】包名必须小写。每个点后面应该只有一个英文单词。包名总是单数格式,而类名可以是复数格式(如有必要)。
正面例子:
com.alibaba.open.util
可以用作 utils 的包名;MessageUtils
可以用作类名。
10 、 【强制】为了便于阅读,应避免使用不常见的缩写。
反例:抽象类(AbstractClass);condi (条件)
11. 【推荐】 如果使用任何设计模式,建议将模式名称包含在类名中。
正面例子:
public class OrderFactory;
public class LoginProxy;
public class ResourceObserver;
笔记:
包含相应的模式名称有助于读者快速理解设计模式中的思想。
12 、 【推荐】public
为了代码简洁,接口类中的方法不要添加任何修饰符,包括, 。请为方法添加有效的Javadoc注释。除了应用程序的常用常量外,不要在接口中定义任何变量。
正面例子:接口中的方法定义:
void f();
常量定义:String COMPANY = "alibaba";
笔记:
在 JDK8 中,允许为接口方法定义一个默认实现,这对所有实现的类都很有价值。
13、接口和对应的实现类命名主要有两条规则:
1)【强制】所有Service和DAO类都必须是基于SOA原则的接口。实现类名称应以Impl结尾。
正面例子:
CacheServiceImpl
实施CacheService
。
2)【推荐】如果接口名称是为了表示接口的能力,那么它的名称应该是一个形容词。
正面例子:
AbstractTranslator
实施Translatable
。
14. [参考] Enumeration 类名应以Enum结尾。其成员应以大写字母拼写,并用下划线分隔。
笔记:枚举确实是一个特殊的常量类,所有的构造方法默认都是私有的。
正面例子:
枚举名称:
DealStatusEnum
; 会员名:SUCCESS / UNKOWN_REASON
。
15. 【参考】不同包层 的命名约定:
A)Service/DAO层方法的命名约定
1)get
作为获取单个对象的方法的名称前缀。
2)list
用作获取多个对象的方法的名称前缀。
3)count
用作统计方法的名称前缀。
4) 使用insert
或save
(推荐)作为保存数据的方法的名称前缀。
5) 使用delete
或remove
(推荐)作为删除数据的方法的名称前缀。
6)update
用作更新数据的方法的名称前缀。
B) 域模型的命名约定
1)数据对象:DO,其中是表名。
2) 数据传输对象:DTO,其中是与域相关的名称。
3)值对象:VO,其中在大多数情况下是网站名称。
4) POJO一般指向DO/DTO/BO/VO,但不能用于命名为*POJO。
常量约定
1 、 【强制】除预定义外,禁止在编码中使用魔法值。
反例:String key = "Id#taobao_" + tradeId;
2. 【强制】 long 或Long 变量应使用'L' 而不是'l',因为'l' 很容易被误认为是数字1。
反例:
Long a = 2l
,很难分清是21号还是龙2号。
3 、 【推荐】常量应根据其功能分别放在不同的常量类中。例如,缓存相关的常量可以放入,CacheConsts
而配置相关的常量可以保存在ConfigConsts
.
笔记:在一个大的完整常量类中很难找到一个常量。
4. 【推荐】常量可以在以下5个不同的层共享:在多个应用程序中共享;在应用程序内部共享;在子项目中共享;在一个包中共享;在课堂上分享。
1)在多个应用程序中共享:保存在一个库中,constant
在client.jar目录下;
2)在应用程序中共享:保存在应用程序内的共享模块中,在constant
目录下;
反例:显而易见的变量名也应该定义为应用程序中的公共共享常量。以下定义在生产环境中导致异常:它返回false,但预期返回true。A.YES.equals(B.YES)
A类public static final String YES = "yes";
定义: B类定义:public static final String YES = "y";
3)子项目共享:放在constant
当前项目的目录下;
4) 包内共享:放在constant
当前包目录下;
5)在类中共享:在类中定义为“私有静态最终”。
5. [推荐]如果值在固定范围内或变量具有属性,则使用枚举类。下面的例子显示了额外的信息(它是哪一天)可以包含在枚举中:
正面例子:公共枚举{周一(1)、周二(2)、周三(3)、周四(4)、周五(5)、周六(6)、周日(7);}
格式样式
1. 【强制】大括号规则。如果没有内容,只需在同一行中使用{} 。否则:
1) 左大括号前没有换行符。
2) 左大括号后的换行符。
3) 右大括号前的换行符。
4) 右大括号后的换行,仅当大括号终止语句或终止方法体、构造函数或命名类时。如果后跟或逗号,则右大括号后没有换行符 。else
2 、 【强制】 '('字符与其后字符之间不使用空格。')'字符及其前字符相同。请参阅第 5 条规则中的正例。
3 、 【必选】 if/for/while/switch等关键字与括号之间必须有一个空格。
4 、 【强制】运算符左右两边各有一个空格,如'='、'&&'、'+'、'-'、三元运算符等。
5. 【强制】每打开一个新的块或类似块的构造,缩进增加四个空格。当块结束时,缩进返回到前一个缩进级别。制表符不用于缩进。
笔记:
要防止制表符用于缩进,您必须配置 IDE。例如,IDEA 中的“使用制表符”应取消选中,Eclipse 中应选中“为制表符插入空格”。
正面例子:
public static void main(String[] args) { // four spaces indent String say = "hello"; // one space before and after the operator int flag = 0; // one space between 'if' and '('; // no space between '(' and 'flag' or between '0' and ')' if (flag == 0) { System.out.println(say); } // one space before '{' and line break after '{' if (flag == 1) { System.out.println("world"); // line break before '}' but not after '}' if it is followed by 'else' } else { System.out.println("ok"); // line break after '}' if it is the end of the block } }
6 、 【强制】 Java代码的列限制为120个字符。除 import 语句外,任何超出此限制的行都必须按如下方式换行:
1) 第二行应相对于第一行有 4 个空格。第三行及其后的行应与第二行对齐。
2) 运算符应与以下上下文一起移至下一行。
3) 字符'.' 应该连同它后面的方法一起移到下一行。
4) 如果有多个参数超出最大长度,则应在逗号后插入换行符。
5) 括号前不应出现换行符。
正面例子:
StringBuffer sb = new StringBuffer(); // line break if there are more than 120 characters, and 4 spaces indent at // the second line. Make sure character '.' moved to the next line // together. The third and fourth lines are aligned with the second one. sb.append("zi").append("xin"). .append("huang")... .append("huang")... .append("huang");
反例:
StringBuffer sb = new StringBuffer(); // no line break before '(' sb.append("zi").append("xin")...append ("huang"); // no line break before ',' if there are multiple params invoke(args1, args2, args3, ... , argsX);
7 、 【强制】多参数方法的逗号和下一个参数之间必须有一个空格。
正面例子:
以下方法定义中的' , '字符后使用一个空格。
f("a", "b", "c");
8 、 【强制】文本文件的字符集编码为UTF-8,换行符为Unix格式,不能是Windows格式。
9. 【推荐】变量不用多个空格对齐。
正面例子:
int a = 3; long b = 4L; float c = 5F; StringBuffer sb = new StringBuffer();
笔记:
插入几个空格来对齐上面的变量很麻烦。
10. 【推荐】使用单个空行分隔具有相同逻辑或语义的部分。
笔记:没有必要使用多个空行来做到这一点。
面向对象规则
1. 【强制】静态字段或方法直接引用其类名,而不是其对应的对象名。
2. 【强制】接口或抽象类的重写方法必须标注@Override
注解。
反例:对于
getObject()
andget0bject()
,第一个有一个字母“O”,第二个有一个数字“0”。要准确判断覆盖是否成功,需要@Override
注解。同时,一旦抽象类中的方法签名发生变化,实现类会立即报告编译时错误。
3. [强制] varargs仅在所有参数的类型和语义相同时才推荐使用。Object
应避免使用带类型的参数。
笔记:
具有可变参数功能的参数必须位于参数列表的末尾。(不推荐使用可变参数功能进行编程。)
正面例子:
public User getUsers(String type, Integer... ids);
4 、 【强制】禁止修改方法签名,以免影响调用者。@Deprecated
当一个接口被弃用时,一个带有新服务的明确描述的注释是必要的。
5. 【强制】禁止使用已弃用的类或方法。
笔记:例如,
decode(String source, String encode)
应该使用不推荐使用的方法来代替decode(String encodeStr)
。一旦一个接口被弃用,接口提供者就有义务提供一个新的接口。同时,客户端程序员有义务使用新的接口。
6. 【强制】由于调用equals方法NullPointerException
时可能会抛出,所以equals应该由一个常量或者一个绝对不为null的对象调用。Object
正面例子:
"test".equals(object);
反例:
object.equals("test");
笔记:
java.util.Objects#equals
(JDK7 中的实用程序类)推荐使用。
7. [强制]使用equals
方法,而不是引用相等==
来比较原始包装类。
笔记:考虑这个任务:
Integer var = ?
. 当它适合从-128 到 127的范围时,我们可以==
直接用于比较。因为Integer
对象将由 生成IntegerCache.cache
,它重用现有对象。然而,当它适合前一个范围的互补集时,该Integer
对象将被分配到堆中,不会重用现有对象。这是一个陷阱。因此equals
推荐该方法。
8. 【强制】判断浮点数等价,==
不能用于基本类型,equals
不能用于包装类。
笔记:浮点数由尾数和指数组成,类似于科学计数法的系数和指数。大多数小数部分不能用二进制精确表示。
反例:
float a = 1.0f - 0.9f;
float b = 0.9f - 0.8f;
if (a == b) {
// seems like this block will be executed
// but the result of a == b is false
}
Float x = Float.valueOf(a);
Float y = Float.valueOf(b);
if (x.equals(y)) {
// seems like this block will be executed
// but the result of x.equals(y) is false
}
正面例子:
(1) 指定误差范围。如果两个浮点数之差在此范围内,则认为它们相等。
float a = 1.0f - 0.9f;
float b = 0.9f - 0.8f;
float diff = 1e-6f;
if (Math.abs(a - b) < diff) {
System.out.println("true");
}
(2)BigDecimal
用于操作。
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
BigDecimal c = new BigDecimal("0.8");
BigDecimal x = a.subtract(b);
BigDecimal y = b.subtract(c);
if (x.equals(y)) {
System.out.println("true");
}
9. 【强制】使用原始数据类型和包装类的规则:
1) POJO 类的成员必须是包装类。
2) RPC 方法的返回值和参数必须是包装类。
3) 【推荐】局部变量应该是原始数据类型。
注意:为了提醒消费者显式赋值,POJO 类中的成员没有初始值。作为消费者,您应该自己检查诸如NullPointerException
入库等问题。
正例:由于数据库查询的结果可能为null,将其分配给原始日期类型将导致NullPointerException
自动装箱的风险。
反例:考虑交易量幅度的输出,例如±x%。作为原始数据,当调用 RPC 服务失败时,会分配默认返回值: 0% ,这是不正确的。应该分配一个连字符-代替。因此,包装类的null值可以表示附加信息,例如调用 RPC 服务失败、异常退出等。
10 、 【强制】在定义DO、DTO、VO等POJO类时,不要给成员赋值任何默认值。
11 、 【强制】为避免反序列化失败,当需要更新序列化类时,如添加一些新成员,不要更改serialVersionUID 。如果需要完全不兼容的更新,请更改serialVersionUID的值,以防反序列化时出现混淆。
笔记:serialVersionUID的不一致可能会导致
InvalidClassException
在运行时出现。
12. 【强制】禁止构造方法中的业务逻辑。所有初始化都应该在init
方法中实现。
13 、 【强制】方法toString
必须在POJO类中实现。super.toString
如果当前类扩展了另一个 POJO 类,则应在实现开始时调用该方法。
笔记:我们可以
toString
直接调用 POJO 中的方法来打印属性值,以便在方法在运行时抛出异常时检查问题。
14 、 【推荐】使用索引访问String中split方法生成的数组时,一定要检查最后一个分隔符是否为null,避免IndexOutOfBoundException
。
笔记:
String str = "a,b,c,,"; String[] ary = str.split(","); // The expected result exceeds 3. However it turns out to be 3. System.out.println(ary.length);
15. 【推荐】一个类中的多个构造方法或同名方法应该放在一起,以提高可读性。
16. 【推荐】类中声明方法的顺序为:
公共或受保护方法->私有方法->getter/setter方法。
笔记:作为消费者和提供者最关心的,公共方法应该放在第一屏。受保护的方法仅由子类关心,但在模板设计模式中它们有可能变得至关重要。私有方法,即黑盒方法,基本上对客户来说并不重要。Service 或 DAO 的getter/setter方法由于重要性较低,应放在类实现的末尾。
17. 【推荐】setter方法,参数名与字段名一致。不建议在getter/setter方法中实现业务逻辑,会增加排查难度。
反例:
public Integer getData() { if (true) { return data + 100; } else { return data - 100; } }
18. [推荐]连接多个字符串时,使用循环体内部的append
方法。StringBuilder
反例:
String str = "start"; for (int i = 0; i < 100; i++) { str = str + "hello"; }
笔记:
根据反编译的字节码文件,对于每个循环,它分配一个
StringBuilder
对象,附加一个字符串,最后通过方法返回一个String
对象。toString
这是对内存的极大浪费。
19. 【推荐】关键字final应该用在以下情况:
1)一个不允许被继承的类,或者一个不允许重新赋值的局部变量。
2) 不允许修改的参数。
3) 不允许被覆盖的方法。
20. 【推荐】谨慎使用中的clone
方法复制对象Object
。
笔记:
clone
in的默认实现Object
是浅(非深)副本,它将字段作为指针复制到内存中的相同对象。
21. [推荐]严格限制类中成员的访问级别:
1)如果禁止使用类外的关键字进行分配,则 必须使用构造方法。
2)构造方法不允许在实用程序类中或在实用程序类中。
3)从继承者访问的非静态类变量必须是.
4)除了包含它们的类之外,任何人都无法访问的非静态类变量必须是.
5)除了包含它们的类之外,任何人都无法访问的静态变量必须是.
6)在确定它们是否为 时应考虑静态变量。
private
`new`
public
`default`
protected
private
private
final
7)除了包含它们的类之外,任何人都无法访问的类方法必须是private
.
8)从继承者访问的类方法必须是protected
.
笔记:我们应该严格控制对任何类、方法、参数和变量的访问。松散的访问控制会导致模块的有害耦合。想象以下情况。对于
private
班级成员,我们可以随时将其删除。但是,当涉及到public
班级成员时,我们必须在任何更新发生之前三思而后行。
收藏
1 、 【强制】hashCode和equals的用法如下:
1)如果equals被覆盖,则覆盖hashCode。 2) 必须为 a 的元素覆盖这两个方法,因为它们用于确保不会在 中插入重复的对象。 3) 对于用作 的键的任何对象,必须重写这两个方法。 Set
`Set` Map
笔记:
String
`Map因为
String`定义了这两个方法,所以可以作为key 。
2. 【强制】不要给keySet()
//返回的集合对象添加元素values()
,entrySet()
否则UnsupportedOperationException
会被抛出。
3. [强制]不要向/从 中的方法返回的不可变对象添加或删除Collections
,例如emptyList()
/ singletonList()
。
反例:将元素添加到
Collections.emptyList()
将抛出UnsupportedOperationException
.
4. 【强制】类中不要强制转换subListArrayList
,否则 ClassCastException
会抛出:java.util.RandomAccessSubList
不能强制转换为java.util.ArrayList
。
笔记:
subList
ofArrayList
是一个内部类,它是一个视图ArrayList
。对 的所有操作Sublist
都会影响原始列表。
5. 【强制】使用subList时,修改原始列表的大小时要小心。在对subListConcurrentModificationException
执行遍历、添加或删除时可能会导致。
6. 【必选】用于toArray(T[] array)
将列表转换为数组。输入数组类型应与大小为 的列表相同list.size()
。
反例:
不要使用
toArray
没有参数的方法。由于返回类型是Object[]
,ClassCastException
将其转换为不同的数组类型时将被抛出。正面例子:
List<String> list = new ArrayList<String>(2); list.add("guan"); list.add("bao"); String[] array = new String[list.size()]; array = list.toArray(array);
笔记:
使用
toArray
带参数的方法时,传递与列表大小相同的输入。如果输入数组大小不够大,该方法将在内部重新分配大小,然后返回新数组的地址。如果大小大于需要,额外的元素(index[list.size()]
和更高版本)将被设置为null。
7. 【强制】不要使用Arrays.asList
将数组转为列表后会修改列表的方法,否则add/remove/clear等方法会抛出UnsupportedOperationException
.
笔记:
的结果
asList
是 的内部类Arrays
,它没有实现修改自身的方法。Arrays.asList
只是一个传输接口,里面的数据以数组的形式存储。String[] str = new String[] { "a", "b" }; List<String> list = Arrays.asList(str);
情况1:
list.add("c");
会抛出运行时异常。
案例2:str[0]= "gujin";
list.get(0)
将被修改。
8. 【强制】add
泛型通配符不能用method with <? Extends T>
,method不能get
用with <? super T>
,可能会出错。
笔记:关于PECS(Producer Extends Consumer Super)原理:
1)Extends
适用于经常阅读的场景。
2)Super
适用于频繁插入的场景。
9. [强制]不要在foreach循环中删除或添加元素到集合中。请用于Iterator
删除项目。Iterator
执行并发操作时对象应该同步。
反例:
List<String> a = new ArrayList<String>(); a.add("1"); a.add("2"); for (String temp : a) { if ("1".equals(temp)){ a.remove(temp); } }
笔记:
如果你试图用“2”代替“1”,你会得到意想不到的结果。
正面例子:
Iterator<String> it = a.iterator(); while (it.hasNext()) { String temp = it.next(); if (delete condition) { it.remove(); } }
10 、 【强制】 JDK 7及以上版本,Comparator
需满足以下三个要求,否则Arrays.sort
会Collections.sort
抛出IllegalArgumentException
.
笔记:
1) 比较 x,y 和 y,x 应该返回相反的结果。
2) 如果 x>y 且 y>z,则 x>z。
3) 如果 x=y,那么比较 x 和 z 以及比较 y 和 z 应该返回相同的结果。反例:
如果 o1 等于 o2,下面的程序将无法处理这种情况,这在实际情况下可能会导致异常:
new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { return o1.getId() > o2.getId() ? 1 : -1; } }
11. 【推荐】如果可能的话,在初始化集合时设置一个大小。
笔记:更好地用于
ArrayList(int initialCapacity)
初始化ArrayList
.
12. [推荐]使用entrySet
而不是keySet
遍历KV图。
笔记:实际上,
keySet
遍历 map 两次,首先转换为Iterator
object,然后从HashMap
key 中获取 value。EntrySet
仅迭代一次并将键和值放入更有效的条目中。JDK8中的使用Map.foreach
方法。正面例子:
values()
返回一个包含所有值的列表,keySet()
返回一个包含所有值的集合,entrySet()
返回一个 kv 组合对象。
13. 【推荐】仔细检查一个k/v集合是否可以存储空值,参考下表:
收藏 | 钥匙 | 价值 | 极好的 | 笔记 |
---|---|---|---|---|
哈希表 | 不允许为空 | 不允许为空 | 字典 | 线程安全 |
并发哈希映射 | 不允许为空 | 不允许为空 | 抽象地图 | 段锁 |
树状图 | 不允许为空 | 允许为空 | 抽象地图 | 线程不安全 |
哈希映射 | 允许为空 | 允许为空 | 抽象地图 | 线程不安全 |
反例:很困惑
HashMap
,很多人认为null是允许的ConcurrentHashMap
。实际上, 在输入空值NullPointerException
时会抛出。
14. 【参考】正确使用集合的排序和排序,避免unsorted和unordered的负面影响。
笔记:排序意味着它的迭代遵循特定的排序规则。有序意味着每次遍历中元素的顺序是稳定的。例如
ArrayList
,是有序且未排序的,HashMap
是无序且未排序的,TreeSet
是有序且已排序的。
15. 【参考】 set只存储唯一值,去重操作可以快速执行。避免使用方法contains ofList
进行遍历、比较和去重。
并发
1 、 【强制】在初始化单例实例及其所有方法时,应确保线程安全。
笔记:资源驱动类、实用程序类和单例工厂类都包括在内。
2 、 【强制】一个有意义的线程名有助于跟踪错误信息,所以在创建线程或线程池时要指定一个名称。
正面例子:
public class TimerTaskThread extends Thread { public TimerTaskThread() { super.setName("TimerTaskThread"); … } }
3 、 【强制】线程由线程池提供。不允许显式创建线程。
笔记:使用线程池可以减少创建和销毁线程的时间,节省系统资源。如果我们不使用线程池,则会创建大量类似的线程,从而导致“内存不足”或过度切换问题。
4. 【强制】线程池应该由ThreadPoolExecutor
而不是创建Executors
。这些将使线程池的参数易于理解。它还可以降低系统资源耗尽的风险。
笔记:以下是使用创建
Executors
线程池产生的问题:
1)FixedThreadPool
和SingleThreadPool
:
最大请求队列大小Integer.MAX_VALUE
。大量请求可能导致OOM。
2)CachedThreadPool
和ScheduledThreadPool
:
允许创建的线程数是Integer.MAX_VALUE
。创建过多线程可能会导致 OOM。
5. 【强制】 SimpleDateFormat
不安全,不要定义为静态变量。DateUtils
如果必须,必须使用lock 或class。
正面例子:
使用时注意线程安全
DateUtils
。建议使用如下:private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() { @Override protected DateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd"); } };
笔记:
在JDK8中,
Instant
可以用来替换Date
,Calendar
被替换LocalDateTime
,Simpledateformatter
被替换DateTimeFormatter
。
6 、 【强制】 remove()
方法必须通过ThreadLocal
变量来实现,尤其是使用线程池时,线程经常被复用。否则可能会影响后续业务逻辑,导致内存泄漏等意外问题。
正面例子:
objectThreadLocal.set(someObject); try { ... } finally { objectThreadLocal.remove(); }
7 、 【强制】在高并发场景下,Lock
同步调用要考虑性能。块锁比方法锁好。对象锁比类锁好。
8 、 【强制】同时对多个资源、数据库中的表和对象加锁时,加锁顺序要保持一致,避免死锁。
笔记:如果线程 1 对表 A、B、C 加锁后确实更新了,那么线程 2 的锁顺序也应该是 A、B、C,否则可能会发生死锁。
9、【强制】当通过阻塞方式获取锁时,比如在阻塞队列中等待,从Lock实现的lock()必须放在try块之外。此外,请确保 lock() 和 try 块之间没有任何方法,以防在 finally 块中不会释放锁。
注1:如果 lock() 和 try 块之间抛出了一些异常,它将无法释放锁,导致其他线程无法获得锁。
笔记2:
如果 lock() 在 try 块和 lock() 之间未能成功运行,则 unlock() 可能不起作用。然后将调用 AQS(AbstractQueuedSynchronizer) 方法(取决于类的实现)并抛出 IllegalMonitorStateException。
注3:
在 Lock 对象中实现 lock() 方法时,可能会抛出未经检查的异常,导致与注 2 相同的结果。
10 、 【强制】同时修改一条记录时需要加锁,避免更新失败。在应用层,缓存中添加锁,或者使用版本作为更新戳在数据库中添加乐观锁。
笔记:如果访问冲突概率小于 20%,建议使用乐观锁,否则使用悲观锁。乐观锁重试次数不少于3次。
11 、 【强制】 run multiple TimeTask
by usingScheduledExecutorService
而不是Timer
因为Timer
在捕获异常失败的情况下会杀死所有正在运行的线程。
12. 【推荐】当使用CountDownLatch
异步操作转为同步操作时,每个线程必须countdown
在退出前调用方法。确保在线程运行期间捕获任何异常,以countdown
执行方法。如果主线程无法到达await
方法,程序将返回直到超时。
笔记:注意,子线程抛出的异常不能被主线程捕获。
13. 【推荐】避免Random
多线程使用实例。尽管共享此实例是安全的,但对同一种子的竞争会损害性能。
笔记:
Random
instance 包括java.util.Random
和的实例Math.random()
。正面例子:
JDK7之后
ThreadLocalRandom
可以直接使用API。但是在JDK7之前,需要在每个线程中创建实例。
14. 【推荐】在并发场景中,一个简单的解决方案是通过使用双重检查锁定来优化延迟初始化问题(参考双重检查锁定被破坏声明),将对象类型声明为volatile
。
反例:
class Foo { private Helper helper = null; public Helper getHelper() { if (helper == null) { synchronized(this) { if (helper == null) helper = new Helper(); } } return helper; } // other functions and members... }
15. 【供参考】 volatile
用于解决多线程不可见内存的问题。Write-Once-Read-Many可以解决变量同步问题。但是Write-Many不能解决线程安全问题。对于count++
,请使用以下示例:
AtomicInteger count = new AtomicInteger();
count.addAndGet(1);
笔记:在 JDK8 中,
LongAdder
推荐使用它减少乐观锁的重试次数,并且性能优于AtomicLong
.
16. 【参考】HashMap
容量不够时resize可能会因为高并发导致死链接和CPU占用率高。在开发中避免这种风险。
17. 【参考】 ThreadLocal
无法解决共享对象的更新问题。建议使用由同一线程中的所有操作共享的静态对象。 ThreadLocal
流控制语句
1. 【强制】在一个switch
block中,每个case都需要用break/return来结束。如果不是,则应包含注释以描述在哪种情况下将停止。在每个switch
块中,必须存在默认语句,即使它是空的。
2. [强制]大括号与if、else、for、do和while语句一起使用,即使主体只包含一个语句。避免使用以下示例:
if (condition) statements;
3. 【推荐】尽量else
少用,if-else
语句可以换成:
if (condition) {
...
return obj;
}
// other logic codes in else could be moved here
笔记:
if()...else if()...else...
如果必须使用类似语句来表达逻辑, 【强制】嵌套条件层级不应超过三个。正面例子:
if-else
具有超过三个嵌套条件级别的代码可以由保护语句或状态设计模式替换。守卫语句示例:
public void today() {
if (isBusy()) {
System.out.println("Change time.");
return;
}
if (isFree()) {
System.out.println("Go to travel.");
return;
}
System.out.println("Stay at home to learn Alibaba Java Coding Guidelines.");
return;
}
4. 【推荐】条件语句中不要使用复杂的语句(getXxx/isXxx等常用方法除外)。使用布尔变量临时存储复杂语句的结果会增加代码的可读性。
笔记:
许多语句中的逻辑
if
非常复杂。读者需要分析条件表达式的最终结果来决定在某些条件下会执行什么语句。正面例子:
// please refer to the pseudo-code as follows boolean existed = (file.open(fileName, "w") != null) && (...) || (...); if (existed) { ... }
反例:
if ((file.open(fileName, "w") != null) && (...) || (...)) { ... }
5 、 【推荐】使用循环语句时要考虑性能。以下操作最好在循环语句之外处理,包括对象和变量声明、数据库连接、try-catch
语句。
6. [推荐]输入参数的大小应检查,尤其是批量操作。
7. 【参考】输入参数需要在以下场景中进行检查:
1)低频实现方式。
2)长时间执行的方法可以忽略参数检查的开销,但如果非法参数导致异常,则得不偿失。因此,在长时间执行的方法中仍然建议进行参数检查。
3) 需要极高稳定性或可用性的方法。
4)开放API方法,包括RPC/API/HTTP。
5) 权限相关的方法。
8. 【供参考】输入参数不需要验证的情况:
1)方法很可能在循环中实现。应包含一条注释,告知应在外部进行参数检查。
2)底层的方法调用频率很高,一般不需要检查。例如,如果DAO层和Service层部署在同一个服务器,DAO层的参数检查可以省略。
3)如果所有参数都经过检查或可管理,则只能在内部实现的私有方法。
代码注释
1 、 【强制】 类、类变量、方法都应使用Javadoc 。格式应该是'/ comment /',而不是'// xxx'。
笔记:在IDE中,悬停时可以直接看到Javadoc ,这是提高效率的好方法。
2 、 【强制】抽象方法(包括接口中的方法)需要用Javadoc进行注释。Javadoc应包括方法指令、参数描述、返回值和可能的异常。
3. 【强制】每节课都应包括作者信息和日期。
4 、 【强制】方法中的单行注释要放在要注释的代码上面,用 using//
和多行用 using /* */
。应仔细注意注释的对齐方式。
5 、 【强制】所有枚举类型字段都应注释为Javadoc风格。
6. 【推荐】如果英文不能正确描述问题,可以在评论中使用当地语言。关键字和专有名词应保留为英文。
反例:将“TCP 连接超时”解释为“传输控制协议连接超时”只会让人更难理解。
7 、 【推荐】当代码逻辑发生变化时,需要同时更新注释,尤其是参数、返回值、异常和核心逻辑的变化。
8. 【供参考】注释代码时需要添加注释。
笔记:如果以后有可能恢复代码,则需要添加合理的解释。如果没有,请直接删除,因为svn或git会记录代码历史。
9、【供参考】评论要求:
1)能够准确表达设计思想和代码逻辑。
2)能够表示业务逻辑,帮助其他程序员快速理解。一大段没有任何注释的代码对读者来说是一场灾难。评论是为自己和他人写的。即使在很长一段时间后,也可以快速回忆起设计理念。需要时,其他人可以迅速接管工作。
10. 【参考】正确的命名和清晰的代码结构是不言自明的。需要避免过多的注释,因为如果代码逻辑发生变化,可能会导致更新工作过多。
反例:
// put elephant into fridge put(elephant, fridge);
方法名和参数名已经代表了该方法的作用,所以不需要添加额外的注释。
11. 【参考】评论中的标签(例如TODO,FIXME)需要包含作者和时间。标签需要通过频繁扫描及时处理和清除。有时在线故障是由这些未处理的标签引起的。
1)TODO:TODO表示逻辑需要完成,但还没有完成。实际上,TODO 是Javadoc的一个成员,虽然它在Javadoc中还没有实现,但已经被广泛使用。TODO 只能用在类、接口和方法中,因为它是一个Javadoc标记。
2)FIXME:FIXME用于表示代码逻辑不正确或不起作用,应及时修复。
其他
1. 【强制】使用正则表达式时,需要进行预编译,以提高匹配性能。
笔记:不要
Pattern pattern = Pattern.compile(.);
在方法体内定义。
2 、 【强制】 velocity中使用POJO的属性时,直接使用属性名。速度引擎会getXxx()
自动调用 POJO。在布尔属性方面,速度引擎将调用isXxx()
(命名布尔属性时不要使用is作为前缀)。
笔记:对于包装类
Boolean
,速度引擎将首先调用getXxx()
。
3 、 【强制】变量从后端传递到velocity引擎时必须加感叹号,如$!{var}。
笔记:如果属性为空或不存在,${var} 将直接显示在网页上。
4. 【强制】返回类型Math.random()
为double,取值范围为0<=x<1(0可以)。如果需要随机整数,请勿将 x 乘以 10,然后将结果四舍五入。正确的方法是使用属于 Random Object 的nextInt
or方法。nextLong
5. 【强制】用于System.currentTimeMillis()
获取当前毫秒。不要使用new Date().getTime()
.
笔记:为了获得更准确的时间,请使用
System.nanoTime()
. 在JDK8中,使用Instant
类来处理时间统计等情况。
6. 【推荐】 velocity模板文件中最好不要包含变量声明、逻辑符号或任何复杂的逻辑。
7. 【推荐】如果可能,初始化任何数据结构时都需要指定大小,以避免无限增长导致的内存问题。
8 、 【推荐】发现被废弃的代码或配置,坚决从项目中移除。
笔记:
及时删除过时的代码或配置,避免代码冗余。
正面例子:
对于暂时删除并可能重复使用的代码,使用
///
添加一个合理的注释。public static void hello() { /// Business is stopped temporarily by the owner. // Business business = new Business(); // business.active(); System.out.println("it's finished"); }
二、异常和日志
例外
1. 【强制】不要捕获JDK中定义的Runtime异常,如和。相反,建议尽可能进行预检查。NullPointerException
`IndexOutOfBoundsException`
笔记:仅在难以处理预检查时才使用 try-catch,例如
NumberFormatException
.正面例子:
if (obj != null) {...}
反例:
try { obj.method() } catch(NullPointerException e){…}
2. 【强制】普通控制流不要使用异常。它无效且不可读。
3. 【强制】对一大块代码使用try-catch是不负责任的。使用 try-catch 时要清楚稳定和不稳定的代码。意味着不会抛出异常的稳定代码。对于不稳定的代码,尽可能具体地捕获异常处理。
4. 【强制】不要抑制或忽略异常。如果你不想处理它,然后重新扔它。顶层必须处理异常并将其转换为用户可以理解的内容。
5. 【强制】方法抛出异常时,一定要调用回滚。
6 、 【强制】可关闭的资源(流、连接等)必须在finally块中处理。永远不要从finally块中抛出任何异常。
笔记:使用try-with-resources语句安全地处理可关闭资源 (Java 7+)。
7. [强制]不要在finally块中使用return 。finally块中的return语句将导致异常或导致try-catch块中的返回值被丢弃。
8 、 【强制】要捕获的Exception类型需要和已经抛出的类型是同一个类或者超类。
9 、 【推荐】方法的返回值可以为null。返回空集合或对象不是强制性的。当方法可能返回null时,在Javadoc中显式指定。调用者需要进行空检查以防止.NullPointerException
笔记:检查返回值,以及考虑远程调用失败或发生其他运行时异常的可能性是调用者的责任。
10. [推荐]最常见的错误之一是NullPointerException
. 注意以下几种情况:
1)如果返回类型是原始类型,返回包装类的值可能会导致NullPointerException
.
反例:public int f() { return Integer }
取消装箱null值将抛出NullPointerException
.
2) 数据库查询的返回值可能为null。
3) 集合中的元素可能为null,即使Collection.isEmpty()
返回false。
4) 来自 RPC 的返回值可能是null。
5) 存储在会话中的数据可能为null。
6)方法链,如obj.getA().getB().getC()
,很可能造成NullPointerException
。
正例:用于Optional
避免空值检查和 NPE (Java 8+)。
11. 【推荐】使用“抛出异常”或“返回错误码”。对于 HTTP 或开放 API 提供程序,必须使用“错误代码”。建议在应用程序内抛出异常。对于跨应用的 RPC 调用,通过封装isSuccess、错误码和简短的错误信息,结果优先。
笔记:RPC 方法返回 Result 的好处:
1) 如果没有捕获到异常,使用 'throw exception' 方法会发生运行时错误。
2)如果没有附加堆栈信息,使用简单的错误消息分配自定义异常无助于解决问题。如果附加了堆栈信息,当频繁出错时,数据序列化和传输性能损失也是问题。
12. 【推荐】不要直接扔RuntimeException
, Exception
, 或Throwable
。建议使用定义良好的自定义异常,例如DAOException
,ServiceException
等。
13. 【供参考】避免重复代码(Do not Repeat Yourself,又称DRY原则)。
笔记:
随意复制粘贴代码,难免会出现重复代码。如果您将逻辑保留在一个地方,则在需要时更容易更改。如有必要,将公共代码提取到方法、抽象类甚至共享模块中。
正面例子:
对于具有多个以相同方式验证参数的公共方法的类,最好提取如下方法:
private boolean checkParam (DTO dto) { ... }
日志
1 、 【强制】日志系统(Log4j、Logback)中不要直接使用API。建议改用日志框架SLF4J中的API,它采用Facade模式,有利于保持日志处理的一致性。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Abc.class);
2. 【强制】日志文件需要至少保存15天,因为每周都会发生一些异常。
3. 【强制】 Application的扩展日志(如打点、临时监控、访问日志等)命名约定:
appName_logType_logName.log
logType:推荐分类为stats、desc、monitor、visit等
logName:日志描述.
这种方案的好处: 文件名显示了日志所属的应用程序、日志的类型以及日志的用途。它也有利于分类和搜索。
正面例子:mppserver应用程序中监控时区转换异常的日志文件名:mppserver_monitor_timeZoneConvert.log
笔记:
建议对日志进行分类。错误日志和业务日志尽量分开存放。不仅方便开发者查看,也方便系统监控。
4. [强制] TRACE / DEBUG / INFO级别的日志必须使用条件输出或占位符。
笔记:
logger.debug ("Processing trade with id: " + id + " symbol: " + symbol);
如果日志级别为warn,则不会打印上述日志。但是,它将执行字符串连接运算符。会调用symboltoString()
的方法,浪费系统资源。正面例子:
if (logger.isDebugEnabled()) { logger.debug("Processing trade with id: " + id + " symbol: " + symbol); }
正面例子:
logger.debug("Processing trade with id: {} and symbol : {} ", id, symbol);
5. [强制]确保 Log4j logger 的 additivity 属性设置为false,以避免冗余并节省磁盘空间。
正面例子:
<logger name="com.taobao.ecrm.member.config" additivity="false" \>
6 、 【强制】异常信息应包含两种信息:上下文信息和异常堆栈。如果您不想处理异常,请重新抛出它。
正面例子:
logger.error(various parameters or objects toString + "_" + e.getMessage(), e);
7. 【推荐】认真记录日志。有选择地使用Info级别,不要在生产环境中使用Debug级别。如果使用Warn来记录业务行为,请注意日志的大小。确保服务器磁盘没有过满,记得及时删除这些日志。
笔记:输出大量无效日志会对系统性能产生一定影响,不利于快速定位问题。请想想日志:你真的有这些日志吗?您可以做什么来查看此日志?解决问题容易吗?
8. 【推荐】 Level Warn用于记录无效参数,用于在出现问题时跟踪数据。Level Error只记录系统逻辑错误、异常等重要错误信息。
三、 MySQL 规则
表架构规则
1. 【强制】表示True或False概念的列,必须命名为is_xxx,其数据类型应为unsigned tinyint(1 为True,0 为False)。
笔记:所有具有非负值的列都必须是unsigned。
2. 【强制】表名和列名必须由小写字母、数字或下划线组成。不允许以数字开头的名称和在两个下划线之间仅包含数字(无其他字符)的名称。列命名时要谨慎,因为更改列名的成本很高,并且不能在预发布环境中发布。
正面例子:getter_admin、task_config、level3_name
反例:
GetterAdmin、taskConfig、level_3_name
3. 【强制】表名不能用复数名词。
4 、 【强制】关键字,如desc、range、match、delayed等,不宜使用。可以参考 MySQL 官方文档。
5 、 【必选】主键索引名称以pk_为前缀,后跟列名;唯一索引应通过在其列名前加上uk_来命名;正常索引应格式化为idx_[column_name]。
笔记:pk表示主键,uk表示唯一键,idx是索引的缩写。
6. 【强制】小数应输入为十进制。不允许使用float和double 。
笔记:存储浮点数和双精度数时可能会丢失精度,进而导致数据比较结果不正确。当要存储的数据范围超出十进制类型覆盖的范围时,建议将整数部分和小数部分分开存储。
7. 【强制】如果要存储在该列中的信息长度几乎相同,则使用char 。
8 、 【强制】varchar的长度不能超过5000,否则定义为text
。最好将它们存储在单独的表中,以免影响其他列的索引效率。
9. 【强制】一张表必须包含以下三列:id、gmt_create和gmt_modified。
笔记:id为主键,无符号 bigint,自增,步长为 1。gmt_create和gmt_modified的类型应为DATE_TIME。
10. [推荐]推荐将表名定义为[table_business_name]_[table_purpose]。
正面例子:Tiger_task / tiger_reader / mpp_config
11. [推荐]尽量定义数据库名称与应用程序名称相同。
12. [推荐]一旦更改列含义或添加新的可能状态值,请更新列注释。
13 、 【推荐】一些合适的列可以冗余存储在多个表中,以提高搜索性能,但必须注意一致性。冗余列不应该是:
1) 经常修改的列。2) 用很长的varchar或text
键入的列。
正面例子:产品类别名称很短,经常使用,并且几乎从不更改/固定值。它们可能会冗余存储在相关表中以避免连接查询。
14 、 【推荐】分表仅在单表超过500万行或表容量超过2GB时才推荐。
笔记:如果预计数据量达不到这个等级,请不要在建表过程中进行分片。
15. 【参考】合适的char列长度,不仅可以节省数据库和索引存储空间,还可以提高查询效率。
正面例子:无符号类型可以避免错误地存储负值,但也可以覆盖更大的数据代表范围。
目的 | 年龄 | 推荐的数据类型 | 范围 |
---|---|---|---|
人类 | 150岁以内 | 无符号小整数 | 无符号整数:0 到 255 |
龟 | 百年历史 | 无符号小整数 | 无符号整数:0 到 65,535 |
恐龙 化石 |
数千万年的历史 | 无符号整数 | 无符号整数: 0 到 42.9 亿左右 |
太阳 | 大约50亿岁 | 无符号大整数 | 无符号整数:0 到 10^19 左右 |
索引规则
1 、 【强制】业务逻辑适用的情况下,应使用唯一索引。
笔记:唯一索引对插入效率的负面影响可以忽略不计,但它显着提高了查询速度。此外,即使在应用层进行了完整的检查,根据墨菲定律,只要没有唯一索引,仍然可能产生脏数据。
2 、 【强制】 如果涉及三个以上的表,则不允许JOIN 。要连接的列必须具有绝对相似的数据类型。确保要连接的列已编入索引。
笔记:即使只连接了 2 个表,也应考虑索引和 SQL 性能。
3 、 【强制】在varchar列上添加索引时必须指定索引长度。索引长度应根据数据的分布情况设置。
笔记:通常对于char列,长度为 20 的索引可以区分 90% 以上的数据,通过count(distinct left(column_name, index_length)) / count( )* 计算得出。
4 、 【强制】 分页搜索时不允许使用LIKE '%...'或LIKE '%...%' 。如果确实需要,可以使用搜索引擎。
笔记:索引文件具有 B-Tree 的最左前缀匹配特性。如果未确定左前缀值,则无法应用索引。
5 、 【推荐】使用ORDER BY子句时,利用索引顺序。ORDER BY子句的最后一列应位于复合索引的末尾。原因是为了避免影响查询性能的file_sort问题。
正面例子:a=? 和 b =?按 c 订购;索引为:a_b_c
反例:
如果查询条件包含范围,则索引顺序不生效,例如where a>10 order by b;索引a_b无法激活。
6 、 【推荐】使用覆盖索引进行查询,避免搜索索引后的额外查询。
笔记:如果我们需要查看一本书的第 11 章的标题,是否需要转到第 11 章开始的那一页?不,因为目录实际上包括标题,它用作覆盖索引。
正面例子:
索引类型包括主键索引、唯一索引和公共索引。覆盖索引与查询效果有关。参考解释结果时,使用索引可能会出现在额外的列中。
7 、 【推荐】使用late join或者sub-query来优化多页面的场景。
笔记:MySQL 没有绕过偏移行,而是完全检索偏移+N行,然后丢弃偏移行并返回 N 行。当偏移量很大时,它的效率非常低。解决方法是限制返回的页数,或者当页数超过预定义的阈值时重写 SQL 语句。
正面例子:
先快速定位到需要的id范围,然后join:
select a. from table1 a,(select id from table1 where some_condition* LIMIT 100000, 20) b where a.id=b.id;
8. 【推荐】 SQL 性能优化的目标是EXPLAIN的结果类型达到REF级别,或者至少RANGE ,或者CONSTS,如果可能的话。
反例:注意EXPLAIN结果中INDEX的类型,因为对数据库索引文件进行全扫描非常慢,其性能几乎等于全表扫描。
内容:
最多有一个匹配行,由优化器读取。它非常快。
参考:
使用正常索引。
范围:
检索给定范围的索引,当使用 =、<>、>、>=、<、<=、IS NULL、<=>、BETWEEN、或 IN() 运算符。
9、【推荐】添加复合索引时,将最有区别的列放在最左边。
正面例子:对于子条款where a=? 和 b =?,如果 a 列的数据几乎是唯一的,添加索引idx_a就足够了。
笔记:
当查询条件中同时存在相等和不相等检查时,添加索引时先将列置于相等条件。例如,哪里a>?和 b =?, b应该作为索引的第一列,即使列a更具辨别力。
10. [供参考]在添加索引时,请避免以下误解:
1)每个查询都需要一个索引是错误的。
2)索引消耗故事空间并显着降低更新,插入操作是错误的。
3)唯一索引都应该从应用层通过“检查和插入”来实现是错误的。
SQL 规则
1. 【强制】不要使用 COUNT(column_name) 或 COUNT(constant_value) 代替 COUNT()。COUNT() 是SQL92定义的标准语法,用于计算行数。它不是特定于数据库的,与NULL和non-NULL无关。
笔记:COUNT() 计算 NULL 行,而 COUNT(column_name) 不考虑NULL*值行。
2. [强制] COUNT(distinct column) 计算该列中具有不同值的行数,不包括NULL值。请注意,如果其中一列的所有值都是NULL ,则 COUNT(distinct column1, column2) 返回0,即使另一列包含不同的非 NULL值。
3 、 【强制】当一列的所有值都是NULL时,COUNT(column)返回0,而SUM(column)返回NULLNullPointerException
,所以使用SUM()时要注意问题。
正面例子:可以通过这种方式避免 NPE 问题:
SELECT IF(ISNULL(SUM(g)), 0, SUM(g)) FROM table;
4. [强制]使用ISNULL()检查NULL值。将NULL与任何其他值进行比较时,结果将为NULL 。
笔记:1) NULL<>NULL返回NULL,而不是false。
2) NULL=NULL返回NULL,而不是true。
3) NULL<>1返回NULL,而不是true。
5. [强制]使用分页逻辑的DB查询编码时,一旦count为0就立即返回,避免执行后面的分页查询语句。
6. 【强制】 不允许外键和级联更新。所有外键相关的逻辑都应该在应用层处理。
笔记:例如 Student 表有student_id作为主键,score 表有student_id作为外键。当student.student_id更新时,score.student_id 更新也会被触发,这称为级联更新。外键和级联更新适用于单机、低并行系统,不适用于分布式、高并行集群系统。级联更新被强烈阻止,因为它可能导致数据库更新风暴。外键影响数据库插入效率。
7. 【强制】不允许使用存储过程。它们难以调试、扩展且不可移植。
8 、 【强制】在更正数据、删除和更新DB记录时,应先SELECT ,以保证数据的正确性。
9. [推荐] IN子句应避免使用。IN子句的记录集大小应仔细评估,如果无法避免,应控制在 1000 以内。
10. [参考]出于全球化需求,字符应以UTF-8表示和存储,注意字符数计数。
笔记:SELECT LENGTH(“轻松工作”); 返回12。
SELECT CHARACTER_LENGTH(“轻松工作”); 返回4。考虑到它与UTF-8
的区别,如果需要, 使用UTF8MB4编码来存储表情符号。
11. [参考]编码时不建议使用 TRUNCATE,即使它比DELETE快,并且使用较少的系统、事务日志资源。因为TRUNCATE没有事务也没有触发 DB触发器,所以可能会出现问题。
笔记:就功能而言,TRUNCATE TABLE类似于没有WHERE子句的DELETE 。
ORM 规则
1 、 【强制】查询时要指定具体的列名,不能使用*。
笔记:
- 增加解析成本。2)添加或删除查询列时可能会导致与resultMap
不匹配。
2 、 【强制】POJO类的布尔属性名不能以is为前缀,DB列名以is为前缀。需要属性和列之间的映射。
笔记:参考POJO类和 DB 列定义的规则,在resultMap中需要映射。MyBatis Generator生成的代码可能需要调整。
3 、 【强制】不要使用resultClass作为返回参数,即使所有的类属性名与DB列相同,也需要对应的DO定义。
笔记: 需要映射配置,解耦DO定义和表列,方便维护。
4 、 【必选】 xml配置中的参数要慎重。请勿用于${}
代替#{}
, #param#
。SQL 注入可能以这种方式发生。
5. 【强制】 iBatis内置queryForList(String statementName, int start, int size)不推荐使用。
笔记:
它可能会导致OOM问题,因为它的实现是检索statementName对应SQL语句的所有DB记录,然后通过subList应用大小子集。
正面例子:
在sqlmap.xml中使用#start#、#size#。
Map<String, Object> map = new HashMap<String, Object>(); map.put("start", start); map.put("size", size);
6 、 【强制】不要使用HashMap或HashTable作为DB查询结果类型。
7. [强制] gmt_modified列应该在更新数据库记录的同时更新当前时间戳。
8. 【推荐】不要定义通用的表更新接口,接受POJO作为输入参数,并且总是更新表集c1=value1,c2=value2,c3=value3,...不管要更新的列。最好不要更新不相关的列,因为这样容易出错,效率不高,而且增加了binlog存储。
9. 【参考】不要过度使用@Transactional。由于事务影响DB的QPS,可能需要考虑相关的回滚,包括缓存回滚、搜索引擎回滚、消息补齐、统计调整等。
10. 【参考】 的compareValue是一个常数(通常是一个数字),用于与属性值进行比较。表示当属性不为空且不为空时执行相应的逻辑。表示属性不为空时执行相关逻辑。
四、项目规范
应用层
1. 【推荐】上层默认依赖下层。箭头表示直接依赖。例如:Open Interface可以依赖Web Layer,也可以直接依赖Service Layer等:
- 开放接口:在这一层服务被封装成RPC接口,或者通过Web层暴露为HTTP接口;该层还实现了网关安全控制、流量控制等。
- 视图:在此层中每个终端的模板渲染和执行。渲染方式包括速度渲染、JS渲染、JSP渲染、移动端显示等。
- Web层:该层主要用于实现前向访问控制、基本参数校验或不可复用服务。
- 服务层:在这一层实现具体的业务逻辑。
- 管理者层:该层为通用业务流程层,包含以下特性:
1)封装第三方服务,对返回值和异常进行预处理;
2)Service Layer通用能力的沉淀,如缓存方案、中间件通用处理;3) 与DAO 层
交互,包括多个DAO类的组合和重用。 - DAO 层:数据访问层,与 MySQL、Oracle 和 HBase 进行数据交互。
- 外部接口或第三方平台:这一层包括来自其他部门或公司的RPC开放接口。
2. 【供参考】DAO层的很多异常不能用细粒度的异常类来捕获。推荐的方法是使用catch (Exception e)
, 和throw new DAOException(e)
。在这些情况下,不需要打印日志,因为日志应该在Manager Layer/Service Layer中被捕获和打印。服务层
的异常日志必须尽可能多地记录参数信息,使调试更简单。 如果Manager Layer和Service Layer部署在同一台服务器上,日志逻辑要与DAO Layer保持一致
. 如果分别部署,日志逻辑应该是一致的。
在Web层不能抛出异常,因为它已经在最上层了,没有办法处理异常情况。如果异常可能导致页面渲染失败,则应将页面重定向到带有友好错误信息的友好错误页面。
在开放接口中,应使用错误代码和错误消息来处理异常。
3. 【参考】领域模型的层次:
- DO(Data Object):对应数据库表结构,通过DAO层向上传递数据源对象。
- DTO(Data Transfer Object):服务层和管理者层向上传输的对象。
- BO(Business Object):封装业务逻辑的对象,可以通过Service Layer输出。
- Query:携带上层查询请求的数据查询对象。
Map
注意:如果查询条件超过2个,禁止使用。 - VO(View Object):显示层中使用的对象,通常是从Web层转移过来的。
库规范
1. 【强制】 GAV定义规则:
1) G roupID: com.{company/BU}.{business line}.{sub business line}
,最多4级
笔记:{company/BU} 例如:阿里巴巴、淘宝、天猫、速卖通等业务单元级别;子业务线是可选的。
正面例子:
com.taobao.tddl com.alibaba.sourcing.multilang
2)artifactID:产品名称-模块名称。
正面例子:
tc-client
//uic-api
_tair-tool
3)版本:请参考以下
2. 【强制】库命名约定:prime version number
. secondary version number
. revision number
1)prime版本号:当有不兼容的API修改,或者可以改变产品方向的新功能时需要更改。
2)次要版本号:更改为向后兼容修改。
3)修订号:为修复错误或添加不修改方法签名并保持 API 兼容性的功能而更改。
笔记:初始版本必须是1.0.0,而不是0.0.1。
3. 【强制】在线申请不依赖SNAPSHOT版本(安全包除外);正式发布必须从中央存储库验证,以确保 RELEASE 版本号具有连续性。版本号不允许被覆盖。
笔记:避免使用SNAPSHOT是为了保证应用发布的幂等性。另外,打包时可以加快代码编译速度。如果当前版本是1.3.3,那么下一个版本的编号可以是1.3.4、1.4.0或2.0.0。
4 、 【强制】添加或升级库时,非必要时保持依赖库版本不变。如果有变化,必须正确评估和检查。建议使用命令dependency:resolve比较前后的信息。如果结果相同,用命令dependency:tree找出不同之处,用去掉不必要的库。
5 、 【强制】枚举类型可以定义或用于库中的参数类型,但不能用于接口返回类型(POJO也不允许包含枚举类型)。
6 、 【强制】使用一组库时,需要定义统一的版本变量,避免版本号不一致。
笔记:使用
springframework-core
,springframework-context
,springframework-beans
同版本时,建议使用统一的版本变量 ${spring.version}。
7 、 【必选】对于相同的GroupId和ArtifactId,子项目中的Version必须相同。
笔记:在本地调试时,使用子项目中指定的版本号。但是当合并到一个war中时,lib目录中只应用了一个版本号。因此,可能会出现离线调试正确,但在线启动后出现故障的情况。
8. 【推荐】所有POM文件中的依赖声明都应该放在块中。应在块中指定依赖项的版本。
笔记:在中指定了依赖项的版本,但不导入依赖项。因此,在子项目中需要显式声明依赖项,其版本和范围是从父POM中读取的。默认情况下,主POM中中的所有声明将被所有子项目自动导入和继承。
9. 【推荐】建议库不要包含配置,至少不要增加配置项。
10. 【参考】为避免库的依赖冲突,发布者应遵循以下原则:
1)简单可控: 去除所有不必要的API和依赖,只包含Service API,必要的领域模型对象,Utils类,常量,枚举等。如果必须包含其他库,最好按照提供的范围进行设置,并让用户依赖特定的版本号。不依赖具体的日志实现,只依赖日志框架。
2)稳定可追溯:应记录每个版本的更改日志。使检查库所有者和源代码的位置变得容易。除非用户主动更新版本,否则不应更改应用程序中打包的库。
服务器规格
1 、 【推荐】对于高并发服务器,建议降低TCP协议的time_wait值。
笔记:默认情况下,操作系统会在 240 秒后关闭处于time_wait状态的连接。在高并发的情况下,服务器可能无法建立新的连接,因为time_wait状态的连接太多,所以需要减小time_wait的值。
正面例子:
通过修改 Linux 服务器上的_etcsysctl.conf文件来修改默认值 (Sec):
net.ipv4.tcp\_fin\_timeout = 30
2. 【推荐】增加服务器支持的文件描述符的最大数量。
笔记:大多数操作系统设计为将TCP/UDP连接作为文件进行管理,即一个连接对应一个文件描述符。大多数Linux服务器支持的File Descriptor的最大数量是1024。当并发连接数很大时,由于缺少File Descriptor,很容易出现“打开太多文件”的错误,这会导致新的连接无法成立。
3. 【推荐】设置JVM-XX:+HeapDumpOnOutOfMemoryError
参数,当OOM发生时JVM会输出dump信息。
笔记:OOM并不经常发生,几个月才一次。发生错误时打印的转储信息对于错误检查非常有价值。
4 、【参考】内部重定向使用forward,外部重定向使用URL组装工具。否则会出现 URL 维护不一致的问题和潜在的安全风险。
五、安全规范
1. 【强制】用户拥有的页面或功能必须经过授权。
笔记:防止未经授权检查访问和操纵他人的数据,例如查看或修改他人的订单。
2. 【强制】不允许直接展示用户敏感数据。显示的数据必须脱敏。
笔记:个人电话号码应显示为:158**9119。中间 4 位数字被隐藏以防止隐私泄露。
3 、 【强制】用户输入的 SQL参数要仔细检查或受METADATA限制,防止SQL注入。禁止通过字符串连接SQL访问数据库。
4 、 【强制】用户输入的任何参数都必须经过校验。
笔记:
忽略参数检查可能会导致:
- 由于页面大小过大而导致内存泄漏
- 由于恶意排序导致数据库查询缓慢
- 任意重定向
- SQL注入
- 反序列化注入
- 重拒绝服务
笔记:
在 Java中,正则表达式用于验证客户端输入。一些正则表达式可以毫无问题地验证常见的用户输入,但如果攻击者使用特殊构造的字符串进行验证,则可能导致死循环。
5 、 【强制】禁止将用户数据输出到没有安全过滤或适当转义的HTML页面。
6. 【强制】表单和AJAX提交必须经过CSRF安全检查。
笔记:CSRF(Cross-site Request Forgery)是一种常见的编程缺陷。对于有CSRF泄露的应用/网站,只要受害者用户在不通知的情况下访问,攻击者就可以提前构造URL并修改数据库中的用户参数。
7 、 【强制】必须使用正确的防重放限制,如号码限制、疲劳控制、验证码检查,避免滥用平台资源,如短信、邮件、电话、订单、支付.
笔记:例如,如果对手机发送验证码的次数和频率没有限制,可能会打扰用户,浪费短信平台资源。
8 、 【推荐】在用户产生内容的场景(如发帖、评论、即时消息),必须采用防诈骗词过滤等风控策略。
参考文章:github地址:https://github.com/alibaba/Alibaba-Java-Coding-Guidelines](https://github.com/alibaba/p3c)
github编码规范:Table of Contents | Alibaba-Java-Coding-Guidelines
gitee地址:阿里巴巴Java开发手册