Alibaba Java Coding Guidelines(中文版)

376 阅读12分钟

Alibaba Java Coding Guidelines(中文版)

前言

我们很高兴推出阿里巴巴Java编码指南,该指南整合了阿里巴巴集团技术团队的最佳编程实践。大量的Java编程团队对跨项目的代码质量提出了苛刻的要求,因为我们鼓励重用和更好地理解彼此的程序。我们过去见过许多编程问题。例如,有缺陷的数据库表结构和索引设计可能会导致软件体系结构缺陷和性能风险。再举一个例子,混乱的代码结构使其难以维护。此外,未经身份验证的易受攻击的代码容易受到黑客的攻击。为了解决这些问题,我们为阿里巴巴的Java开发人员开发了这个文档。

本文档由编程规范异常和日志MySQL 规范、项目规范和安全规范五个部分组成。根据问题的严重程度,每个规范分为三个级别:强制性推荐性和参考性。进一步澄清表述为:
(1)“说明”,说明内容;
(2)“正面例子”,其中描述了推荐的编码和实施方法;
(3)“反例”,描述注意事项和实际错误案例。

本文档的主要目的是帮助开发人员提高代码质量。因此,开发人员可以最大限度地减少潜在和重复的代码错误。此外,规范对于现代软件架构至关重要,这些架构可以实现有效的协作。打个比方,交通法规旨在保护公共安全,而不是剥夺驾驶权。很容易想象没有限速和交通信号灯的交通混乱。开发适当的软件规范和标准的目的不是破坏程序的创造力和优雅,而是通过限制过度个性化来提高协作效率。

我们将继续收集社区的反馈,以改进阿里巴巴Java编码指南。

1. 编程规范

命名约定

1.  [必填]  名称不应以下划线或美元符号开头或结尾。

反例:

_name / __name / Object/name/名称Object / name_ / 名称 / 对象$

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 开头。异常类名必须以异常结尾。测试用例名称应以要测试的类名开头,以 Test 结尾。

7.  [必填]  括号是数组类型的一部分。定义可以是:字符串[] 参数;

反例:

字符串参数[];

8.  [必填]  定义布尔变量时不要添加“is”作为前缀,因为这可能会导致某些 Java 框架中的序列化异常。

反例:

布尔值是成功; 方法名称将是,然后 RPC 框架会将变量名称推断为“成功”,从而导致序列化错误,因为它找不到正确的属性。isSuccess()

9.  [必填]  包应以小写字符命名。每个点后应该只有一个英语单词。包名称始终采用单数格式,而类名可以在必要时采用复数格式。

正面例子:

com.alibaba.open.util可用作 utils 的包名称; 可以用作类名。MessageUtils

10.  [必填]  为便于阅读,应避免使用不常见的缩写。

反例:

AbsClass (AbstractClass);康迪(条件)

11.  [推荐]  如果使用任何设计模式,建议将模式名称包含在类名中。

正面例子:

public class OrderFactory; ``public class LoginProxy; public class ResourceObserver;

注意:

包括相应的模式名称有助于读者快速理解设计模式中的想法。

12.  [推荐]  为了简化编码,不要在接口类中的方法中添加任何修饰符,包括 。请为方法添加有效的Javadoc注释。不要在接口中定义除应用程序的公共常量之外的任何变量。public

正面例子:

接口中的方法定义:常量定义:
void f(); ``String COMPANY = "alibaba";

注意:

在 JDK8 中,允许为接口方法定义默认实现,这对于所有实现的类都很有价值。

  1. 接口和相应的实现类命名有两个主要规则:
    1) [强制性] 所有服务和 DAO类都必须是基于SOA原则的接口。实现类名应以 Impl 结尾。

正面例子:

CacheServiceImpl要实现 .CacheService

2)  [推荐] 如果接口名称是为了表示接口的能力,那么它的名字应该是形容词。

正面例子:

AbstractTranslator要实现 .Translatable

14.  [供参考]  枚举类名应以枚举结尾。其成员应以大写单词拼写,并用下划线分隔。

注意:

枚举确实是一个特殊的常量类,默认情况下所有构造函数方法都是私有的。

正面例子:

枚举名称:;会员名称: .DealStatusEnum``SUCCESS / UNKOWN_REASON

15.  [供参考]  不同包层的命名约定:
A) 服务/DAO 层方法
的命名约定 1) 用作获取单个对象的方法的名称前缀。
2) 用作获取多个对象的方法的名称前缀。
3) 用作统计方法的名称前缀。
4) 使用或(推荐)作为保存数据的方法的名称前缀。
5) 使用 或(推荐)作为删除数据的方法的名称前缀。
6) 用作更新数据的方法的名称前缀。
B) 域模型
的命名约定 1) 数据对象:*DO,其中 * 是表名。
2) 数据传输对象:*DTO,其中 * 是与域名相关的名称。
3)值对象:VO,其中在大多数情况下是网站名称。
4) POJO 通常指向 DO/DTO/BO/VO,但不能用于命名为 *POJO。get``list``count``insert``save``delete``remove``update

常量约定

1.  [必填]  除预定义外,编码中禁止使用魔术值。

反例:

字符串键 = “Id#taobao_” + tradeId;

2.  [必填] 长变量或长变量应使用“L”而不是“l”,因为“l”很容易被错误地视为数字1。

反例:

Long a = 2l,很难分辨是21号还是2号长。

3.  [推荐] 常量应根据其功能放在不同的常量类中。例如,可以放入与缓存相关的常量,而将与配置相关的常量保留在 中。CacheConsts``ConfigConsts

注意:

很难在一个大的完全常量类中找到一个常数。

4.  [推荐]  常量可以在以下 5 个不同的层中共享:在多个应用程序中共享;在应用程序内共享;在子项目中共享;在包中共享;在类中共享
1)在多个应用程序中共享:保存在库中,在客户端.jar的目录下;
2)在应用程序中共享:保存在应用程序中的共享模块中,在目录下;
反例:明显的变量名也应该定义为应用程序中的公共共享常量。以下定义在生产环境中导致异常:它返回 false,但预期返回 true
A类中的定义: B类中的定义:
3)在子项目中共享:
放置在当前项目的目录下;
4)在包中共享:放在当前包的目录下;
5)在类中共享:定义为类内的“私有静态决赛”。constant``constant``A.YES.equals(B.YES)``public static final String YES = "yes";``public static final String YES = "y";``constant``constant

