当前位置:   article > 正文

JAVA开发规范

java开发规范

一、编程规范

1.1、命名风格

(1)不能以下划线或者美元符号开始和结束;

(2)不能使用拼音和英文混合方式,不能直接中文方式;

(3)类名使用UpperCamelCase风格,但是DO/BO/DTO/VO/AO/PO/UID除外: 正解:MarcoPolo / UserDO / XmlService / TcpUdpDeal / TaPromotion 反例:macroPolo / UserDo / XMLService / TCPUDPDeal / TAPromotion

(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存储不重复对象是根据hashCodeequals判断,所以Set存储对象必须重写这两个方法、如果自定义对象作为Map的键,必须重写这两个方法;(String重写了这两个方法所以可以很方便使用String对象作为key来使用)

(2)ArrayListsubList()(它返回原来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.sortCollections.sort会抛出异常:

  • x,y比较结果和y,x的比较结果相反;

  • x>y,y>z则x>z;

  • x=y则x,y比较结果和y,z比较结果相同;

(8) 高度注意 Map 类集合 K/V 能不能存储 null 值的情况,如下表格:

集合类KeyValueSuper说明
Hashtable不允许为 null不允许为 nullDictionary线程安全
ConcurrentHashMap不允许为 null不允许为 nullAbstractMap锁分段技术( 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 代替 DateLocalDateTime 代替 CalendarDateTimeFormatter 代替 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.Datejava.sql.Time 3java.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的方式来处理,比如NullPointerExceptionIndexOutOfBoundsException(无法通过预检查的异常除外,比如解析字符串形式数字时必须通过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.outSystem.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原则;

(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接口;

    img

(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)避免如下误解: 敏捷开发 = 讲故事 + 编码 + 发布 说明: 敏捷开发是快速交付迭代可用的系统,省略多余的设计方案,摒弃传统的审批流程,但核心关键点上 的必要设计和文档沉淀是需要的

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/989702
推荐阅读
相关标签
  

闽ICP备14008679号