转载:阿里巴巴开发规范<精简>

转载:@ Hey 锡瑞 阿里巴巴开发规范_Hey 锡瑞的博客-CSDN博客_阿里开发规范文档https://blog.csdn.net/RuiKe1400360107/article/details/105907166

一、编程规范
1.1、命名风格
(1)不能以下划线或者美元符号开始和结束;
(2)不能使用拼音和英文混合方式,不能直接中文方式;
(3)类名使用UpperCamelCase风格,但是DO/BO/DTO/VO/AO/PO/UID字母需要大写
(4)方法名、参数名、成员变量、局部变量使用lowerCameCase风格,驼峰形式;
(5)常量命名全部大写,单词下划线隔开,不嫌名字长;
(6)抽象类Abstract或者Base开头,异常类Exception结尾,测试类Test结尾;
(7)POJO中布尔型变量不加is前缀,可能引起序列化错误;
(8)包名全小写,点分符号之间只有一个英语单词;
(9)杜绝不规范缩写;
(10)避免在子父类的成员变量之间、不同代码块的局部变量之间采用完全相同的命名;
(11)在常量与变量的命名时,表示类型的名词放在词尾,以提高辨识度;
(12)接口中类的方法和属性不要加任何修饰符号(public也不要加),并加上注释(JDK8 中接口允许有默认实现);
(13)Service/DAO层方法命名规约:
获取单个对象get做前缀、获取多个对象list做前缀,复数做结尾(如listObjects)、
获取统计值得方法用count做前缀、插入的方法用save/insert做前缀、
删除方法用remove/delete做前缀、修改的方法用update做前缀;
(14)领域模型命名规约:
数据对象:xxxDO ,xxx是表名、数据传输对象xxxDTO、展示对象xxxVO,xxx一般是网页名字、POJO是DO/DTO/BO/VO的简称禁止命名成xxxPOJO;
1.2、常量定义
(1)不允许未经过定义的常量直接出现在代码中;
(2)long或者Long赋值时,数值后面使用大写L不能小写l,小写容易跟数值1混淆;
(3)不要使用一个常量类维护所有常量;
(4)如果一个变量仅在一个固定范围内变化用enum类型定义;
1.3、代码风格
(1)大括号的约定。如果大括号内为空直接写成{},如果非空:
左大括号前不换行、左大括号后换行;
右大括号前换行、右大括号后还有else等代码则不换行——>表示终止的右大括号后必须换行;
(2)左小括号和字符之间不出现空格、右小括号和字符之间不出现空格。左大括号前需要空格;
反例: if (空格 a == b 空格)
(3)if/for/while/switch/do等保留字与括号之间必须加空格;
(4)二元三元运算符左右两边都要加一个空格;
(5)采用4个空格缩进,禁止使用tab字符;
(6)注释的双斜杠与注释内容之间仅有一个空格;
// 这是示例注释,请注意在双斜线之后有一个空格
(7)单行字符数限制不超过120个,超出要换行,换行时有下面原则:
第二行相对第一行缩进4个空格,从第三行开始不缩进;
运算符与下文一起换行;
方法调用的点符号与下文一起换行;
方法调用中多个参数需要换行时,在逗号后进行;
在括号前不要换行;
(8)方法参数定义或者传入时,多个逗号后必须加空格;
(9)单个方法的总行数不超过80行;
1.4、OOP规范
(1)避免通过一个类的对象引用访问此类的静态变量或者静态方法,会增加解析成本,直接通过类名访问即可;
(2)所有的覆写方法必须加@Override注解;
(3)尽量不用可变参数编程(可变参数必须放置在参数列表的最后
),相同参数类型相同业务含义才可以使用可变参数,避免使用Object;
正例: public List<User> listUsers(String type, Long... ids) {...}
(4)接口过时必须加@Deprecated 注解,并清晰地说明采用的新接口或者新服务是什么;
(5)不能使用过时的类或方法;
(6)Object的equals方法容易抛出空指针异常,应该使用常量或者确定值得对象来调用equals;
正例: "test".equals(object);
反例: object.equals("test");
说明: 推荐使用 java.util.Objects#equals(JDK7 引入的工具类)
(7)所有的相同类型的包装类对象之间值的比较,全部使用 equals 方法比较:
说明: 对于 Integer var = ? 在-128 至 127 之间的赋值, Integer 对象是在 IntegerCache.cache 产生,
会复用已有对象,这个区间内的 Integer 值可以直接使用==进行判断,但是这个区间之外的所有数据,都
会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用 equals 方法进行判断。
(8)基本数据类型与包装数据类型的使用标准如下:
所有POJO类属性必须使用包装类、RPC方法返回值和阐述必须使用包装类、所有局部变量使用基本类型;
(9)定义DO/DTO/VO等POJO类时,不能设定任何属性默认值;
(10)序列化新增属性时,不要修改serialVersionUID字段,避免反序列化失败。如果完全不兼容升级,为了避免反序列化混乱,要修改该字段值(serialVersionUID不一致会抛出序列化运行时异常)
(11)构造方法禁止加入任何业务逻辑,如果有初始化逻辑请在init()方法中;
(12)POJO类(POJO类的作用是方便程序员使用数据库中的数据表,不包含业务逻辑的单纯用来存储数据的 java类),必须写toString方法,如果继承了另一个POJO类,需要在前面加一下super.toString;
(13)禁止在POJO类中同时存在对应属性XXX的isXxx()和getXxx()方法;
(14)setter 方法中,参数名称与类成员变量名称一致, this.成员名 = 参数名。在getter/setter 方法中, 不要增加业务逻辑,增加排查问题的难度:
反例:
public Integer getData() {
if (condition) {
return this.data + 100;
} else {
return this.data - 100;
}
}
(15)任何货币金额,均以最小货币单位且整形类型来进行存储;
(16)浮点数之间等值判断,基本数据类型不能用==来比较,包装类型数据不能用equals来判断;
(17)定义数据对象DO类时属性类型要与数据库字段相匹配:
正例: 数据库字段的 bigint 必须与类属性的 Long 类型相对应。
反例: 某个案例的数据库表 id 字段定义类型 bigint unsigned,实际类对象属性为 Integer,随着 id 越来
越大,超过 Integer 的表示范围而溢出成为负数
(18)禁止BigDecimal(double)的方式把 double 值转化为 BigDecimal 对象(会存在精度损失风险);

 
1.5、集合处理
(1)关于hashCode和equals的处理:
只要重写equals必须重写hashCode、
因为Set存储不重复对象是根据hashCode和equals判断,所以Set存储对象必须重写这两个方法、
如果自定义对象作为Map的键,必须重写这两个方法;
(String重写了这两个方法所以可以很方便使用String对象作为key来使用)
(2)ArrayList的subList()(它返回原来list的从[fromIndex, toIndex)之间这一部分的视图)结果不可强转成ArrayList,因为该方法返回的是内部类并不是一个List而是一个视图、在原集合元素的增加或者删除都会熬制子列表的遍历、增加、删除产生异常;
(3)集合转数组的方法,必须使用集合的toArray(T[] array),传入的是类型完全一样的数组,大小是list.size();
(4)使用工具类Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的add/remove/clear方法会抛出异常(因为asList返回的是Arrays内部类,没有实现集合修改方法,使用适配器模式只是修改接口,后台数据仍然是数组);
(5)泛型通配符<? extends T>来接收返回的数据,此写法的泛型集合不能用add()方法、<? super T>不能使用get()方法,作为接口调用赋值时容易错;
(6)不要在foreach循环对元素remove/add操作。remove元素请使用Iterator方式,如果并发操作需要对Iterator对象加锁;
(7)Comparator(比较器,用于排序、分组)实现类要满足如下三个条件,不然不然 Arrays.sort,Collections.sort会抛出异常:
x,y比较结果和y,x的比较结果相反、x>y,y>z则x>z、x=y则x,y比较结果和y,z比较结果相同;
(8) 高度注意 Map 类集合 K/V 能不能存储 null 值的情况,如下表格:

