阿里Java开发手册(自我总结版一)
又一次阅读阿里编程规范,发现有不少条目又给忘了,离上次阅读大概一个多月。我的阅读习惯应该还不错,每次看书籍之类的不会只看一遍,因为之前的经验发现,只看一遍的内容,会特别快的遗忘,在脑子里只会留下淡淡的印象,知道看过哪块的内容,但详细的内容就模糊了,面试时经历过这样尴尬的场面...
现在我看书,通常是第一遍通读,了解大概;第二遍细度核心知识点,强化理解核心内容;然后在脑中回思书中的核心内容,有不明白的地方就再翻书看一遍,做到理解,转化为自己的知识,效果来看还不错,你也可以试下!
但发现在阿里编程规范这里还是会有问题,条目太多,按照顺序记忆可以做到一段时间内不忘记,但很多条目是日常编程不会用到的,时间稍长就会出现遗忘的情形,在编程的过程中万一不小心踩了雷就有点伤,所以尝试从自己思考问题的角度重新组织日常编码需要的注意事项,争取转化为编程时的本能_
一、环境准备
检查下自己的IDE设置,确保设置为以下内容,以mac eclipse为例。
- IDE的text file encoding设置为 UTF-8;IDE中文件的换行符使用Unix格式,不要使用 Windows 格式(Eclipse -> 偏好设置 -> General -> WorkSpace -> 设置Text file encoding 和New text file line delimiter)。
- 采用4个空格缩进,禁止使用tab字符,如果使用tab缩进,必须设置1个tab为4个空格(Eclipse -> 偏好设置 -> General -> Editors -> Text Editors -> 设置Displayed tab width为4 -> 勾选Insert spaces for tabs)。
- Code Templates设置:为了统一编码规范,通常团队会有一个通用的Code Templates,问下同事,让他们导一份codetemplates.xml给你,如果你需要自己构建一个,百度一下吧,这里不再展开了。
- 安装阿里云代码规范插件,历史代码或新项目里面,通过插件扫码,可以发现很多不规范的点,个人认为是java开发必装插件之一(eclipse插件中文安装手册);
二、命名
通用约束
- 代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。
- 代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。
- 杜绝完全不规范的缩写,避免望文不知义。反例:AbstractClass“缩写”命名成 AbsClass。
- 为了达到代码自解释的目标,任何自定义编程元素在命名时,使用尽量完整的单词组合来表达其意,如从远程仓库拉取代码的类命名为 PullCodeFromRemoteRepository。
- 如果模块、接口、类、方法使用了设计模式,在命名时体现出具体模式。
在日常开发中常见的涉及命名的点有:项目名称、包名、类名、方法名、变量名、数据库名、表名、字段名、索引名称、URL命名、文件名。
1. 项目名称
小写字母或数字,不能使用数字开头,多个单词之间使用中划线连接,能准确的表达出项目的核心作用,如code-generator。(个人推荐)
2. 包名
- 包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式。
- 如果表示版本关系等特殊情况,可以使用数字,如org.apache.commons.lang3.StringUtils
3. 类名
- 类名使用 UpperCamelCase 风格,必须遵从驼峰形式,但以下情形例外:DO / BO / DTO / VO / AO。
- 抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类命名以它要测试的类的名称开始,以 Test 结尾。
- 对于 Service 和 DAO 类,基于 SOA 的理念,暴露出来的服务一定是接口,内部的实现类用 Impl 的后缀与接口区别。
- 枚举类名建议带上 Enum 后缀,枚举成员名称需要全大写,单词间用下划线隔开,如:枚举名字为ProcessStatusEnum的成员名称:SUCCESS / UNKOWN_REASON。
- 禁止使用非字母字符,特殊情况下可以使用2、4等数字。
4. 属性名
- 使用 lowerCamelCase 风格,必须遵从驼峰形式。
- POJO类中布尔类型的变量,都不要加 is,否则部分框架解析会引起序列化错误,如标记删除字段用deleted而不是isDeleted。
- 尽量不要使用非字母字符
5. 方法名
- 使用 lowerCamelCase 风格,必须遵从驼峰形式;
- Service/DAO层方法命名规约
- 获取单个对象的方法用get做前缀。
- 获取多个对象的方法用list做前缀。
- 获取统计值的方法用count做前缀。
- 插入的方法用save/insert做前缀。
- 删除的方法用remove/delete做前缀。
- 修改的方法用update做前缀。
- 尽量不要使用非字母字符,如数字,下划线,中划线等。
6. 参数名、变量名
使用 lowerCamelCase 风格,必须遵从驼峰形式。
7. 常量
- 常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。
- 不允许任何魔法值(即未经定义的常量)直接出现在代码中。反例:String key = "Id#taobao_" + tradeId;
8. 数据库名
尽量和项目名称保持一致,如出现特殊情况按照项目名称命名规则进行命名。
9. 表名
- 必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只 出现数字,不允许出现任何大写字母,建议将类名内不同单词用下划线分割作为表名。
- 表名不使用复数名词,表名应该仅仅表示表里面的实体内容,不应该表示实体数量,对应于 DO 类名也是单数 形式,符合表达习惯。
- 表的命名可以考虑加上“业务名称_表的作用”,但需要规范统一命名,不要多种方式混合使用,如果表数据不是特别大,没有必要使用。
- 禁用保留字,如 desc、range、match、delayed 等,请参考 MySQL 官方保留字。
10. 字段名
- 必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只 出现数字,不允许出现任何大写字母,建议将属性名内不同单词用下划线分割作为字段名。
- 禁用保留字,如 desc、range、match、delayed 等,请参考 MySQL 官方保留字。
- 表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint ( 1表示是,0表示否)。如标记删除字段deleted对应的字段名为is_deleted。
11. 索引名
- 主键索引名为 pk_字段名;
- 唯一索引名为 uk_字段名;
- 普通索引名则为 idx_字段名。
12. 文件名
必须使用小写字母或数字,禁止数字开头,多个单词使用中划线连接,涉及的内容有:js文件、图片、css文件、jsp页面等(个人推荐)。
13. URL命名
- 必须使用小写字母或数字,禁止数字开头,多个单词使用中划线连接。
- 限制URL层级不要太深(个人感觉3层以内较好)。
三、代码风格
代码风格实际上就是合理使用缩进、空格、换行,目的是让代码可读性更强。大家记忆时记清楚用什么样的缩进,哪些时候使用空格,哪些时候使用换行,单行代码不要太长就可以,这部分内容通过两三次的回顾很容易养成习惯,因为是编码中最长用到的。
可参考以下代码:
public static void main(String[] args) {
// 缩进 4 个空格
String say = "hello";
// 运算符的左右必须有一个空格
int flag = 0;
// 关键词if/for/while/switch/do与括号之间必须有一个空格,括号内的 flag 与左括号,0 与右括号不需要空格
if (flag == 0) {
System.out.println(say);
}
// 左大括号前加空格且不换行;左大括号后换行
if (flag == 1) {
System.out.println("world");
// 右大括号前换行,右大括号后有 else,不用换行
} else {
System.out.println("ok");
// 在右大括号后直接结束,则必须换行
}
StringBuffer sb = new StringBuffer();
// 超过120个字符的情况下,换行缩进4个空格,点号和方法名称一起换行
sb.append("zi").append("xin")...
.append("huang")...
.append("huang")...
.append("huang");
// 任何二目、三目运算符的左右两边都需要加一个空格
int result = flag == 0 ? 1 : 0;
}
下边是另外几点需要注意的:
- 注释的双斜线与注释内容之间有且仅有一个空格。
- 方法参数在定义和传入时,多个参数逗号后边必须加空格,如method("a", "b")。
- 没有必要增加若干空格来使某一行的字符与上一行对应位置的字符对齐。
- 方法体内的执行语句组、变量的定义语句组、不同的业务逻辑之间或者不同的语义 之间插入一个空行。相同业务逻辑和语义之间不需要插入空行。没有必要插入多个空行进行隔开。
四、注释
1. 项目
项目建议提供注释,说明项目的作用,核心逻辑或需要注意的特别事项,方便其他人快速对项目形成整体了解。添加方式有两种:
- 在根目录下创建README.md或README.txt
- 在根目录下创建doc文件夹,在其创建README.md或README.txt,还可以在该目录下存储一些初始化语句、索引创建语句等内容
2. 包
说明该包的核心作用,注释可在package-info.java内设置,建议在核心包上有选择添加。
3. 类
所有类都需要添加注释,类注释采用javadoc规范的方式( /** */ ),注释包括:该类的主要作用及使用时的一些注意事项、创建者和创建日期,可通过codetemplate来统一格式。
4. 类方法
类方法注释采用javadoc规范的方式( /** */ ),注释包括:该方法的作用、使用注意事项、参数、返回值、抛出异常信息,可通过codetemplate来统一格式,添加注释时可以模仿jdk内方法的注释。
如果注释的工作量太大,对于私有方法,可以简化注释,做到能清晰说明其作用即可。但是所有的抽象方法(包括接口中的方法)的注释必须严格的按照javadoc规范进行添加,需要额外指出对子类的实现要求,或者调用注意事项。
当然也会有例外情况,如controller内使用swagger2提供在线文档,那就没必要再提供javadoc注释了。
5. 类属性
所有的类属性都需要添加注释,所有可用通过类名.属性、对象实例.属性调用的类属性都应该使用javadoc进行注释,如常量、枚举类型字段;POJO类的属性个人感觉使用行内注释会更合适一些,普通实例变量建议使用javadoc方式。
6. 方法内
方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释使用/* */注释,注意与代码对齐。
可以这样记忆// 和 /* */只在方法内部和POJO内使用,其他时候都使用javadoc方式的注释。
7. 其他建议
- 注释使用中文,专有名词与关键字保持英文原文即可。
- 代码修改的同时,注释也要进行相应的修改。
- 如果一段代码需要注释掉,在上方详细说明;如果代码已无用,直接删除更好。
- TODO(待办事宜),FIXME(错误,不能工作)这类特殊注释标记,添加时留下添加人、时间、预计处理时间,并及时处理,不要写了就不管了。
对于注释的要求:第一、能够准确反应设计思想和代码逻辑;第二、能够描述业务含义,使别的程序员能够迅速了解到代码背后的信息。完全没有注释的大段代码对于阅读者形同天书,注释是给自己看的,即使隔很长时间,也能清晰理解当时的思路;注释也是给继任者看的,使其能够快速接替自己的工作。
额外的,好的命名、代码结构是自解释的,注释力求精简准确、表达到位。避免出现注释的一个极端:过多过滥的注释,代码的逻辑一旦修改,修改注释是相当大的负担。
五、日志
1.日志配置
主要有日志文件名称、日志输出级别、日志是否向上层传递、日志保留时间。
- 日志文件推荐至少保存15天,因为有些异常具备以“周”为频次发生的特点。
- 避免重复打印日志,浪费磁盘空间,务必在 log4j.xml 中设置 additivity=false
- 谨慎地记录日志。生产环境禁止输出 debug 日志;有选择地输出 info 日志;如果使 用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘 撑爆,并记得及时删除这些观察日志。
- 应用中的扩展日志(如打点、临时监控、访问日志等)命名方式:appName_logType_logName.log。logType:日志类型,推荐分类有stats/desc/monitor/visit 等;logName:日志描述。这种命名的好处:通过文件名就可知 道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找。
2.日志使用
- 应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架SLF4J 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
- 对 trace/debug/info 级别的日志输出,必须使用条件输出形式或者使用占位符的方式。
- 异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过 关键字 throws 往上抛出。推荐
logger.error(各类参数或者对象toString + "_" + e.getMessage(), e);
这种格式,不要简单的输出e.getMessage(),那样具体的错误信息都被隐藏,很不利于排查问题。 - 注意日志输出级别和使用场景,避免大量调试日志被生成,最近就遇到过一次在高频接口输出调试信息,忘记清理,在调用高峰拖死了tomcat;在生成环境,尽量只记录异常信息和较重要的业务信息,且控制其量。
- while、for循环等内部的异常需要特别注意发生异常的条件,曾经遇到过while循环从redis队列内取值(事件),redis服务宕机,结果异常日志刷爆磁盘的情况。
六、异常处理
主要涉及什么情况下添加异常处理,捕获异常后的处理方式,finally内注意事项及其它几点建议。
1. 异常捕获
- 不要捕获IndexOutOfBoundsException、NullPointerException等运行时异常,这类异常可以通过条件判断避免;也不要使用异常来做流程控制,异常的处理效率比条件分支低;
- 不要对大段代码进行try-catch,只在可能发生异常且需要对异常处理的场景有针对性的进行try-catch捕获异常;
2. 异常处理
- 捕获异常后严禁什么也不做或者直接e.printStackTrace()输出信息至控制台;如果不想处理,将该异常抛给上层,最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容;
- 在代码中使用“抛异常”还是“返回错误码”,对于公司外的 http/api 开放接口必须 使用“错误码”;而应用内部推荐异常抛出;跨应用间 RPC 调用优先考虑使用 Result 方式,封 装 isSuccess()方法、“错误码”、“错误简短信息”。
- 定义时区分unchecked/checked 异常,避免直接抛出newRuntimeException(), 更不允许抛出 Exception 或者 Throwable,应使用有业务含义的自定义异常。
3. finally使用
- 在finally块中进行资源回收(关闭资源对象、流对象)时,有异常也要做try-catch
- finally块中不能使用return,finally块中的Return返回后方法结束执行,不会执行try-catch块中的return语句;
4. 其它建议
- 有 try 块放到了事务代码中,catch 异常后,如果需要回滚事务,一定要注意手动回 滚事务。
- 防止 NPE,是程序员的基本修养,注意 NPE 产生的场景;
七、单元测试
1. 单元测试要求
- 单元测试代码必须写在如下工程目录:src/test/java,不允许写在业务代码目录下
- 单元测试应该是有测试框架定时自动执行的,不需要人工参与;单元测试用例之间不能互相调用,也不能依赖执行的先后次序;单元测试时可以重复执行的,不能受到外界环境的影响。
- 单元测试通常是方法级别,至多类级别。
- 单元测试作为一种质量保障手段,不建议项目发布后补充单元测试用例,建议在项目提测前完成单元测试。核心业务、核心应用、核心模块的增量代码确保单元测试通过.
- 在设计评审阶段,开发人员需要和测试人员一起确定单元测试范围,单元测试最好覆盖所有测试用例(UC)。
- 单元测试的基本目标:语句覆盖率达到 70%;核心模块的语句覆盖率和分支覆盖率都 要达到 100%
2. 单元测试实现建议
- 对于数据库相关的查询,更新,删除等操作,不能假设数据库里的数据是存在的,或者直接操作数据库把数据插入进去,请使用程序插入或者导入数据的方式来准备数据。
- 和数据库相关的单元测试,可以设定自动回滚机制,不给数据库造成脏数据。或者对单元测试产生的数据有明确的前后缀标识。
- 对于不可测的代码建议做必要的重构,使代码变得可测,避免为了达到测试要求而 书写不规范测试代码。
- 编写单元测试代码遵守 BCDE 原则,以保证被测试模块的交付质量。
1)B:Border,边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等。
2)C:Correct,正确的输入,并得到预期的结果。
3)D:Design,与设计文档相结合,来编写单元测试。
4)E:Error,强制错误信息输入(如:非法数据、异常流程、非业务允许输入等),并得 到预期的结果。
ps:如何写出合格的单元测试代码我也有点蒙,目前是核心service代码会写单元测试进行功能验证,有相关经验的朋友希望能给点建议。
八、安全规约
1. 操作权限
隶属于用户个人的页面或者功能必须进行权限控制校验。防止没有做水平权限校验就可随意访问、修改、删除别人的数据,比如查看他人的私信 内容、修改他人的订单。
2. XSS
跨站脚本攻击(Cross Site Scripting),可以对用户录入内容进行白名单过滤,去除非法信息。禁止向 HTML 页面输出未经安全过滤或未正确转义的用户数据。
3. CSRF
CSRF(Cross-site request forgery)跨站请求伪造,是一类常见编程漏洞。对于存在 CSRF 漏洞的应用/网站,攻击者可以事先构造好URL,只要受害者用户一访问,后台便在用户不知情情况下对数据库中用户参数进行相应修改。表单、AJAX提交必须执行 CSRF 安全过滤,通常可以通过额外的token字段来实现。
4. SQL注入
用户输入的 SQL 参数严格使用参数绑定或者 METADATA 字段值限定,防止 SQL 注入,禁止字符串拼接 SQL 访问数据库。
5. 用户请求参数有效性验证
忽略参数校验可能导致:
- page size 过大导致内存溢出
- 恶意 order by 导致数据库慢查询
- 任意重定向
- SQL 注入
- 反序列化注入
- 正则输入源串拒绝服务 ReDoS,Java 代码用正则来验证客户端的输入,有些正则写法验证普通用户输入没有问题,但是如果攻击人员使用的是特殊构造的字符串来验证,有可能导致死循环的结果。
6. 防重放限制
在使用平台资源,譬如短信、邮件、电话、下单、支付,必须实现正确的防重放限制, 如数量限制、疲劳度控制、验证码校验,避免被滥刷、资损。 如注册时发送验证码到手机,如果没有限制次数和频率,那么可以利用此功能骚扰到其它用户,并造成短信平台资源浪费(曾经阿里云的短信被一晚上刷了几百块的...)。
7. 防刷
发贴、评论、发送即时消息等用户生成内容的场景必须实现防刷、文本内容违禁词过滤等风控策略,敏感词过滤可以考虑阿里云的绿网服务。
8. 信息脱敏
用户敏感数据禁止直接展示,必须对展示数据进行脱敏。 查看个人手机号码会显示成:158****9119,隐藏中间 4 位,防止隐私泄露。
MySQL和编程实践(集合、并发处理)等部分后续另起一篇文章来写。
仍建议先看几遍《阿里巴巴Java开发手册(终极版)》,然后再以自己的方式理解记忆。