Java Web开发规范总结

分类:计算机 | Web开发 | 综合 1055
更新:2022-08-05 18:08:44
编辑

1 前端

1.1 html

https://github.com/ecomfe/spec/blob/master/html-style-guide.md

1.2 css

1.2.1 命名规范

  • css文件全部采用小写方式, 以下划线分隔,例如:my_logo.css
  • 类名使用小写字母,以中划线分隔
  • id采用驼峰式命名
  • scss中的变量、函数、混合、placeholder采用驼峰式命名

1.2.2 文件编码

[建议] CSS 文件使用无 BOM 的 UTF-8 编码。

解释:UTF-8 编码具有更广泛的适应性。BOM 在使用程序或工具处理文件时可能造成不必要的干扰。

1.2.3 缩进

[强制] 使用 4 个空格做为一个缩进层级,不允许使用 2 个空格 或 tab 字符。

1.2.4 空格

以下几种情况不需要空格:

  • 属性名后
  • 多个规则的分隔符','前
  • !important '!'后
  • 属性值中'('后和')'前
  • 行末不要有多余的空格

以下几种情况需要空格:

  • 属性值前
  • 选择器'>', '+', '~'前后
  • '{'前
  • !important '!'前
  • @else 前后
  • 属性值中的','后
  • 注释'/'后和'/'前

1.2.5 换行

以下几种情况不需要换行:

  • '{'前

以下几种情况需要换行:

  • '{'后和'}'前
  • 每个属性独占一行
  • 多个规则的分隔符','后
/* not good */
.element
{color: red; background-color: black;}

/* good */
.element {
    color: red;
    background-color: black;
}

/* not good */
.element, .dialog {
    ...
}

/* good */
.element,
.dialog {
    ...
}

1.2.6 注释

注释统一用'/* */'(scss中也不要用'//'),具体参照右边的写法;

缩进与下一行代码保持一致;

可位于一个代码行的末尾,与代码间隔一个空格。

/* Modal header */
.modal-header {
    ...
}

/*
 * Modal header
 */
.modal-header {
    ...
}

.modal-header {
    /* 50px */
    width: 50px;

    color: red; /* color red */
}

1.2.7 选择器

[强制] 当一个 rule 包含多个 selector 时,每个选择器声明必须独占一行。

示例:

/* good */
.post,
.page,
.comment {
    line-height: 1.5;
}

/* bad */
.post, .page, .comment {
    line-height: 1.5;
}

[强制] >、+、~ 选择器的两边各保留一个空格。

示例:

/* good */
main > nav {
    padding: 10px;
}

label + input {
    margin-left: 5px;
}

input:checked ~ button {
    background-color: #69C;
}

/* bad */
main>nav {
    padding: 10px;
}

label+input {
    margin-left: 5px;
}

input:checked~button {
    background-color: #69C;
}

[强制] 属性选择器中的值必须用双引号包围。

解释:不允许使用单引号,不允许不使用引号。

示例:

/* good */
article[character="juliet"] {
    voice-family: "Vivien Leigh", victoria, female;
}

/* bad */
article[character='juliet'] {
    voice-family: "Vivien Leigh", victoria, female;
}

1.2.8 属性书写顺序

[建议] 同一 rule set 下的属性在书写时,应按功能进行分组,并以 Formatting Model(布局方式、位置) > Box Model(尺寸) > Typographic(文本相关) > Visual(视觉效果) 的顺序书写,以提高代码的可读性。

解释:

Formatting Model 相关属性包括:position / top / right / bottom / left / float / display / overflow 等

Box Model 相关属性包括:border / margin / padding / width / height 等

Typographic 相关属性包括:font / line-height / text-align / word-wrap 等

Visual 相关属性包括:background / color / transition / list-style 等

另外,如果包含 content 属性,应放在最前面。

示例:

.sidebar {
    /* formatting model: positioning schemes / offsets / z-indexes / display / ...  */
    position: absolute;
    top: 50px;
    left: 0;
    overflow-x: hidden;

    /* box model: sizes / margins / paddings / borders / ...  */
    width: 200px;
    padding: 5px;
    border: 1px solid #ddd;

    /* typographic: font / aligns / text styles / ... */
    font-size: 14px;
    line-height: 20px;

    /* visual: colors / shadows / gradients / ... */
    background: #f5f5f5;
    color: #333;
    -webkit-transition: color 1s;
       -moz-transition: color 1s;
            transition: color 1s;
}

1.2.9 属性简写

[建议] 在可以使用缩写的情况下,尽量使用属性缩写。

示例:

/* good */
.post {
    font: 12px/1.5 arial, sans-serif;
}

/* bad */
.post {
    font-family: arial, sans-serif;
    font-size: 12px;
    line-height: 1.5;
}

[建议] 使用 border / margin / padding 等缩写时,应注意隐含值对实际数值的影响,确实需要设置多个方向的值时才使用缩写。

解释:

border / margin / padding 等缩写会同时设置多个属性的值,容易覆盖不需要覆盖的设定。如某些方向需要继承其他声明的值,则应该分开设置。

示例:

/* centering <article class="page"> horizontally and highlight featured ones */
article {
    margin: 5px;
    border: 1px solid #999;
}

/* good */
.page {
    margin-right: auto;
    margin-left: auto;
}

.featured {
    border-color: #69c;
}

/* bad */
.page {
    margin: 5px auto; /* introducing redundancy */
}

.featured {
    border: 1px solid #69c; /* introducing redundancy */
}

1.2.10 颜色

[强制] RGB颜色值必须使用十六进制记号形式 #rrggbb。不允许使用 rgb()。

解释:

带有alpha的颜色信息可以使用 rgba()。使用 rgba() 时每个逗号后必须保留一个空格。

