文章目录
- 通用编码规范
- 1 前言
- 2 代码风格
- 2.1 结构
- 2.2 命名
- 2.2.1 通用命名
- 2.2.2 编程语言中的命名
- [强制] 类名、接口名以 UpperCamelCase 风格编写
- [强制] 类名使用名词或名词短语
- [建议] 接口使用形容词或形容词短语
- [强制] 测试类的命名以它要测试的类的名称开始,以 Test 结束
- [强制] 方法名以 lowerCamelCase 风格编写
- [建议] 方法名使用动宾短语
- [强制] 常量名以 CONSTANT_CASE 风格编写
- [建议] 减少代码中的硬编码
- [强制] 常量必须用常量修饰符修饰
- [强制] 非常量字段名以 lowerCamelCase 风格编写
- [强制] 参数名以 lowerCamelCase 风格编写。
- [强制] 局部变量名以 lowerCamelCase 风格编写
- [建议] 类型变量可用以下两种风格之一进行命名
- [建议] boolean 类型的变量命名以 is 或 has 开头
- [建议] 集合、数组类型的变量,常用名词复数
- 2.3 注释
- 3 语言特性
- 4 附录
- GIT 使用规范
- Java编程规范
- MySQL 编码规范
- 1 前言
- 2 命名规范
- 3 设计规范
- 4 使用规范
- 4.1 查询
- [强制] 不用 `select *`
- [建议] 用 IN 代替 OR
- [建议] 用 UNION 代替 OR
- [建议] SQL 语句中 IN 包含的值不应过多,控制在 200 个以内,最多不得超过300
- [建议] 平衡范式与冗余,效率优先,往往牺牲范式
- [强制] 禁止在数据库中存储图片、文件等大数据
- [建议] 高并发环境不要使用外键
- [建议] 合理控制单表数据量
- [建议] 限制单库表数量在300以内,最多不要超过500个表
- [建议] 单表字段数不要太多,字段数建议在20以内,最多不要大于50个
- [建议] sql语句尽可能简单
- [建议] 事务时间尽可能短
- [强制] 保证冗余字段数据的一致性
- [建议] 合理分离冷热数据
- [建议] 不在数据库做运算
- [建议] 当只要一行数据时使用 LIMIT 1
- [强制] 禁止在线上做数据库压力测试
- [建议] 减少与数据库交互次数,尽量采用批量SQL语句
- [建议] 获取大量数据时,建议分批次获取数据,每次获取数据少于 2000 条,结果集应小于 1M
通用编码规范
1 前言
1.1 说明
有些编程规范是大部分编程语言通用的,特把它独立出来。
示例中我采用 Java 语言。有时也混杂其他语言。
本编码规范完全适用于 Java 和 Javascript 的编程规范,对其他语言也有借鉴作用。
1.2 好的编码规范
什么样的规范才算是好的编程规范?
“大家好,才是真的好”。不是你觉得好就是好,而是绝大部分人都觉得这个编程规范好,才是一份好的规范。当然,如果你的编程风格与主流的不符合,我的建议是:强迫自己改正。
本编程规范主要参考 Google Java 编程规范,并且绝大部分规范符合谷歌编程规范,除了以下几点与其不同:
- 代码缩进。谷歌建议缩进 2 个空白符;本规范建议缩进 4 个空白符。
- 好像没了?想到再补充。
此外,本规范还参考了百度等互联网大公司的编程规范,主要是对谷歌编程规范的补充(谷歌编程规范不够详细,所涉及的规范主要是编程风格的)
当然,也有一些个人的见解。
1.3 基本准则
[建议] 坚持一致原则
维护别人写的代码,应当遵守一致原则。即修改后的代码的编程风格应该与之前的代码一致。
使用必读
不同的要求严格程度不同,参照 RFC2119 分成三个等级:
-
[强制]
表示必须(MUST)或者不允许(MUST NOT)这样做。 -
[建议]
表示一般情况下应该(SHOULD)或者不应该(SHOULD NOT)这样做,但是在某些特定情况下可以忽视这个要求。 -
[可选]
表示这个要求完全是可选的,你可以(MAY)这样做,也可以不这样做,视个人喜好而定。
2 代码风格
2.1 结构
2.1.1 缩进
[强制] 采用 4 个空格缩进而不是 2 个空格或 tab 字符
虽然谷歌推荐 2 个空格的缩进,但 4 个空格缩进的代码可读性明显更强。现在很多代码编辑器支持输入时自动把 tab 字符转化为 4 个空格符。
每当开始一个新的块,缩进增加2个空格,当块结束时,缩进返回先前的缩进级别。缩进级别适用于代码和注释。
// good
void foo() {
int a = 0;
}
// bad
int a = 0;
}
[强制] 所有的块状结构都要缩进
void foo() {
if (condition) {
// statement
}
}
[强制] switch 语句缩进要合理
switch (foo) {
case 1:
// code
break;
case 1:
// code
break;
default:
break;
}
2.1.2 空格
[强制] 注释时,注释符两边的空白符必不可少
- 双斜杠后面的空格必不可少。
- 如果在一条语句后做注释,则双斜杠两边都要一个空格。
- 可以允许多个空格,但没有必要。
// good
int foo = 0; // 这是一个注释示例
/* 这是一个注释示例 */
/** 这是一个注释示例 */
// bad
int foo = 0; //这是一个注释示例
int foo = 0;// 这是一个注释示例
/*这是一个注释示例*/
/**这是一个注释示例*/
[强制] 任何保留字(如if, while for等等)与紧随其后的左括号之间要有一个空格
// good
if (foo == 0) {
}
// bad
if(foo == 0) {
}
[强制] 任何保留字(如else、catch等)与其前面的右大括号之间要有一个空格
// good
if (foo == 0) {
} else {
}
// bad
if(foo == 0) {
}else {
}
[强制] 任何左大括号 {
前必须加一个空格
两个例外:
- @SomeAnnotation({a, b})(不使用空格)。
- String[][] x = foo;(大括号间没有空格)。
[强制] 在任何二元或三元运算符的两侧必须有空格
这也适用于以下“类运算符”符号:
类型界限中的&(<T extends Foo & Bar>)。
catch块中的管道符号(catch (FooException | BarException e)。
foreach语句中的分号。
// good
int i = 1 + 2;
// bad
int i = 1+2;
[强制] 一元运算符与操作对象之间不允许有空格
// good
if (!isOk) {
}
i++;
// bad
if (! isOk) {
}
i ++;
[强制] 在 ,
、:
、;
及右括号 )
后必须空格
for (int i = 0; i < 100; i++) {
}
int i = (int) 3.5f;
[建议] 数组初始化中,大括号内的空格是可选的
new int[] {5, 6};
new int[] { 5, 6 };
[强制] 行尾不得有多余的空格
[强制] 本规范没有要求的空格不要随便乱加
几个经典的错误例子:
// good
void foo(int i) {}
// bad
void foo (int i) {}
// bad
void foo( int i) {}
// bad
void foo(int i ) {}
[建议] 水平对齐,没这个必要,也不建议这么做
虽然增加了代码的可读性,但给维护带来问题。很多时候为了保持对齐,做了一些无用功。所以即使对于已经使用水平对齐的代码,我们也不需要去保持这种风格。
// good
int foo = 1;
String s = "foo";
// bad
int foo = 1;
String s = "foo";
2.1.3 空行
[强制] 类内连续成员之间,必须有 1 个空行
类内连续的成员(字段,构造函数,方法,嵌套类,静态初始化块,实例初始化块等)之间,必须有一个空行
例外:两个连续字段之间的空行是可选的,用于字段的空行主要用来对字段进行逻辑分组。
[建议] 类内的第一个成员前或最后一个成员后的空行是可选的
既不鼓励也不反对这样做,视个人喜好而定。
[建议] 多个连续的空行是允许的,但没必要这样做,也不推荐这样做
[建议] 在函数体内,语句的逻辑分组间使用空行
这样做可增强代码的可读性
[建议] 没意义的空行不要乱加
2.1.4 换行
[强制] 列限制:80、100,还是 120?
一个项目可以选择一行80个字符或100个字符的列限制。
我推荐 100 个字符。
任何一行如果超过这个字符数限制,必须换行。
例外:
不可能满足列限制的行(例如,Javadoc中的一个长URL,或是一个长的JSNI方法参考)。
package和import语句(见3.2节和3.3节)。
注释中那些可能被剪切并粘贴到shell中的命令行。
[建议] 自动换行的基本准则是:更倾向于在更高的语法级别处断开
[强制] 如果在非赋值运算符处断开,那么在该符号前断开
这条规则也适用于以下“类运算符”符号:点分隔符(.),类型界限中的&(<T extends Foo & Bar>),catch块中的管道符号(catch (FooException | BarException e)
// good
int i = 1 + 2 + 3 + ... + 1000000
+ 1000001;
// bad
int i = 1 + 2 + 3 + ... + 1000000 +
1000001;
[强制] 如果在赋值运算符处断开,通常的做法是在该符号后断开
这条规则也适用于foreach语句中的分号
// good,虽然在这里没必要换行
int i =
100000000;
// bad
int i
= 100000000;
[强制] 自动换行时函数名与左括号留在同一行
[强制] 自动换行时逗号(,)与其前面的内容留在同一行
[强制] 自动换行时第一行后的每一行至少比第一行多缩进 4 个空格
[强制] 非空块得换行遵守 K & R 风格
对于非空块,大括号遵循Kernighan和Ritchie风格 (Egyptian brackets):
- 左大括号前不换行
- 左大括号后换行
- 右大括号前换行
- 如果右大括号是一个语句、函数体或类的终止,则右大括号后换行; 否则不换行。例如,如果右大括号后面是else或逗号,则不换行。
Java的enum类有一些例外,在 Java 编程规范讲解。
// good
class Foo {
public void foo() {
if (condition) {
something();
} else {
other();
}
}
}
// bad
class
{
}
// bad
if (condition)
{
something();
}
else
{
other();
}
[可选] 空块可以用简洁版本,不换行
一个空的块状结构里什么也不包含,大括号可以简洁地写成{},不需要换行。
例外:如果它是一个多块语句的一部分(if/else 或 try/catch/finally) ,即使大括号内没内容,右大括号也要换行。
void doNothing() {}
括号
[强制] 函数的返回值不可以用括号包住
不然会降低可读性。
// good
return count + 100;
// bad
return (count + 100);
[强制] 任何标识符的命名都不能采用拼音
// good
class Student {}
// bad
class Xuesheng {}
2.2 命名
2.2.1 通用命名
[建议] 良好的命名应该是自解释的
良好的命名应该能够顾名思义,不需要注释
// good
int studentCount;
// bad
int count; // 学生数量
[建议] 谨慎使用缩略词
不常见的缩略词会降低代码的可读性。尽量避免缩写,除非该缩写是众所周知的,如HTML、URL等等。
[建议] 命名应该是简短且有意义的
2.2.2 编程语言中的命名
[强制] 类名、接口名以 UpperCamelCase 风格编写
// good
class Foo {...}
// bad
class foo {...}
[强制] 类名使用名词或名词短语
[建议] 接口使用形容词或形容词短语
接口命名多以able或ible结尾
interface Runable {...}
interface Accessible {...}
[强制] 测试类的命名以它要测试的类的名称开始,以 Test 结束
class HashTest {
}
HashIntegrationTest {
}
[强制] 方法名以 lowerCamelCase 风格编写
[建议] 方法名使用动宾短语
也可以使用动词。
下划线可能出现在JUnit测试方法名称中用以分隔名称的逻辑组件。一个典型的模式是:test_,例如testPop_emptyStack。 并不存在唯一正确的方式来命名测试方法。
// good
void initView() {}
void updateData() {}
// not good
void init() {}
void update() {}
[强制] 常量名以 CONSTANT_CASE 风格编写
全部字母大写,用下划线分隔单词。
那,到底什么算是一个常量?
每个常量都是一个静态final字段,但不是所有静态final字段都是常量。在决定一个字段是否是一个常量时, 考虑它是否真的感觉像是一个常量。例如,如果任何一个该实例的观测状态是可变的,则它几乎肯定不会是一个常量。 只是永远不打算改变对象一般是不够的,它要真的一直不变才能将它示为常量。
// Constants
static final int NUMBER = 5;
static final ImmutableList<String> NAMES = ImmutableList.of("Ed", "Ann");
static final Joiner COMMA_JOINER = Joiner.on(','); // because Joiner is immutable
static final SomeMutableType[] EMPTY_ARRAY = {};
enum SomeEnum { ENUM_CONSTANT }
// Not constants
static String nonFinal = "non-final";
final String nonStatic = "non-static";
static final Set<String> mutableCollection = new HashSet<String>();
static final ImmutableSet<SomeMutableType> mutableElements = ImmutableSet.of(mutable);
static final Logger logger = Logger.getLogger(MyClass.getName());
static final String[] nonEmptyArray = {"these", "can", "change"};
这些名字通常是名词或名词短语。
[建议] 减少代码中的硬编码
代码中不允许出现直接硬编码的字面常量, 尤其是 重复出现 的硬编码。
你需要做的是把硬编码定义成常量。
如果常量只在一个类中用到,则在类中定义。否则可以在公共类中定义常量。
[强制] 常量必须用常量修饰符修饰
// good
final int MAX_COUNT = 99;
// bad
int int MAX_COUNT = 99;
[强制] 非常量字段名以 lowerCamelCase 风格编写
这些名字通常是名词或名词短语。
[强制] 参数名以 lowerCamelCase 风格编写。
参数应该避免用单个字符命名。
[强制] 局部变量名以 lowerCamelCase 风格编写
比起其它类型的名称,局部变量名可以有更为宽松的缩写。
虽然缩写更宽松,但还是要避免用单字符进行命名,除了临时变量和循环变量。
即使局部变量是final和不可改变的,也不应该把它示为常量,自然也不能用常量的规则去命名它。
允许在循环中使用单个字符的变量
避免在多重循环中同时使用 i 和 j 这种容易混淆的变量名
[建议] 类型变量可用以下两种风格之一进行命名
- 单个的大写字母,后面可以跟一个数字(如:E, T, X, T2)。
- 以类命名方式(5.2.2节),后面加个大写的T(如:RequestT, FooBarT)。
[建议] boolean 类型的变量命名以 is 或 has 开头
// good
boolean isReady = false;
// bad
boolean ready = false;
[建议] 集合、数组类型的变量,常用名词复数
// good
Student student;
Student[] students;
List<Student> students;
// not good
List<Student> someStudent;
// bad
List<Student> list;
2.3 注释
这里的注释不包括文档注释
[强制] 单行注释用 //
而不是 /* */
[强制] 块注释与其周围的代码在同一缩进级别
// good
if (condition) {
// 这是一段注释
something();
}
// bad
if (condition) {
// 这是一段注释
something();
}
[强制] 多行注释可以是/* … /风格,也可以是// …风格。对于多行的/ … /注释,后续行必须从开始, 并且与前一行的*对齐
/* 这是一段很长很长很长...................很长
* 很长很长的注释。
*/
// 这也是一段很长很长很长...................很长
// 很长很长的注释。
/* 我不会告诉你这三种写法都是
* 可以的 */
[强制] 注释不要封闭在由星号或其它字符绘制的框架里。
这样做很可能给维护带来不必要的麻烦
// bad
/****************************************
* 这是一个
* 很漂亮但是没什么卵用注释
***************************************/
[建议] 禁止没意义的注释
// bad
int foo; // 定义一个变量
// 创建一个Foo类
class Foo {
}
为什么这里是建议,而不是强制?因为很多时候,判断一句注释是不是废话还跟开发者水平有关。
举个不是很恰当的例子:
// bad
// 创建一个线程并执行
new Thread(new Runnable() {
public void run() {
something();
}
}).start();
稍微懂点 Java 的都知道这句注释是句废话,但对于一个刚入门,没接触过多线程的就不这么认为了,后面这条建议是对本条建议的补充说明:
[建议] 注释一般不包含语言本身的语法、语言内置的API的说明、第三方类库(如 Spring 等)某个函数的用法的说明
不懂的去看相应的开发文档。
[建议] 对整段代码进行注释说明,而不是逐行注释
[建议] 只进行必要的注释,注释不是越多越好
3 语言特性
3.1 变量
[强制] 每次只声明一个变量
// good
int foo;
int foo2;
// bad
int foo, foo2;
// bar
var foo = 1,
foo2 = 2,
foo3 = 3;
[强制] 需要时才声明,并尽快进行初始化
不要在一个代码块的开头把局部变量一次性都声明了,而是在第一次需要使用它时才声明。 局部变量在声明时最好就进行初始化,或者声明后尽快进行初始化。
变量声明与使用的距离越远,代码的阅读与维护成本就越高。
从优化方面讲,这样做也是有好处的。
[建议] 尽量使用局部变量
Java:
尽量使用局部变量,调用方法时传递的参数以及在调用中创建的临时变量都保存在栈(Stack)中,速度较快。其他变量,如静态变量、实例变量等,都在堆(Heap)中创建,速度较慢。另外,依赖于具体的编译器/JVM,局部变量还可能得到进一步优化
[建议] 变量在真正需要的时候才开始创建
// good
if (i == 1) {
String str = "abc";
list.add(str);
}
// bad
String str = "abc";
if (i == 1) {
list.add(str);
}
3.2 语句
[强制] 一行最多一个语句
// good
int foo;
int foo2;
// bad
int foo; int foo2;
[强制] 禁止连续赋值
连续赋值不仅影响可读性,而且很多时候容易出错。
// good
a = 1;
b = 1;
c = 1;
a = b = c = 1;
[建议] 避免深层嵌套
多级嵌套降低了代码的可读性。
[强制] 使用大括号,即使大括号是可选的
大括号与if, else, for, do, while语句一起使用,即使只有一条语句(或是空),也应该把大括号写上。
// good
if (i > 0) {
i++;
}
// bad
if (i > 0)
i++;
条件
[可选] 对于相同变量或表达式的多值条件,用 switch 代替 if
// good
switch (i) {
case 1:
// ......
break;
case 2:
case 3:
case 4:
// ......
break;
}
// bad
if (i == 1) {
// ......
} else if (i == 1 || i == 2 || i == 3) {
// ......
}
[可选] 如果函数或全局中的 else 块后没有任何语句,可以删除 else
// good
function getName() {
if (name) {
return name;
}
return 'unnamed';
}
// bad
function getName() {
if (name) {
return name;
} else {
return 'unnamed';
}
}
循环
[建议] 避免在循环中重复获取长度
// bad
for (int i = 0; i < list.size(); i++) {
}
// good
for (int i = 0, int size = list.size(); i < size; i++) {
}
// good
int size = list.size();
for (int i = 0, ; i < size; i++) {
}
[建议] 嵌套循环将小循环写在外层
// good
for (int i = 0; i < 5; i++) {
for (int k = 0; k < 5000; k++) {
}
}
// bad
for (int k = 0; k < 5000; k++) {
for (int i = 0; i < 5; i++) {
}
}
[建议] 避免在循环中做耗时的工作
- 循环中不要使用try-catch()语句
- 不要在循环中调用synchronized(同步)方法
- 循环中不要频繁声明对象,对象可以在循环外创建
- 循环中尽量避免数据库查询操作等耗时的操作
- …
3.3 函数
[建议] 函数的长度控制在 50 行以内
太长的函数难以维护。
关于一个函数的规范行数没有统一标准,40行、60行也可以。
[建议] 函数的参数控制在 6 个以内
面向对象
[强制] 除非必要,否则不允许使用 public 修饰类的属性
4 附录
4.1 驼峰式命名法(CamelCase)
驼峰式命名法分大驼峰式命名法(UpperCamelCase)和小驼峰式命名法(lowerCamelCase)。 有时,我们有不只一种合理的方式将一个英语词组转换成驼峰形式,如缩略语或不寻常的结构(例如"IPv6"或"iOS")。Google指定了以下的转换方案。
名字从散文形式(prose form)开始:
把短语转换为纯ASCII码,并且移除任何单引号。例如:“Müller’s algorithm"将变成"Muellers algorithm”。
把这个结果切分成单词,在空格或其它标点符号(通常是连字符)处分割开。
推荐:如果某个单词已经有了常用的驼峰表示形式,按它的组成将它分割开(如"AdWords"将分割成"ad words")。 需要注意的是"iOS"并不是一个真正的驼峰表示形式,因此该推荐对它并不适用。
现在将所有字母都小写(包括缩写),然后将单词的第一个字母大写:最后将所有的单词连接起来得到一个标识符。
每个单词的第一个字母都大写,来得到大驼峰式命名。
除了第一个单词,每个单词的第一个字母都大写,来得到小驼峰式命名。
示例:
:
// good
XmlHttpRequest // XML HTTP request
newCustomerId // new customer ID
innerStopwatch // inner stopwatch
supportsIpv6OnIos // supports IPv6 on iOS
YouTubeImporter // YouTube importer
YoutubeImporter // 不推荐
// bad
XMLHTTPRequest
newCustomerID
innerStopWatch
supportsIPv6OnIOS
Note:在英语中,某些带有连字符的单词形式不唯一。例如:"nonempty"和"non-empty"都是正确的,因此方法名checkNonempty和checkNonEmpty也都是正确的。
文章目录
- 通用编码规范
- 1 前言
- 2 代码风格
- 2.1 结构
- 2.2 命名
- 2.2.1 通用命名
- 2.2.2 编程语言中的命名
- [强制] 类名、接口名以 UpperCamelCase 风格编写
- [强制] 类名使用名词或名词短语
- [建议] 接口使用形容词或形容词短语
- [强制] 测试类的命名以它要测试的类的名称开始,以 Test 结束
- [强制] 方法名以 lowerCamelCase 风格编写
- [建议] 方法名使用动宾短语
- [强制] 常量名以 CONSTANT_CASE 风格编写
- [建议] 减少代码中的硬编码
- [强制] 常量必须用常量修饰符修饰
- [强制] 非常量字段名以 lowerCamelCase 风格编写
- [强制] 参数名以 lowerCamelCase 风格编写。
- [强制] 局部变量名以 lowerCamelCase 风格编写
- [建议] 类型变量可用以下两种风格之一进行命名
- [建议] boolean 类型的变量命名以 is 或 has 开头
- [建议] 集合、数组类型的变量,常用名词复数
- 2.3 注释
- 3 语言特性
- 4 附录
- GIT 使用规范
- Java编程规范
- MySQL 编码规范
- 1 前言
- 2 命名规范
- 3 设计规范
- 4 使用规范
- 4.1 查询
- [强制] 不用 `select *`
- [建议] 用 IN 代替 OR
- [建议] 用 UNION 代替 OR
- [建议] SQL 语句中 IN 包含的值不应过多,控制在 200 个以内,最多不得超过300
- [建议] 平衡范式与冗余,效率优先,往往牺牲范式
- [强制] 禁止在数据库中存储图片、文件等大数据
- [建议] 高并发环境不要使用外键
- [建议] 合理控制单表数据量
- [建议] 限制单库表数量在300以内,最多不要超过500个表
- [建议] 单表字段数不要太多,字段数建议在20以内,最多不要大于50个
- [建议] sql语句尽可能简单
- [建议] 事务时间尽可能短
- [强制] 保证冗余字段数据的一致性
- [建议] 合理分离冷热数据
- [建议] 不在数据库做运算
- [建议] 当只要一行数据时使用 LIMIT 1
- [强制] 禁止在线上做数据库压力测试
- [建议] 减少与数据库交互次数,尽量采用批量SQL语句
- [建议] 获取大量数据时,建议分批次获取数据,每次获取数据少于 2000 条,结果集应小于 1M
GIT 使用规范
前言
规范
[建议] master 分支仅用来发布新版本,不允许在上面开发
使用分支能够有效地避免不同开发工作之间的相关干扰。
当需要开发新功能、修复bug、试验新的想法时,应该新建一个分支,待开发工作完成并测试后,再把工作分区合并到主分区上。
[强制] 提交时须带上清晰的描述
提交信息格式:
第一行:一句话简单总结一下你做的修改(别超过50个字)
第二行:空行(必须空行)
剩余行:详细描述。为什么要做这次改动?跟以前的实现有什么不一样?
[建议] 相关的改动才放在一起提交
一次提交(git commit)应该只包含相关的改动。比如说,修复两个不同的bug就应该分开来做两次提交。提交的改动越小(或越少),其他开发者理解起来就越容易;如果改动有问题,退回去也比较方便。Git有一个暂存区域(staging area)的概念,它还允许你暂存文件的某些部分,这更便于你创建非常细粒度的提交。
[建议] 经常性地提交
经常提交势必让你每次提交的东西都很少,也有助于你只提交相关的改动。并且,你还能更频繁地与别人共享代码。通过这种方式,所有人在集成代码时都会感觉更轻松,也就能避免一些不必要的冲突。相比之下,如果每次提交的东西很多、改动很大、时间间隔很长,那么在代码合并(merge)过程中产生的冲突就很难解决了。
[建议] 高频率、细粒度地提交
把大功能的实现尽可能分解成更多的相对独立的小模块,每个小模块测试完成后提交修改,再开始下一模块的开发。
这样做能保证每次提交的内容高度相关,方便定为错误、解决合并冲突。
[建议] 提交之前进行测试
提交之前进行测试,测试完成并且没有错误才提交。
但当你把代码推送(git push)到服务器与别人共享时,这个问题就大了——在这之前,请务必测试你的代码!
Java编程规范
1 前言
Java编程必须遵守通用编程规范和本编程规范。
2 代码风格
2.1 文件
[建议] 源文件编码格式为UTF-8。
2.2 结构
2.3 命名
2.3.2 特殊转义序列
源文件结构
一个源文件包含(按顺序地):
许可证或版权信息(如有需要)
package语句
import语句
一个顶级类(只有一个)
以上每个部分之间用一个空行隔开。
3.2 package语句
[强制] package语句不换行
3.3 import语句
[强制] import 语句不要使用通配符
// good
import java.util.Date;
import java.util.Map;
// bad
import java.util.*;
[强制] import语句不换行
每个import语句独立成行。
[建议] import语句按照一定的规则分组
import语句分为以下几组,按照这个顺序,每组由一个空行分隔:
所有的静态导入独立成组
com.google imports(仅当这个源文件是在com.google包下)
第三方的包。每个顶级包为一组,字典序。例如:android, com, junit, org, sun
java imports
javax imports
组内不空行,按字典序排列。
3.4 类声明
3.4.1 只有一个顶级类声明
每个顶级类都在一个与它同名的源文件中(当然,还包含.java后缀)。
例外:package-info.java,该文件中可没有package-info类。
[建议] 每个类应该以某种逻辑去排序它的成员
新的方法不能总是习惯性地添加到类的结尾
推荐按照访问权限的大小分别排列属性和方法:
- public 数据成员
- protected 数据成员
- 包级别(没有访问修饰符的,默认为friendly) 数据成员
- private 数据成员
- 构造函数
- public 方法
- protected 方法
- private 方法
- 包级别方法
[强制] 构造函数必须写在所有方法的前面
[强制] 重载方法必须写在一起
一个类的多个构造函数,或是多个同名方法,这些函数/方法应该按顺序出现在一起,中间不要放进其它函数/方法。
4.7 用小括号来限定组:推荐
除非作者和reviewer都认为去掉小括号也不会使代码被误解,或是去掉小括号能让代码更易于阅读,否则我们不应该去掉小括号。 我们没有理由假设读者能记住整个Java运算符优先级表。
注解
[建议] 一个注解独占一行。位于方法的前面,Javadoc(如果有的话)的后面。
@Override
@Nullable
public String getNameIfPresent() { ... }
[建议] 单个注解可以和签名的第一行出现在同一行
@Override public int hashCode() { ... }
[建议] 应用于字段的注解紧随文档块出现,应用于字段的多个注解允许与字段出现在同一行。例如:
@Partial @Mock DataLoader loader;
2.3 命名
5.1 对所有标识符都通用的规则
[强制] 标识符只能使用ASCII字母和数字
具体命名参照通用编程规范,这里不再赘述。
严格来说,遵守本规范的Java代码中,标识符只有三种形式:
UpperCamelCase、lowerCamelCase 和 CONSTANT_CASE。
除了常量外,所有标识符只由字母和数字组成。
[建议] 不使用特殊前缀或后缀
// good
int count;
String name;
// bad
int iCount; // 常用 i 来表示整形
String sName; // 常用 s 来表示字符串
String nName; // 常用 m 来表示 数据成员
TODO 这条建议是否应该改成强制?
// good?
Button btnClose; // 关闭按钮
包命名规范
[强制] 包名全部小写,连续的单词只是简单地连接起来,不使用下划线
// good
com.google.util.imagetool
// bad
com.google.util.imageTool
com.google.util.image_tool
[强制] 包名采用反域名命名规则,至少四级
一、二级包名通常是域名的反写,因为域名不会重复。
如果有域名则采用域名的反写:
chenjianhang.com => com.chenjianhang
abc.net => net.abc
如果没有域名:
一级包名为域名后缀,如com、net、cn、me等。
公司的项目通常是com,个人的项目可以选择me,团队的项目可以采用team,其他的也可以。
二级包名是公司、组织、机构、团队或者个人的名称。
三级包名是项目名。
四级包名为模块名或层级名。
com.baidu.tieba.view
com.sina.weibo.xxx
2.4 注释
Javadoc
7.1 格式
[强制] Javadoc 格式必须正确
只允许下面这两种形式
当整个 Javadoc 块能容纳于一行,且没有 Javadoc 标记 @XXX
时,可以使用单行形式。
/**
* 这是一个多行的 Javadoc 注释
* 这是一个多行的 Javadoc 注释
*/
public void foo() { ... }
/** 这是一个单行的 Javadoc 注释 */
7.1.2 段落
空行(即,只包含最左侧星号的行)会出现在段落之间和Javadoc标记(@XXX)之前(如果有的话)。 除了第一个段落,每个段落第一个单词前都有标签
,并且它和第一个单词间没有空格。
7.1.3 Javadoc标记
标准的Javadoc标记按以下顺序出现:@param, @return, @throws, @deprecated, 前面这4种标记如果出现,描述都不能为空。 当描述无法在一行中容纳,连续行需要至少再缩进4个空格。
7.2 摘要片段
每个类或成员的Javadoc以一个简短的摘要片段开始。这个片段是非常重要的,在某些情况下,它是唯一出现的文本,比如在类和方法索引中。
这只是一个小片段,可以是一个名词短语或动词短语,但不是一个完整的句子。它不会以A {@code Foo} is a…或This method returns…开头, 它也不会是一个完整的祈使句,如Save the record…。然而,由于开头大写及被加了标点,它看起来就像是个完整的句子。
Tip:一个常见的错误是把简单的Javadoc写成/** @return the customer ID /,这是不正确的。它应该写成/* Returns the customer ID. */。
需要 Javadoc 的情况
[强制] 除了文档后面讲到的几个例外,每个 public 类及它的每个 public 和 protected 成员处必须使用 Javadoc
[建议] getter 和 setter 方法,如果没必要,可以不写 Javadoc注释
// bad
/** 返回学生数量 */
public int getStudentCount() {
return studentCount;
}
当然,如果方法名中有比较偏僻的英文词汇,或者有一些相关信息是需要读者了解的, Javadoc 注释不应该省略
/** 返回符合规范的名称 */
public String getCanonicalName() {
}
[建议] 单元测试类中的测试方法很多时候不需要文档说明
[建议] 如果一个方法重载了超类中的方法,那么Javadoc并非必需的
[建议] 对于包外不可见的类、方法和数据成员,如有需要,也是要使用Javadoc的
2 语言特性
条件表达式
[强制] 如果条件表达式是一个 boolean 类型的变量,禁止与 true 或 false 比较
boolean isOk = true;
// good
if (isOk) {
}
// good
if (!isOk) {
}
// bad
if (isOk == true) {
}
if (isOk == false) {
}
自增自减
[强制] 如果仅仅是自增或者自减,使用自增/自减表达式:
// good
i++;
i--;
// bad
i = i + 1;
i = i - 1;
[强制] 禁止自增表达式和其他运算符混合用
// bad
i = i++; // Java 和 C 执行结果是不一样的
if (c++ = d++) {
...
}
[强制] 禁止使用难以理解、容易混淆的表达式
// good
a = b + c;
d = a + r;
// bad
d = (a = b + c) + r;
if ((a == b) && (c == d)) // good
if (a == b && c == d) // 不反对
位运算符
[建议] 除非是对性能要求极高,否则不允许使用位运算符来代替除法
// good
i /= 4;
// bad
i = i >> 2;
返回语句
// good
return booleanExpression;
// bad
if (booleanExpression) {
return true;
} else {
return false;
}
条件运算符
[建议] 如果条件运算符 ?
前面有二元运算符,条件表达式应该用括号扩起来
// good
i = (x >= 0) ? x : -x;
// bad
i = x >=0 ? x : -x;
[建议] 使用条件表达式增强代码的可读性
// good
return (condition ? x : y);
// bad
if (condition) {
return x;
}
return y;
// god
i = (x >= 0) ? x : -x;
// bad
if (x >= 0) {
i = x;
} else {
i = -x;
}
switch语句
[强制] switch 语句内的语句组必须正确的终止
所有 case 语句和 default 语句必须满足三条规范之一:
- 语句通过break、continue、return 或抛出异常来终止。如示例的
case 3
。 - 语句通过一条注释来说明程序将继续执行到下一个语句组。如示例的
case 2
。 - 语句希望继续执行到下一个语句组,并且语句内不需要执行任何代码,此时语句内不允许任何空行。如示例的
case 1
。
switch (input) {
case 1: // 注意case 1 与 case 2 之间不能有空行
case 2:
prepareOneOrTwo();
// 不用break
case 3:
handleOneTwoOrThree();
break;
default:
handleLargeNumber(input);
break;
}
[强制] switch 语句必须包含 default 语句,并且写在所有 case 语句的后面
即使 default 语句什么代码也不包含,你也必须这么做
数组
[建议] 数组初始化可写成块状结构
new int[] { 0, 1, 2, 3 };
new int[] {
0, 1, 2, 3
};
new int[] {
0,
1,
2,
3
};
new int[] {
0, 1,
2, 3
};
new int[]
{0, 1, 2, 3};
[强制] 非C语言风格的数组声明
中括号是类型的一部分。
// good
String[] args;
// bad
String args[];
类
[建议] 类和成员的修饰符应该按顺序出现
推荐的顺序:
public protected private abstract static final transient volatile synchronized native strictfp
枚举类
枚举常量间用逗号隔开,换行可选。
没有方法和文档的枚举类可写成数组初始化的格式:
private enum Suit { CLUBS, HEARTS, SPADES, DIAMONDS }
由于枚举类也是一个类,因此所有适用于其它类的格式规则也适用于枚举类。
3 待办事项
[强制] TODO 前后保持一个空格
类似的还有 XXX 和 FIXME。
// good
// TODO 这是一句必要的说明
// bad
//TODO 这是一句必要的说明
[强制] 模块编码完成后,删除系统生成的 TODO
// bad
// TODO Auto-generated method stub
[建议] 合理地使用待办事项标记
- TODO:代码还未编写。一般会说明需要添加什么功能。
- XXX:功能已实现,有待改进或优化。一般会说明要改进的地方。
- FIXME:代码有错误,不能正常工作,需要修改。一般会说明如何修正。
// TODO 这里需要添加XXX功能
// XXX 建议这里使用的排序改成冒泡排序,效率更高
// FIXME 当用户没有输入时,这里有bug,需加个判断
4 编程实践
[强制] 只要是合法的,就把@Override注解给用上
[强制] 捕获的异常必须处理
常见的处理方式就是打印日志。
如果它被认为是不可能的,则把它当作一个AssertionError重新抛出。
如果它确实是不需要在catch块中做任何响应,需要做注释加以说明。
try {
int i = Integer.parseInt(response);
return handleNumericResponse(i);
} catch (NumberFormatException ok) {
// it's not numeric; that's fine, just continue
}
return handleTextResponse(response);
例外:在测试中,如果一个捕获的异常被命名为expected,则它可以被不加注释地忽略。下面是一种非常常见的情形,用以确保所测试的方法会抛出一个期望中的异常, 因此在这里就没有必要加注释。
try {
emptyStack.pop();
fail();
} catch (NoSuchElementException expected) {
}
[强制] 静态成员必须使用类进行调用
class Foo {
public static void foo() {
}
}
// good
Foo.foo();
// bad
new Foo().foo();
[强制] 禁止重载Object.finalize
如果你觉得非要重载Object.finalize不可,请先仔细阅读和理解《Effective Java》 第7条:“Avoid Finalizers”,然后不要使用它。
编译器相关
警告
[建议] 认真对待代码中的警告,并尽可能处理掉
[强制] 过时的方法和类应当不再使用,用新的方法替换
@Deprecated
API
[建议] 多个字符串拼接时用StringBuilder
避免频繁地通过 +
进行字符串拼接
**StringBuffer **
线程
线程池
[建议] 如果程序频繁创建线程,则可以考虑使用线程池
[建议] 提倡run方法中使用无限循环的形式,然后使用一个布尔型标识控制循环的停止
未整理
[建议] 禁止随意使用静态变量
举个不恰当的例子:
// good
class Foo {
String name;
void sayHello() {
System.out.println("My name is" + name);
}
}
// bad
class Foo {
static String name;
static void sayHello() {
System.out.println("My name is" + name);
}
}
不可否认的是,很多刚学 java 的新手常常这么做。
[建议] 尽可能避免调用本类的getter、setter方法
一是为了简洁、二是为了效率
// good
class Foo {
String name;
public String getName() {
return name;
}
void sayHello() {
System.out.println("My name is" + name);
}
}
// bad
class Foo {
String name;
public String getName() {
return name;
}
void sayHello() {
System.out.println("My name is" + getName());
}
}
[建议] 调用本类的属性和方法时一般不加this
为了简洁。
如果和参数冲突,才加 this。
// good
class Foo {
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
// bad
class Foo {
String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
[建议] 用静态代替虚拟
如果不需要访问某对象的字段,将方法设置为静态,调用会加速15%到20%。这也是一种好的做法,因为你可以从方法声明中看出调用该方法不需要更新此对象的状态。
[建议] 常量用 static final 修饰
// good
static final String DATABASE_NAME = "foo";
// bad
final String DATABASE_NAME = "foo";
常见缩写
anim // animation
avg // average
bg // background
buf // buffer
ctrl // control
del // delete
doc // document
err // error
esc // escape
inc // increment
info // infomation
init // initial
ic // icon
img // image
len // length
lib // library
msg // message
pwd // password
pos // position
srv // server
str // string
tmp // temp
lambda表达式
[建议] 一般的程序没有用到 lambda 表达式的必要
lambda 表达式可读性低。
MySQL 编码规范
1 前言
本规范适用于 MYSQL 数据库设计,对其他数据库设计也有参考价值。
2 命名规范
[强制] 库名、表名、字段名必须使用小写字母,并采用下划线分割
[强制] 库名、表名、字段名尽可能简短,禁止超过 32 个字符
为了统一规范、易于辨识以及减少传输量。
[强制] 库名、表名、字段名禁止使用 MySQL 保留字
当库名、表名、字段名等属性含有保留字时,SQL语句必须用反引号引用属性名称,这将使得SQL语句书写、SHELL脚本中变量的转义等变得非常复杂。
常见关键字,如:name,time ,datetime password 等。
2.1 表命名规范
简短、有意义、易于理解的英文单词。
谨慎使用缩写。
必要的注释。
[建议] 表名加前缀
表的前缀一般用系统或模块的名称缩写,而不是没有意义的t(table)。
[建议] 临时库、表名必须以tmp为前缀,并以日期为后缀
例如 tmp_test01_20130704。
[建议] 备份库、表必须以bak为前缀,并以日期为后缀
例如 bak_test01_20130704。
[建议] 表名不可以太长,最好不要超过3个英文单词长度(22个字母)。
[强制] 使用英文单词的单数形式
字段命名规范
[建议] 简短、有意义、易于理解的英文单词
例如 UI_UserID(表tb_user_info-list)
系统中属于是业务内的编号字段,代表一定业务信息,建议字段命名为code , 如工作单编号wf_code .
[建议] 不要在字段中包含数据类型,如:datetime
不要在数据库表字段(列名)命名时重复表名,可以使用表名首字母(不包含数据库表名前缀)
2.2 视图命名规范
视图的命名请遵循以下命名规范:UV _ + 系统模块缩写(与表前缀类似)+_ + 功能标识 + 代表视图查询的主要表名(不带前缀)或功能的英文单词或英文单词缩写。
如果一个视图只对一个表进行查询,建议视图的名称就用视图所查询的表的表名(不带前缀)。这样有利于根据表名找到相应的视图。
注:UV是userView缩写
存储过程命名规范
存贮过程的命名请遵循以下命名规范:USP_ + 系统模块缩写(与表前缀类似)+_ + 功能标识 + 代表存贮过程操作的主要表名(不带前缀)或功能的英文单词或英文单词缩写。
如果一个存贮过程只对一个表进行操作,建议存贮过程的名称就用存贮过程所操作的表的表名(不带前缀)。这样有利于根据表名找到相应的存贮过程。例如:
用于新增的存贮过程USP_MESSAGE_Add_Model
用于修改的存贮过程USP_ MESSAGE_Upt_Model
用于删除的存贮过程USP_ MESSAGE_Del_ Modele
注:USP是user stored procedure
触发器命名规范
3 设计规范
3.1 表设计规范
[建议] 使用INNODB存储引擎
INNODB引擎是MySQL5.5版本以后的默认引擘,支持事务、行级锁,有更好的数据恢复能力、更好的并发性能,同时对多核、大内存、SSD等硬件支持更好,支持数据热备份等,因此INNODB相比MyISAM有明显优势。
[建议] 建议使用 UNSIGNED 存储非负数值
同样的字节数,非负存储的数值范围更大。如TINYINT有符号为 -128-127,无符号为0-255。
[建议] 强烈建议使用TINYINT来代替ENUM类型
ENUM类型在需要修改或增加枚举值时,需要在线DDL,成本较大;ENUM列值如果含有数字类型,可能会引起默认值混淆。
[建议] 根据第三范式(3NF)来设计数据库,再根据效率做必要反范式设计
[建议] 避免使用NULL字段
原因:
- NULL字段很难查询优化;
- NULL字段的索引需要额外空间;
- NULL字段的复合索引无效;
建议用 0、空串、当前时间或特殊值代替 NULL 值。
[建议] 合理选择字段类型
INT 类型占存储空间小,速度快。
例如:用UNSIGNED INT 而不是 char(15) 存储 IPv4。
通过MySQL函数inet_ntoa和inet_aton来进行转化IPv4。
SELECT INET_ATON('209.207.224.40'); 3520061480
SELECT INET_NTOA(3520061480); 209.207.224.40
[建议] 用好字段类型
整型:
- tinyint(1Byte)最大255
- smallint(2Byte)最大 3 万多,无符号最大 6 万多
- mediumint(3Byte)最大 800 多万,无符号最大 1600 多万
- int(4Byte)最大 21 亿多,无符号最大 42 亿多
- bigint(8Byte)
建议:
- 无符号整型除非超过了某个级别的最大值,否则不允许使用更大范围的类型。比如储存的整型是无符号的,且最大不超过 1600 万,就用 mediumint 而不是 int。
- 自增序列类型的字段只能使用unsigned int,防止超出违反(比如用户大批量导入数据)。
浮点型:
建议:
- 使用 DECIMAL(M,D) 而不是 DECIMAL;
- 建议乘以固定倍数转换成整数存储,可以节省存储空间,且不会带来任何附加维护成本。
字符型:
- text(2Byte)最大 65,535
- MEDIUMTEXT 最大长度为16,777,215。
- LONGTEXT 最大长度为4,294,967,295
建议:
- varchar的性能会比text高很多。
- 尽量不要用text。
- 禁用blob,实在避免不了blob,请拆表。
时间型:
建议:
- 如果时间字段只需要精确到天,那就用date类型。
- 需要精确(年月日时分秒)的时间字段,可以使用datetime,timestamp
优先使用enum或set
例如:sex
enum (‘F’, ‘M’)。
(4)字段的描述
a.字段必须填写描述信息(注释)
(5)加索引规则
a.表建好后数据库自动为表生成一个索引(为
自动增长的列生成唯一索引),如果在对这列添加索引,数据库会给一个警告,内容大概是,已经为这列添加了索引,建议修改索引名称和自动增长列名保持一致,为了方便使用。
b.如果在添加索引时,建议索引名称和数据库列名保持一致,为了方便使用
c.如果字段事实上是与其它表的关键字相关联而未设计为外键引用,需建索引。
d.如果字段与其它表的字段相关联,需建索引。
e.如果字段需做模糊查询之外的条件查询,需建索引。
f.除了主关键字允许建立簇索引外,其它字段所建索引必须为非簇索引。
[强制] 请为每一张表设计一个与业务无关的主键字段。
主键是不可修改的,当业务变更时,与业务有关的主键会造成很多问题。比如你用int型做文章的id,但是如果在未来某一天文章数超过了无符号整形的最大值,你将没法将主键修改成bigint。或者为了给用户起一个唯一id用了自增主键,但是如果未来有其他的项目用户要合并进来,他也是这么做的。这时候为了区分不同的项目可能要在这个用户id前加一个前缀,这时候也没法修改主键的值。主键之所以叫做主键就是到什么时候都不能改,所以最好的方案就是使用自增数字id做主键,并且不要给这个主键赋予一个业务相关的意义。
根据数据量,可以采用自增 INT 主键或者其他类型的主键。
典型错误例子:
使用学号作为学生表的主键;使用用户名作为用户表的主键。
[建议] 不用联合主键
应尽量避免在库表中引入与业务逻辑相关的主键关系。
复合主键的引入,很大程度上意味着业务逻辑已经侵入到数据存储逻辑之中。
给原来的“复合主键”做唯一索引。
[建议] 使用自增主键
如果是非互联网应用,数据量不是很大,优先考虑使用自增主键。
- 占用存储空间小。
- insert等操作执行效率高。
互联网等数据量大的应用,因为要分库分表,不推荐使用自增主键。
[建议] varchar 主键不建议使用 UUID
原因:
- InnoDB 采用聚合索引(按照主键的顺序来排放数据),而 UUID 是无序的,会造成插入时的排序和数据移动,带来很大的I/O,会影响效率。
- 效率不高。数据在索引的时候效率会随着体积的增加而降低。
- uuid主键id显得冗长,不够友好。
- 存储一个UUID要花费更多的空间。
制定合适的主键生成策略,保证主键的唯一性,有序性和简短。
自增 id 作为主键(或者是改用有特定顺序的键),uuid作为唯一键。
索引
[建议] 谨慎合理使用索引
改善查询、减慢更新;
索引一定不是越多越好能不加就不加,要加的一定得加;
覆盖记录条数过多不适合建索引,例如“性别”;
[建议] 为搜索字段建索引
[建议] 不在低基数列上建立索引
例如“性别”。
大部分场景下,低基数列上建立索引的精确查找,相对于不建立索引的全表扫描没有任何优势,而且增大了IO负担。
[强制] 禁止重复索引。
重复索引增加维护负担、占用磁盘空间,且没有任何好处。
[强制] 字符字段必须使用前缀索引(MySQL)
视图设计规范
在视图中必须说明以下内容:
- 目的:说明此视图的作用。
- 创建者:首次创建此视图的人的姓名。在此请使用中文全名,不允许使用英文简称。
- 修改者、修改日期、修改原因:如果有人对此视图进行了修改,则必须在此视图的前面加注修改者姓名、修改日期及修改原因。
- 对视图各参数及变量的中文注解
建议:在数据库中创建一个文本文件保存创建脚本
存储过程设计规范
在存贮过程中必须说明以下内容:
- 目的:说明此存贮过程的作用。
- 作者:首次创建此存贮过程的人的姓名。在此请使用中文全名,不允许使用英文简称。
- 创建日期:创建存贮过程时的日期。
- 修改记录:
修改记录需包含修改顺序号、修改者、修改日期、修改原因,修改时不能直接在原来的代码上修改,也不能删除原来的代码,只能先将原来的代码注释掉,再重新增加正确的代码。修改顺序号的形式为:log1,log2,log3。。。,根据修改次数顺序增加,同时在注释掉的原来的代码块和新增的正确代码块前后注明修改顺序号。
5. 对存贮过程各参数及变量的中文注解。
建议:在数据库中创建一个文本文件保存创建脚本
安全
[强制] 禁止在数据库中存储明文密码
采用加密字符串存储密码,并保证密码不可解密,同时采用随机字符串加盐保证密码安全。防止数据库数据被公司内部人员或黑客获取后,采用字典攻击等方式暴力破解用户密码。
4 使用规范
4.1 查询
[强制] 不用 select *
SELECT 只获取必要的字段。
消耗cpu,io,内存,带宽;这种程序不具有扩展性;
减少网络带宽消耗;
能有效利用覆盖索引;
表结构变更对程序基本无影响。
[建议] 用 IN 代替 OR
IN是范围查找,MySQL内部会对IN的列表值进行排序后查找,比OR效率更高。
or的效率是n级别;
in的效率是log(n)级别;
/* good */
select id from t where phone in ('159', '136');
/* bad */
select id from t where phone='159' or phone='136';
[建议] 用 UNION 代替 OR
MySQL 的索引合并很弱智。
/* good */
select id from t where phone='159'
union
select id from t where name='jonh'
/* bad */
select id from t where phone = '159' or name = 'john';
[建议] SQL 语句中 IN 包含的值不应过多,控制在 200 个以内,最多不得超过300
[建议] 平衡范式与冗余,效率优先,往往牺牲范式
[强制] 禁止在数据库中存储图片、文件等大数据
数据库通常存储的是文件的路径(多用相对路径)。
[建议] 高并发环境不要使用外键
高并发环境不要使用外键,太容易产生死锁,应由程序保证约束。
[建议] 合理控制单表数据量
int型不超过1000w,含char则不超过500w。
mysql在处理大表(char的表>500W行,或int表>1000W)时,性能就开始明显降低,所以要采用不同的方式控制单表容量
A:根据数据冷热,对数据分级存储,历史归档
B:采用分库/分表/分区表,横向拆分控制单表容量
C:对于OLTP系统,控制单事务的资源消耗,遇到大事务可以拆解,采用化整为零模式,避免特例影响大众
[建议] 限制单库表数量在300以内,最多不要超过500个表
[建议] 单表字段数不要太多,字段数建议在20以内,最多不要大于50个
[建议] sql语句尽可能简单
拆分复杂 SQL 为多个小 SQL,避免大事务,减少锁时间。
- 一条 sql 只能在一个 cpu 运算,拆分后可以使用多核 CPU。
- 简单的 SQL 容易使用到 MySQL 的 QUERY CACHE。
- 减少锁表时间特别是 MyISAM。
[建议] 事务时间尽可能短
bad case:
上传图片事务
[强制] 保证冗余字段数据的一致性
确保数据更新的同时冗余字段也被更新。
[建议] 合理分离冷热数据
将大字段、访问频率低的字段拆分到单独的表中存储,分离冷热数据。
有利于有效利用缓存,防止读入无用的冷数据,较少磁盘IO,同时保证热数据常驻内存提高缓存命中率。
[建议] 不在数据库做运算
不在索引列上进行数学运算或函数运算。
cpu计算务必移至业务层。