5.  [推荐]  如果值位于固定范围内或变量具有属性,请使用枚举类。下面的示例演示枚举中可以包含额外信息(是哪一天):

正面例子:

公共 Enum{ 星期一(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.  [推荐]  使用单个空行分隔具有相同逻辑或语义的部分。

注意:

没有必要使用多个空行来执行此操作。

OOP 规则

1.  [必填]  静态字段或方法应直接由其类名引用,而不是其对应的对象名。

2.  [必需]  接口或抽象类中重写的方法必须标有注释。@Override

反例:

对于 和 ,第一个有一个字母“O”,第二个有一个数字“0”。要准确确定覆盖是否成功,需要注释。同时,一旦抽象类中的方法签名发生更改,实现类将立即报告编译时错误。getObject()``get0bject()``@Override

3.  [强制]  仅当所有参数的类型和语义相同时,才建议使用 varargs。应避免使用带类型的参数。Object

注意:

具有 varargs 功能的参数必须位于参数列表的末尾。(不建议使用 varargs 功能进行编程。

正面例子:

public User getUsers(String type, Integer... ids);

4.  【强制】 禁止修改方法签名,以免影响调用方。弃用接口时,需要对新服务进行显式描述的注释。@Deprecated

5.  [强制]  禁止使用已弃用的类或方法。

注意:

例如,应该使用代替已弃用的方法。弃用接口后,接口提供程序有义务提供新接口。同时,客户端程序员有义务使用新的接口。decode(String source, String encode)``decode(String encodeStr)

6.  [强制]  由于在调用 equals 方法时可能会抛出,因此 equals 应该由**常量或绝对不为 null 的对象调用。NullPointerException``Object

正面例子:

"test".equals(object);

反例:

object.equals("test");

注意:

java.util.Objects#equals(JDK7 中的实用程序类)是推荐的。

7.  [强制] 使用方法,而不是引用相等,来比较原始包装类。equals``==

注意:

请考虑此作业:。当它符合 -128 到 127 的范围时,我们可以直接用于比较。因为对象将由 生成,这会重用现有对象。但是,当它适合前一个范围的互补集时,对象将被分配到堆中,这不会重用现有对象。这是一个陷阱。因此,建议使用该方法。Integer var = ?``==``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 类中的成员没有初始值。作为消费者,您应该自己检查诸如仓库条目之类的问题。
正面示例:由于数据库查询的结果可能为 null,因此将其分配给基元日期类型将导致由于自动装箱而导致的风险。
反例:考虑交易量幅度的输出,如±x%。 作为原始数据,当涉及到调用 RPC 服务失败时,将分配默认返回值:0%, 这是不正确的。应改为分配类似 - 的连字符。因此,包装类的 null 值可以表示其他信息,例如调用 RPC 服务失败、异常退出等。NullPointerException``NullPointerException

10.  [必填]  在定义 DO、DTO、VO 等 POJO 类时,不要为成员分配任何默认值。

11.  【必填项】 为避免反序列化失败,当序列化类需要更新时,请勿更改 serialVersionUID,例如添加一些新成员。如果需要完全不兼容的更新,请更改 serialVersionUID 的值,以防反序列化时出现混淆。

注意:

serialVersionUID 的不一致可能会导致运行时。InvalidClassException

12.  [强制] 禁止构造函数方法中的业务逻辑。所有初始化都应在方法中实现。init

13.  [必填]  该方法必须在 POJO 类中实现。如果当前类扩展了另一个 POJO 类,则应在实现开始时调用该方法。toString``super.toString

注意:

我们可以直接在 POJO 中调用该方法来打印属性值,以便在方法在运行时引发异常时检查问题。toString

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 方法

注意:

作为消费者和提供者最关心的问题,公共方法应该放在第一个屏幕上。受保护的方法仅由子类负责,但它们在模板设计模式方面有机会至关重要。私有方法,即黑盒方法,对客户来说基本上并不重要。服务或 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

注意:

in 的默认实现是浅拷贝(非深层复制),它将字段作为指向内存中相同对象的指针复制。clone``Object

21.  [推荐]  定义类中成员的访问级别,有严格的限制:1) 构造函数方法必须是,
如果禁止在类外使用 using 关键字进行分配。
2) 构造函数方法不允许在实用程序类中。 3) 从继承体访问的非静态类变量必须是 。

4) 除了包含它们的类之外,没有人可以访问的非静态类变量必须是 。
5) 除了包含它们的类之外,任何人都无法访问的静态变量必须是 。
6) 在确定静态变量是否为 .
7)除了包含它们的类之外,任何人都无法访问的类方法必须是。
8) 从继承体访问的类方法必须是 。private``new``public``default``protected``private``private``final``private``protected

注意:

我们应该严格控制任何类、方法、参数和变量的访问。松散的访问控制会导致模块的有害耦合。想象一下以下情况。对于类成员,我们可以根据需要尽快将其删除。但是,当涉及到类成员时,我们必须在发生任何更新之前三思而后行。private``public

收集

1.  [必填]  哈希代码和等于的使用应遵循:**
1) 如果等于被覆盖,则覆盖哈希代码。**
2) 必须为 a 的元素覆盖这两个方法,因为它们用于确保不会在 中插入重复对象。
3) 对于用作键的任何对象,必须覆盖这两个方法。Set``Set``Map

注意:

String可以用作 SINCE 的键,定义了这两种方法。Map``String

2.  【必填】 不要在 // 返回的集合对象中添加元素,否则会被抛出。keySet()``values()``entrySet()``UnsupportedOperationException

3.  [强制]  不要在 中的方法返回的不可变对象中添加或删除,例如 /。Collections``emptyList()``singletonList()

