进入项目组开始编码之前,需要掌握一些开发的常识和规范,先学习一遍《阿里巴巴 Java 开发手册》;这里仅选择部分较为实用、小白经常出问题的部分做一下介绍。
1.1 命名风格
第一条:【强制】代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。
尽管 $ 可以作为标识符使用,然而我们应该尽量避免对其使用。
原因: $ 通常在编译器生成的标识符名称中使用,如果我们也使用这个符号,可能会有一些意想不到的错误发生....
意想不到的错误示例:
就本程序来说,会生成 3 个 class 文件(如果可以编译的话),分别是 User$VIP.class
(顶层类)、User.class
与 User$VIP.class
(User 类的成员类,也就是类 VIP)。由于试图存在两个 User$VIP.class 所以才会报错!
第三至第六条:【强制】
1.类名使用 UpperCamelCase 风格,方法名、参数名、成员变量、局部变量都同意使用 lowerCamelCase 风格,必须遵从驼峰形式。
2.变量命名全部大写,单词兼用下划线隔开,力求予以表达完整清楚,不要嫌名字太长。
抽象类命名使用 Abstract 或 Base 开头;
异常类命名使用 Exception 结尾;
测试类命名以它要测试的类名开始,以 Test 结尾;
接口的实现类以接口名+Impl结尾;
MyBatis的IDAO接口名,例如NormalActivityAwardDAO,以DAO结尾;表的实体类以DO结尾,如ActivitySignRecordDO;
第八条:【强制】 POJO 类中布尔类型的变量都不要加 is 前缀,否则部分框架解析会引起序列化错误。
反例:定义为基本数据类型 Boolen isDeleted; 的属性,它的方法名称也是 isDeleted() ,RPC 框架在反向解析的时候,"误以为" 对应的属性名称是 deleted ,导致属性获取不到抛出异常。
第十三条:【推荐】 接口类中的方法和属性不要加任何修饰符号(public 也不要加),保持代码的间接性,并加上有效的 Javadoc 注释。尽量不要在接口里定义变量,如果一定要定义变量,必须是与接口方法相关的,并且是整个应用的基础变量。
正例: 接口方法签名: void commit(); 接口基础变量: String COMPANY = "alibaba";
1.2 常量定义
第二条:【强制】 long 或者 Long 初始赋值时,使用大写的 L,不能是小写的 l。小写的 l 容易跟数字 1 混淆,造成误解。
说明: Long a = 2l; 写得是数字的 21 还是 Long 型的 2?
第三条:【推荐】 不要使用一个常量类维护所有变量,要按常量功能进行归类,分开维护。
说明: 大而全的变量类,非得使用查找功能才能定位到修改的常量,不利于理解和维护。
正例:缓存相关常量放在类 CacheConsts 下;系统配置相关常量放在 ConfigConsts 下。
1.3 代码格式
第八条:【强制】 方法参数在定义和传入时,多个参数逗号后边必须加空格。
正例:下例中实参的"one",后边必须要有一个空格。 method("one", "two", "three");
1.4 OOP 规约
第二条:【强制】 所有的复写方法,必须加 @Override 注解。
说明: getObject() 与 get0bject() 的问题。一个是字母 O,一个是数字 0,
加 @Override 注解可以准确判断是否覆盖成功。另外,如果在抽象类中对方法签名进行修改,其实现类会马上编译报错。
第七条:【强制】 所有相同类型的包装类对象之间值得比较,全部使用 equals 方法
说明: 对于 Intergre var = ? 在 -128~127 范围内的赋值, Integer 对象是在 IntegerCache.cache 中产生的,会复用已有的对象,这个区间内的 Integer 值可以直接使用 == 进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象。这是一个大坑,推荐使用 equals 方法进行判断。
1.5 集合处理
第七条:【强制】 不要在 foreach 循环里进行元素的 remove / add 操作。remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁。
第十一条:【推荐】 高度注意 Map 类集合 K/V 能不能存储 null 值得情况
1.6 并发处理
第三条:【强制】 线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
说明: 使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源,解决资源不足的问题。如果不适用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者 "过渡切换" 的问题。
1.7 控制语句
第二条:【强制】 在 if / else / for / while / do 语句中,必须使用大括号。即使只有一行代码,也应该避免采用单行的编码方式:if (condition) statements;
第三条:【强制】 在高并发场景中,避免使用 "等于" 判断作为终端或退出的条件
说明: 如果并发控制没有处理好,容易产生等值判断被 "击穿" 的情况,应使用大于或小于的区间判断条件来代替。
反例:判断剩余奖品数量等于 0 时,终止发放奖品,但因为并发处理错误导致奖品数量瞬间变成了负数,这样的话,活动无法终止。
第四条:【推荐】 在表达异常的分支时,尽量少用 if-else 方式
说明: 如果不得不使用 if()...else if()...else... 方式表达逻辑,【强制】 避免后续代码维护困难,请勿超过 3 层。
1.8 注释规约
第一条:【强制】 类、类属性、类方法的注释必须使用 Javadoc 规范,使用 /*内容/ 格式,不得使用 //xxx 方式
第二条:【强制】 所有的抽象方法(包括接口中的方法)必须要用 Javadoc 注释,除了返回值、参数、异常说明外,还必须指出该方法做什么事情,实现什么功能。
说明: 对子类的实现要求,或者调用注意事项,请一并说明。
@return
@throws
@param
@description
第三条:【强制】 所有的类都必须添加创建者和创建日期。
1.9 其他
第六条:【推荐】 不要在视图模板中加入任何复杂的逻辑。
说明: 根据 MVC 理论,视图的职责是展示,不要抢模型和控制器的工作。
第4章:安全规约
"安全生产,责任重于泰山。" 这句话同样适用于软件生产,本章主要说明编程中需要注意的比较基础的安全准则。
第一条:【强制】 隶属于用户个人的页面或者功能必须进行权限控制校验
说明: 放置皆有做水平权限校验就可以随意访问、修改、删除别人的数据,比如查看他人的私信内容、修改他人的订单。
第二条:【强制】 用户敏感数据禁止直接展示,必须对展示数据进行脱敏。
说明: 个人手机号码会显示为 158****9119,隐藏中间 4 位,防止个人隐私泄露。
第三条:【强制】 用户输入的 SQL 参数严格使用参数绑定或者 METADATA 字段值限定,防止 SQL 注入,禁止字符串拼接 SQL 访问数据库。
SQL注入就是在URL里面显示的使用"变量名=值"的方法,后台直接解析这些值并作为SQL语句的参数;SQL注入就是把SQL语句作为"变量名=值"中的值传进去了,修改了正常的SQL验证的逻辑;
解决办法:
1.严格用户权限验证;
2.不要把输入的内容放在URL里面;
3.输入过滤;
4.多层逻辑验证;
5.使用工具检测;…
第四条:【强制】 用户请求传入的任何参数必须做有效性验证
说明: 忽略参数校验可能导致如下情况。
1)page size 过大导致内存溢出
2)恶意 order by 导致数据库慢查询
3)任意重定向
4)SQL 注入
5)反序列化注入
6)正则输入源串拒绝服务 ReDoS
Java 代码用正则来验证客户端的输入,有些正则写法验证普通用户输入没有问题,但是如果攻击人员使用的是特殊构造的字符串来验证,则有可能导致死循环。
第五条:【强制】 禁止向 HTML 页面输出未经安全过滤或未正确转义的用户数据。
第七条:【强制】 在使用平台资源,譬如短信、邮件、电话、下单、支付,必须实现正确的防重放限制,如数量限制、疲劳度控制、验证码校验,避免被滥刷、资损。
说明:如注册时发送验证码到手机,如果没有限制次数和频率,那么可以利用此功能骚扰到其它用户,并造成短信平台资源浪费。
数据库相关(公司也有专门的数据库限制,如查询不超过3张表等)
5.1 建表规约
第二条:【强制】 表名、字段名必须使用小写字母或数字 , 禁止出现数字开头,禁止两个下划线中间只出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。
说明: MySQL 在 Windows 下不区分大小写,但在 Linux 下默认区分大小写。因此,数据库名、表明、字段名都不允许出现任何大写字母,避免节外生枝。
第四条:【强制】禁用保留字,如 desc 、 range 、 match 、 delayed 等,请参考 MySQL 官方保留字。
第五条: 【强制】主键索引名为 pk_ 字段名;唯一索引名为 uk _字段名 ; 普通索引名则为 idx _字段名。
说明: pk_ 即 primary key;uk _ 即 unique key;idx _ 即 index 的简称。
第六条:【强制】小数类型为 decimal ,禁止使用 float 和 double 。
说明:float 和 double 在存储的时候,存在精度损失的问题,很可能在值的比较时,得到不正确的结果。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数分开存储。
第八条:【强制】 varchar 是可变长字符串,不预先分配存储空间,长度不要超过 5000,如果存储长度大于此值,定义字段类型为 text ,独立出来一张表,用主键来对应,避免影响其它字段索引效率。
第九条:【强制】表必备三字段: id , gmt _ create , gmt _ modified
说明:其中 id 必为主键,类型为 unsigned bigint 、单表时自增、步长为 1。 gmt _ create ,gmt _ modified 的类型均为 date _ time 类型。
第十条: 【推荐】表的命名最好是加上"业务名称_表的作用"。
正例: tiger _ task / tiger _ reader / mpp _ config
第十五条:【参考】合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度。
5.2 索引规约
第五条: 【推荐】如果有 order by 的场景,请注意利用索引的有序性。 order by 最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现 file _ sort 的情况,影响查询性能。
正例: where a =? and b =? order by c; 索引: a _ b _ c
反例:索引中有范围查找,那么索引有序性无法利用,如: WHERE a >10 ORDER BY b; 索引 a _ b 无法排序。
第九条: 【推荐】建组合索引的时候,区分度最高的在最左边。
正例:如果 where a =? and b =? , a 列的几乎接近于唯一值,那么只需要单建 idx _ a 索引即可。
说明: 存在非等号和等号混合判断条件时,在建索引时,请把等号条件的列前置。如: where a >? and b = ? 那么即使 a 的区分度更高,也必须把 b 放在索引的最前列。
第六条: 【强制】不得使用外键与级联,一切外键概念必须在应用层解决。
说明: ( 概念解释 ) 学生表中的 student _ id 是主键,那么成绩表中的 student _ id 则为外键。如果更新学生表中的 student _ id ,同时触发成绩表中的 student _ id 更新,则为级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群 ; 级联更新是强阻塞,存在数据库更新风暴的风险 ; 外键影响数据库的插入速度。
第八条: 【强制】数据订正时,删除和修改记录时,要先 select ,避免出现误删除,确认无误才能执行更新语句。
5.4 ORM 映射
整个规约对自己来说都挺有用的,因为正好涉及到这方面,幸好感觉脸不怎么疼。
第一条:【强制】在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。
说明: 1 ) 增加查询分析器解析成本。2 ) 增减字段容易与 resultMap 配置不一致。
第二条:【强制】 POJO 类的 布尔 属性不能加 is ,而数据库字段必须加 is _,要求在 resultMap 中进行字段与属性之间的映射。
说明: 参见定义 POJO 类以及数据库字段定义规定,在 <resultMap>中 增加映射,是必须的。在 MyBatis Generator 生成的代码中,需要进行对应的修改。
第三条:【强制】不要用 resultClass 当返回参数,即使所有类属性名与数据库字段一一对应,也需要定义 ; 反过来,每一个表也必然有一个与之对应。
说明: 配置映射关系,使字段与 DO 类解耦,方便维护。
第七条:【强制】更新数据表记录时,必须同时更新记录对应的 gmt _ modified 字段值为当前时间。
第九条:【参考】@ Transactional 事务不要滥用。事务会影响数据库的 QPS ,另外使用事务的地方需要考虑各方面的回滚方案,包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等。