集合类 Key    Value   Super    说明
Hashtable    不允许为 null不允许为 null  Dictionary    线程安全
ConcurrentHashMap    不允许为 null不允许为 null  

AbstractMap   
 

锁分段技术( JDK8:CAS)    

TreeMap    不允许为 null允许为 nullAbstractMap    线程不安全
HashMap    允许为 null允许为 nullAbstractMap    线程不安


反例: 由于 HashMap 的干扰,很多人认为 ConcurrentHashMap 是可以置入 null 值,而事实上, 存储
null 值时会抛出 NPE 异常

(9)判断集合内部元素是否为空用isEmpty()方法,而不是size()==0方式;
(10)在使用 java.util.stream.Collectors 类的toMap()方法转化为Maori集合时,一定要使
用含有参数类型为 BinaryOperator,参数名为 mergeFunction 的方法,否则当出现相同 key
值时会抛出 IllegalStateException 异常。
说明: 参数 mergeFunction 的作用是当出现 key 重复时,自定义对 value 的处理策
(11)在使用 java.util.stream.Collectors 类的 toMap()方法转为 Map 集合时,一定要注
意当 value 为 null 时会抛 NPE 异常;
(12)使用Map方法keySet()/values()/entrySet()返回集合对象时,不可以对其进行添加元素操作;
(13)Collections 类返回的对象,如: emptyList()/singletonList()等都是 immutable list,
不可对其进行添加或者删除元素的操作;
(14)使用集合转数组必须使用集合的toArray(T[] array),传入的是类型完全一致,长度为0 的空数组(直接使用 toArray 无参方法存在问题,此方法返回值只能是 Object[]类,若强转其它类型数组将出现错误)
说明: 使用 toArray 带参方法,数组空间大小的 length,
等于 0,动态创建与 size 相同的数组,性能最好;
大于 0 但小于 size,重新创建大小等于 size 的数组,增加 GC 负担;
等于 size,在高并发情况下,数组创建完成之后, size 正在变大的情况下,负面影响与 2 相同;
大于 size,空间浪费,且在 size 处插入 null 值,存在 NPE 隐患
(15)在使用 Collection 接口任何实现类的addAll()方法时,都要对输入的集合参数进行NPE判断;
1.6、并发处理规范
(1)获取单例对象需要保证现成安全,其中的方法也要保证现成安全;
(2)创建线程或者线程池要指定有意义的名称;
(3)线程资源必须通过线程池提供,不允许在应用中自行显式创建;
(4)线程池创建不允许使用Executors创建,而是ThreadPoolExecutor的方式,这样可以了解线程池运行规则,避免资源耗尽;
(5)SimpleDateFormat是线程不安全的类,一般不要定义为static,如果定义为static则要加锁(推荐使用DateUtils工具类);
(JDK1.8中:可以使用 Instant 代替 Date, LocalDateTime 代替 Calendar,DateTimeFormatter 代替 SimpleDateFormat)
(6)高并发时要考虑锁性能损耗。能用无锁数据结构就不要用锁、能用区块锁就不要锁整个方法、能用对象锁就不要用类锁、避免在s锁代码块中调用RPC方法;
(7)对多个资源、数据表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁:
说明: 线程一需要对表 A、 B、 C 依次全部加锁后才可以进行更新操作,那么线程二的加锁顺序
也必须是 A、 B、 C,否则可能出现死锁
(8)并发修改同一个记录时避免丢失修改,需要加锁。要么在应用层加、要么在缓存加、要么在数据库加乐观锁使用version作为更新依据(乐观锁重试次数不得小于三次,每次冲突概率小于20%则用乐观锁,大于则用悲观锁);
(9)多线程并行处理定时任务时,Timer运行多个TimeTasks时只要其中之一没有补货抛出异常,其他任务便会自动终止运行,使用 ScheduledExecutorService 则没有这个问题;
(10)volatile解决多线程内存不可见问题,对于一写多读有效,但是对于多写无效,需要用原子类;
(11)在使用阻塞等待获取锁的方式中,必须在try代码块之外,并且加锁方法与try代码块之间没有任何可能抛出异常的方法调用,避免加锁成功后在finally中无法解锁;
(12)资金相关的金融敏感信息,使用悲观锁策略;
(13)使用 CountDownLatch 进行异步转同步操作,每个线程退出前必须调用 countDown 方
法,线程执行代码注意 catch 异常,确保 countDown 方法被执行到,避免主线程无法执行至
await 方法,直到超时才返回结果;
(14)避免 Random 实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一 seed
导致的性能下降;
1.7、控制语句规范
(1)在一个switch块内,每个case要么通过break/return来终止要么注释到哪个case为止、必须包含一个default语句并且放在最后,即使空代码;
(2)在 if/else/for/while/do 语句中必须使用大括号。 即使只有一行代码,避免采用
单行的编码方式: if (condition) statements;
(3)在高并发场景,避免使用“等于”判断作为中断或者退出条件(使用大于或者小于的区间来代替);
(4)不要在条件判断中执行其它复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性;
正例:
// 伪代码如下
final boolean existed = (file.open(fileName, "w") != null) && (...) || (...);
if (existed) {
...
}
(5)当switch括号内的变量类型为String并且此变量为外部参数时,必须先进行null判断;
(6)三目运算符 condition? 表达式 1 : 表达式 2 中,高度注意表达式 1 和 2 在类型对齐
时,可能抛出因自动拆箱导致的 NPE 异常
说明: 以下两种场景会触发类型对齐的拆箱操作:
表达式 1 或表达式 2 的值只要有一个是原始类型。
表达式 1 或表达式 2 的值的类型不一致,会强制拆箱升级成表示范围更大的那个类型。
反例:
Integer a = 1;
Integer b = 2;
Integer c = null;
Boolean flag = false;
// a*b 的结果是 int 类型,那么 c 会强制拆箱成 int 类型,抛出 NPE 异常
Integer result=(flag? a*b : c);
(7)取反运算符!尽量少使用
1.8、注释规范
(1)类、类属性、类方法注释必须使用/**内容*/格式,不得使用// xxx 方式;
(2)所有抽象方法(包括接口中的方法)必须要用注释。除了返回值、参数、异常说明外,还必须指出该方法功能;
(3)所有类必须添加创建者和创建日期;
(4)方法内部单行注释在被注释语句上方另外起一行使用//注释,方法内部多行注释用/* */;
(5)所有枚举类型字段必须注释;
1.9、日期时间规范
(1)日期格式化时,传入pattern中表示年份统一使用小写y(大写Y可能会跨年);
说明:yyyy 表示当天所在的年,YYYY表示当天所在周所在的年,周可能会跨年那么返回的YYYY就是下年了
正例: 表示日期和时间的格式如下所示:new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
(2)日期格式大写M小写m,大写H和小写H的意义:
表示月份是M表示分钟是小写m、24小时制是大写H,12小时制是小写h
(3)获取当前毫秒数: System.currentTimeMillis(); 而不是 new Date().getTime();
(4)不允许在程序任何地方中使用: java.sql.Date 、 java.sql.Time 3、java.sql.Timestamp
(5)不要在程序中写死一年为 365 天、注意闰年的 2 月份有 29 天;
(6)使用枚举值来指代月份。如果使用数字,注意 Date, Calendar 等日期相关类的月份month 取值在 0-11 之间;
1.10、其他
(1)使用正则表达式时,利用好预编译功能可以有效加快正则匹配速度;
(2)velocity 调用 POJO 类的属性时,建议直接使用属性名取值即可;
(3)后台输送给页面的变量必须加$!{var},因为如果var等于null或者不存在,那么${var}会直接显示在页面;
(4)注意 Math.random() 这个方法返回是 double 类型,注意取值的范围 0≤x<1(能够取到零值,注意除零异常) ,如果想获取整数类型的随机数,不要将 x 放大 10 的若干倍然后取整,直接使用 Random 对象的 nextInt 或者 nextLong 方法;
(5)获取当前毫秒数,用System.currentTimeMillis(); 而不是 new Date().getTime();
(6)任何数据结构的构造或者初始化都应该指定大小,避免数据结构无限增长吃光内存;
二、异常日志规范
2.1、异常处理
(1)Java类库中定义的可以通过预检查方式规避的RuntimeException异常不应该通过catch的方式来处理,比如NullPointerException, IndexOutOfBoundsException(无法通过预检查的异常除外,比如解析字符串形式数字时必须通过NumberFormatException来实现)
正例: if (obj != null) {...}
反例: try { obj.method(); } catch (NullPointerException e) {…}
(2)异常不要用来做流程控制、条件控制;
(3)catch时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码,对于非稳定代码catch尽可能进行区分异常类型,再做对应的异常处理(就是多个catch)
注意:对大段代码进行 try-catch,使程序无法根据不同的异常做出正确的应激反应,也不利于定位问题,不够负责
(4)捕获异常是为了处理它不然请将该异常抛给它的调用者。最外层的业务使用者必须处理异常,并且转化为用户可以理解的内容;
(5)有try块放到事务代码中,catch异常后如果需要回滚事务,一定要注意手动回滚事务;
(6)finally块必须对资源对象、流对象进行关闭,有异常也要做try-catch;
(可以使用try-with-resources)
(7)不要在finally块中使用return(如果这样方法结束执行,不会再执行try块的return);
(8)捕获异常与抛出异常必须是完全匹配,或者捕获异常是抛出异常的父类;
(9)方法的返回值可以为null,不强制返回空集合或者空对象,必须添加注释说明什么情况下会返回null值;
说明:为了防止NPE(空异常)。即使 被调用方法 返回空集合或者空对象,对调用者 来说必须考虑远程调用失败、序列化失败、运行时异常等场景返回null的情况
(10)NPE产生的几种场景:
返回类型为基本类型,return为包装类型,自动拆箱会NPE:
反例: public int f() { return Integer 对象}, 如果为 null,自动解箱抛 NPE、
数据库的查询结果可能为null、
集合数据元素即使isNotEmpty,取出的数据元素也可能为null、
远程调用返回对象时,一律要进行空指针判断、
Session中获取的数据,建议NPE检查、
级联调用例如obj.getA().getB().getC()容易产生NPE(使用Java8的Optional类防止NPE问题)
(11)定义时区分区分 unchecked / checked 异常,避免直接抛出 new RuntimeException(),更不允许抛出Exception或者Throwable。应使用有业务含义的自定义异常,推荐业界已定义过的自定义异常,如:DAOException / ServiceException 等;
(12)对于公司外的http/api开放接口必须使用“错误码”,应用内部推荐异常抛出,RPC调用推荐Result方式,封装isSuccess()方法、“错误码”、“错误简短信息”;
(13)全部正常,但不得不填充错误码时返回五个零:00000;
(14)错误码不提现版本号和错误等级、制定规则:快速溯源、简单记忆、沟通标准化;
(15)编号不与公司业务和组织架构挂钩,一切与平台申请先到先得,编号是永久固定;
(16)不要随意定义新的错误码;
(17)错误码不能直接输出给用户作为提示信息使用;
2.2、日志规范
(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)应用中扩展日志(访问日志、临时监控等)命名方式:appName_logType_logName.log;
(4)对trace/debug/info级别的日志输出,必须使用条件输出形式 或者 使用占位符方式:
说明: logger.debug("Processing trade with id: " + id + " and symbol: " + symbol);
如果日志级别是 warn,上述日志不会打印,但是会执行字符串拼接操作,如果 symbol 是对象,
会执行 toString()方法,浪费了系统资源,执行了上述操作,最终日志却没有打印。
正例: (条件) 建设采用如下方方式:
if (logger.isDebugEnabled()) {
logger.debug("Processing trade with id: " + id + " and symbol: " + symbol);
}
正例: (占位符)
logger.debug("Processing trade with id: {} and symbol : {} ", id, symbol);
(5)避免重复打印日志,浪费磁盘空间,务必在 log4j.xml 中设置 additivity=false;
(6)异常信息应该包含两类信息:案发现场信息和异常堆栈信息,如果不做处理则通过throws网上抛出;
正例: logger.error(各类参数或者对象 toString() + "_" + e.getMessage(), e);
(7)生产环境禁止输出debug日志,有选择地输出info日志,如果使用warn则要注意输出量,及时删除这些观察日志;
(8)使用warn日志级别来记录用户输入参数错误,避免用户投诉时无从适从,不要在此场景打出error级别避免频繁报警;
(9)尽量用英文描述日志错误信息;
(10)生产环境禁止直接使用 System.out 或 System.err 输出日志或使用e.printStackTrace()打印异常堆栈;
(11)日志打印时禁止直接用JSON工具将对象转换成String;
三、单元测试
(1)好的单元测试遵守AIR原则(全自动化、独立性、可重复),单元测试在线上运行时像空气(AIR)不存在,但是在测试质量保障上很重要;
(2)单元测试必须全自动执行,通常被定期执行,不需要人工检验,不准用System.out人肉验证,必须使用assert来验证;
(3)单元测试用例之间不能互相调用,也不能依赖执行的先后次序:
反例: method2 需要依赖 method1 的执行, 将执行结果作为 method2 的输入;
(4)单元测试是可重复执行的,不能受外界环境的影响;
(5)单元测试要保证粒度够小,至多类级别至少方法级别;
(6)新增代码及时补充单元测试,如果影响了原有测试则及时修正;
(7)元测试代码必须写在如下工程目录: src/test/java,不允许写在业务代码目录下;
说明: 源码构建时会跳过此目录,而单元测试框架默认是扫描此目录
(8)单元测试代码遵守BCDE原则(Border,边界值测试,Correct,正确的输入,并得到预期的结果,Design,与设计文档相结合,来编写单元测试,Error,强制错误信息输入,并得到预期的结果)
(9)和数据库相关的单元测试,可以自定义回滚机制不给数据库造成脏数据,或者对测试数据有明确前后缀标识;
 