反例:

将元素添加到将抛出 .Collections.emptyList()``UnsupportedOperationException

  1. 【强制】 不要在类中投射子列表,否则会被抛出:不能投射到。ArrayList``ClassCastException``java.util.RandomAccessSubList``java.util.ArrayList

注意:

subList的是一个内部类,它是 的视图。上的所有操作都将影响原始列表。ArrayList``ArrayList``Sublist

5.  【必填】 使用子列表时,修改原列表大小时要小心。在子列表上执行遍历、添加或删除时可能会导致此问题。ConcurrentModificationException

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);

注意:

将方法与参数一起使用时,传递与列表大小相同的输入。如果输入数组大小不够大,该方法将在内部重新分配大小,然后返回新数组的地址。如果大小大于所需,则额外元素(及更高版本)将设置为 nulltoArray``index[list.size()]

7.  【强制】 不要使用使用后会修改列表的方法将数组转换为列表,否则添加/删除/清除等方法会抛出。Arrays.asList``UnsupportedOperationException

注意:

的结果是 的内部类,它不实现修改自身的方法。 只是一个传输的接口,其中的数据以数组形式存储。asList``Arrays``Arrays.asList

String[] str = new String[] { "a", "b" };
List<String> list = Arrays.asList(str); 

情况 1:将引发运行时异常。
情况2:将被修改。list.add("c");``str[0]= "gujin";``list.get(0)

8.  [必填] 方法不能用于泛型通配符,方法不能与 一起使用,这可能出错。add``<? Extends T>``get``<? super T>

注意:

关于PECS(生产者延伸消费超级)原则:
1)适用于经常阅读的场景。
2)适用于频繁插入的场景。Extends``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);
    }
}

注意:

如果尝试将“1”替换为“2”,则会得到意想不到的结果。

正面例子:

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.  [推荐]  用于代替遍历 KV 贴图。entrySet``keySet

注意:

实际上,遍历映射两次,首先转换为对象,然后从 by 键获取值。 仅迭代一次,并将键和值放在条目中,效率更高。在 JDK8 中使用方法。keySet``Iterator``HashMap``EntrySet``Map.foreach

正面例子:

values()返回包含所有值的列表,返回包含所有值的集合,返回 K-V 组合对象。keySet()``entrySet()

13.  【推荐】 仔细检查一个 k/v 集合是否可以存储值,请参考下表:

收集钥匙价值注意
哈希表不允许空不允许空字典线程安全
ConcurrentHashMap不允许空不允许空抽象地图分段锁定
树状图不允许空允许空抽象地图线程不安全
哈希地图允许空允许空抽象地图线程不安全

反例:

困惑的是,很多人认为 null 是允许的。实际上,在输入值时会被抛出。HashMap``ConcurrentHashMap``NullPointerException

14.  【供参考】 正确使用集合的排序和顺序,避免无排序和无序的负面影响。

注意:

排序意味着其迭代遵循特定的排序规则。有序表示每个导线中元素的顺序是稳定的。例如 已排序和未排序,是无序和未排序的,已排序和排序。ArrayList``HashMap``TreeSet

15.  [供参考]  重复数据删除操作可以快速执行,因为 set 仅存储唯一值。避免使用方法包含 的执行遍历、比较和重复数据消除。List

并发

1.  [强制] 初始化单例实例以及其中的所有方法时应确保线程安全。

注意:

资源驱动类、实用程序类和单例工厂类都包括在内。

2.  [必填]  有意义的线程名称有助于跟踪错误信息,因此请在创建线程或线程池时分配名称。

正面例子:

 public class TimerTaskThread extends Thread {
		public TimerTaskThread() {
			super.setName("TimerTaskThread"); 
		    … 
		}
 }

3.  [必填]  线程应由线程池提供。不允许显式创建线程。

注意:

使用线程池可以减少创建和销毁线程的时间,并节省系统资源。如果我们不使用线程池,将创建许多类似的线程,从而导致“内存不足”或过度切换问题。

4.  [必填]  线程池应由 创建,而不是 。这将使线程池的参数易于理解。它还将降低系统资源耗尽的风险。ThreadPoolExecutor``Executors

注意:

以下是使用 创建线程池所产生的问题:1) 和:

最大请求队列大小。大量请求可能会导致 OOM。
2) 和 :
允许创建的线程数为 。创建过多线程可能会导致 OOM。Executors``FixedThreadPool``SingleThreadPool``Integer.MAX_VALUE``CachedThreadPool``ScheduledThreadPool``Integer.MAX_VALUE

5.  【强制】 不安全,不要定义为静态变量。如果必须,则必须使用锁或类。SimpleDateFormat``DateUtils

正面例子:

使用时注意线程安全。建议按以下方式使用: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 block 之间抛出一些异常,它将无法释放锁,导致其他线程无法获取锁。

注2:

如果 lock() 在 try block 和 lock() 之间无法成功运行,则 unlock() 可能无法正常工作。然后AQS(AbstractQueuedSyncr)方法将被调用(取决于类的实现),并且将抛出IllegalMonitorStateException。

注3:

在 Lock 对象中实现 lock() 方法时,可能会抛出未经检查的异常,从而导致与注释 2 相同的结果。

10.  【必填】 同时修改一条记录时,需要用锁避免更新失败。在应用程序层、缓存中添加锁,或使用版本作为更新标记在数据库中添加乐观锁。

注意:

如果访问冲突概率小于 20%,建议使用乐观锁,否则使用悲观锁。乐观锁定的重试次数应不少于3。

11.  [强制]  使用而不是因为运行多个,以防无法捕获异常,则会杀死所有正在运行的线程。TimeTask``ScheduledExecutorService``Timer``Timer

12.  【推荐】 用于将异步操作转换为同步操作时,每个线程在退出前必须调用方法。确保在线程运行期间捕获任何异常,以允许执行方法。如果主线程无法到达方法,程序将返回,直到超时。CountDownLatch``countdown``countdown``await

注意:

请注意,子线程引发的异常无法被主线程捕获。

