《Java 开发手册》是阿里巴巴集团技术团队的集体智慧结晶和经验总结,经历了多次大规模 一线实战的检验及不断完善,公开到业界后,众多社区开发者踊跃参与,共同打磨完善,系统化地 整理成册。现代软件行业的高速发展对开发者的综合素质要求越来越高,因为不仅是编程知识点, 其它维度的知识点也会影响到软件的最终交付质量。
比如:数据库的表结构和索引设计缺陷可能带 来软件上的架构缺陷或性能风险;工程结构混乱导致后续维护艰难;没有鉴权的漏洞代码易被黑客 攻击等等。所以本手册以 Java 开发者为中心视角,划分为编程规约、异常日志、单元测试、安全规 约、MySQL 数据库、工程结构、设计规约七个维度,再根据内容特征,细分成若干二级子目录。
另外,依据约束力强弱及故障敏感性,规约依次分为强制、推荐、参考三大类。在延伸信息中, “说明”对规约做了适当扩展和解释;“正例”提倡什么样的编码和实现方式;“反例”说明需要 提防的雷区,以及真实的错误案例。
一、环境准备
检查下自己的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.StringUtils3. 类名
类名使用 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 *** = new StringBuffer(); // 超过120个字符的情况下,换行缩进4个空格,点号和 *** 名称一起换行 ***.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-catchfinally块中不能使用return,finally块中的Return返回后 *** 结束执行,不会执行try-catch块中的return语句;4. 其它建议
有 try 块放到了事务代码中,catch 异常后,如果需要回滚事务,一定要注意手动回 滚事务。防止 NPE,是程序员的基本修养,注意 NPE 产生的场景;