四、安全规约
(1)隶属于用户个人的页面或者功能必须进行权限控制校验;
(2)用户敏感数据禁止直接展示,必须对展示数据进行脱敏;
(3)用户输入的SQL参数严格使用参数绑定或者METADATA字段值限定,防止SQL注入,禁止字符串拼接SQL访问数据库;
(4)用户输入的任何参数必须做有效性0验证;
(5)禁止向HTML页面输出未经安全过滤或者未正确转义的用户数据;
(6)表单、 AJAX 提交必须执行 CSRF(跨站请求伪造是一类常见编程漏洞) 安全验证;
(7)在使用平台资源,譬如短信、邮件、电话、下单、支付,必须实现正确的防重放的机制,如数量限制、疲劳度控制、验证码校验,避免被滥刷而导致资损;
(8)发贴、评论、发送即时消息等用户生成内容的场景必须实现防刷、文本内容违禁词过滤等风控策略;
五、MySQL数据库规范
5.1、建表规范
(1)表达是或者否的字段,必须使用is_xxx方式命名,数据类型必须是unsigned tinyint(1表示是,0表示否)
说明:任何字段如果为非负数,必须是unsigned、
POJO类任何布尔型变量都不要加is前缀、
正例: 表达逻辑删除的字段名 is_deleted, 1 表示删除, 0 表示未删除
(2)表名、字段名必须小写字母(不能大写,因为Linux下区分大小写)或者数字,禁止出现数字开头,禁止两个下划线中间只出现数字(字段名修改代价很大。因为无法预发布)
正例: aliyun_admin, rdc_config, level3_name
反例: AliyunAdmin, rdcConfig, level_3_name
(3)表名不使用复数名词(表名应该仅仅表示表里的实体内容,不应该表示实体数量)
(4)禁用保留字,如:desc、range、match、delayed等;
(5)主键索引名为pk_字段名、唯一索引名为uk_字段名、普通索引名为idx_字段名;
说明: pk_ 即 primary key; uk_ 即 unique key; idx_ 即 index 的简称
(6)小数类型为decimal,禁止使用float和double;
(7)如果存储字符串长度几乎相等,使用char定长字符串类型;
(8)varchar是可变长字符串,不预先分配存储空间,长度不要超过5000(如果超过5000则定义为text,独立出来一张表用主键来对应);
(9)表必备三个字段id、gmt_create、gmtmodified
说明: 其中 id 必为主键,类型为 bigint unsigned、单表时自增、步长为 1、
 gmt_create,gmt_modified 的类型均为 datetime 类型,前者现在时表示主动创建,后者过去分词表示被动更新