13.  【推荐】 避免多线程使用实例。尽管共享此实例是安全的,但在同一种子上的竞争会损害性能。Random

注意:

Random实例包括 和 的实例。java.util.Random``Math.random()

正面例子:

JDK7 之后,可以直接使用 API。但在 JDK7 之前,需要在每个线程中创建实例。ThreadLocalRandom

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``count++

AtomicInteger count = new AtomicInteger(); 
count.addAndGet(1); 

注意:

在 JDK8 中,建议使用,这样可以减少乐观锁定的重试次数,并且具有比 更好的性能。LongAdder``AtomicLong

16 . [供参考]  当容量不足时调整大小可能会因为高并发导致死链和高 CPU 使用率。在开发中避免这种风险。HashMap

17.  [供参考]  无法解决共享对象的更新问题。建议使用由同一线程中的所有操作共享的静态对象。ThreadLocal``ThreadLocal

流控制语句

  1. [必填] 在一个区块中,每个案例都应通过中断/返回完成。如果没有,则应包括一个注释来描述它将在哪种情况下停止。在每个块中,必须存在默认语句,即使它是空的。switch``switch

2.  [必填]  大括号与 ifelsefordo 和 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 层和服务层部署在同一服务器中,则可以省略 DAO 层中的参数检查。
3 ) 私有方法,如果所有参数都经过检查或管理,则只能在内部实现。

代码注释

1.  [强制性]  Javadoc 应该用于类、类变量和方法。格式应为“/** 注释 **/”,而不是“// xxx”。

注意:

在 IDE 中,悬停时可以直接看到 Javadoc,这是提高效率的好方法。

2.  [必填] 抽象方法(包括接口中的方法)应该由Javadoc注释。Javadoc 应该包括方法指令、参数描述、返回值和可能的异常。

3.  [必填项]  每堂课都应包括作者和日期的信息。

4.  [必填] 方法中的单行注释应放在要注释的代码上方,使用 和多行使用 。应仔细注意注释的对齐情况。//``/* */

5.  [必填]  所有枚举类型字段都应注释为 Javadoc 样式。

6.  [推荐] 如果英语无法正确描述问题,可以在评论中使用本地语言。关键字和专有名词应保留为英文。

反例:

将“TCP连接超时”解释为“传输控制协议连接超时”只会使其更难理解。

7.  【推荐】 代码逻辑发生变化时,需要同时更新注释,尤其是参数、返回值、异常和核心逻辑的变化。

8.  [供参考]  注释掉代码时需要添加注释。

注意:

如果代码以后可能会恢复,则需要添加合理的解释。如果没有,请直接删除,因为代码历史记录将由 svn 或 git 记录。

9、 【供参考】 评语要求:
1)能够准确表述设计思路和代码逻辑。
2)能够表示业务逻辑,帮助其他程序员快速理解。没有任何注释的大量代码对读者来说是一场灾难。评论是为自己和他人写的。即使经过很长时间,也可以快速回忆起设计理念。工作可以在需要时由其他人快速接管。

10.  [供参考]  正确的命名和清晰的代码结构是不言自明的。需要避免过多的注释,因为如果代码逻辑发生更改,可能会导致更新工作过多。

反例:

// put elephant into fridge
put(elephant, fridge);  

方法和参数的名称已经表示该方法的作用,因此无需添加额外的注释。

11.  [供参考]  注释中的标签(例如 TODO、FIXME)需要包含作者和时间。标签需要通过频繁扫描及时处理和清除。有时在线故障是由这些未经处理的标签引起的。
1)待办事项:待办事项意味着逻辑需要完成,但尚未完成。实际上,TODO 是 Javadoc 的一员,虽然它还没有在 Javadoc 中实现,但**已经被广泛使用。TODO只能在类,接口和方法中使用,因为它是Javadoc标签。
2)FIXME:FIXME是用来表示代码逻辑不正确或者不起作用的,应及时修复。

其他

1.  【强制】 使用正则表达式时,需要进行预编译,以提高匹配性能。

注意:

不要在方法主体中定义。Pattern pattern = Pattern.compile(.);

2.  【必填】 在速度中使用POJO的属性时,请直接使用属性名称。速度引擎将自动调用 POJO。在布尔属性方面,速度引擎将调用(命名布尔属性时不要使用 is 作为前缀)。getXxx()``isXxx()

注意:

对于包装类,速度引擎将首先调用。Boolean``getXxx()

3.  [必填]  变量从后端传递到 speedocity 引擎时必须添加感叹号,例如 $!{var}.

注意:

如果属性为 null 或不存在,${var} 将直接显示在网页上。

4.  [必填]  返回类型为双精度,取值范围为 0 <=x<1(可能为 0)。如果需要随机整数,请不要将 x 乘以 10,然后将结果四舍五入。正确的方法是使用属于随机对象的或方法。Math.random()``nextInt``nextLong

5.  [必填]  用于获取当前毫秒。请勿使用 .System.currentTimeMillis()``new Date().getTime()

注意:

为了获得更准确的时间,请使用 .在 JDK8 中,使用类来处理时间统计等情况。System.nanoTime()``Instant

6.  [推荐]  最好不要在速度模板文件中包含变量声明、逻辑符号或任何复杂的逻辑。

7.  [推荐]  如果可能的话,初始化任何数据结构时都需要指定 Size,以避免无限增长导致的内存问题。

8.  [推荐] 注意到过时的代码或配置应坚决从项目中移除。

注意:

及时删除过时的代码或配置,避免代码冗余。

正面例子:

对于暂时删除并可能重复使用的代码,请使用 / //  添加合理的注释。

public static void hello() {
    /// Business is stopped temporarily by the owner.
    // Business business = new Business();
    // business.active();
    System.out.println("it's finished");
}

2. 异常和日志

例外

1.  [必填]  不要捕获 JDK 中定义的运行时异常,例如 和 。相反,建议尽可能进行预检查。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 语句将导致异常或导致 try-catch 块中丢弃返回值。

8.  [必填]  要捕获的异常类型需要与已抛出的类型相同或超类。