示例:

/* good */
.success {
    box-shadow: 0 0 2px rgba(0, 128, 0, .3);
    border-color: #008000;
}

/* bad */
.success {
    box-shadow: 0 0 2px rgba(0,128,0,.3);
    border-color: rgb(0, 128, 0);
}

[强制] 颜色值可以缩写时,必须使用缩写形式。

示例:

/* good */
.success {
    background-color: #aca;
}

/* bad */
.success {
    background-color: #aaccaa;
}

[建议] 颜色值中的英文字符采用小写。如不用小写也需要保证同一项目内保持大小写一致。

示例:

/* good */
.success {
    background-color: #aca;
    color: #90ee90;
}

/* good */
.success {
    background-color: #ACA;
    color: #90EE90;
}

/* bad */
.success {
    background-color: #ACA;
    color: #90ee90;
}

1.3 js

1.3.1 命名规范

[强制] js文件全部采用小写方式, 以下划线分隔,例如:my_logo.js

[强制] 变量 使用 Camel命名法。

示例:

var loadingModules = {};

[强制]常量 使用 全部字母大写,单词间下划线分隔 的命名方式。

示例:

var HTML_ENTITY = {};

[强制]函数 使用 Camel命名法。

示例:

function stringFormat(source) {
}

[强制] 函数的参数使用Camel命名法。

示例:

function hear(theBells) {
}

[强制]类 使用 Pascal命名法。

示例:

function TextNode(options) {
}

[强制] 类的方法 / 属性 使用 Camel命名法。

示例:

function TextNode(value, engine) {
    this.value = value;
    this.engine = engine;
}

TextNode.prototype.clone = function () {
    return this;
};

[强制]枚举变量 使用 Pascal命名法,枚举的属性 使用 全部字母大写,单词间下划线分隔 的命名方式。

示例:

var TargetState = {
    READING: 1,
    READED: 2,
    APPLIED: 3,
    READY: 4
};

[强制]命名空间 使用 Camel命名法。

示例:

equipments.heavyWeapons = {};

[强制] 由多个单词组成的缩写词,在命名中,根据当前命名法和出现的位置,所有字母的大小写与首字母的大小写保持一致。

示例:

function XMLParser() {
}

function insertHTML(element, html) {
}

var httpRequest = new HTTPRequest();

[强制]类名 使用 名词。

示例:

function Engine(options) {
}

[建议]函数名 使用 动宾短语。

示例:

function getStyle(element) {
}

[建议] boolean 类型的变量使用 is 或 has 开头。

示例:

var isReady = false;
var hasMoreCommands = false;

[建议] Promise对象用动宾短语的进行时表达。

示例:

var loadingData = ajax.get('url');
loadingData.then(callback);

1.3.2 缩进

[强制] 使用 4 个空格做为一个缩进层级,不允许使用 2 个空格 或 tab 字符。

1.3.3 空格

[强制] 二元运算符两侧必须有一个空格,一元运算符与操作对象之间不允许有空格。

示例:

var a = !arr.length;
a++;
a = b + c;

