jeff
2024-04-20 36f69a3493ae7224fdbdfbb2b00071f2f843118f
后端开发规范.md
@@ -136,10 +136,13 @@
4) POJO 是 DO/DTO/BO/VO 的统称,禁止命名成 xxxPOJO。
## (二)常量定义 
### 1. 【强制】不允许任何魔法值(即未经定义的常量)直接出现在代码中。
反例:String key = "Id#taobao_" + tradeId;
 cache.put(key, value);
### 2. 【强制】long 或者 Long 初始赋值时,使用大写的 L,不能是小写的 l,小写容易跟数字 1 混
淆,造成误解。
反例:
``` java
String key = "Id#taobao_" + tradeId;
cache.put(key, value);
```
### 2. 【强制】long 或者 Long 初始赋值时,使用大写的 L,不能是小写的 l,小写容易跟数字 1 混淆,造成误解。
说明:Long a = 2l; 写的是数字的 21,还是 Long 型的 2?
### 3. 【推荐】不要使用一个常量类维护所有常量,按常量功能进行归类,分开维护。
说明:大而全的常量类,非得使用查找功能才能定位到修改的常量,不利于理解和维护。
@@ -197,9 +200,10 @@
   } else { 
      System.out.println("ok"); 
      // 在右大括号后直接结束,则必须换行 
   }
   }
}
```
### 6. 【强制】注释的双斜线与注释内容之间有且仅有一个空格。
正例:// 注释内容,注意在//和注释内容之间有一个空格。
### 7. 【强制】单行字符数限制不超过 120 个,超出需要换行,换行时遵循如下原则:
@@ -217,6 +221,7 @@
.append("huang")... 
.append("huang");
```
反例:
``` java
StringBuffer sb = new StringBuffer(); 
@@ -227,11 +232,13 @@
method(args1, args2, args3, ... 
, argsX);
```
### 8. 【强制】方法参数在定义和传入时,多个参数逗号后边必须加空格。
正例:下例中实参的"a",后边必须要有一个空格。
``` java
method("a", "b", "c"); 
```
### 9. 【强制】IDE 的 text file encoding 设置为 UTF-8; IDE 中文件的换行符使用 Unix 格式,不要使用 Windows 格式。
 
### 10. 【推荐】没有必要增加若干空格来使某一行的字符与上一行对应位置的字符对齐。
@@ -242,6 +249,7 @@
float c = 5F; 
StringBuffer sb = new StringBuffer(); 
```
说明:增加 sb 这个变量,如果需要对齐,则给 a、b、c 都要增加几个空格,在变量比较多的
情况下,是一种累赘的事情。
### 11. 【推荐】方法体内的执行语句组、变量的定义语句组、不同的业务逻辑之间或者不同的语义
@@ -269,10 +277,12 @@
``` java
"test".equals(object);
```
反例:
``` java
object.equals("test");
```
说明:推荐使用 java.util.Objects#equals(JDK7 引入的工具类)
### 7. 【强制】所有的相同类型的包装类对象之间值的比较,全部使用 equals 方法比较。
说明:对于 Integer var = ? 在-128 至 127 范围内的赋值,Integer 对象是在IntegerCache.cache 产生,会复用已有对象,这个区间内的 Integer 值可以直接使用==进行
@@ -308,6 +318,7 @@
// 预期大于 3,结果是 3
System.out.println(ary.length); 
```
### 14. 【推荐】当一个类有多个构造方法,或者多个同名方法,这些方法应该按顺序放置在一起,
便于阅读,此条规则优先于第 15 条规则。
### 15. 【推荐】 类内方法定义顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter方法。 
@@ -392,6 +403,7 @@
String[] array = new String[list.size()]; 
array = list.toArray(array); 
```
反例:直接使用 toArray 无参方法存在问题,此方法返回值只能是 Object[]类,若强转其它
类型数组将出现 ClassCastException 错误。
### 5. 【强制】使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方
@@ -420,6 +432,7 @@
   } 
}
```
反例:
``` java
List<String> list = new ArrayList<String>(); 
@@ -431,6 +444,7 @@
   } 
}
```
说明:以上代码的执行结果肯定会出乎大家的意料,那么试一下把“1”换成“2”,会是同样的
结果吗?
### 8. 【强制】 在 JDK7 版本及以上,Comparator 要满足如下三个条件,不然 Arrays.sort,
@@ -448,6 +462,7 @@
   } 
};
```
### 9. 【推荐】集合初始化时,指定集合初始值大小。
说明:HashMap 使用 HashMap(int initialCapacity) 初始化,
正例:initialCapacity = (需要存储的元素个数 / 负载因子) + 1。注意负载因子(即 loader 
@@ -489,6 +504,7 @@
   }
```
### 3. 【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资
源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者
@@ -511,6 +527,7 @@
   } 
}; 
```
说明:如果是 JDK8 的应用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar,
DateTimeFormatter 代替 SimpleDateFormat,官方给出的解释:simple beautiful strong
immutable thread-safe。
@@ -554,6 +571,7 @@
   // other methods and fields... 
}
```
### 13. 【参考】volatile 解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题,
但是如果多写,同样无法解决线程安全问题。如果是 count++操作,使用如下类实现:
AtomicInteger count = new AtomicInteger(); count.addAndGet(1); 如果是 JDK8,推
@@ -578,6 +596,7 @@
   return obj; 