9.  [推荐]  方法的返回值可以为 null。返回空集合或对象不是强制性的。在 Javadoc 中显式指定该方法何时可能返回 null。调用方需要进行检查以防止 。NullPointerException

注意:

调用方负责检查返回值,并考虑远程调用失败或其他运行时异常发生的可能性。

10.  [推荐]  最常见的错误之一是 。注意以下情况:
1)如果返回类型是原语,则返回包装类的值可能会导致。
反例:取消空值的装箱将抛出 .
2) 数据库查询的返回值可能为
3) 集合中的元素可以为 null,即使返回 false
4) 来自 RPC 的返回值可能为
5) 存储在会话中的数据可能为
6)方法链,如,很可能造成。
正面示例:用于避免空检查和 NPE (Java 8+)。NullPointerException``NullPointerException``public int f() { return Integer }``NullPointerException``Collection.isEmpty()``obj.getA().getB().getC()``NullPointerException``Optional

11.  【推荐】 使用“抛出异常”或“返回错误码”。对于 HTTP 或开放 API 提供程序,必须使用“错误代码”。建议在应用程序内引发异常。对于跨应用程序 RPC 调用,通过封装 isSuccess错误代码和简短错误消息来首选结果。

注意:

返回 RPC 方法的结果的好处:
1) 如果未捕获异常,使用“抛出异常”方法将发生运行时错误。
2)如果没有附加堆栈信息,则分配带有简单错误消息的自定义异常无助于解决问题。如果附加了堆栈信息,则当频繁发生错误时,数据序列化和传输性能损失也是问题。

12.  【推荐】 请勿直接投掷、、或直接投掷。建议使用定义完善的自定义异常,例如 、 等。RuntimeException``Exception``Throwable``DAOException``ServiceException

13.  【供参考】 避免重复代码(不要重复自己,又称 DRY 原则)。

注意:

随意复制和粘贴代码将不可避免地导致代码重复。如果将逻辑保存在一个位置,则在需要时更容易更改。如有必要,将公共代码提取到方法、抽象类甚至共享模块中。

正面例子:

对于具有许多以相同方式验证参数的公共方法的类,最好提取如下方法:

private boolean checkParam (DTO dto) {
    ...
}

原木

1.  【必填】 不要直接在日志系统(Log4j、Logback)中使用API。建议改用日志框架SLF4J中的API,它使用外观模式,有利于保持日志处理的一致性。

import org.slf4j.Logger; 
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Abc.class); 

2.  [必需]  日志文件需要保留至少 15 天,因为某些类型的异常每周都会发生。

3.  【必填】 应用扩展日志的命名规范(如RBI、临时监控、访问日志等):
appName_logType_logName.log
日志类型:推荐分类为统计、描述、监控访问等。 日志名称日志 描述

此方案的优点:文件名显示日志属于哪个应用程序、日志类型以及日志的用途。这也有利于分类和搜索。

正面例子:

用于监视 mppserver 应用程序中时区转换异常的日志文件的名称:mppserver_monitor_timeZoneConvert.log

注意:

建议对日志进行分类。错误日志和业务日志应尽可能分开存储。它不仅便于开发人员查看,而且便于系统监控。

4.  [必需]  跟踪/调试/信息级别的日志必须使用条件输出或占位符。

注意:

logger.debug ("Processing trade with id: " + id + " symbol: " + symbol);如果日志级别为警告,则不会打印上述日志。但是,它将执行字符串连接运算符。 将调用符号的方法,这是对系统资源的浪费。toString()

正面例子:

if (logger.isDebugEnabled()) { 
    logger.debug("Processing trade with id: " + id + " symbol: " + symbol); 
}     

正面例子:

logger.debug("Processing trade with id: {} and symbol : {} ", id, symbol);

5.  [必填]  确保 Log4j 记录器的可加性属性设置为 false,以避免冗余并节省磁盘空间。

正面例子:

<logger name="com.taobao.ecrm.member.config" additivity="false" >

6.  [必填]  异常信息应包含两种类型的信息:上下文信息和异常堆栈。如果不想处理异常,请重新引发异常。

正面例子:

logger.error(various parameters or objects toString + "_" + e.getMessage(), e);

7.  [推荐]  仔细记录日志。有选择地使用信息级别,不要在生产环境中使用调试级别。如果使用警告来记录业务行为,请注意日志的大小。确保服务器磁盘没有超满,并记得及时删除这些日志。

注意:

大量输出无效日志会对系统性能产生一定影响,不利于快速定位问题。请考虑日志:您真的有这些日志吗?您可以执行哪些操作来查看此日志?解决问题容易吗?

8.  [推荐] 级别警告用于记录无效参数,用于在出现问题时跟踪数据。级别错误仅记录系统逻辑错误、异常和其他重要错误消息。

3. MySQL 规则

表架构规则

1.  [必填] 表达真或假概念的列必须命名为is_xxx,其数据类型应为无符号tinyint(1为,0为)。****

注意:

所有具有非负值的列都必须是无符号的。

2.  [必填]  表和列的名称必须由小写字母、数字或下划线组成。不允许以数字开头的名称以及两个下划线之间仅包含数字(没有其他字符)的名称。应谨慎命名列,因为更改列名的成本很高,并且无法在预发布环境中发布。

正面例子:

getter_admin、task_config level3_name

反例:

GetterAdmin, taskConfig, level_3_name

3.  [必填]  不允许使用复数名词作为表名。

  1. 【必填】 关键词,如去角范围匹配延迟等,不宜使用。它可以从MySQL官方文档中引用。

5.  【必填】 主键索引名称应以pk_ 为前缀,后跟列名;唯一索引应通过在其列名前面加上 uk_  来命名;并且正常索引应格式化为 idx_[column_name]。

注意:

PK 表示主键,UK 表示唯一键,IDX 是索引的缩写。

6.  [必填] 小数应输入为十进制不允许浮动双精度

注意:

当存储浮点数和双精度数时,可能会有精度损失,这反过来可能导致不正确的数据比较结果。当要存储的数据范围超出十进制类型涵盖的范围时,建议将整数和小数部分分开存储。