(10)表的命名最好是“业务名称_表的作用”;
(11)单表行数超过500W行或者单表容量超过2G才推荐分表分库;
说明:如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表
5.2、索引的规范
(1)具有唯一特性的字段,即使是多个字段的组合也必须构成唯一索引;
说明: 不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明
显的; 只要没有唯一索引,根据墨菲定律,必然有脏数据产生
(2)超过3个表禁止join。需要join的字段,数据类型必须绝对一致、多表关联查询时要保证被关联字段需要有索引(即使双表 join 也要注意表索引);
(3)在varchar字段上建立索引时必须制定索引长度,没必要对全字段建立索引,根据实际文本区分决定索引长度即可;
说明: 索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为 20 的索引,区分
度会高达 90%以上,可以使用 count(distinct left(列名, 索引长度))/count(*)的区分度
来确定。
(4)页面搜索禁止左模糊 或者 全模糊(可以右模糊),如果需要请走搜索引擎来解决;
说明:索引文件具有B-Tree的最左前缀匹配特性,如果左边的值未确定那么无法使用此索引
(5)如果有order by的场景,请注意利用索引的有序性。order by的最后字段是组合索引的一部分,并且放在索引组合顺序的最后:
正例: 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最好;
(9)建组合索引的时候,区分度最高的在最左边;
正例: 如果 where a=? and b=?, a 列的几乎接近于唯一值,那么只需要单建 idx_a 索引即可、
说明: 存在非等号和等号混合判断条件时,在建索引时,请把等号条件的列前置。如: where c>? and d=?
那么即使 c 的区分度更高,也必须把 d 放在索引的最前列, 即建立组合索引 idx_d_c
 