[强制] 用作代码块起始的左花括号{ 前必须有一个空格。

示例:

// good
if (condition) {
}

while (condition) {
}

function funcName() {
}

// bad
if (condition){
}

while (condition){
}

function funcName(){
}

[强制] if / else / for / while / function / switch / do / try / catch / finally 关键字后,必须有一个空格。

示例:

// good
if (condition) {
}

while (condition) {
}

(function () {
})();

// bad
if(condition) {
}

while(condition) {
}

(function() {
})();

[强制] 在对象创建时,属性中的: 之后必须有空格,: 之前不允许有空格。

示例:

// good
var obj = {
    a: 1,
    b: 2,
    c: 3
};

// bad
var obj = {
    a : 1,
    b:2,
    c :3
};

[强制] 函数声明、具名函数表达式、函数调用中,函数名和( 之间不允许有空格。

示例:

// good
function funcName() {
}

var funcName = function funcName() {
};

funcName();

// bad
function funcName () {
}

var funcName = function funcName () {
};

funcName ();

[强制] , 和 ; 前不允许有空格。如果不位于行尾,, 和 ; 后必须跟一个空格。

示例:

// good
callFunc(a, b);

// bad
callFunc(a , b) ;

[强制] 在函数调用、函数声明、括号表达式、属性访问、if / for / while / switch / catch 等语句中,() 和 [] 内紧贴括号部分不允许有空格。

示例:

// good

callFunc(param1, param2, param3);

save(this.list[this.indexes[i]]);

needIncream && (variable += increament);

if (num > list.length) {
}

while (len--) {
}


// bad

callFunc( param1, param2, param3 );

save( this.list[ this.indexes[ i ] ] );

needIncreament && ( variable += increament );

if ( num > list.length ) {
}

while ( len-- ) {
}

[强制] 单行声明的数组与对象,如果包含元素,{} 和 [] 内紧贴括号部分不允许包含空格。

解释:

声明包含元素的数组与对象,只有当内部元素的形式较为简单时,才允许写在一行。元素复杂的情况,还是应该换行书写。

示例:

// good
var arr1 = [];
var arr2 = [1, 2, 3];
var obj1 = {};
var obj2 = {name: 'obj'};
var obj3 = {
    name: 'obj',
    age: 20,
    sex: 1
};

// bad
var arr1 = [ ];
var arr2 = [ 1, 2, 3 ];
var obj1 = { };
var obj2 = { name: 'obj' };
var obj3 = {name: 'obj', age: 20, sex: 1};

[强制] 行尾不得有多余的空格。

1.3.4 换行

换行的地方,行末必须有','或者运算符;

以下几种情况不需要换行:

  • 下列关键字后:else, catch, finally
  • 代码块'{'前

以下几种情况需要换行:

  • 代码块'{'后和'}'前
  • 变量赋值后
// not good
var a = {
    b: 1
    , c: 2
};

x = y
    ? 1 : 2;

// good
var a = {
    b: 1,
    c: 2
};

x = y ? 1 : 2;
x = y ?
    1 : 2;

// no need line break with 'else', 'catch', 'finally'
if (condition) {
    ...
} else {
    ...
}

try {
    ...
} catch (e) {
    ...
} finally {
    ...
}

// not good
function test()
{
    ...
}

// good
function test() {
    ...
}

// not good
var a, foo = 7, b,
    c, bar = 8;

// good
var a,
    foo = 7,
    b, c, bar = 8;

1.3.5 单行注释

双斜线后,必须跟一个空格;

缩进与下一行代码保持一致;

可位于一个代码行的末尾,与代码间隔一个空格。

if (condition) {
    // if you made it here, then all security checks passed
    allowed();
}

var zhangsan = 'zhangsan'; // one space after code

1.3.6 多行注释

最少三行, '*'后跟一个空格,具体参照右边的写法;

建议在以下情况下使用:

  • 难于理解的代码段
  • 可能存在错误的代码段
  • 浏览器特殊的HACK代码
  • 业务逻辑强相关的代码
/*
 * one space after '*'
 */
var x = 1;

1.3.7 文档注释

[强制] 为了便于代码阅读和自文档化,以下内容必须包含以 /**...*/ 形式的块注释中。

解释:

  1. 文件
  2. namespace
  3. 函数或方法
  4. 类属性
  5. 事件
  6. 全局变量
  7. 常量
  8. AMD 模块

[强制] 文档注释前必须空一行。

/**
 * @func
 * @desc 一个带参数的函数
 * @param {string} a - 参数a
 * @param {number} b=1 - 参数b默认值为1
 * @param {string} c=1 - 参数c有两种支持的取值</br>1—表示x</br>2—表示xx
 * @param {object} d - 参数d为一个对象
 * @param {string} d.e - 参数d的e属性
 * @param {string} d.f - 参数d的f属性
 * @param {object[]} g - 参数g为一个对象数组
 * @param {string} g.h - 参数g数组中一项的h属性
 * @param {string} g.i - 参数g数组中一项的i属性
 * @param {string} [j] - 参数j是一个可选参数
 */
function foo(a, b, c, d, g, j) {
    ...
}

1.3.8 函数

无论是函数声明还是函数表达式,'('前不要空格,但'{'前一定要有空格;

函数调用括号前不需要空格;

立即执行函数外必须包一层括号;

不要给inline function命名;

参数之间用', '分隔,注意逗号后有一个空格。

1.3.9 数组、对象

对象属性名不需要加引号;

对象以缩进的形式书写,不要写在一行;

数组、对象最后不要有逗号。

// not good
var a = {
    'b': 1
};

var a = {b: 1};

var a = {
    b: 1,
    c: 2,
};

// good
var a = {
    b: 1,
    c: 2
};

1.3.10 括号

下列关键字后必须有大括号(即使代码块的内容只有一行):if, else, for, while, do, switch, try, catch, finally, with。

// not good
if (condition)
    doSomething();

// good
if (condition) {
    doSomething();
}

1.3.11 null

适用场景:

  • 初始化一个将来可能被赋值为对象的变量
  • 与已经初始化的变量做比较
  • 作为一个参数为对象的函数的调用传参
  • 作为一个返回对象的函数的返回值

不适用场景:

  • 不要用null来判断函数调用时有无传参
  • 不要与未初始化的变量做比较
// not good
function test(a, b) {
    if (b === null) {
        // not mean b is not supply
        ...
    }
}

var a;

if (a === null) {
    ...
}

// good
var a = null;

if (a === null) {
    ...
}

1.3.12 undefined

永远不要直接使用undefined进行变量判断;

使用typeof和字符串'undefined'对变量进行判断。

// not good
if (person === undefined) {
    ...
}

// good
if (typeof person === 'undefined') {
    ...
}

1.3.13 其它

用'===', '!=='代替'==', '!=';

for-in里一定要有hasOwnProperty的判断;

不要在内置对象的原型上添加方法,如Array, Date;

不要在内层作用域的代码里声明了变量,之后却访问到了外层作用域的同名变量;

不要在一句代码中单单使用构造函数,记得将其赋值给某个变量;

不要在同个作用域下声明同名变量;

不要在一些不需要的地方加括号,例:delete(a.b);

不要使用未声明的变量(全局变量需要加到.jshintrc文件的globals属性里面);

不要在循环内部声明函数;

不要像这样使用构造函数,例:new function () { ... }, new Object;

不要混用tab和space;

不要在一处使用多个tab或space;

换行符统一用'LF';

1.4 图片命名规则

全部采用小写方式, 以下划线分隔,例如:my_logo.png

1.5 vue项目

https://zhuanlan.zhihu.com/p/427306188

2 后台

1、【强制】类名使用 UpperCamelCase 风格,但以下情形例外:DO / BO / DTO / VO / AO / PO / UID 等。

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

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

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

正例: localValue / getHttpMessage() / inputUserId

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

正例:MAX_STOCK_COUNT

反例:MAX_COUNT

4、【强制】抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类 命名以它要测试的类的名称开始,以 Test 结尾。

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

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

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

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

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

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

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

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

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

9、【推荐】如果模块、接口、类、方法使用了设计模式,在命名时需体现出具体模式。

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

正例:

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

10、接口和实现类的命名有两套规则:

1)【强制】对于 Service 和 DAO 类,基于 SOA 的理念,暴露出来的服务一定是接口,内部的实现类用 Impl 的后缀与接口区别。

正例:CacheServiceImpl 实现 CacheService 接口。

2)【推荐】如果是形容能力的接口名称,取对应的形容词为接口名(通常是–able 的形式)。