7.  [必填]  如果要存储在该列中的信息长度几乎相同,请使用 char

  1. 【必填】 瓦尔查尔的长度不得超过5000,否则定义为。最好将它们存储在单独的表中,以避免其对其他列的索引效率产生影响。text

9.  [必填]  表格必须包含以下三列:idgmt_create 和 gmt_modified

注意:

id 是主键,它是无符号的 bigint 和自递增的,步长为 1。应DATE_TIME gmt_creategmt_modified的类型。

  1. [推荐] 建议将表名定义为  [table_business_name]_[table_purpose]。

正面例子:

tiger_task / tiger_reader / mpp_config

11.  [推荐]  尝试定义与应用程序名称相同的数据库名称。

12.  [推荐]  更改列含义或添加新的可能状态值后更新列注释。

13.  [推荐]  为了提高搜索性能,可以将一些适当的列冗余存储在多个表中,但必须注意一致性。冗余列不应是:
1) 频繁修改的列。
2) 用很长的 varchar 或文本键入的列。

正面例子:

产品类别名称简短,经常使用,并且几乎从不更改/固定值。它们可以冗余存储在相关表中,以避免联接查询。

14.  【推荐】 仅当单表行数超过 5 万行或表容量超过 2GB 时,才建议进行数据库分片。

注意:

如果预期数据量未达到此等级,请不要在创建表时进行分片。

15.  [供参考]  适当的 char 列长不仅节省了数据库和索引存储空间,还提高了查询效率。

正面例子:

无符号类型可以避免错误地存储负值,但也可以覆盖更大的数据代表性范围。

对象年龄建议的数据类型范围
150岁以内无符号的天印无符号整数:0 到 255
turtle百年历史无符号的斯莫林特无符号整数:0 到 65,535
恐龙 化石数千万年的历史无符号整数无符号整数: 0 到 4.29 亿左右
太阳约5亿年未签名的比格特无符号整数:0 到 10^19 左右

索引规则

1.  [必填]  如果业务逻辑适用,则应使用唯一索引。

注意:

唯一索引对插入效率的负面影响可以忽略不计,但它显著提高了查询速度。此外,根据墨菲定律,即使在应用程序层进行了完整的检查,只要没有唯一索引,仍可能生成脏数据。

2.  [必填]  如果涉及三个以上的表,则不允许加入。要连接的列必须具有绝对相似的数据类型。确保已为要联接的列编制索引。

注意:

即使只连接了 2 个表,也应考虑索引和 SQL 性能。

3.  [必填]  在 varchar 列上添加索引时必须指定索引长度。索引长度应根据数据的分布进行设置。

注意:

通常对于字符列,长度为 20 的索引可以区分 90% 以上的数据,这是通过 count(distinct left(column_name, index_length)) / count() * 计算得出的。

  1. 使用分页搜索时,不允许使用  [必填]  喜欢 '%...' 或 LIKE '%...%'  。**如果确实需要,可以使用搜索引擎。

注意:

索引文件具有 B 树最左边的前缀匹配特征。如果未确定左前缀值,则无法应用索引。

5.  [推荐]  使用 ORDER BY 子句时,请使用索引顺序。ORDER BY 子句的最后一列应位于复合索引的末尾。原因是为了避免file_sort问题,这会影响查询性能。

正面例子:

其中 a=? 和 b=? 按 c 排序; 指数为 : a_b_c </巴西>

反例:

如果查询条件包含范围,则索引顺序不会生效,例如,其中 a>10 按 b 排序; 无法激活索引a_b

6.  [推荐] 使用覆盖索引进行查询,避免搜索索引后进行额外查询。

注意:

如果我们需要检查一本书第11章的标题,我们是否需要转到第11章开始的页面?否,因为目录实际上包含标题,标题用作覆盖索引。

正面例子:

索引类型包括主键索引、唯一索引和公共索引覆盖索引与查询效果有关。当引用解释结果时,使用索引可能会出现在额外的列中。

7.  [推荐] 使用延迟联接子查询来优化具有多个页面的方案。

注意:

MySQL不是绕过**偏移行,而是检索完全偏移+  N行,然后删除偏移行并返回N行。当偏移非常大时,效率非常低。解决方案是限制要返回的页数,或者在页码超过预定义阈值时重写 SQL 语句。

正面例子:

首先快速找到所需的 id 范围,然后连接:
从表 1 中选择 a.*,(从表 1 中选择 id,其中 some_condition LIMIT 100000,20) b 其中 a.id=b.id;

8.  [推荐]  SQL 性能优化的目标是 EXPLAIN 的结果类型达到 REF 级别,或者至少达到 RANGE,或者如果可能的话达到 CONSTS

反例:

请注意 EXPLAIN 结果中的索引类型,因为对数据库索引文件执行完全扫描非常慢,其性能几乎等于全表扫描。

常量:

最多有一个匹配的行,由优化程序读取。它非常快。

裁判:

使用正常索引。

范围:

检索给定的索引范围,当使用 =、<>、>、>=、<、<=、IS NULL、<=>、BETWEEN 或 IN() 运算符将键列与常量进行比较时,可以使用该范围。

9.  [推荐] 添加复合索引时,将最具辨别性的列放在最左边。

正面例子:

对于 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 和非 NULL 无关。

注意:

COUNT() 将空行计数,而 COUNT(column_name) 不考虑空*值行。

2.  [必填]  COUNT(非重复列)计算此列中具有不同值的行数,不包括值。请注意,如果其中一列的所有值均为 NULL, 则 COUNT(非重复列 1,列 2)返回 0,即使另一列包含不同的非 NULL 值也是如此。

3.  【必填】 当一列的所有值均为 NULL 时,COUNT(列) 返回 0,而 SUM(列)返回 NULL, 因此在使用 SUM() 时要注意问题。**NullPointerException

正面例子:

NPE问题可以通过以下方式避免:
从表中选择IF(ISNULL(SUM(g)),0,SUM(g));