5.3、SQL语句
(1)不要使用count(列名)或者count(常量)来替代count(*);
说明:count(*)会统计值为NULL的行,而count(列名)不会统计此列为NULL的行
(2)count(distinct col) 计算该列除 NULL 之外的不重复行数,注意 count(distinct col1,
col2) 如果其中一列全为 NULL,那么即使另一列有不同的值,也返回为 0;
(3)当某一列的值全是 NULL 时, count(col)的返回结果为 0,但 sum(col)的返回结果为
NULL,因此使用 sum()时需注意 NPE 问题;
正例: 可以使用如下方式来避免 sum 的 NPE 问题: SELECT IFNULL(SUM(column), 0) FROM table
(4)使用ISNULL()来判断是否为NULL值(NULL 与任何值的直接比较都为 NULL,而不是true或者false);
(5)代码中写分页查询逻辑时,若count为0应直接返回,避免执行后面的分页语句;
(6)禁止使用外键和级联,一切外键概念必须在应用层解决;
说明: (概念解释) 学生表中的 student_id 是主键,那么成绩表中的 student_id 则为外键。如果更新学
生表中的 student_id,同时触发成绩表中的 student_id 更新, 即为级联更新。外键与级联更新适用于单机
低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库
的插入速度
(7)禁止使用存储过程;
(8)数据修正时先select,避免误删,确认无误才能执行更新语句;
(9)对于数据库中表记录的查询和变更,只要涉及多个表,都需要在列名前加表的别名或者表名进行限定;
说明: 对多表进行查询记录、更新记录、删除记录时,如果对操作列没有限定表的别名(或表名),并且
操作列在多个表中存在时,就会抛异常
正例: select t1.name from table_first as t1 , table_second as t2 where t1.id=t2.id;
(10)SQL 语句中表的别名前加 as,并且以 t1、 t2、 t3、 ...的顺序依次命名;
(11)in操作能避免则避免,in后边集合元素数量控制在1000以内最好;
5.4、ORM映射
(1)在表查询中一律不要使用* 作为查询的字段列表,需要哪些字段明确写明;
(2)POJO布尔型属性不能加is,而数据库必须加is_,要求在resultMap中进行字段与属性之间的映射;
(3)不要用 resultClass 当返回参数,即使所有类属性名与数据库字段一一对应,也需要定义<resultMap>;反过来,每一个表也必然有一个<resultMap>与之对应。
(4)sql.xml 配置参数使用: #{}, #param# 不要使用${} 此种方式容易出现 SQL 注入;
(5)iBATIS 自带的 queryForList(String statementName,int start,int size)不推荐使用;
(6)不允许直接拿HashMap和Hashtable作为查询结果集的输出;
(7)更新数据表记录时,必须同时更新记录对应的 gmt_modified 字段值为当前时间;
(8)不要写一个大而全的数据更新接口。 传入为 POJO 类,不管是不是自己的目标更新字
段,都进行 update table set c1=value1,c2=value2,c3=value3; 这是不对的。执行 SQL 时,
不要更新无改动的字段,一是易出错;二是效率低;三是增加 binlog 存储
(9)@Transactional 事务不要滥用。会影响数据库的QPS,使用事务的地方需要考虑各方面的回滚方案,包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等;
六、工程结构