正例:AbstractTranslator 实现 Translatable 接口。

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

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

正例:枚举名字为 ProcessStatusEnum 的成员名称:SUCCESS / UNKNOWN_REASON。

12、【参考】各层命名规约:

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

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

2) 获取多个对象的方法用 list 做前缀,复数形式结尾如:listObjects。

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。

2.1.2 常量定义

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

反例:

String key = "Id#taobao_" + 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) 应用内共享常量:放置在一方库中,通常是子模块中的 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;
 } }

2.1.3 代码格式

1、【强制】大括号的使用约定。如果是大括号内为空,则简洁地写成{}即可,不需要换行;如果是非空代码块则:

1) 左大括号前不换行。

2) 左大括号后换行。

3) 右大括号前换行。

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

正例:

if (evdataModelbasetreeList.size() > 0) {
    evid = evdataModelbasetreeList.get(0).getEvid();
} else {
    EvdataModelbasetree evdataModelbasetree = new EvdataModelbasetree();
    evdataModelbasetree.setParentid(evid);
    evdataModelbasetree.setModelnodelevel(4);
    evid = UuidUtil.GetUUID();
    evdataModelbasetree.setEvid(evid);
    evdataModelbasetree.setModelnodename(filename.substring(0, filename.lastIndexOf(".gim")));
    evdataModelbasetree.setModelnodeorder("0");
    evdataModelbasetree.setYeartime((short) 0);
    evdataModelbasetreeMapper.insertSelective(evdataModelbasetree);
}

2、【强制】if/for/while/switch/do 等保留字与括号之间都必须加空格。

3、【强制】任何二目、三目运算符的左右两边都需要加一个空格。

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

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

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

正例:

public static void main(String[] args) {
    // 缩进 4 个空格 
    String say = "hello";
    // 运算符的左右必须有一个空格 
    int flag = 0;
    // 关键词 if 与括号之间必须有一个空格,括号内的 f 与左括号,0 与右括号不需要空格 
    if (flag == 0) {
        System.out.println(say);
    }

    // 左大括号前加空格且不换行;左大括号后换行 
    if (flag == 1) {
        System.out.println("world");
    // 右大括号前换行,右大括号后有 else,不用换行 
    } else {
        System.out.println("ok");
    // 在右大括号后直接结束,则必须换行 
    }
}

5、【强制】注释的双斜线与注释内容之间有且仅有一个空格。

正例:

// 这是示例注释,请注意在双斜线之后有一个空格

String ygb = new String();

6、 【强制】单行字符数限制不超过 120 个,超出需要换行,换行时遵循如下原则:

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

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

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

4) 方法调用中的多个参数需要换行时,在逗号后进行。

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

正例:

StringBuffer sb = new StringBuffer();
// 超过 120 个字符的情况下,换行缩进 4 个空格,点号和方法名称一起换行
sb.append("zi").append("xin")...
        .append("huang")...
        .append("huang")...
        .append("huang");

反例:

StringBuffer sb = new StringBuffer();
// 超过 120 个字符的情况下,不要在括号前换行
sb.append("zi").append("xin")...append
        ("huang");
// 参数很多的方法调用可能超过 120 个字符,不要在逗号前换行
        method(args1, args2, args3, ...
        , argsX);

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

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

method(args1, args2, args3);

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

9、【推荐】单个方法的总行数不超过 80 行。

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

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

2.1.4 OOP规约

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

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

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

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

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

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

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

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

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

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

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

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

说明:

String str = "a,b,c,,"; 
String[] ary = str.split(","); 
// 预期大于 3,结果是 3
System.out.println(ary.length);

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

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

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

反例:

public Integer getData() {
    if (condition) {
        return this.data + 100;
    } else {
        return this.data - 100;
    }
}

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

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

反例:

String str = "start"; 
for (int i = 0; i < 100; i++) {
    str = str + "hello";
}

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

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

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

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

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

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

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

说明:对象的 clone 方法默认是浅拷贝,若想实现深拷贝需要重写 clone 方法实现域对象的深度遍历式拷贝。

2.1.5 注释规约

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

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

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

说明:对子类的实现要求,或者调用注意事项,请一并说明。

3、【强制】所有的类都必须添加创建者和创建日期。

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

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

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

说明:代码与注释更新不同步,就像路网与导航软件更新不同步一样,如果导航软件严重滞后,就失去了导航的意义。

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

说明:代码被注释掉有两种可能性:1)后续会恢复此段代码逻辑。2)永久不用。前者如果没有备注信息,难以知晓注释动机。后者建议直接删掉(代码仓库保存了历史代码)。

8、【参考】特殊注释标记,请注明标记人与标记时间。注意及时处理这些标记,通过标记扫描,经常清理此类标记。线上故障有时候就是来源于这些标记处的代码。

1) 待办事宜(TODO):( 标记人,标记时间,[预计处理时间])

表示需要实现,但目前还未实现的功能。这实际上是一个 Javadoc 的标签,目前的 Javadoc还没有实现,但已经被广泛使用。只能应用于类,接口和方法(因为它是一个 Javadoc 标签)。

2) 错误,不能工作(FIXME):(标记人,标记时间,[预计处理时间])

在注释中用 FIXME 标记某代码是错误的,而且不能工作,需要及时纠正的情况。

2.2 MVC规范

2.2.1 整体分层

controller 层

service 层

manager 层

dao 层

2.2.2 controller 层规范

1) 只允许在 method 上添加 RequestMapping 注解,不允许加在 class 上(为了方便的查找 url,放到 url 不能一次性查找出来)

