提要
前几天在微博看见分享的一篇阿里内部技术手册,觉得其中有部分内容还是很值得我们借鉴的,当然,大部分内容都是常识性的内容,但是其中有一部分内容我觉得是平常我们开发时遇到不止一次的内容,需要我们注意的地方。因此整理下来以便自己能时常温习,如果能给别人省下时间那就更好了。
编程规约
命名规约
类名使用UpperCamelCase
但是下列情况例外:领域模型相关,比如DO/DTO/VO/DAO等。PS:日常开发领域模型我也习惯小写,无论哪种,一定要在项目组里约定好即可。
pojo类布尔类型的变量都不加is,否则可能会遇到部分框架序列化问题。
接口方法和属性不要加任何修饰符号(包括public),辅以javadoc注释,尽量不要在接口内定义常量
接口和实现命名规则
1、对于Service和DAO,暴露的是接口,内部用Impl标识与接口区别。
2、对于形容能力的接口,取形容词标识(比如AbstractTranslator实现Translatable)
各层命名规约
A) Service/DAO 层方法命名规约
1) 获取单个对象的方法用 get 做前缀。
2) 获取多个对象的方法用 list 做前缀。
3) 获取统计值的方法用 count 做前缀。
4) 插入的方法用 save(推荐)或 insert 做前缀。
5) 删除的方法用 remove(推荐)或 delete 做前缀。
6) 修改的方法用 update 做前缀。
B) 领域模型命名规约
1) 数据对象:xxxDO,xxx 即为数据表名。
2) 数据传输对象:xxxDTO,xxx 为业务领域相关的名称。
3) 展示对象:xxxVO,xxx 一般为网页名称。
4) POJO 是 DO/DTO/BO/VO 的统称,禁止命名成 xxxPOJO。
常量定义
不允许直接出现未定义常量的值、long定义最后要加L。常量要归类,不要放在一起。
OOP规约
所有的覆写方法,都要加注解。
尽量少用可变参数。
equals方法应该总把确定有值的一方放在前面(这个很重要),jdk7可以使用Objects.equals()。
基本数据类型和包装数据类型使用标准:
1) 所有的 POJO 类属性必须使用包装数据类型。
2) RPC 方法的返回值和参数必须使用包装数据类型。
3) 所有的局部变量推荐使用基本数据类型
pojo必须实现tostring方法。
字符串拼接使用StringBuilder,不要使用string。
应该添加final修饰的:
1) 不需要重新赋值的变量,包括类属性、局部变量。
2) 对象参数前加 final,表示不允许修改引用的指向。
3) 类方法确定不允许被重写。
类成员和方法访问控制:
1) 如果不允许外部直接通过 new 来创建对象,那么构造方法必须是 private。
2) 工具类不允许有 public 或 default 构造方法。
3) 类非 static 成员变量并且与子类共享,必须是 protected。
4) 类非 static 成员变量并且仅在本类使用,必须是 private。
5) 类 static 成员变量如果仅在本类使用,必须是 private。
6) 若是 static 成员变量,必须考虑是否为 final。
7) 类成员方法只供类内部调用,必须是 private。
8) 类成员方法只对继承类公开,那么限制为 protected。
任何类、方法、参数、变量,严控访问范围。过宽泛的访问范围,不利于模块解耦。
并发处理
线程资源必须通过线程池提供,不允许应用显示创建线程。
高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁
对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁。
多线程并行处理定时任务时,Timer 运行多个 TimeTask 时,只要其中之一没有捕获抛出的异常, 其它任务便会自动终止运行, 使用 ScheduledExecutorService 则没有这个问题
线程池不允许使用 Executors 去创建,而是通ThreadPoolExecutor 的方式
控制语句
switch 块内,都必须包含一个 default 语句并且放在最后,即使它什么代码也没有
推荐尽量少用 else
if-else 的方式可以改写成:
if(condition){
…
return obj;
}
// 接着写 else 的业务逻辑代码;
不要在条件判断中执行复杂的语句,以提高可读性。定义对象、变量、获取数据库连接,不必要的 try-catch 操作都应该移到循环体外。
方法中需要进行参数校验的场景(校验参数要在Controller和Service校验)
1) 调用频次低的方法。
2) 执行时间开销很大的方法,参数校验时间几乎可以忽略不计,但如果因为参数错误导致
中间执行回退,或者错误,那得不偿失。
3) 需要极高稳定性和可用性的方法。
4) 对外提供的开放接口,不管是 RPC/API/HTTP 接口。
方法中不需要参数校验的场景
1) 极有可能被循环调用的方法,不建议对参数进行校验。但在方法说明里必须注明外部参数检查。
2) 底层的方法调用频度都比较高,一般不校验。毕竟是像纯净水过滤的最后一道,参数错误不太可能到底层才会暴露问题。一般 DAO 层与Service 层都在同一个应用中,部署在同一台服务器中,所以 DAO 的参数校验,可以省略。
3) 被声明成 private 只会被自己代码所调用的方法,如果能够确定调用方法的代码传入参数已经做过检查或者肯定不会有问题,此时可以不校验参数。
注释规约
修改代码的同时要相应修改注释。
注释的代码要注释为何注释,如果不要了直接删除。
尽量编写自注释的代码,让人看代码就好像看注释一样。
TODO是很重要的,随时想到随时打上,注上想到的内容
异常日志
异常处理
无需捕获runtimeexception。
异常不要用作流程控制,条件控制。
严禁大段代码try catch。catch异常需要回滚try记得手动回滚,或利用spring切面事务。
防止NPE(nulpointerexception)是每个程序员必备的素质。
1) 返回类型为包装数据类型,有可能是 null,返回 int 值时注意判空。
反例:public int f(){ return Integer 对象},如果为 null,自动解箱抛 NPE。
2) 数据库的查询结果可能为 null。
3) 集合里的元素即使 isNotEmpty,取出的数据元素也可能为 null。
4) 远程调用返回对象,一律要求进行 NPE 判断。
5) 对于 Session 中获取的数据,建议 NPE 检查,避免空指针。
6) 级联调用 obj.getA().getB().getC();一连串调用,易产生 NPE。
代码中应该抛异常还是返回错误码。公司外的开放接口必须是错误码,对应公司内部应该是异常,跨应用间 RPC 调用优先考虑使用 Result 方式,封装 isSuccess、“错误码”、“错误简短信息”。
日志规约
日志文件推荐至少保存 15 天,因为有些异常具备以“周”为频次发生的特点
应用中的扩展日志(如打点、临时监控、访问日志等)命名方式:appName_logType_logName.log
对 trace/debug/info 级别的日志输出需要条件输出形式
避免重复打印日志,浪费磁盘空间,务必在 log4j.xml 中设置 additivity=false
异常信息应该包括两类信息:案发现场信息和异常堆栈信息。logger.error(各类参数或者对象 toString + "_" + e.getMessage(), e);
记录日志需谨慎,不要输出无用日志。
Mysql规约
建表规约
表达是与否概念的字段, 必须使用 is_xxx 的方式命名, 数据类型是 unsigned tinyint。
表名、字段名必须使用小写字母或数字:(getter_admin,task_config,level3_name)
表名不使用复数名词,禁止使用保留字,唯一索引名为 uk_字段名;普通索引名则为 idx_字段名。
表必备三字段:id, gmt_create, gmt_modified。
表的命名最好是加上“业务名称_表的作用”,比如(tiger_task,mpp_config)
字段允许适当冗余,以提高性能,但是必须考虑数据同步的情况。冗余字段应遵循:
1)不是频繁修改的字段。
2)不是 varchar 超长字段,更不能是 text 字段。
单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表。
索引规约
业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引
超过三个表禁止 join。 需要 join 的字段, 数据类型保持绝对一致; 多表关联查询时,保证被关联的字段需要有索引
在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度
Sql规约
不要使用 count(列名)或 count(常量)来替代 count()。count()会统计值为 NULL 的行,而 count(列名)不会统计此列为 NULL 值的行
count(distinct col) 计算该列除 NULL 之外的不重复数量。注意 count(distinctcol1, col2) 如果其中一列全为 NULL,那么即使另一列有不同的值,也返回为 0
使用 ISNULL() 来判断是否为 NULL 值
1) NULL<>NULL 的返回结果是 NULL ,不是 false 。
2) NULL=NULL 的返回结果是 NULL ,不是 true 。
3) NULL<>1 的返回结果是 NULL ,而不是 true
写代码分页count为0直接返回,不执行分页逻辑
不得使用外键与级联,一切外键概念必须在应用层解决。
禁止使用存储过程,存储过程难以调试和扩展,更没有移植性
idb删除修改记录先select,避免误删,确认之后删除。in操作避免,即使用in的数量在1000以下。
ORM规约
POJO 类的 boolean 属性不能加 is,而数据库字段必须加 is_,要求在 resultMap 中进行字段与属性之间的映射
xml 配置中参数注意使用:#{},#param# 不要使用${} 此种方式容易出现 SQL 注入。(重要)
更新数据表记录时,必须同时更新记录对应的 gmt_modified 字段值为当前时间
不要写一个大而全的数据更新接口,传入为 POJO 类,不管是不是自己的目标都更新字段。
工程规约
应用分层
推荐
Service 层:相对具体的业务逻辑服务层。
Manager 层:通用业务处理层,它有如下特征:
1) 对第三方平台封装的层,预处理返回结果及转化异常信息;
2) 对 Service 层通用能力的下沉,如缓存方案、中间件通用处理;
3) 与 DAO 层交互,对 DAO 的业务通用能力的封装。
服务器规约
给 JVM 设置-XX:+HeapDumpOnOutOfMemoryError 参数, 让 JVM 碰到 OOM 场景时输出 dump信息。
安全规约
可被用户直接访问的功能必须进行权限控制校验。
用户敏感数据禁止直接展示,必须对展示数据脱敏(手机号,身份证等需要隐去几位)。
用户输入的 SQL 参数严格使用参数绑定或者 METADATA 字段值限定,防止 SQL 注入,禁止字符串拼接 SQL 访问数据库
用户请求传入的任何参数必须做有效性验证。
page size 过大导致内存溢出
恶意 order by 导致数据库慢查询
正则输入源串拒绝服务 ReDOS
任意重定向
SQL 注入
Shell 注入
反序列化注入