6.1、应用分层
(1)阿里分布式架构分层理解:

开放接口层:可以直接封装Service方法暴露成RPC接口、通过Web封装成http接口、网关控制等;

终端显示层:各个端的模块渲染并执行显示的层。主要是velocity渲染,JS渲染,JSP渲染,移动端展示等;

Web层:主要对访问控制进行转发,各类基本参数校验,或者不福永的业务简单处理等;

Service层:对具体的业务逻辑服务层;

Manager层:通用业务处理层,有如下特征:

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

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

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

DAO层:数据访问层,与底层MySQL、Oracle、Hbase、OB等进行数据交互;

外部接口或第三方平台:包括其他部门RPC开放接口。基础平台,与其他公司的HTTP接口;

(2)分层异常处理规约:

DAO层:异常类型多,无法用细粒度异常进行catch,使用 catch(Exception e)方式,并 throw new DAOException(e),不需要打印日志,因为日志在 Manager/Service 层一定需要捕获并打印到日志文件中去;
Service层:该层必须记录出错日志到磁盘;
Manager 层:与 Service 同机部署,日志方式与 DAO 层处理一致,如果是单独部署,则采用与 Service 一致的处理方式;
Web层:禁止往上抛出异常,因为已经处于顶层,如果这时异常页面无法渲染,就直接跳到友好提示错误页面;
开放接口层:将异常处理成错误码和错误信息方式返回;
(3)分层领域模型规约:

DO:此对象与数据库表结构一一对应,通过DAO层向上传输数据源对象;
DTO:数据传输对象,Service或者Manager向外传输的对象;
BO:业务对象,可以又Service层输出的封装业务逻辑的对象;
Query:查询数据对象,各层接手上层的查询请求(超过2个的参数查询封装,禁止使用Map类传输);
VO:显示层对象,通常是Web向模块渲染引擎层传输的对象;
 
6.2、二方库依赖
(1)定义GAV遵守以下规则:
GroupID 格式: com.{公司/BU }.业务线 [.子业务线],最多 4 级、
ArtifactID 格式:产品线名-模块名。语义不重复不遗漏,先到中央仓库去查证一下
(2)二方库版本号命名方式:主版本号.次版本号.修订号
主版本号: 产品方向改变, 或者大规模 API 不兼容, 或者架构不兼容升级、
次版本号: 保持相对兼容性,增加主要功能特性,影响范围极小的 API 不兼容修改、
修订号: 保持完全兼容性, 修复 BUG、 新增次要功能特性等
说明: 注意起始版本号必须为: 1.0.0,而不是 0.0.1。
反例: 仓库内某二方库版本号从 1.0.0.0 开始,一直默默“升级”成 1.0.0.64,完全失去版本的语义信息
(3)线上应用不要依赖 SNAPSHOT 版本( 安全包除外) ;正式发布的类库必须先去中央仓库进行查证,使 RELEASE 版本号有延续性,且版本号不允许覆盖升级;
说明: 不依赖 SNAPSHOT 版本是保证应用发布的幂等性
(4)二方库的新增或升级,保持除功能点之外的其它 jar 包仲裁结果不变。如果有改变,
必须明确评估和验证;
(5)二方库里可以定义枚举类型,参数可以使用枚举类型,但是接口返回值不允许使用枚
举类型或者包含枚举类型的 POJO 对象;
(6)依赖于一个二方库群时,必须定义一个统一的版本变量,避免版本号不一致;
(7)禁止在子项目的 pom 依赖中出现相同的 GroupId,相同的 ArtifactId,但是不同的
Version;
说明: 在本地调试时会使用各子项目指定的版本号,但是合并成一个 war,只能有一个版本号出现在最后的
lib 目录中。 曾经出现过线下调试是正确的,发布到线上却出故障的先例
(8)所有 pom 文件中的依赖声明放在<dependencies>语句块中,所有版本仲裁放在
<dependencyManagement>语句块中;
(9)二方库不要有配置项,最低限度不要再增加配置项;
(10)不要使用不稳定的工具包或者 Utils 类;
6.3、服务器
(1)高并发服务器建议调小TCP协议的time_wait超时时间;
(2)调大服务器所支持的最大文件句柄数;
(3)给 JVM 环境参数设置-XX:+HeapDumpOnOutOfMemoryError 参数,让 JVM 碰到 OOM
场景时输出 dump 信息;
(4)在线上生产环境, JVM 的 Xms 和 Xmx 设置一样大小的内存容量, 避免在 GC 后调整
堆大小带来的压力;
(5)服务器内部重定向必须使用 forward; 外部重定向地址必须使用 URL Broker 生成, 否
则因线上采用 HTTPS 协议而导致浏览器提示“不安全“。此外,还会带来 URL 维护不一致的
问题;
6.4、设计规约
(1)存储方案和底层数据结构的设计获得评审一致通过,并沉淀成为文档;
(2)在需求分析阶段,如果与系统交互的 User 超过一类并且相关的 User Case 超过 5 个,使用用例图来表达更加清晰的结构化需求;
(3)如果某个业务对象的状态超过 3 个,使用状态图来表达并且明确状态变化的各个触发条件;
(4)如果系统中某个功能的调用链路上的涉及对象超过 3 个,使用时序图来表达并且明确各调用环节的输入与输出;
(5)如果系统中模型类超过 5 个,并且存在复杂的依赖关系,使用类图来表达并且明确类之间的关系;
(6)如果系统中超过 2 个对象之间存在协作关系,并且需要表示复杂的处理流程,使用活动图来表示;
(7)类在设计与实现时要符合单一原则;
(8)慎使用继承的方式来进行扩展,优先使用聚合/组合的方式来实现;
(9)系统设计阶段,根据依赖倒置原则,尽量依赖抽象类与接口,有利于扩展与维护;
(10)系统设计阶段,注意对扩展开放,对修改闭合;
(11)避免如下误解: 敏捷开发 = 讲故事 + 编码 + 发布
说明: 敏捷开发是快速交付迭代可用的系统,省略多余的设计方案,摒弃传统的审批流程,但核心关键点上
的必要设计和文档沉淀是需要的
————————————————
版权声明:本文为CSDN博主「Hey 锡瑞」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/RuiKe1400360107/article/details/105907166

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值