正例:

@RestController
public class DepartmentController {

    @GetMapping("/department/list")
    public ResponseDTO<List<DepartmentVO>> listDepartment() {
        return departmentService.listDepartment();
    }

反例:

@RequestMapping ("/department")
public class DepartmentController {

    @GetMapping("/list")
    public ResponseDTO<List<DepartmentVO>> listDepartment() {
        return departmentService.listDepartment();
    }

2)不推荐使用 rest 命名 url, 只能使用 get/post 方法。url 命名上规范如下:

虽然 Rest 大法好,但是有时并不能一眼根据 url 看出来是什么操作,所以我们选择了后者,这个没有对与错,只有哪个更适合我们的团队。 /业务模块/子模块/动作

正例:

GET  /department/get/{id}      查询某个部门详细信息
POST /department/query         复杂查询
POST /department/add           添加部门
POST /department/update        更新部门
GET  /department/delete/{id}   删除部门

3)每个方法必须添加 swagger 文档注解 @ApiOperation ,并填写接口描述信息,描述最后必须加上作者信息 @author 哪吒 。

正例:

@ApiOperation("更新部门信息 @author 哪吒")
    @PostMapping("/department/update")
    public ResponseDTO<String> updateDepartment(@Valid @RequestBody DeptUpdateDTO deptUpdateDTO) {
        return departmentService.updateDepartment(deptUpdateDTO);
    }

4)controller 负责协同和委派业务,充当路由的角色,每个方法要保持简洁:

不做任何的业务逻辑操作;

不做任何的参数、业务校验,参数校验只允许使用@Valid 注解做简单的校验;

不做任何的数据组合、拼装、赋值等操作;

正例:

@ApiOperation("添加部门 @author 哪吒")
    @PostMapping("/department/add")
    public ResponseDTO<String> addDepartment(@Valid @RequestBody DepartmentCreateDTO departmentCreateDTO) {
        return departmentService.addDepartment(departmentCreateDTO);
    }

5)只能在 controller 层获取当前请求用户,并传递给 service 层。

因为获取当前请求用户是从 ThreadLocal 里获取取的,在 service、manager、dao 层极有可能是其他非 request 线程调用,会出现 null 的情况,尽量避免

@ApiOperation("添加员工 @author yandanyang")
    @PostMapping("/employee/add")
    public ResponseDTO<String> addEmployee(@Valid @RequestBody EmployeeAddDTO employeeAddDTO) {
        LoginTokenBO requestToken = SmartRequestTokenUtil.getRequestUser();
        return employeeService.addEmployee(employeeAddDTO, requestToken);
    }

2.2.3 service 层规范

1)合理拆分 service 文件,如果业务较大,请拆分为多个 service。

如订单业务,所有业务都写到 OrderService 中会导致文件过大,故需要进行拆分如下:

OrderQueryService 订单查询业务
OrderCreateService 订单新建业务
OrderDeliverService 订单发货业务
OrderValidatorService 订单验证业务

2)谨慎处理 @Transactional 事务注解的使用,不要简单对 service 的方法添加个 @Transactional 注解就觉得万事大吉了。应当合并对数据库的操作,尽量减少添加了@Transactional方法内的业务逻辑。

@Transactional 注解内的 rollbackFor 值必须使用异常的基类 Throwable.class

对于@Transactional 注解,当 spring 遇到该注解时,会自动从数据库连接池中获取 connection,并开启事务然后绑定到 ThreadLocal 上,如果业务并没有进入到最终的 操作数据库环节,那么就没有必要获取连接并开启事务,应该直接将 connection 返回给数据库连接池,供其他使用(比较难以讲解清楚,如果不懂的话就主动去问)。

反例:

@Transactional(rollbackFor = Throwable.class)
    public ResponseDTO<String> upOrDown(Long departmentId, Long swapId) {
        // 验证 1
        DepartmentEntity departmentEntity = departmentDao.selectById(departmentId);
        if (departmentEntity == null) {
            return ResponseDTO.wrap(DepartmentResponseCodeConst.NOT_EXISTS);
        }
        // 验证 2
        DepartmentEntity swapEntity = departmentDao.selectById(swapId);
        if (swapEntity == null) {
            return ResponseDTO.wrap(DepartmentResponseCodeConst.NOT_EXISTS);
        }
        // 验证 3
        Long count = employeeDao.countByDepartmentId(departmentId)
        if (count != null && count > 0) {
            return ResponseDTO.wrap(DepartmentResponseCodeConst.EXIST_EMPLOYEE);
        }
        // 操作数据库 4
        Long departmentSort = departmentEntity.getSort();
        departmentEntity.setSort(swapEntity.getSort());
        departmentDao.updateById(departmentEntity);
        swapEntity.setSort(departmentSort);
        departmentDao.updateById(swapEntity);
        return ResponseDTO.succ();
    }

以上代码前三步都是使用 connection 进行验证操作,由于方法上有@Transactional 注解,所以这三个验证都是使用的同一个 connection。

若对于复杂业务、复杂的验证逻辑,会导致整个验证过程始终占用该 connection 连接,占用时间可能会很长,直至方法结束,connection 才会交还给数据库连接池。

对于复杂业务的不可预计的情况,长时间占用同一个 connection 连接不是好的事情,应该尽量缩短占用时间。

正例:

DepartmentService.java

    public ResponseDTO<String> upOrDown(Long departmentId, Long swapId) {
        DepartmentEntity departmentEntity = departmentDao.selectById(departmentId);
        if (departmentEntity == null) {
            return ResponseDTO.wrap(DepartmentResponseCodeConst.NOT_EXISTS);
        }
        DepartmentEntity swapEntity = departmentDao.selectById(swapId);
        if (swapEntity == null) {
            return ResponseDTO.wrap(DepartmentResponseCodeConst.NOT_EXISTS);
        }
        Long count = employeeDao.countByDepartmentId(departmentId)
        if (count != null && count > 0) {
            return ResponseDTO.wrap(DepartmentResponseCodeConst.EXIST_EMPLOYEE);
        }
        departmentManager.upOrDown(departmentSort,swapEntity);
        return ResponseDTO.succ();
    }


    DepartmentManager.java

    @Transactional(rollbackFor = Throwable.class)
    public void upOrDown(DepartmentEntity departmentEntity ,DepartmentEntity swapEntity){
        Long departmentSort = departmentEntity.getSort();
        departmentEntity.setSort(swapEntity.getSort());
        departmentDao.updateById(departmentEntity);
        swapEntity.setSort(departmentSort);
        departmentDao.updateById(swapEntity);
    }

将数据在 service 层准备好,然后传递给 manager 层,由 manager 层添加@Transactional 进行数据库操作。

3)需要注意的是:注解 @Transactional 事务在类的内部方法调用是不会生效的

反例:如果发生异常,saveData方法上的事务注解并不会起作用

@Service
public class OrderService{

    public void createOrder(OrderCreateDTO createDTO){
        this.saveData(createDTO);
    }

    @Transactional(rollbackFor = Throwable.class)
    public void saveData(OrderCreateDTO createDTO){
        orderDao.insert(createDTO);
    }
}

Spring采用动态代理(AOP)实现对bean的管理和切片,它为我们的每个class生成一个代理对象。只有在代理对象之间进行调用时,可以触发切面逻辑。而在同一个class中,方法A调用方法B,调用的是原对象的方法,而不通过代理对象。所以Spring无法拦截到这次调用,也就无法通过注解保证事务了。简单来说,在同一个类中的方法调用,不会被方法拦截器拦截到,因此事务不会起作用。

解决方案:

可以将方法放入另一个类,如新增 manager层,通过spring注入,这样符合了在对象之间调用的条件。 启动类添加 @EnableAspectJAutoProxy(exposeProxy = true),方法内使用AopContext.currentProxy()获得代理类,使用事务。

SpringBootApplication.java

@EnableAspectJAutoProxy(exposeProxy = true)
@SpringBootApplication
public class SpringBootApplication {}

OrderService.java

public void createOrder(OrderCreateDTO createDTO){
    OrderService orderService = (OrderService)AopContext.currentProxy();
    orderService.saveData(createDTO);
}

4)service是具体的业务处理逻辑服务层,尽量避免将web层某些参数传递到service中。

反例:

public ResponseDTO<String> handlePinganRequest(HttpServletRequest request){
    InputStreamReader inputStreamReader = new InputStreamReader(request.getInputStream(), "GBK");
    BufferedReader reader = new BufferedReader(inputStreamReader);
    StringBuilder sb = new StringBuilder();
    String str;
    while ((str = reader.readLine()) != null) {
        sb.append(str);
    }
    if(!JSON.isValid(msg)){
      return ResponseDTO.wrap(ResponseCodeConst.ERROR_PARAM);
    }
    PinganMsgDTO PinganMsgDTO = JSON.parseObject(msg,PinganMsgDTO.class);
    // 示例结束
}

反例中出现的问题:

反例中把 HttpServletRequest 传递到service中,是为了获取Request流中的字符信息,然后才是真正的业务处理。按照分层的初衷:将代码、业务逻辑解耦,正确的做法应该是handlePinganRequest方法将String字符作为参数直接处理业务,将从Request中获取字符的操作放入controller中。

另一个坏处是不方便做单元测试,还得一个new一个HttpServletRequest并制造一个InputStream,然而这样做并不能模拟到真实的业务情景及数据。

2.2.4 manager 层规范

manager 层的作用(引自《阿里 java 手册》):

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

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

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

2.2.5 dao 层规范

优先使用 mybatis-plus 框架。如果需要多个数据源操作的,可以选择使用 SmartDb 框架。

1)所有 Dao 继承自 BaseMapper

2)禁止使用 Mybatis-plus 的 Wrapper 条件构建器

3)禁止直接在 mybatis xml 中写死常量,应从 dao 中传入到 xml 中

3)建议不要使用星号 * 代替所有字段

正例:

NoticeDao.java

Integer noticeCount(@Param("sendStatus") Integer sendStatus);

NoticeMapper.xml

<select id="noticeCount" resultType="integer">
    select
    count(1)
    from t_notice
    where
    send_status = #{sendStatus}
</select>

反例:

NoticeDao.java

Integer noticeCount();

NoticeMapper.xml

<select id="noticeCount" resultType="integer">
    select
    count(1)
    from t_notice
    where
    send_status = 0
</select>

3)dao层方法命名规范

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

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

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

插入的方法用 save/insert 做前缀。

删除的方法用 remove/delete 做前缀。

修改的方法用 update 做前缀。

建议:dao层方法命名尽量以sql语义命名,避免与业务关联。

正例:

List<PerformanceDTO> listByMonthAndItemId(@Param("month") String month, @Param("itemId") Integer itemId);

反例:

List<PerformanceDTO> getInternalData(@Param("month") String month, @Param("itemId") Integer itemId);

反例中出现的不规范操作:

get代表单个查询,批量查询的应该 list 开头。

命名与业务关联,局限了dao方法的使用场景和范围,降低了方法的复用性,造成他人困惑以及重复造轮子。

2.3 SpringBoot项目

2.3.1 项目命名规范

全部采用小写方式, 以下划线分隔。

例:my_project_name

2.3.2 项目结构