4.  [必填]  使用 ISNULL()  检查值。将 NULL 与任何其他值进行比较时,结果将为 NULL

注意:

1) NULL<>NULL 返回 NULL, 而不是 false
2) NULL=NULL 返回 NULL, 而不是 true
3) NULL<>1 返回 NULL, 而不是 true

5.  【必填】 使用分页逻辑对数据库查询进行编码时,一旦计数为0,就应立即返回,避免执行分页查询语句。

6.  [强制] 不允许外级联更新。所有与外键相关的逻辑都应在应用层中处理。

注意:

例如,学生表将student_id作为主键,分数表将student_id作为外键。更新student.student_id时,还会触发score.student_id更新,这称为级联更新外键级联更新适用于单机、低并行系统,不适用于分布式、高并行集群系统。级联更新被强阻止,因为它可能会导致数据库更新风暴。外键会影响数据库插入效率。

7.  [强制]  不允许存储过程。它们难以调试、扩展且不可移植。

8.  【必填项】 更正数据、删除和更新数据库记录时,应先进行 SELECT,以确保数据正确无误。

9.  [推荐]  应避免使用IN条款。如果无法避免,则应仔细评估 IN 子句的记录集大小并将其控制在 1000 以内。

10.  [供参考]  出于全球化的需要,字符应使用 UTF-8 表示和存储,并注意字符数计数。

注意:

选择长度(“轻松工作”);返回 12
选择CHARACTER_LENGTH(“轻松工作”);返回 4
如果需要,请使用 UTF8MB4 编码来存储表情符号,同时考虑到它与 UTF-8 的区别。

11.  [供参考]  编码时不建议使用 TRUNCATE,即使它比 DELETE 快并且使用更少的系统、事务日志资源。由于 TRUNCATE 没有事务或触发数据库触发器,因此可能会出现问题。

注意:

在功能方面,TRUNCATE TABLE 类似于没有 WHERE 子句的 DELETE

ORM规则

1.  【必填】 查询时应指定特定的列名,而不是使用 *。

注意:

1) * 增加解析成本。
2)在添加或删除查询列时,可能会引入与结果映射不匹配的情况。

2.  [必填]  POJO 类的布尔属性名称不能以 is 为前缀,而 DB 列名应**以 is 为前缀。属性和列之间的映射是必需的。

注意:

请参考 POJO 类和 DB 列定义的规则,结果映射中需要映射MyBatis 生成器生成的代码可能需要调整。

3.  【必填项】 不要使用 resultClass 作为返回参数,即使所有类属性名都与 DB 列相同,也需要相应的 DO 定义。

注意: 需要映射配置来分离 DO 定义和表列,这反过来又便于维护。

4.  [必填项]  谨慎使用xml配置中的参数。不要代替 、 。SQL 注入可能以这种方式发生。${}``#{}``#param#

5.  [必填]  不建议使用 iBatis 内置 queryForList(字符串语句名称、int start、int size)。

注意:

它可能会导致 OOM 问题,因为它的实现是检索语句名称对应的 SQL 语句的所有数据库记录,然后通过 subList 应用开始,大小子集。

正面例子:

sqlmap.xml中使用#start# , #size#

Map<String, Object> map = new HashMap<String, Object>();  
map.put("start", start);  
map.put("size", size);  

6.  [必填项]  不要使用 HashMap 或 HashTable 作为数据库查询结果类型。

7.  [必填]  gmt_modified列应与数据库记录更新同时使用当前时间戳进行更新。

8.  [推荐]  不要定义接受 POJO 作为输入参数的通用表更新接口,并且始终更新表集 c1=值 1, c2=值 2, c3=值 3, ... 无论要更新的列是什么。最好不要更新不相关的列,因为它容易出错,效率不高,并且会增加二进制日志存储。

9.  [供参考]  不要过度使用 @Transactional。由于事务影响数据库的QPS,需要考虑相关的回滚,包括缓存回滚、搜索引擎回滚、消息编造、统计调整等。

10.  [供参考]  比较值   是一个常量(通常是一个数字),用于与属性值进行比较。  表示在属性不为空且不为空时执行相应的逻辑。  表示在属性不为 null 时执行相关逻辑。

4. 项目规范

应用层

1.  【推荐】 上层默认依赖下层。箭头表示直接依赖。例如:开放接口可以依赖于 Web 层,也可以直接依赖服务层等:

  • 开放接口: 在此层中,服务被封装为通过 Web 层公开为 RPC 接口或 HTTP 接口;该层还实现了网关安全控制、流量控制等。
  • 视图: 在此图层中,每个终端的模板渲染并执行。渲染方式包括速度渲染、JS 渲染、JSP 渲染和移动显示等。
  • 网页图层: 该层主要用于实现前向访问控制、基础参数验证或不可复用的服务。
  • 服务层: 在此层中实现具体的业务逻辑。
  • 管理器层: 该层是常见的业务流程层,包含以下功能:
    1)封装第三方服务,对返回值和异常进行预处理;
    2)服务层通用能力的沉淀,如缓存解决方案、中间件通用处理等;
    3)与DAO层交互,包括多个DAO类的组合和重用**。
  • DAO 层:数据访问层,与 MySQL、Oracle 和 HBase 交互的数据。
  • 外部接口或第三方平台: 该层包括来自其他部门或公司的 RPC 开放接口。

2.  [供参考]  DAO 层中的许多异常无法使用细粒度异常类来捕获。推荐的方法是使用 、 和 。在这些情况下,无需打印日志,因为日志应该已被捕获并打印在管理器层/服务层中。
有关服务层异常的日志必须尽可能多地记录参数信息,以使调试更简单。
如果管理器层和服务部署在同一台服务器中,日志逻辑应与 DAO 层一致。如果单独部署,日志逻辑应彼此一致。
在 Web 层中,无法抛出异常,因为它已经在顶层,无法处理异常情况。如果异常可能导致在呈现页面时失败,则应将页面重定向到包含友好错误信息的友好错误页面。
开放接口中,应使用错误代码错误消息来处理异常。catch (Exception e)``throw new DAOException(e)

