(
一
)
异常处理
1.
【强制】
Java
类库中定义的一类
RuntimeException
可以通过预先检查进行规避,而不应该
通过
catch
来处理,比如:
IndexOutOfBoundsException
,
NullPointerException
等等。
说明:
无法通过预检查的异常除外,如在解析一个外部传来的字符串形式数字时,通过
catch
NumberFormatException
来实现。
正例:
if (obj != null) {...}
反例:
try { obj.method() } catch (NullPointerException e) {...}
2.
【强制】异常不要用来做流程控制,条件控制,因为异常的处理效率比条件分支低。
3.
【强制】对大段代码进行
try
-
catch
,这是不负责任的表现。
catch
时请分清稳定代码和非稳
定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的
catch
尽可能进行区分
异常类型,再做对应的异常处理。
4.
【强制】捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请
将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的
内容。
5.
【强制】有
try
块放到了事务代码中,
catch
异常后,如果需要回滚事务,一定要注意手动回
滚事务。
6.
【强制】
finally
块必须对资源对象、流对象进行关闭,有异常也要做
try
-
catch
。
说明:
如果
JDK
7
及以上,可以使用
try
-
with
-
resources
方式。
7.
【强制】不能在
finally
块中使用
return
,
finally
块中的
return
返回后方法结束执行,不
会再执行
try
块中的
return
语句。
8.
【强制】捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类。
说明:
如果预期对方抛的是绣球,实际接到的是铅球,就会产生意外情况。
9.
【推荐】方法的
返回值可以为
null
,不强制返回空集合,或者空对象等,必须添加注释充分
说明什么情况下会返回
null
值。调用方需要进行
null
判断防止
NPE
问题。
说明:
本手册明确
防止
NPE
是调用者的责任。即使被调用方法返回空集合或者空对象,对调用
者来说,也并非高枕无忧,必须考虑到远程调用失败、序列化失败、运行时异常等场景返回
null
的情况。
10.
【推荐】防止
NPE
,是程序员的基本修养,注意
NPE
产生的场景:
1
)
返回类型为基本数据类型,
return
包装数据类型的对象时,自动拆箱有可能产生
NPE
。
反例:
public int f()
{
return Integer
对象
}
, 如果为
null
,自动解箱抛
NPE
。
2
)
数据库的查询结果可能为
null
。
3
)
集合里的元素即使
isNotEmpty
,取出的数据元素也可能为
null
。
4
)
远程调用返回对象时,一律要求进行空指针判断,防止
NPE
。
5
)
对于
Session
中获取的数据,建议
NPE
检查,避免空指针。
6
)
级联调用
obj
.
getA()
.
getB()
.
getC()
;
一连串调用,易产生
NPE
。
正例:
使用
JDK8
的
Optional
类来防止
NPE
问题。
11.
【推荐】定义时区分
unchecked
/
checked
异常,避免直接抛出
new RuntimeException()
,
更不允许抛出
Exception
或者
Throwable
,应使用有业务含义的自定义异常。推荐业界已定义
过的自定义异常,如:
DAOException
/
ServiceException
等。
12.
【参考】在代码中使用
“
抛异常
”
还是
“
返回错误码
”
,对于公司外的
http
/
api
开放接口必须
使用
“
错误码
”
;
而应用内部推荐异常抛出
;
跨应用间
RPC
调用优先考虑
使用
Result
方式
,封
装
isSuccess()
方法
、
“
错误码
”
、
“
错误简短信息
”
。
说明:
关于
RPC
方法返回方式使用
Result
方式的理由:
1
)
使用抛异常返回方式,调用方如果没有捕获到就会产生运行时错误。
2
)
如果不加栈信息,只是
new
自定义异常,加入自己的理解的
error message
,对于调用
端解决问题的帮助不会太多。如果加了栈信息,在频繁调用出错的情况下,数据序列化和传输
的性能损耗也是问题。
13.
【参考】避免出现重复的代码
(
Don
’
t Repeat Yourself
)
,即
DRY
原则。
说明:
随意复制和粘贴代码,必然会导致代码的重复,在以后需要修改时,需要修改所有的副
本,容易遗漏。必要时抽取共性方法,或者抽象公共类,甚至是组件化。
正例:
一个类中有多个
public
方法,都需要进行数行相同的参数校验操作,这个时候请抽取:
private boolean checkParam(DTO dto) {...}
(二)MYSQL
建表规约
1.
【强制】表达是与否概念的字段,必须使用
is
_
xxx
的方式命名,数据类型是
unsigned tinyint
(
1
表示是,
0
表示否
)
。
说明:
任何字段如果为非负数,必须是
unsigned
。
正例:
表达逻辑删除的字段名
is_deleted
,
1
表示删除,
0
表示未删除。
2.
【强制】表名、字段名必须使用小写字母或数字
,
禁止出现数字开头,禁止两个下划线中间只
出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。
说明:
MySQL
在
Windows
下不区分大小写,但在
Linux
下默认是区分大小写。因此,数据库
名、表名、字段名,都不允许出现任何大写字母,避免节外生枝。
正例:
aliyun
_
admin
,
rdc
_
config
,
level
3_
name
反例:
AliyunAdmin
,
rdcConfig
,
level
_3_
name
3.
【强制】表名不使用复数名词。
说明:
表名应该仅仅表示表里面的实体内容,不应该表示实体数量,对应于
DO
类名也是单数
形式,符合表达习惯。
4.
【强制】禁用保留字,如
desc
、
range
、
match
、
delayed
等,请参考
MySQL
官方保留字。
5.
【强制】主键索引名为
pk_
字段名;唯一索引名为
uk
_
字段名
;
普通索引名则为
idx
_
字段名。
说明:
pk_
即
primary key
;
uk
_
即
unique key
;
idx
_
即
index
的简称。
6.
【强制】小数类型为
decimal
,禁止使用
float
和
double
。
说明:
float
和
double
在存储的时候,存在精度损失的问题,很可能在值的比较时,得到不
正确的结果。如果存储的数据范围超过
decimal
的范围,建议将数据拆成整数和小数分开存储。
7.
【强制】如果存储的字符串长度几乎相等,使用
char
定长字符串类型。
8.
【强制】
varchar
是可变长字符串,不预先分配存储空间,长度不要超过
5000
,如果存储长
度大于此值,定义字段类型为
text
,独立出来一张表,用主键来对应,避免影响其它字段索
引效率。
9.
【强制】表必备三字段:
id
,
gmt
_
create
,
gmt
_
modified
。
说明:
其中
id
必为主键,类型为
unsigned bigint
、单表时自增、步长为
1
。
gmt_create,
gmt_modified
的类型均为
date_time
类型,前者现在时表示主动创建,后者过去分词表示被
动更新。
10.
【推荐】表的命名最好是加上
“
业务名称
_
表的作用
”
。
正例:
alipay
_
task
/
force
_
project
/
trade
_
config
11.
【推荐】库名与应用名称尽量一致。
12.
【推荐】如果修改字段含义或对字段表示的状态追加时,需要及时更新字段注释。
13.
【推荐】字段允许适当冗余,以提高查询性能,但必须考虑数据一致。冗余字段应遵循:
1
)
不是频繁修改的字段。
2
)
不是
varchar
超长字段,更不能是
text
字段。
正例:
商品类目名称使用频率高,字段长度短,名称基本一成不变,可在相关联的表中冗余存
储类目名称,避免关联查询。
14.
【推荐】单表行数超过
500
万行或者单表容量超过
2
GB
,才推荐进行分库分表。
说明:
如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。
15.
【参考】合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检
索速度。
正例:
如下表,其中无符号值可以避免误存负数,且扩大了表示范围。
(
二
)
索引规约
1.
【强制】业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。
说明:
不要以为唯一索引影响了
insert
速度,这个速度损耗可以忽略,但提高查找速度是明
显的
;
另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必
然有脏数据产生。
2.
【强制】超过三个表禁止
join
。需要
join
的字段,数据类型必须绝对一致
;
多表关联查询时,
保证被关联的字段需要有索引。
说明:
即使双表
join
也要注意表索引、
SQL
性能。
3.
【强制】在
varchar
字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据
实际文本区分度决定索引长度即可。
说明:
索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为
20
的索引,区分
度会高达
90%
以上,可以使用
count(distinct left(
列名
,
索引长度
))
/
count(
*
)
的区分度
来确定。
4.
【强制】页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。
说明:
索引文件具有
B
-
Tree
的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索
引。
5.
【推荐】如果有
order by
的场景,请注意利用索引的
有序性
。
order by
最后的字段是组合
索引的一部分,并且放在索引组合顺序的最后,避免出现
file
_
sort
的情况,影响查询性能。
正例:
where a
=?
and b
=?
order by c;
索引:
a
_
b
_
c
反例:
索引中有范围查找,那么索引有序性无法利用,如:
WHERE a
>10
ORDER BY b;
索引
a
_
b
无法排序。
6.
【推荐】利用覆盖索引来进行查询操作,避免回表。
说明:
如果一本书需要知道第
11
章是什么标题,会翻开第
11
章对应的那一页吗?目录浏览
一下就好,这个目录就是起到覆盖索引的作用。
正例:
能够建立索引的种类:主键索引、唯一索引、普通索引,而覆盖索引是一种查询的一种
效果,用
explain
的结果,
extra
列会出现:
using index
。
7.
【推荐】利用延迟关联或者子查询优化超多分页场景。
说明:
MySQL
并不是跳过
offset
行,而是取
offset
+
N
行,然后返回放弃前
offset
行,返回
N
行,那当
offset
特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过
特定阈值的页数进行
SQL
改写。
正例:
先快速定位需要获取的
id
段,然后再关联:
SELECT a.* FROM
表
1 a, (select id from
表
1 where
条件
LIMIT 100000,20 ) b where a.id=b.id
8.
【推荐】
SQL
性能优化的目标:至少要达到
range
级别,要求是
ref
级别,如果可以是
consts
最好。
说明:
1
)
consts
单表中最多只有一个匹配行
(
主键或者唯一索引
)
,在优化阶段即可读取到数据。
2
)
ref
指的是使用普通的索引
(
normal index
)
。
3
)
range
对索引进行范围检索。
反例:
explain
表的结果,
type
=
index
,索引物理文件全扫描,速度非常慢,这个
index
级
别比较
range
还低,与全表扫描是小巫见大巫。
9.
【推荐】建组合索引的时候,区分度最高的在最左边。
正例:
如果
where a
=?
and b
=?
,
a
列的几乎接近于唯一值,那么只需要单建
idx
_
a
索引即
可。
说明:
存在非等号和等号混合判断条件时,在建索引时,请把等号条件的列前置。如:
where a
>?
and b
=?
那么即使
a
的区分度更高,也必须把
b
放在索引的最前列。
10.
【推荐】防止因字段类型不同造成的隐式转换,导致索引失效。
11.
【参考】创建索引时避免有如下极端误解:
1
)
宁滥勿缺。认为一个查询就需要建一个索引。
2
)
宁缺勿滥。认为索引会消耗空间、严重拖慢更新和新增速度。
3
)
抵制惟一索引。认为业务的惟一性一律需要在应用层通过
“
先查后插
”
方式解决。
(
三
)SQL
语句
1.
【强制】不要使用
count(
列名
)
或
count(
常量
)
来替代
count(
*
)
,
count(
*
)
是
SQL
92
定义的
标准统计行数的语法,跟数据库无关,跟
NULL
和非
NULL
无关。
说明:
count(
*
)
会统计值为
NULL
的行,而
count(
列名
)
不会统计此列为
NULL
值的行。
2.
【强制】
count(distinct col)
计算该列除
NULL
之外的不重复行数,注意
count(distinct
col
1,
col
2
)
如果其中一列全为
NULL
,那么即使另一列有不同的值,也返回为
0
。
3.
【强制】当某一列的值全是
NULL
时,
count(col)
的返回结果为
0
,但
sum(col)
的返回结果为
NULL
,因此使用
sum()
时需注意
NPE
问题。
正例:
可以使用如下方式来避免
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.
【强制】在代码中写分页查询逻辑时,若
count
为
0
应直接返回,避免执行后面的分页语句。
6.
【强制】不得使用外键与级联,一切外键概念必须在应用层解决。
说明:
以学生和成绩的关系为例,学生表中的
student
_
id
是主键,那么成绩表中的
student
_
id
则为外键。如果更新学生表中的
student
_
id
,同时触发成绩表中的
student
_
id
更新,即为
级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群
;
级联更新是强阻
塞,存在数据库更新风暴的风险
;
外键影响数据库的插入速度。
7.
【强制】禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。
8.
【强制】数据订正时,删除和修改记录时,要先
select
,避免出现误删除,确认无误才能执
行更新语句。
9.
【推荐】
in
操作能避免则避免,若实在避免不了,需要仔细评估
in
后边的集合元素数量,控
制在
1000
个之内。
10.
【参考】如果有全球化需要,所有的字符存储与表示,均以
utf
-8
编码,注意字符统计函数
的区别。
说明:
SELECT LENGTH(
"
轻松工作
"
)
;
返回为
12
SELECT CHARACTER
_
LENGTH(
"
轻松工作
"
)
;
返回为
4
如果需要存储表情,那么选择
utfmb
4
来进行存储,注意它与
utf
-8
编码的区别。
11.
【参考】
TRUNCATE TABLE
比
DELETE
速度快,且使用的系统和事务日志资源少,但
TRUNCATE
无事务且不触发
trigger
,有可能造成事故,故不建议在开发代码中使用此语句。
说明:
TRUNCATE TABLE
在功能上与不带
WHERE
子句的
DELETE
语句相同。