https://blog.csdn.net/qq_31432773/article/details/115768079?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-115768079-blog-124696686.pc_relevant_multi_platform_featuressortv2dupreplace&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-115768079-blog-124696686.pc_relevant_multi_platform_featuressortv2dupreplace&utm_relevant_index=1

https://www.it610.com/article/1280045312605437952.htm

https://blog.csdn.net/wang_j1/article/details/107512996?spm=1001.2101.3001.6650.3&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-3-107512996-blog-115768079.pc_relevant_multi_platform_whitelistv3&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-3-107512996-blog-115768079.pc_relevant_multi_platform_whitelistv3&utm_relevant_index=5

2.3.2.1 代码层结构

  • Application.java:工程启动类。
  • config目录:配置信息类,读取相关配置,比如RedisConfig.java、MysqlDataSourceConfig.java等。
  • constant目录:存放常量类。
  • controller目录:存放前端控制器。
  • dao目录:数据访问层,在mybatis项目中就是mapper目录。
  • enums目录:存放枚举类。
  • exception目录:自定义异常,异常错误码。
  • mapper目录:mybatis对应的数据访问层。
  • pojo目录:mybatis对应的实体类。
  • service目录:数据服务接口层。
  • serviceImpl目录:数据服务实现层。
  • util目录:工具类库
  • vo目录:视图包装对象(View Object),用于封装客户端请求的数据,防止部分数据泄露(如:管理员ID),保证数据安全,不破坏原有的实体类结构。

2.3.2.2 资源目录结构

  • mapper目录:存放mybatis映射文件。
  • mapper.config目录:存放mybatis配置文件。
  • static目录:存放html、css、js、图片等资源。
  • templates目录:用于存放jsp、thymeleaf等模板文件
  • application.yml:项目配置文件

2.4 统一前端请求返回结果

2.4.1 返回结果Java实体类

/**
 * @Author: zzc
 * @Description: 返回前端的统一对象
 * @Date: 2020/7/22
 */
public class ResponseWrapper {
    //        200    OK    请求成功
    //        201    Created    已创建。成功请求并创建了新的资源
    //        202    Accepted    已接受。已经接受请求,但未处理完成
    //        301    Moved Permanently    永久移动
    //        302    Moved Temporarily    临时移动
    //        304    Not Modified    未修改
    //        307    Temporary Redirect    临时重定向,与302类似
    //        400    Bad Request    请求语法错误,服务器无法理解
    //        401    Unauthorized    请求要求用户的身份认证
    //        403    Forbidden    拒绝执行此请求
    //        404    Not Found    找不到资源
    //        405    Method Not Allowed    请求的方法不允许
    //        500    Internal Server Error    服务器内部错误
    //        502    Bad Gateway    网关错误
    //        504    Gateway Time-out    超时
    private Integer code;//状态码
    private Boolean isSuccess;//状态
    private String message;//消息
    private Object result; //数据对象,可以使用Map<String, Object>的格式添加

    public static ResponseWrapper OK(String message) {
        return new ResponseWrapper(200, true, message, null);
    }

    public static ResponseWrapper OK(String message, Object result) {
        return new ResponseWrapper(200, true, message, result);
    }

    public static ResponseWrapper fail(Integer code,String message){
        return new ResponseWrapper(code, false, message, null);
    }


    public ResponseWrapper(Integer code, Boolean isSuccess, String message, Object result) {
        this.code = code;
        this.isSuccess = isSuccess;
        this.message = message;
        this.result = result;
    }

