# code-style **Repository Path**: shuahub/code-style ## Basic Information - **Project Name**: code-style - **Description**: 代码风格规范示例项目 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-03-17 - **Last Updated**: 2024-06-13 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # code-style ## 目录 * [介绍](#介绍) * [编程规约](#编程规约) * [一、命名风格](#一命名风格) * [二、常量定义](#二常量定义) * [三、代码格式](#三代码格式) * [四、OOP规约](#四oop规约) * [五、日期时间](#五日期时间) * [六、集合处理](#六集合处理) * [七、并发处理](#七并发处理) * [八、控制语句](#八控制语句) * [九、注释规约](#九注释规约) * [十、前后端规约](#十前后端规约) * [十一、其他](#十一其他) * [异常日志](#异常日志) * [一、异常处理](#一异常处理) * [二、日志规约](#二日志规约) * [单元测试](#单元测试) * [安全规约](#安全规约) * [数据库规约](#数据库规约) * [一、建表规约](#一建表规约) * [二、索引规约](#二索引规约) * [三、SQL语句](#三sql语句) * [四、ORM映射](#四orm映射) * [工程结构](#工程结构) * [一、二方库](#一二方库) * [二、服务器](#二服务器) * [附件:专有名词](#附件专有名词) ## 介绍 无规矩不成方圆,无规范难以协同。对软件来说,适当的 规范和标准绝不是消灭代码内容的创造性、优雅性,而是限制过度个性化,以一种普遍认可的统一方式一起做事,提升协作效率,降低沟通成本。代码的字里行间流淌的是软件系统的血液,代码质 量的提升是尽可能少踩坑,杜绝踩重复的坑,切实提升系统稳定性,码出质量 ## 编程规约 ### 一、命名风格 1.【强制】所有编程相关的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束
反例:_name/__name/$Object/name_/name$/Object$ 2.【强制】所有编程相关的命名严禁使用拼音与英文混合的方式
正例:ali / alibaba / taobao / kaikeba / aliyun / youku / hangzhou等国际通用的名称,可视同英文
反例:DaZhePromotion【打折】 / getPingfenByName()【评分】 / Stringfw【福娃】/int变量名=3
3.【强制】类名使用UpperCamelCase风格,以下情形例外:DO/PO/DTO/BO/VO/UID等
正例:ForceCode / UserDO / HtmlDTO / XmlService / TcpUdpDeal / TaPromotion
反例:forcecode / UserDo / HTMLDto / XMLService / TCPUDPDeal / TAPromotion 4.【强制】方法名、参数名、成员变量、局部变量都统一使用lowerCamelCase风格
正例:localValue / getHttpMessage() / inputUserId
5.【强制】常量命名应该全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长
正例:MAX_STOCK_COUNT / CACHE_EXPIRED_TIME 6.【强制】抽象类命名使用Abstract或Base开头;异常类命名使用Exception结尾,测试类命名以它要测试的类的名称开始,以Test结尾
7.【强制】POJO类中的任何布尔类型的变量,都不要加is前缀,否则部分框架解析会引起序列化错误
反例:定义为布尔类型BooleanisDeleted的字段,它的getter方法也是isDeleted(),部分框架在反向解析时,“误以 为”对应的字段名称是deleted,导致字段获取不到,得到意料之外的结果或抛出异常 8.【强制】包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式
正例:应用工具类包名为com.alibaba.ei.kunlun.aap.util;类名为MessageUtils(此规则参考spring的框架结构) 9.【强制】对于Service和DAO类,基于SOA的理念,暴露出来的服务一定是接口,内部的实现类用Impl的后缀与接口区别
正例:CacheServiceImpl实现CacheService接口 10.【强制】Jackson在首字母大写或者第二个字母大写的变量序列化会存在问题,避免变量如此命名 11.【推荐】枚举类名不要有Enum后缀,枚举成员名称需要全大写,单词间用下划线隔开
正例:JDK中java.lang.annotation.ElementType;Spring中org.springframework.boot.ansi.AnsiColor ### 二、常量定义 1.【强制】不允许任何魔法值(即未经预先定义的常量)直接出现在代码中
反例: ```java // 开发者A定义了缓存的key String key = "Id#taobao_" + tradeId; cache.put(key, value); // 开发者B使用缓存时直接复制少了下划线,即key是"Id#taobao"+tradeId,导致出现故障。Stringkey="Id#taobao"+tradeId; cache.get(key); ``` 2.【强制】long或Long赋值时,数值后使用大写L,不能是小写l,小写容易跟数字混淆,造成误解
3.【强制】浮点数类型的数值后缀统一为大写的D或F 4.【推荐】如果变量值仅在一个固定范围内变化用enum类型来定义 ### 三、代码格式 1.【推荐】使用spring-javaformat-maven-plugin插件格式化代码 2.【强制】if/for/while/switch/do等保留字与左右括号之间都必须加空格 3.【强制】任何二目、三目运算符的左右两边都需要加一个空格
说明:包括赋值运算符=、逻辑运算符&&、加减乘除符号等 4.【强制】注释的双斜线与注释内容之间有且仅有一个空格 5.【推荐】不同逻辑、不同语义、不同业务的代码之间插入一个空行,分隔开来以提升可读性 ### 四、OOP规约 1.【强制】避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可 2.【强制】所有的覆写方法,必须加@Override注解 3.【强制】相同参数类型,相同业务含义,才可以使用的可变参数,参数类型避免定义为Object 4.【强制】外部正在调用的接口或者二方库依赖的接口,不允许修改方法签名,避免对接口调用方产生影响。接口过时必须加@Deprecated注解,并清晰地说明采用的新接口或者新服务是什么 5.【强制】不能使用过时的类或方法 6.【强制】Object的equals方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals
正例:"test".equals(param);
反例:param.equals("test");
说明:推荐使用JDK7引入的工具类java.util.Objects#equals(Object a, Object b) 7.【强制】所有整型包装类对象之间值的比较,全部使用equals方法比较
说明:对于Integervar=?在-128至127之间的赋值,Integer对象是在IntegerCache.cache产生,会复用已有对象,这个区间内的Integer值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用equals方法进行判断 8.【强制】浮点数之间的等值判断,基本数据类型不能使用==进行比较,包装数据类型不能使用equals进行判断
正例: ```java float a = 1.0F - 0.9F; float b = 0.9F - 0.8F; float diff = 1e - 6F; if (Math.abs(a-b)反例: ```java float a = 1.0F - 0.9F; float b = 0.9F - 0.8F; if (a == b) { // 预期进入此代码块,执行其它业务逻辑 // 但事实上a==b的结果为false } Float x = Float.valueOf(a); Float y = Float.valueOf(b); if (x.equals(y)) { // 预期进入此代码块,执行其它业务逻辑 // 但事实上equals的结果为false } ``` 说明:浮点数采用“尾数+阶码”的编码方式,类似于科学计数法的“有效数字+指数”的表示方式。二进制无法精确表示大部分的十进制小数 9.【强制】BigDecimal的等值比较应使用compareTo()方法,而不是equals()方法
说明:equals()方法会比较值和精度(1.0 与1.00 返回结果为false),而compareTo()则会忽略精度 10.【强制】定义数据对象DO类时,属性类型要与数据库字段类型相匹配
正例:数据库字段的bigint必须与类属性的Long类型相对应
反例:某业务的数据库表id字段定义类型为bigintunsigned,实际类对象属性为Integer,随着id越来越大,超过Integer的表示范围而溢出成为负数,此时数据库id不支持存入负数抛出异常产生线上故障 11.【强制】禁止使用构造方法BigDecimal(double)的方式把double值转化为BigDecimal对象
正例:优先推荐入参为String的构造方法,或使用BigDecimal的valueOf方法,此方法内部其实执行了Double的toString,而Double的toString按double的实际能表达的精度对尾数进行了截断
说明:BigDecimal(double)存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。如: BigDecimalg=newBigDecimal(0.1F);实际的存储值为:0.100000001490116119384765625 12.【强制】定义DO/ PO /DTO/VO等POJO类时,不要设定任何属性默认值 13.【强制】序列化类新增属性时,请不要修改serialVersionUID字段,避免反序列失败;如果完全不兼容升级,避免反序列化混乱,那么请修改serialVersionUID值 14.【强制】构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在init方法中 15.【强制】POJO类必须写toString方法。使用IDE中的工具source>generatetoString时,如果继承了另一个POJO类,注意在前面加一下super.toString() 16.【强制】禁止在POJO类中,同时存在对应属性xxx的isXxx()和getXxx()方法 17.【强制】使用Lombok时@Builder注解会导致默认值失效,在有默认值的实体中禁止使用该注解 18.【推荐】使用索引访问用String的split方法得到的数组时,需做最后一个分隔符后有无内容的检查,否则会有抛IndexOutOfBoundsException的风险 19.【推荐】当一个类有多个构造方法,或者多个同名方法,这些方法应该按顺序放置在一起,便于阅读 20.【推荐】类内方法定义的顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter方法 21.【推荐】循环体内,字符串的连接方式,使用StringBuilder的append方法进行扩展
反例: ```java String str = "start"; for (int i = 0; i < 100; i++) { str = str + "hello"; } ``` 说明:反编译出的字节码文件显示每次循环都会new出一个StringBuilder对象,然后进行append操作,最后通过toString() 返回String对象,造成内存资源浪费 ### 五、日期时间 1.【强制】日期格式化时,传入pattern中表示年份统一使用小写的y
说明:日期格式化时,yyyy表示当天所在的年,而大写的YYYY代表是weekinwhichyear(JDK7之后引入的概念),意思是当天所在的周属于的年份,一周从周日开始,周六结束,只要本周跨年,返回的YYYY就是下一年 2.【强制】获取当前毫秒数:System.currentTimeMillis();而不是newDate().getTime() 3.【强制】不允许在程序任何地方中使用:1)java.sql.Date 2)java.sql.Time 3)java.sql.Timestamp ### 六、集合处理 1.【强制】只要覆写equals,就必须覆写hashCode 2.【强制】判断所有集合内部的元素是否为空,使用CollectionUtils.isEmpty(Collection collection),禁止使用size()==0和isEmpty() 3.【强制】在使用java.util.stream.Collectors类的toMap()方法转为Map集合时,自定义对value的处理策略
正例: ```java List> pairArrayList = new ArrayList<>(3); pairArrayList.add(newPair<>("version", 12.10)); pairArrayList.add(newPair<>("version", 12.19)); pairArrayList.add(newPair<>("version", 6.28)); // 生成的map集合中只有一个键值对:{version=6.28} Mapmap = pairArrayList.stream() .collect(Collectors.toMap(Pair::getKey,Pair::getValue,(v1,v2)->v2)); ``` 反例: ```java String[] departments = new String[]{"RDC","RDC","KKB"}; // 抛出IllegalStateException异常 Mapmap = Arrays.stream(departments) .collect(Collectors.toMap(String::hashCode,str->str)); ``` 4.【强制】使用Map的方法keySet()/values()/entrySet()返回集合对象时,不可以对其进行添加元素操作,否则会抛出UnsupportedOperationException异常 5.【强制】Collections类返回的对象,如:emptyList()/singletonList()等都是immutablelist,不可对其进行添加或者删除元素的操作 6.【强制】ArrayList的subList结果不可强转成ArrayList,否则会抛出ClassCastException异常:java.util.RandomAccessSubListcannotbecasttojava.util.ArrayList
说明:subList()返回的是ArrayList的内部类SubList,并不是ArrayList本身,而是ArrayList的一个视图,对于SubList的所有操作最终会反映到原列表上 7.【强制】在subList场景中,高度注意对父集合元素的增加或删除,均会导致子列表的遍历、增加、删除产生ConcurrentModificationException异常
8.【强制】使用集合转数组的方法,必须使用集合的toArray(T[]array),传入的是类型完全一致、长度为0的空数组
正例: ```java List list = new ArrayList<>(2); list.add("guan"); list.add("bao"); String[] array = list.toArray(newString[0]); ``` 反例:直接使用toArray无参方法存在问题,此方法返回值只能是Object[]类,若强转其它类型数组将出现ClassCastException错误 9.【强制】使用Collection接口任何实现类的addAll()方法时,要对输入的集合参数进行NPE判断 10.【强制】使用工具类Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的add/remove/clear方法会抛出UnsupportedOperationException异常
说明:asList的返回对象是一个Arrays内部类,并没有实现集合的修改方法。Arrays.asList体现的是适配器模式,只是转换接口,后台的数据仍是数组 11.【强制】泛型通配符来接收返回的数据,此写法的泛型集合不能使用add方法,而不能使用get方法,两者在接口调用赋值的场景中容易出错 12.【强制】在无泛型限制定义的集合赋值给泛型限制的集合时,在使用集合元素时,需要进行instanceof判断,避免抛出ClassCastException异常 13.【强制】不要在foreach循环里进行元素的remove/add操作。remove元素请使用iterator方式,如果并发操作,需要对iterator对象加锁 14.【推荐】泛型集合使用时,在JDK7及以上,使用diamond语法或全省略
说明:菱形泛型,即diamond,直接使用<>来指代前边已经指定的类型 15.【推荐】集合初始化时,指定集合初始值大小 16.【推荐】使用entrySet遍历Map类集合KV,而不是keySet方式进行遍历 17.【推荐】高度注意Map类集合K/V能不能存储null值的情况,如下表格:
|集合类 |Key |Value |Super |说明 | |------|-------|-------|----------|------| |Hashtable|不允许为null|不允许为null|Dictionary|线程安全| |TreeMap|不允许为null|允许为null|AbstractMap|线程不安全| |ConcurrentHashMap|不允许为null|不允许为null|AbstractMap|锁分段技术(JDK8:CAS)| |HashMap|允许为null|允许为null|AbstractMap|线程不安全| ### 七、并发处理 1.【强制】获取单例对象需要保证线程安全,其中的方法也要保证线程安全 2.【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯 3.【强制】SimpleDateFormat是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用DateUtils工具类 4.【强制】必须回收自定义的ThreadLocal变量记录的当前线程的值,尤其在线程池场景下,线程经常会被复用,如果不清理自定义的ThreadLocal变量,可能会影响后续业务逻辑和造成内存泄露等问题。尽量在代码中使用try-finally块进行回收 5.【强制】高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁 6.【参考】volatile解决多线程内存不可见问题对于一写多读,是可以解决变量同步问题,但是如果多写,同样无法解决线程安全问题 7.【参考】HashMap在容量不够进行resize时由于高并发可能出现死链,导致CPU飙升,在开发过程中注意规避此风险
说明:参考 https://blog.csdn.net/m0_38113064/article/details/102910934 8.【参考】ThreadLocal对象使用static修饰,ThreadLocal无法解决共享对象的更新问题
说明:这个变量是针对一个线程内所有操作共享的,所以设置为静态变量,所有此类实例共享此静态变量,也就是说在类第一次被使用时装载,只分配一块存储空间,所有此类的对象(只要是这个线程内定义的)都可以操控这个变量 ### 八、控制语句 1.【强制】在一个switch块内,每个case要么通过continue/break/return等来终止,要么注释说明程序将继续执行到哪一个case为止;在一个switch块内,都必须包含一个default语句并且放在最后,即使它什么代码也没有 2.【强制】在if/else/for/while/do语句中必须使用大括号 3.【强制】三目运算符condition?表达式1:表达式2中,高度注意表达式1和2在类型对齐时,可能抛出因自动拆箱导致的NPE异常
反例: ```java Integer a= 1; Integer b= 2; Integer c= null; Booleanf lag = false; // a*b的结果是int类型,那么c会强制拆箱成int类型,抛出NPE异常Integer result = (flag ? a * b : c); ``` 4.【强制】在高并发场景中,避免使用“等于”判断作为中断或退出的条件
反例:判断剩余奖品数量等于0时,终止发放奖品,但因为并发处理错误导致奖品数量瞬间变成了负数,这样的话,活动无法终止
说明:如果并发控制没有处理好,容易产生等值判断被“击穿”的情况,使用大于或小于的区间判断条件来代替 5.【推荐】避免采用取反逻辑运算符 ### 九、注释规约 1.【强制】类、类属性、类方法的注释必须使用Javadoc规范,使用/**内容 */格式,不得使用// xxx方式 2.【强制】所有的抽象方法(包括接口中的方法)必须要用Javadoc注释、除了返回值、参数异常说明外,还必须指出该方法做什么事情,实现什么功能 3.【强制】所有的类都必须添加创建者和创建日期 4.【强制】方法内部单行注释,在被注释语句上方另起一行,使用// 注释。方法内部多行注释使用/* */注释,注意与代码对齐 5.【强制】所有的枚举类型字段必须要有注释,说明每个数据项的用途 6.【参考】好的命名、代码结构是自解释的,注释力求精简准确、表达到位。避免出现注释的另一个极端:过多过滥的注释 7.【参考】特殊注释标记,请注明标记人与标记时间。注意及时处理这些标记,通过标记扫描,经常清理此类标记。线上故障有时候就是来源于这些标记处的代码。1)待办事宜(TODO) 2)错误,不能工作(FIXME) ### 十、前后端规约 1.【强制】在前后端交互的 JSON 格式数据中,所有的 key 必须为小写字母开始的 lowerCamelCase 风格,符合英文表达习惯,且表意完整 2.【强制】对于需要使用超大整数的场景,服务端一律使用 String 字符串类型返回,禁止使用 Long 类型 3.【强制】HTTP 请求通过 URL 传递参数时,不能超过 2048 字节 4.【强制】URL 路径不能使用大写,单词如果需要分隔,统一使用下划线 5.【强制】代表一种资源,只能为名词,推荐使用复数,不能为动词,请求方法已经表达动作意义 6.【强制】生产环境必须使用 HTTPS 7.【强制】服务端发生错误时,返回给前端的响应信息必须包含 HTTP 状态码,errorCode、 errorMessage、用户提示信息四个部分 ### 十一、其他 1.【强制】枚举 enum(括号内)的属性字段必须是私有且不可变 ## 异常日志 ### 一、异常处理 1.【强制】Java 类库中定义的可以通过预检查方式规避的 RuntimeException 异常不应该通过 catch 的方 式来处理,比如:NullPointerException,IndexOutOfBoundsException 等等 2.【强制】异常捕获后不要用来做流程控制,条件控制 3.【强制】catch 时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的 catch 尽可能进行区分异常类型,再做对应的异常处理 4.【强制】捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容 5.【强制】事务场景中,抛出异常被 catch 后,如果需要回滚,一定要注意手动回滚事务 6.【强制】不要在 finally 块中使用 return 7.【强制】捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类 8.【推荐】方法的返回值可以为 null,不强制返回空集合,或者空对象等,必须添加注释充分说明什么情况下会返回 null 值 9.【推荐】定义时区分 unchecked / checked 异常,避免直接抛出 new RuntimeException(),更不允许 抛出 Exception 或者 Throwable,应使用有业务含义的自定义异常 ### 二、日志规约 1.【强制】应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架(SLF4J、JCL—Jakarta Commons Logging)中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一 2.【强制】日志文件至少保存 15 天,因为有些异常具备以“周”为频次发生的特点。对于当天日志,以“应用名.log”来保存,保存在/{统一目录}/{应用名}/logs/目录下,过往日志格式为:{logname}.log.{保存日期},日期格式:yyyy-MM-dd 3.【强制】在日志输出时,字符串变量之间的拼接使用占位符的方式
正例:logger.debug("Processing trade with id : {} and symbol : {}", id, symbol) 4.【强制】生产环境禁止使用 System.out 或 System.err 输出或使用 e.printStackTrace() 打印异常堆栈 5.【强制】异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过关键字throws 往上抛出
正例: ```java logger.error("inputParams: {} and errorMessage: {}", 各类参数或者对象 toString(), e.getMessage(), e); ``` 6.【强制】日志打印时禁止直接用 JSON 工具将对象转换成 String
正例:打印日志时仅打印出业务相关属性值或者调用其对象的 toString() 方法
说明:如果对象里某些get方法被覆写,存在抛出异常的情况,则可能会因为打印日志而影响正常业务流程的执行 7.【推荐】谨慎地记录日志。生产环境禁止输出 debug 日志;有选择地输出 info 日志;如果使用 warn来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑爆,并记得及时删除这些观察日志 8.【推荐】对于 trace / debug / info 级别的日志输出,必须进行日志级别的开关判断
正例: ```java // 如果判断为真,那么可以输出 trace 和 debug 级别的日志 if (logger.isDebugEnabled()) { logger.debug("Current ID is: {} and name is: {}", id, getName()); } ``` 9.【推荐】可以使用warn日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适从。如非必要,请不要在此场景打出 error 级别,避免频繁报警 10.【推荐】为了保护用户隐私,日志文件中的用户敏感信息需要进行脱敏处理 ## 单元测试 1.【强制】单元测试应该是全自动执行的,并且非交互式的。测试用例通常是被定期执行的,执行过程必须完全自动化才有意义。输出结果需要人工检查的测试不是一个好的单元测试。不允许使用System.out来进行人肉验证,单元测试必须使用assert来验证 2.【强制】核心业务、核心应用、核心模块的增量代码确保单元测试通过 3.【强制】单元测试代码必须写在如下工程目录: src/test/java,不允许写在业务代码目录下 4.【推荐】单测的基本目标:语句覆盖率达到 70%;核心模块的语句覆盖率和分支覆盖率都要达到 100% 5.【推荐】对于数据库相关的查询,更新,删除等操作,不能假设数据库里的数据是存在的,或者直接操作数据库把数据插入进去,请使用程序插入或者导入数据的方式来准备数据 6.【推荐】和数据库相关的单元测试,可以设定自动回滚机制,不给数据库造成脏数据。或者对单元测试产生的数据有明确的前后缀标识 ## 安全规约 1.【强制】隶属于用户个人的页面或者功能必须进行权限控制校验 2.【强制】用户敏感数据禁止直接展示,必须对展示数据进行脱敏
正例:中国大陆个人手机号码显示:139****1219,隐藏中间 4 位,防止隐私泄露 3.【强制】用户输入的 SQL 参数严格使用参数绑定或者METADATA字段值限定,防止 SQL 注入,禁止字符串拼接 SQL 访问数据库 4.【强制】对于文件上传功能,需要对于文件大小、类型进行严格检查和控制 5.【强制】配置文件中的密码需要加密 ## 数据库规约 ### 一、建表规约 1.【强制】表达是与否概念的字段,必须使用is_xxx的方式命名 2.【强制】禁用保留字,如desc、range、match、delayed等 3.【强制】如果存储的字符串长度几乎相等,使用char定长字符串类型 4.【强制】varchar是可变长字符串,不预先分配存储空间,长度不要超过5000,如果存储长度大于此值,定义字段类型为text,独立出来一张表,用主键来对应,避免影响其它字段索引率 5.【推荐】字段允许适当冗余,以提高查询性能,但必须考虑数据一致 6.【推荐】单表行数超过500万行或者单表容量超过2GB,才推荐进行分库分表 ### 二、索引规约 1.【强制】业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引 2.【强制】超过三个表禁止join。需要join的字段,数据类型保持绝对一致;多表关联查询时,保证被关联的字段需要有索引 3.【强制】在varchar字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度
说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为20的索引,区分度会高达90%以上,可以使用count(distinctleft(列名,索引长度))/count(*)的区分度来确定 4.【推荐】防止因字段类型不同造成的隐式转换,导致索引失效 ### 三、SQL语句 1.【强制】不要使用count(列名)或count(常量)来替代count(*),count(*)是SQL92定义的标准统计行数的语法,跟数据库无关,跟NULL和非NULL无关 2.【强制】当某一列的值全是NULL时,count(col)的返回结果为0;但sum(col)的返回结果为NULL,因此使用sum()时需注意NPE问题 3.【强制】代码中写分页查询逻辑时,若count为0应直接返回,避免执行后面的分页语句 4.【强制】禁止使用存储过程,存储过程难以调试和扩展,更没有移植性 5.【强制】数据订正(特别是删除或修改记录操作)时,要先select,避免出现误删除的情况,确认无误才能执行更新语句 6.【强制】对于数据库中表记录的查询和变更,只要涉及多个表,都需要在列名前加表的别名(或表名)进行限定 7.【推荐】SQL语句中表的别名前加as,并且以t1、t2、t3、...的顺序依次命名 8.【推荐】in操作能避免则避免,若实在避免不了,需要仔细评估in后边的集合元素数量,控制在100个之内 9.【推荐】不得使用外键与级联,一切外键概念必须在应用层解决
说明:(概念解释)学生表中的student_id是主键,那么成绩表中的student_id则为外键。如果更新学生表中的student_id,同时触发成绩表中的student_id更新,即为级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度 ### 四、ORM映射 1.【强制】在表查询中,一律不要使用*作为查询的字段列表,需要哪些字段必须明确写明 2.【强制】POJO类的布尔属性不能加is,而数据库字段必须加is_,要求在resultMap中进行字段与属性之间的映射 3.【强制】不要用resultClass当返回参数,即使所有类属性名与数据库字段一一对应,也需要定义;反过来,每一个表也必然有一个与之对应
说明:配置映射关系,使字段与DO类解耦,方便维护 4.【强制】xml配置参数使用:#{},#param#不要使用${}此种方式容易出现SQL注入 5.【强制】不允许直接拿HashMap与Hashtable作为查询结果集的输出 6.【强制】更新数据表记录时,必须同时更新记录对应的update_time字段值为当前时间 7.【推荐】@Transactional事务不要滥用。事务会影响数据库的QPS,另外使用事务的地方需要考虑各方面的回滚方案,包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等 ## 工程结构 ### 一、二方库 1.【强制】二方库版本号命名方式:主版本号.次版本号.修订号
1)主版本号:产品方向改变,或者大规模API不兼容,或者架构不兼容升级。
2)次版本号:保持相对兼容性,增加主要功能特性,影响范围极小的API不兼容修改。
3)修订号:保持完全兼容性,修复BUG、新增次要功能特性等 2.【强制】依赖于一个二方库群时,必须定义一个统一的版本变量,避免版本号不一致 3.【强制】禁止在子项目的pom依赖中出现相同的GroupId,相同的ArtifactId,但是不同的Version 4.【推荐】线上应用不要依赖SNAPSHOT版本(安全包除外);正式发布的类库必须先去中央仓库进行查证,使RELEASE版本号有延续性,且版本号不允许覆盖升级 5.【推荐】所有pom文件中的依赖声明放在语句块中,所有版本仲裁放在语句块中 ### 二、服务器 1.【强制】调用远程操作必须有超时设置
说明:类似于HttpClient的超时设置需要自己明确去设置Timeout。根据经验表明,无数次的故障都是因为没有设置超时时间 2.【推荐】高并发服务器建议调小TCP协议的time_wait超时时间 3.【推荐】调大服务器所支持的最大文件句柄数(FileDescriptor,简写为fd) 4.【推荐】给JVM环境参数设置-XX:+HeapDumpOnOutOfMemoryError参数,让JVM碰到OOM场景时输出dump信息 ## 附件:专有名词 - POJO(PlainOrdinaryJava Object):在本规约中,POJO专指只有setter/getter/toString的简单类,包括DO/DTO/BO/VO等 - DO(Data Object):阿里巴巴专指数据库表一一对应的POJO类。此对象与数据库表结构一一对应,通过DAO层向上传输数据源对象 - PO(PersistentObject):也指数据库表一一对应的POJO类。此对象与数据库表结构一一对应,通过DAO层向上传输数据源对象 - DTO(Data Transfer Object ):数据传输对象,Service或Manager向外传输的对象 - BO(Business Object):业务对象,可以由Service层输出的封装业务逻辑的对象 - Query:数据查询对象,各层接收上层的查询请求。注意超过2个参数的查询封装,禁止使用Map类来传输 - VO(View Object):显示层对象,通常是Web向模板渲染引擎层传输的对象 - CAS(Compare AndSwap):解决多线程并行情况下使用锁造成性能损耗的一种机制,这是硬件实现的原子操作。CAS操作包含三个操作数:内存位置、预期原值和新值。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作 - GAV(GroupId、ArtifactId、Version):Maven坐标,是用来唯一标识jar包 - OOP(Object Oriented Programming):本文泛指类、对象的编程处理方式 - AQS(AbstractQueuedSynchronizer):利用先进先出队列实现的底层同步工具类,它是很多上层同步实现类的基础,比如:ReentrantLock、CountDownLatch、Semaphore等,它们通过继承AQS实现其模版方法,然后将AQS 子类作为同步组件的内部类,通常命名为Sync - ORM(ObjectRelation Mapping):对象关系映射,对象领域模型与底层数据之间的转换,本文泛指iBATIS,mybatis 等框架 - NPE(java.lang.NullPointerException):空指针异常 - OOM(Out Of Memory):源于java.lang.OutOfMemoryError,当JVM没有足够的内存来为对象分配空间并且垃圾回收器也无法回收空间时,系统出现的严重状况 - GMT(Greenwich Mean Time):指位于英国伦敦郊区的皇家格林尼治天文台的标准时间,因为本初子午线被定义在通过那里的经线。地球每天的自转是有些不规则的,而且正在缓慢减速,现在的标准时间是协调世界时(UTC),它由原子钟提供 - 一方库:本工程内部子项目模块依赖的库(jar包) - 二方库:公司内部发布到中央仓库,可供公司内部其它应用依赖的库(jar包) - 三方库:公司之外的开源库(jar包)