3.  [供参考]  域模型的层次:

  • DO (数据对象): 与数据库表结构相对应,数据源对象通过 DAO 层向上传输。
  • DTO(数据传输对象): 由服务层和管理器向上传输的对象。
  • BO(业务对象): 封装业务逻辑的对象,可由服务层输出。
  • 查询:承载上层查询请求的数据查询对象。注意:如果查询条件超过 2 个,则禁止使用。Map
  • VO(查看对象): 显示层中使用的对象,通常从 Web 层传输。

库规格

1.  [必填] GAV定义规则: 1)G组ID:
,最多4个级别com.{company/BU}.{business line}.{sub business line}

注意:

{公司/BU} 例如:阿里巴巴、淘宝、天猫、速卖通等业务单元级别;子业务线是可选的。

正面例子:

com.taobao.tddl   com.alibaba.sourcing.multilang

2) 一个rtifactID:产品名称 - 模块名称。

正面例子:

tc-client / uic-api / tair-tool

3)V:请参考以下内容

2.  [必填]  库命名约定: ..
1)质版本号:当有不兼容的API修改时需要更改,或者新功能可以改变产品方向。
2) 次要版本号:更改为向后兼容修改。
3) 修订号:为修复错误或添加不修改方法签名并保持 API 兼容性的功能而更改。prime version number``secondary version number``revision number

注意:

初始版本必须是 1.0.0,而不是 0.0.1。

3.  [强制] 在线应用程序不应依赖SNAPSHOT版本(安全包除外);必须从中央存储库验证正式版本,以确保发布版本号具有连续性。不允许覆盖版本号。

注意:

避免使用 SNAPSHOT 是为了保证应用程序发布的幂等性。此外,它可以在打包时加快代码编译。如果当前版本为 1.3.3,则下一个版本的编号可以是 1.3.4、1.4.0 或 2.0.0。****

4.  [必填]  添加或升级库时,如果没有必要,请保持依赖库的版本不变。如果有变化,必须正确评估和检查。建议使用命令 dependency:resolve 来比较前后的信息。如果结果相同,请使用命令dependency:tree找出差异,并使用 <排除> 来消除不必要的库。

5.  [必填]  枚举类型可以定义或用于库中的参数类型,但不能用于接口返回类型(也不允许包含枚举类型的 POJO)。

6.  【强制】 当使用一组库时,需要定义一个统一的版本变量,以避免版本号的不一致。

注意:

使用相同版本的 、 时,建议使用统一版本变量 ${spring.version}。springframework-core``springframework-context``springframework-beans

7.  [必填]  对于相同的 GroupId 和工件 ID,子项目中的版本必须相同。

注意:

在本地调试期间,使用子项目中指定的版本号。但是当合并到战争中时,lib 目录中只应用一个版本号。因此,可能会出现脱机调试正确,但在联机启动后发生故障的情况。

8.  [推荐]  所有 POM 文件中的依赖声明应放在 <依赖> 块中。依赖项的版本应在 <依赖项管理> 块中指定。

注意:

在  <依赖项管理中> 指定了依赖项的版本,但不导入依赖项。因此,在子项目中,需要显式声明依赖项,其版本和范围是从父 POM 读取的。默认情况下,主 POM 中  <依赖项>  中的所有声明将自动导入并由所有子项目继承。

9.  【推荐】 建议库不要包含配置,至少不要增加配置项。

10.  【供参考】 为避免库的依赖冲突,发布者应遵循以下原则: 1)简单可控:
删除所有不必要的API和依赖,只包含服务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 服务器支持的最大文件描述符数为 1024。当并发连接数很大时,由于缺少文件描述符,很容易出现“打开太多文件”错误,这会导致无法建立新连接。

3.  [推荐] 设置JVM参数,使JVM在发生OOM时输出转储**信息。-XX:+HeapDumpOnOutOfMemoryError

注意:

OOM 并不经常发生,几个月才发生一次。发生错误时打印的转储信息对于错误检查非常有价值。

4.  [供参考]  使用转发进行内部重定向,使用 URL 组装工具进行外部重定向。否则会出现URL维护不一致的问题和潜在的安全风险。

5. 安全规范

1.  [必填] 用户拥有的页面或功能必须经过授权。

注意:

防止未经授权检查访问和操纵他人数据,例如查看或修改他人订单。

2.  【必填项】 不允许直接显示用户敏感数据。显示的数据必须脱敏。

注意:

个人电话号码应显示为:158****9119。中间的 4 位数字是隐藏的,以防止隐私泄露。

3.  【必填】 用户输入SQL参数应仔细检查或受METADATA限制,防止SQL注入。禁止通过字符串串联 SQL 访问数据库。

4.  【必填项】 用户输入的任何参数都必须经过验证检查。

注意:

忽略参数检查可能会导致:

  • 由于页面大小过大而导致内存泄漏
  • 由于恶意排序,数据库查询速度较慢
  • 任意重定向
  • SQL注入
  • 反序列化注入
  • 重做

注意:

在 Java 中,正则表达式用于验证客户端输入。一些正则表达式可以毫无问题地验证常见的用户输入,但如果攻击者使用专门构造的字符串进行验证,则可能导致死循环。

5.  【强制】 禁止在没有安全过滤或正确转义的情况下将用户数据输出到HTML页面。

6.  [必填] 表单和AJAX提交必须通过CSRF安全检查进行过滤。

注意:

CSRF(跨站点请求伪造)是一种常见的编程缺陷。对于存在CSRF泄漏的应用/网站,只要受害用户访问,攻击者就可以提前构建URL,修改数据库中的用户参数,恕不另行通知。

7.  【强制】 有必要使用正确的反重放限制,如号码限制、疲劳控制、验证码检查,避免滥用平台资源,如短信、电子邮件、电话、订单、支付等。

注意:

例如,如果向手机发送验证码时没有时间和频率限制,可能会打扰用户,可能会浪费短信平台资源。

8.  【推荐】 在用户生成内容(如发帖、评论、即时消息)的场景下,必须应用反诈骗词过滤等风控策略。