    public ResponseWrapper() {
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public Boolean getSuccess() {
        return isSuccess;
    }

    public void setSuccess(Boolean success) {
        isSuccess = success;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Object getResult() {
        return result;
    }

    public void setResult(Object result) {
        this.result = result;
    }
}

2.4.2 后台服务接口使用示例

@RequestMapping("/getInfo.do")
public Object getInfo() {
    ResponseWrapper responseWrapper = new ResponseWrapper();

    ...

    if (o != null) {
        responseWrapper.setCode(200);
        responseWrapper.setSuccess(true);
        responseWrapper.setMessage("有数据");
        responseWrapper.setResult(o);
    } else {
        responseWrapper.setCode(500);
        responseWrapper.setSuccess(false);
        responseWrapper.setMessage("没有数据");
        responseWrapper.setResult(o);
    }
    return responseWrapper;
}

2.4.3 前端请求示例

this.ajaxGetFunction('/controller/getInfo.do',{},data=>{
if(data.success){
    this.isshowtext=false;
    var ratedata=data.result.data;
    var data1=data.result.data1;
    var data2=data.result.data2;
    var data3=data.result.data3;
    option.xAxis.data=ratedata;
    option.series[0].data=data3;
    option.series[1].data=data1;
    option.series[2].data=data2;
    myChart.setOption(option);
}else{
    this.isshowtext=true;
}

});

3 数据库(mysql)

3.1 基本设计规范

  • 所有表必须适用Innodb存储引擎,它支持事务,行级锁,更好的恢复性,高并发下性能更好。
  • 表字符集选择UTF8 ,如果需要存储emoj表情,需要使用UTF8mb4(MySQL 5.5.3以后支持)。
  • 所有表和字段都需要添加注释。
  • 尽量控制单表数据量的大小,建议控制在500万以内。否则修改表结构,备份,恢复都会有很大问题。可以用历史数据归档,分库分表等手段来控制数据量大小。
  • 变长字符串尽量使用VARCHAR VARBINARY。
  • 字段数据类型长度选择遵守够用最小原则。
  • 禁止在数据库中存储明文密码。

3.2 命名规范

  • 所有数据库对象名称必须使用小写字母并用下划线分割,禁止出现数字开头,禁止两个下划线中间只出现数字。
  • 所有数据库对象名称禁止使用Mysql保留关键字。
  • 数据库对象的命名要能做到见名识义,并且最好不要超过32个字符。
  • 临时表必须以tmp为前缀并以日期为后缀,备份库,备份表必须以bak为前缀并以日期为后缀。
  • 表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint(1 表示是,0 表示否)。

说明:任何字段如果为非负数,必须是 unsigned。

正例:表达逻辑删除的字段名 is_deleted,1 表示删除,0 表示未删除。

  • 表名不使用复数名词。

说明:表名应该仅仅表示表里面的实体内容,不应该表示实体数量,对应于 DO 类名也是单数形式,符合表达习惯。

  • 表的命名最好是加上“业务名称_表的作用”。

正例:alipay_task / force_project / trade_config

  • 库名与应用名称尽量一致。

3.3 建表规范

  • 如果存储的字符串长度几乎相等,使用 char 定长字符串类型。
  • 合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度。
  • 对于非负型的数据来说,要优先使用无符号整型来存储。无符号相对于有符号可以多出一倍的存储空间。
  • 建议使用timestamp来存储时间类型数据。
  • 尽可能把所有列定义为NOT NULL,然后指定一个默认值。因为索引NULL需要额外的空间来保存是否为空,所以要占用更多的空间。另外进行比较和计算时要对NULL值做特别的处理。
  • 存储精确浮点数必须使用DECIMAL替代FLOAT和DOUBLE。
  • 尽可能不使用TEXT、BLOB类型,建议把BLOB和TEXT列分离到单独的扩展表中。
  • varchar是可变长字符串,不预先分配存储空间,长度不要超过5000,如果存储长度大于此值,定义字段类型为text,独立出来一张表,用主键来对应,避免影响其它字段索引效率。

3.4 索引规范

  • 主键索引名为pk_字段名;唯一索引名为uk_字段名;普通索引名则为idx_字段名。
  • 单张表的索引数量最好不要超过5个。
  • 禁止给表中的每一个列建立索引。
  • 不使用更新频繁的列作为主键,不使用多列主键。这是因为主键频繁更新意味着存储逻辑顺序的频繁变动,必然带来大量的IO操作。
  • 不适用UUID,MD5,HASH,字符串列作为主键,这是因为,我们无法保证他们的自动增长。
  • 主键建议使用自增ID。
  • 区分度最高的列放在联合索引的最左侧。
  • 尽量把字段长度小的列放在联合索引的最左侧,这样页中存储的数据量越大。
  • 使用频繁的列放到联合索引左侧。
  • 业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。

说明:不要以为唯一索引影响了insert速度,这个速度损耗可以忽略,但提高查找速度是明显的;另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。

  • 超过三个表禁止join。需要join的字段,数据类型必须绝对一致;多表关联查询时,保证被关联的字段需要有索引。

说明:即使双表join也要注意表索引、SQL性能。

  • 在varchar字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度即可。

说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为20的索引,区分度会高达90%以上,可以使用count(distinct left(列名, 索引长度))/count(*)的区分度来确定。

  • 利用覆盖索引来进行查询操作,避免回表。

说明:如果一本书需要知道第 11 章是什么标题,会翻开第 11 章对应的那一页吗?目录浏览一下就好,这个目录就是起到覆盖索引的作用。

正例:能够建立索引的种类分为主键索引、唯一索引、普通索引三种,而覆盖索引只是一种查询的一种效果,用 explain 的结果,extra 列会出现:using index。

  • 常见的索引列建议:

1.SELECT UPDATE DELETE语句的WHERE从句中的列

2.包含在order by、group by、distinct中的字段

3.多表join的关联列

  • 尽量避免使用外键。不建议使用外键约束,但一定在表与表之间的关联键上建立索引。外键可用于保证数据的参照完整性,但建议在业务端实现。外键会影响父表与子表的写操作从而降低性能。Mysql会默认给外键建立索引。

3.5 sql规范

  • 拆分复杂的大SQL为多个小SQL。Mysql一个SQL只能使用一个CPU进行计算,SQL拆分后可以通过并行执行来提高处理效率。

  • 使⽤预编译语句,只传参数,比传递SQL语句更高效,降低SQL注用概率。

  • 禁止使用select 进行查询,必须使用select字段列表进行查询。select 会消耗更多的CPU和IO以及网络带宽资源;无法使用覆盖索引;可减少表结构变更带来的影响。

  • 不要使用count(列名)或count(常量)来替代count(),count()是SQL92定义的标准统计行数的语法,跟数据库无关,跟NULL和非NULL无关。

  • 禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。

  • 尽量不使用存储过程、触发器、函数等,让数据库做最擅长的事。

  • 不得使用外键与级联,一切外键概念必须在应用层解决。 外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。外键对于主备复制是禁止的,有了外键,主备复制只能是单线程。

  • TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少,但TRUNCATE无事务且不触发trigger,有可能造成事故,故不建议在开发代码中使用此语句。

  • 避免使用子查询,可以把子查询优化为join操作。这是因为子查询的结果集无法使用索引;子查询会产生临时表操作,如果子查询数据量大则严重影响效率,且消耗过多的CPU及IO资源。

  • 避免使用JOIN关联太多的表。每join一个表多占用一部分内存(join_buffer_size)。join会产生临时表操作,影响查询效率,Mysql最多允许关联61个表,建议不超过5个。

  • in 操作能避免则避免,若实在避免不了,需要仔细评估 in 后边的集合元素数量,控制在 1000 个之内。

  • 类似分页功能的SQL,建议先用主键关联,然后返回结果集,效率会高很多。

  • 使用union all而不是union。从效率上说,union all要比union快很多。

  • UPDATE、DELETE语句不使用LIMIT ,容易造成主从不一致。