```
// 接着写 else 的业务逻辑代码; 
说明:如果非得使用 if()...else if()...else...方式表达逻辑,【强制】避免后续代码维
护困难,请勿超过 3 层。
@@ -598,6 +617,7 @@
   return;
}
```
### 4. 【推荐】除常用方法(如 getXxx/isXxx)等外,不要在条件判断中执行其它复杂的语句,将复
杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性。
说明:很多 if 语句内的逻辑相当复杂,阅读者需要分析条件表达式的最终结果,才能明确什么
@@ -610,12 +630,14 @@
   ...
```
反例:
``` java
if ((file.open(fileName, "w") != null) && (...) || (...)) {
   ...
}
```
### 5. 【推荐】循环体中的语句要考量性能,以下操作尽量移至循环体外处理,如定义对象、变量、
获取数据库连接,进行不必要的 try-catch 操作(这个 try-catch 是否可以移至循环体外)。
### 6. 【推荐】接口入参保护,这种场景常见的是用于做批量操作的接口。
@@ -667,7 +689,8 @@
``` java
// put elephant into fridge 
put(elephant, fridge); 
```
```
方法名 put,加上两个有意义的变量名 elephant 和 fridge,已经说明了这是在干什么,语
义清晰的代码不需要额外的注释。
### 11. 【参考】特殊注释标记,请注明标记人与标记时间。注意及时处理这些标记,通过标记扫描,
@@ -712,6 +735,7 @@
   ...
}
```
反例
``` java
try {
@@ -720,6 +744,7 @@
   ...
}
```
### 2. 【强制】异常不要用来做流程控制,条件控制,因为异常的处理效率比条件分支低。
### 3. 【强制】对大段代码进行 try-catch,这是不负责任的表现。catch 时请分清稳定代码和非稳
定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的 catch 尽可能进行区分
@@ -748,6 +773,7 @@
   return Integer对象
 }
 ```
 如果为 null,自动解箱抛 NPE。
 
2) 数据库的查询结果可能为 null。
@@ -776,6 +802,7 @@
   ...
```
## (二)日志规约 
### 1. 【强制】应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架
SLF4J 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
@@ -784,6 +811,7 @@
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Abc.class); 
```
### 2. 【强制】日志文件推荐至少保存 15 天,因为有些异常具备以“周”为频次发生的特点。
### 3. 【强制】应用中的扩展日志(如打点、临时监控、访问日志等)命名方式:
appName_logType_logName.log。logType:日志类型,推荐分类有
@@ -805,10 +833,12 @@
   logger.debug("Processing trade with id: " + id + " and symbol: " + symbol); 
