魔鬼在细节里
- 2019 年 11 月 27 日
- 筆記
再一次刷了一遍代码规约,又有新的感受和认识,贴一下之前阿里云代码规约认证

image.png
一、编程规约
1、命名风格
** 所有命名不能以下划线或者美元符号开始或者结束,不能有拼音和中文的方式,命名语义要清晰完整 **
接口和实现类: 实现类要以Impl结尾
枚举:名称要以Enum结尾,成员名称全部大写,单词之间用下划线隔开。
类:
命名方式:类名使用首字母大写的驼峰命名,除:DO、VO、DTO、BO、PO、UID等
抽象类使用Abstract或者Base开头,异常类使用Excetion结尾,测试类使用要测试的类名开始Test
结尾
方法、参数、成员变量、局部变量:统一使用首字母小写的驼峰命名
命名方式:成员变量不要以is开头,类型的名次放在词尾,例如:nameList 使用方式:相同成员变量名称不能同时出现在子类和父类,或者同一方法的不同代码块中。
常量:命名全部大写、单词用下划线隔开,命名语义完整
包:包名统一小写,点分隔符之间只有一个单词,不能使用复数,例如:utils
项目各层命名规范:
1)获取单个对象的方法使用get做前缀
2)获取多个对象的方法使用list做前缀,复数结尾,例如:listStudents
3)获取统计值的方法使用count做前缀
4)插入的方法使用save/insert做前缀
5)删除的方法使用remove/delete做前缀
6)修改的方法使用update做前缀
领域模型命名规范:
1)数据对象使用DO结尾,表名开头。例如:student表对应->StudentDO
2)数据传输对象:xxxDTO,xxx为业务领域相关名称。例如:TradeDTO
3)展示对象:xxxVO,xxx一般为网页对象。例如:PortalVO
4)POJO是DO/DTO/BO/VO的统称,禁止使用xxxPOJO命名
2、常量定义
1、不要使用魔法值,维护在常量类中,常量类按照功能分开维护,区分一方库、二方库、子工程、包共享、类共享
2、如果变量值在固定范围内使用enum类型定义
3、在给数值类型赋值时,数值后使用大写,例如:1L
3、代码风格
1、代码缩紧
** 采用4个空格符缩紧,禁止使用tab **
** 不同逻辑、不同语义、不同业务代码之间插入一个空行分割符**
大括号:内为空直接{},非空
<pre class="cm-s-default" style="color: rgb(89, 89, 89); margin: 0px; padding: 0px; background: none 0% 0% / auto repeat scroll padding-box border-box rgba(0, 0, 0, 0);">{ System.out.println(); }`</pre>
小括号:括号前后不需要空格,例如:if (a == b)
if/for/while/switch/do: 与括号之间要加空格
类型和中括号紧挨表示数组, 例如:int[] arrayDemo
二目三目运算符: 左右两边都要加空格,例如: a = b
注释:双斜线与注释内容之间必须要有一个空格,例如:// 这是一个注释
方法总行数不要超过80行;单行字符限制不要超过120,超出换行:
1、第二行相对第一行缩紧4个空格,第三行开始不用
2、运算符、点符号与下文一起换行
3、方法调用多个参数时在逗号后换行
4、括号前不要换行
方法参数之间:在逗号后加空格
工具编码格式:text file encoding 设置为UTF-8
- 工具基本上都有格式化的功能,尽量使用工具来辅助提升开发效率
4、OOP规约
1、类的静态成员或者方法,直接用类名访问
2、所有覆写方法必须加@Override注解
3、不提倡使用可变参数
4、外部调用或者提供的接口,不允许修改方法签名。接口过时使用@Deprecated注解,同时禁止调用有该注解的方法
5、所有整型包装类之间值比较使用equals
6、equals 方法,使用java.utils.Objects#equals 或者"".equals(s)
7、浮点数之间的等值判断,基本数据类型不能用==,包装类不能用equals方法,使用BigDecimal.valueOf或者入参为String的构造方法。同样,BigDecimal不能用==判断值相等。

image.png
[图片上传中…(image-6674ff-1574513603639-2)]
8、基本类型和包装类型的使用标准
1、所有POJO属性必须使用包装类型和写toString方法,而且定义VO/DO等类时,不要对任何属性设置默认值
2、DO要与数据库类型保持一致,禁止同时存在属性对isXxx方法和getXxx方法
3、RPC方法参数和返回值也必须为包装类型,而且参数和返回对象一定要实现Serializable接口
9、构造方法禁止加任何业务逻辑,如果需要就放在init方法中。有多个构造方法时,按照顺序放在一起
10、类中方法定义顺序: 公有方法或者保护方法 -> 私有方法 -> getter/setter方法
11、gettet/setter方法中参数名和变量名称一致,不要在getter/setter方法中增加业务逻辑
12、String字符串在循环体中使用StringBuilder的append方法,在使用split方法中注意最后一个分割符有无内容
13、final关键字,类不允许继承、方法不能被覆写、变量不能被修改
14、Object的clone方法属于浅copy,如果想实现深层copy需要覆写clone方法
5、集合处理
1、List相关
1)ArrayList的subList结果不能强转ArrayList,否则会ClassCastException,而且不能进行添加和删除操作。
2、Set相关
1)必须重写hashCode和equals方法
- 利用set的元素唯一的特性做去重操作
3、Map相关
1)注意Map集合K/V能不能存储null的情况,而且key必须覆写hashCode和equals方法
[图片上传中…(image-6acd58-1574513603639-1)]
2)Map的ketSet、values、entrySet返回的集合对象不能进行添加操作,可以进行删除操作
3)使用entrySet遍历Map类集合KV,而不是使用ketSet。ketSet是遍历了两遍,1.8使用Map.forEach
4、Collections类相关
1)Collections的empyList、singletonList返回的list不能进行添加和删除操作
2)使用Collections任何实现类的addAll方法时必须要做NPE判断
5、使用集合转数组的方法,必须使用集合的toArray(T[] array),传入的类型完全一致、长度为0的数组
6、使用数组转集合Arrays.asList时,不能使用修改集合相关的方法。
7、泛型通配符<? extend T> 来接受返回的数据,此写法不能使用add方法;<? super T> 不能使用get方法
8、在使用无泛型集合时,使用元素时要进行instanceof判断,例如:
<pre class="cm-s-default" style="color: rgb(89, 89, 89); margin: 0px; padding: 0px; background: none 0% 0% / auto repeat scroll padding-box border-box rgba(0, 0, 0, 0);">`List<String> generics = null; List notGenerics = new ArrayList(10); notGenerics.add(new Object()); notGenerics.add(new Integer(1)); generics = notGenerics; // 此处抛出 ClassCastException 异常 String string = generics.get(0);</pre>
9、不要在foreach里进行add/remove操作,remove要使用Iterator方法,有并发操作要对Iterator对象加锁。
10、使用Comparator做排序时,要注意>、=、<三种情况都要考虑
<pre class="cm-s-default" style="color: rgb(89, 89, 89); margin: 0px; padding: 0px; background: none 0% 0% / auto repeat scroll padding-box border-box rgba(0, 0, 0, 0);">List<Integer> list = Arrays.asList(-1, 0, 0, 0, 0, -1, 0, 0, 1, 0, 0, 0, 0, -1, 0, 1, 1, 1, 0, 0, 0, 0, -1, 1, 1, 1, 1, -1, -1, -1, -1, 1); Collections.sort(list, new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o1 > o2 ? 1 : -1; } }); //数组的大小必须大于等于32.小于32时不会出现java.lang.IllegalArgumentException异常. //原因是:java中默认的MIN_MERGE为32.若待排序的数组小于MIN_MERGE时, //会使用Binary Sort,而不会使用TimSort.</pre>
11、集合初始化时,指定集合初始值的大小。例如:HashMap如果无法确定大小,设置16.确定大小设置2的幂次方
6、并发处理
1、获取单例对象要保证线程安全,方法同样也需要。
2、创建线程池的时候要指定有意义的线程池名称,方便出错排查。
3、线程池不能用Executors创建,要使用ThreadPoolExecutor方式,避免资源耗尽,更不能自行显示创建线程。
4、SimpleDateFormat线程不安全,使用DateUtils工具类
5、自定义ThradLocal必须进行try-finally回收,否则线程被重复使用会导致业务逻辑异常和内存泄漏等问题
6、高并发时,锁能不用就不要用,顺序 无锁-> 锁 ,锁区块-> 锁方法,锁对象 ->锁类。注意加锁的顺序和超时时间避免发生死锁。
7、加锁的位置,在加锁和方法之间没有任何可能抛出异常的方法调用,加锁不要放在try块里,finally中解锁。
<pre class="cm-s-default" style="color: rgb(89, 89, 89); margin: 0px; padding: 0px; background: none 0% 0% / auto repeat scroll padding-box border-box rgba(0, 0, 0, 0);">Lock lock = new XxxLock(); // … lock.lock(); try { doSomething(); doOthers(); } finally { lock.unlock(); }</pre>
8、再使用尝试机制获取锁的方式中,先判断是否持有锁。
<pre class="cm-s-default" style="color: rgb(89, 89, 89); margin: 0px; padding: 0px; background: none 0% 0% / auto repeat scroll padding-box border-box rgba(0, 0, 0, 0);">Lock lock = new XxxLock(); // … boolean isLocked = lock.tryLock(); if (isLocked) { try { doSomething(); doOthers(); } finally { lock.unlock(); } }</pre>
9、悲观锁和乐观锁使用标准:冲突少于20%使用乐观锁(CAS|版本号)重试次数不少于3次。金融敏感信息使用悲观锁。
10、多线程并行处理定时任务,Timer运行多个timeTask,只要其中一个没有捕获抛出的异常,其他任务便终止,使用ScheduleExecutorService没有这种情况。
11、使用CountDownLatch进行异步转同步操作,每个线程退出前必须调用countDown方法,线程执行代码注意catch异常确保countDown方法被执行到,避免主线程无法执行至await方法。同理cyclicBarrier
12、避免Random实例被多线程使用
13、在并发场景下,通过双重检查锁实现延迟初始化的优化问题,推荐使用volatile声明目标属性。
14、volatile不能在多写场景下使用。
15、HashMap不能在高并发要求严格的情况下使用,使用ConcurrentHashMap
16、ThreadLoca对象使用static修饰,无法解决共享对象更新的问题。
7、控制语句
1、switch必须有default语句放在最后,注意break是退出switch语句块,return是退出方法体。
2、当switch括号内变量为String时,必须先进行null判断。
3、在高并发中避免使用=作为中断或退出的条件
4、少用if-else组合,最多不能超过3层,不要在判断条件中执行复杂的语句和赋值语句
5、循环体中的语句要考量性能,不要再循环体内做获取数据库链接,try-catch等操作
6、避免使用反逻辑运算符
7、参数校验:调用频率低的方法、执行时间开销大的方法、需要稳定性和可用性的方法、对外提供开发接口
8、不用做参数校验:被循环调用的方法;DAO层的方法;被声明private方法,这些需要做参数校验要在外层做
8、注释规约
注释必须用Javadoc规范 使用 / 内容 / 格式
1、方法必须要使用Javadoc注释、除了参数、返回值、异常信息外还要说明接口的功能
2、所有类必须要添加创建者和创建日期
3、方法内单行使用 //注释 ,多行使用/* */注释
4、所有枚举值必须要有注释,说明用途
5、修改接口功能 注释也要修改
6、禁止注释代码,如果无用就删除。
7、避免注释过多过滥,不要产生注释过多难以维护的极端
8、特殊标记,TODO 要注明标记人、时间、预处理时间,FIXME 错误不能工作,基本上会立即修复,如果做了标记同样要说明标记人、标记时间、处理时间
9、其它
1、正则表达式要用好其预编译功能
<pre class="cm-s-default" style="color: rgb(89, 89, 89); margin: 0px; padding: 0px; background: none 0% 0% / auto repeat scroll padding-box border-box rgba(0, 0, 0, 0);">private static final Pattern pattern = Pattern.compile(regexRule); private void func(…) { Matcher m = pattern.matcher(content); if (m.matches()) { … } }</pre>
2、后台给页面的变量必须加$!{var} ——中间的感叹号
3、获取随机证书类型的随机数,不要将Math.ramdom()放大10的若干倍然后取整,直接使用nextInt或者nextLong方法
4、获取当前毫秒数System.currentTimeMillis()
5、日期格式化时,传入的pattern中表示年份统一的使用小写y
6、不要在视图模版中加任何复杂逻辑
7、及时清理不在使用的代码或者配置
二、异常日志
1、异常处理
1、预检查异常不应该同catch的方式来处理,提前做判断
2、异常不要用做流程控制、条件控制
3、稳定代码不需要catch,捕获非稳定代码就要处理,否则直接抛给上层业务来处理。
4、捕获的异常要和抛出的异常匹配,这里禁止catchException,要捕获具体类型的异常或者父类。
5、finally释放资源或者流,不要在finally里return。
6、在调用二方包、RPC、动态生成类相关方法是使用Throwable类来拦截
7、方法返回可以返回null,但是要添加注释说明null产生的情况。
8、防止NPE的场景:
1)返回类型为基本类型,return包装类是自动拆箱会产生NPE
2)数据库查询null
3)集合的元素即使isNotEmpty,取出的元素也有可能null
4)对于远程返回对象必须要做判断null
5)对于Session中获取的数据,进行NPE检查
6)禁止级联调用 .get().get(), 推荐使用Optional
9、公司对外提供的服务或者开放接口必须使用错误码,应用内部推荐异常抛出,RPC调用使用Result。
10、避免出现重复代码
2、日志规约
1、代码中不能使用Log4j、Logback的API,建议使用sf4j的API
2、所以日志文件至少保留15天,其他重要的日志保存至少不少于6个月
3、打印日志使用占位符的方式,例如: logger.info("accpt system name: {} ,messge : {}",name,message);
4、对于trace/debug/info级别的日志必须进行日志级别的开关验证。
5、避免重复打印日志,浪费磁盘空间,在log4j中设置additivity=false
6、异常信息包括两类:案发信息和异常堆栈信息,如果不处理,通过trows 往上抛
7、生产环境禁止记录debug日志,可以使用warn日志记录用于输入参数错误的情况。尽量使用英文记录日志错误信息。
三、单元测试
1、单元测试必须遵守:自动化、独立性(单个case不依赖)、可重复性
2、单元测试之间独立,不互相依赖
3、单元测试粒度足够小,但是要有度,例如:getter/setter这样的就不用在写单元测试
4、核心业务、核心应用、核心模块的增量代码确保单元测试通过
5、单元测试要放在测试目录下,和项目包结构一致,测试xxx类,以xxx命名Test结尾。
6、单元测试的基本目标:语句覆盖率70%,核心模块的语句覆盖率和分支覆盖率100%
7、单元测试包括:
)边界值:循环边界、特殊取值、特殊时间点、数据顺序等
)正确输入,并达到预期结果
)与设计文档相结合,来编写单元测试
)强制错误信息,参数校验、异常流程并取得预期结果
8、对于数据库相关的查询、更新、删除操作不能假设数据存在或者直接操作数据库
9、和数据库相关单元测试,可以设定自动回滚机制,不给数据库造成张数据,或者给数据加明确的前后缀表识。
10、对不可测代码重构、在设计阶段开发和测试要确定单元测试的范围,单元测试最好覆盖所有的测试用例。
11、单元测试不建议在项目发布后补单元测试,
12、避免以下情况:
)构造方法中做的事情太多
)存在过多的全局变量和静态方法
)存在过多的依赖
)存在过多的条件语句
使用卫语句、策略模式、状态模式重构
13、单元测试的误解:
)测试是由测试人员做的,开发不用太多关注
)单元测试是多余的,做好集成测试和功能正常就好了
)单元测试代码不用维护
)单元测试和线上故障没有辩证关系
四、安全规约
1、隶属于个人的页面必须要做权限验证
2、必须对用户敏感数据脱敏
3、用户输入的SQL参数严格使用参数绑定或者METADATA字段值限定,防止SQL注入
4、用户请求传入的任何参数必须做验证
5、禁止想HTML页面输出未经安全过滤或者正确转义的用户数据
6、表单、AJAX提交必须执行CSRF安全验证
7、平台资源:短信、电话、邮件做好防重机制,避免滥刷
8、发帖、评论、及时消息必须对内容做过滤和违禁处理
五、MySQL数据库
1、建表规约
1、表达是否,是用is_xxx命名,类型使用unsigned tinyint
2、表名、字段名必须使用小写或者数字,不能使用复数,单词之间下划线隔开。禁止两个下划线只有数字,和数字开头
3、禁止使用保留字,例如:desc range、match、delayed等
4、主键索引名为pk_字段名;唯一索引uk_字段名;普通索引idx_字段名
5、小数类型使用decimal,禁止使用float和double
6、字符串长度几乎相等,使用char定长
7、varchar不要超过5000,超过使用text,独立出一张表。
8、必备字段:id、create_time、updae_time
9、数据库名称和应用名称一致
10、修改字段含义,必须要修改注释
11、字段允许适当冗余、但是必须要遵循:不是频繁修改的数据、不是varchar超长字段、更不能是text,不是唯一索引
12、单表超过500万行或者单表容量超过2GB,才推荐使用分库分表
13、适合的字符串长度,提升检索效率,节约空间
2、索引规约
1、业务上具有唯一特性的字段,即使多个字段的组合,也必须建成唯一索引
2、超过三个表禁止join
3、在varchar字段上建立索引必须指定索引长度,没必要全字段建立索引
4、页面上搜索严禁左模糊或者全模糊,如果有需要用搜索引擎来解决
5、如果有order by 的场景,请注意利用索引的有序性,order by最后的字段是组合索引的一部分,放在索引组合顺序的最后,避免出现file_sort的情况,影响性能
6、利用覆盖索引来进行查询,避免回表
7、利用延时关联或者子查询优化超多分页场景
8、SQL性能优化的目标:至少要到range ,要求是ref,最好是consts
9、建组合索引的时候,区分度最高的在最左边。
10、防止因字段类型不同造成的隐式转换,导致索引失效
11、创建索引的误解:
)宁滥勿缺:任务一个查询就要建立一个索引
)宁缺毋滥:认为索引消耗空间,严重拖慢记录的更新和行的新增速度
)抵制唯一索引,认为业务的唯一性需要在应用层通过"先查后插"的方式解决
3、SQL语句
1、不要使用count(列名)、count(常量)来替代count(),count() 是SQL92定义的标准,跟NULL无关
2、count(distinct col) 计算该列除NULL之外的不重复行数,注意count(distinct col,col2) 有一列全为null,返回0
3、某一列全为null,count(col) 为0,sum(sol) 为null
4、使用ISNULL来判断是否为NULL值,NULL与任何值比较都为NULL
5、分页查询,count为0时直接返回,避免执行后面的查询语句
6、不得使用外键和级联,一切外键和级联都必须在应用层解决
7、禁止使用存储过程
8、数据订正时,先select,避免误删除。
9、避免使用in ,如果使用也不能超过100个元素
10、TRUNCATE TABLE 比DELETE快。
4、ORM映射
1、在表查询时一律不要使用*作为查询的字段列表,需要哪些字段必须明确写明。
2、不要直接用resultClass当作返回参数,要使用result一一映射
3、使用#{},不要使用${}防止sql注入
4、iBATIS的queryForList()不推荐使用,全表扫描
5、不允许那HashMap和HashTable作为查询结果集的输出
6、更新表记录是,必须同时更新gmt_modified字段值
7、不要写一个大而全的更新接口
8、@Transactional不要滥用
9、<isEqual>中的campareValue是与属性值对比的常量,一般为数字,<isNotEmpty>表示不为null值时执行
六、工程结构
1、应用分层
[图片上传中…(image-d3ef26-1574513603639-0)]
1、分层如上图、异常规约DAO不需要处理异常直接抛,Service记录异常日志,web不能抛异常,而是跳到错误页面。
2、分层领域模型规约
)DO:数据表一一对应
)DTO:数据传输对象,Service或者Manager向外传输对象
)BO:业务对象,由Service层输出的封装业务逻辑对象
)AO:应用对象,在Web层和Service之间抽象的复用对象模型
)VO:展示层对象
)Query:数据查询参数
2、二方库依赖
1、GroupID格式:com.{公司/BU}.业务线.[子业务线]
2、ArtifactID格式:产品线-模块名
3、Version:版本,主版本、次版本、修订号。起始版本1.0.0
4、线上应用不要依赖SNAPSHOT版本
5、二方库跟新升级保持功能点之外的其它jar包仲裁结果不变
6、参数可以使用枚举,返回值不能
7、依赖一个二方库群时,统一一个版本变量
8、禁止子项目出现pom相同的GroupID、AtrifactID,但是Version 不相同
9、底层基础技术、核心数据管理、硬件端系统谨慎引入第三方实现
10、所有pom文件中的依赖声明放在<dependencies> 所有版本仲裁放在<dependencyManagement>中
11、二方库不要有配置项
12、避免二方库依赖冲突,二方库的发布者应当遵循:1、移除不必要的API和依赖,只包含ServiceAPI
2、每个版本变化都要维护和记录
3、服务器
1、高并发服务建议调小TCP协议的time_wait时间
2、调大服务器所支持的最大文件句柄数
3、给JVM配置参数:-XX:+HeapDumpOnOutOfMemoryError,当有OOM时输出dump信息
4、线上环境,JVM的Xms 和Xmx设置大小一样的内存,避免GC后调整堆大小带来的压力
5、服务器内部重定向使用foward,外部重定向使用URL拼装工具
七、设计规约
1、存储方案和底层数据结构设计通过后,沉淀成文档
2、在需求分析阶段,如果与系统交互的User超过一类并且相关Case超过5个,使用用例图来表达结构化需求。
3、如果业务对象超过3个使用状态图来表达状态变化的各个触发条件
4、如果系统中某个功能的调用链路涉及对象超过3个,使用时序图来表达个环节的输入输出
5、系统模型中类超过5个,使用类图
6、系统中超过2个对象之间协作关系,使用活动图来表示
7、需求分析和系统设计在考虑主干功能是,需要充分评估异常流程和业务边界
8、类在设计与实现时要符合单一原则
9、谨慎使用继承的方式进行扩展,优先使用聚合/组合的方式
10、系统设计、根据依赖倒置原则,尽量依赖抽象类与接口,有利于扩展与维护。
11、共性业务或共性行为抽取出来公共模块、公共配置、公共类、公共方法、避免重复代码的情况、
12、避免误解:敏捷开发 = 讲故事+编码+发布
13、系统设计的目的是:明确需求、理顺逻辑、后期维护、次要目的是指导编码。
14、设计的本质就是识别和表达系统难点,找到系统变化点,并隔离变化点
15、系统架构设计的目的:
)确定系统边界,在技术上做/不做
)确定系统内模块之间的关系,依赖关系
)后续设计和演化的原则
)非功能性需求,安全、可用、可扩展性
16、在做无障碍产品设计,要考虑到:
)所有可交互的控件元素能被tab聚焦
)用于登陆校验和请求拦截的验证码都要提供图形验证以外的其他验证
)自定义的控件类型需明确交互方式。