```
正例:(占位符)
``` java
logger.debug("Processing trade with id: {} and symbol : {} ", id, symbol); 
```
### 5. 【强制】避免重复打印日志,浪费磁盘空间,务必在 log4j.xml 中设置 additivity=false。
正例:\<logger name="com.taobao.dubbo.config" additivity="false"> 
### 6. 【强制】异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过
@@ -947,7 +977,6 @@
### 10. 【推荐】表的命名最好是加上“业务名称_表的作用”。
正例:alipay_task / force_project / trade_config
### 11. 【推荐】库名与应用名称尽量一致。
### 12. 【推荐】如果修改字段含义或对字段表示的状态追加时,需要及时更新字段注释。
### 13. 【推荐】字段允许适当冗余,以提高查询性能,但必须考虑数据一致。冗余字段应遵循:
@@ -977,7 +1006,6 @@
实际文本区分度决定索引长度即可。
说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为 20 的索引,区分
度会高达 90%以上,可以使用 count(distinct left(列名, 索引长度))/count(*)的区分度
来确定。
### 4. 【强制】页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。
@@ -1013,7 +1041,6 @@
说明:存在非等号和等号混合判断条件时,在建索引时,请把等号条件的列前置。如:where a>? 
and b=? 那么即使 a 的区分度更高,也必须把 b 放在索引的最前列。
### 10. 【推荐】防止因字段类型不同造成的隐式转换,导致索引失效。
### 11. 【参考】创建索引时避免有如下极端误解:
1)宁滥勿缺。认为一个查询就需要建一个索引。
@@ -1041,10 +1068,8 @@
级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻
塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。
### 7. 【强制】禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。
### 8. 【强制】数据订正时,删除和修改记录时,要先 select,避免出现误删除,确认无误才能执
行更新语句。
### 8. 【强制】数据订正时,删除和修改记录时,要先 select,避免出现误删除,确认无误才能执行更新语句。
### 9. 【推荐】in 操作能避免则避免,若实在避免不了,需要仔细评估 in 后边的集合元素数量,控
制在 1000 个之内。
### 10. 【参考】如果有全球化需要,所有的字符存储与表示,均以 utf-8 编码,注意字符统计函数
@@ -1077,15 +1102,13 @@
### 6. 【强制】不允许直接拿 HashMap 与 Hashtable 作为查询结果集的输出。
说明:resultClass=”Hashtable”,会置入字段名和属性值,但是值的类型不可控。
### 7. 【强制】更新数据表记录时,必须同时更新记录对应的 gmt_modified 字段值为当前时间。
### 8. 【推荐】不要写一个大而全的数据更新接口。传入为 POJO 类,不管是不是自己的目标更新字
段,都进行 update table set c1=value1,c2=value2,c3=value3; 这是不对的。执行 SQL
时,不要更新无改动的字段,一是易出错;二是效率低;三是增加 binlog 存储。
### 9. 【参考】@Transactional 事务不要滥用。事务会影响数据库的 QPS,另外使用事务的地方需
要考虑各方面的回滚方案,包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等。
### 10. 【参考】\<isEqual>中的 compareValue 是与属性值对比的常量,一般是数字,表示相等时带
上此条件;\<isNotEmpty>表示不为空且不为 null 时执行;\<isNotNull>表示不为 null 值时
### 10. 【参考】\<isEqual>中的 compareValue 是与属性值对比的常量,一般是数字,表示相等时带上此条件;\<isNotEmpty>表示不为空且不为 null 时执行;\<isNotNull>表示不为 null 值时
执行。 
# 六、工程结构 
@@ -1171,20 +1194,16 @@
正例:在 linux 服务器上请通过变更/etc/sysctl.conf 文件去修改该缺省值(秒):
net.ipv4.tcp_fin_timeout = 30
### 2. 【推荐】调大服务器所支持的最大文件句柄数(File Descriptor,简写为 fd)。
说明:主流操作系统的设计是将 TCP/UDP 连接采用与文件一样的方式去管理,即一个连接对
应于一个 fd。主流的 linux 服务器默认所支持最大 fd 数量为 1024,当并发连接数很大时很
容易因为 fd 不足而出现“open too many files”错误,导致新的连接无法建立。 建议将 linux
服务器所支持的最大句柄数调高数倍(与服务器的内存数量相关)。
### 3. 【推荐】给 JVM 设置-XX:+HeapDumpOnOutOfMemoryError 参数,让 JVM 碰到 OOM 场景时输出
dump 信息。
### 3. 【推荐】给 JVM 设置-XX:+HeapDumpOnOutOfMemoryError 参数,让 JVM 碰到 OOM 场景时输出dump 信息。
说明:OOM 的发生是有概率的,甚至有规律地相隔数月才出现一例,出现时的现场信息对查错
非常有价值。
### 4. 【推荐】在线上生产环境,JVM 的 Xms 和 Xmx 设置一样大小的内存容量,避免在 GC 后调整堆
大小带来的压力。
### 5. 【参考】服务器内部重定向使用 forward;外部重定向地址使用 URL 拼装工具类来生成,否则
会带来 URL 维护不一致的问题和潜在的安全风险。
### 4. 【推荐】在线上生产环境,JVM 的 Xms 和 Xmx 设置一样大小的内存容量,避免在 GC 后调整堆大小带来的压力。
### 5. 【参考】服务器内部重定向使用 forward;外部重定向地址使用 URL 拼装工具类来生成,否则会带来 URL 维护不一致的问题和潜在的安全风险。
 
# 附 1:版本历史