5. 阿里巴巴程序员开发手册黄山版
学习的动力不止于此:
最近阅读了刘典武大神推荐的阿里巴巴Java开发手册泰山版。里面的很多规范不仅仅适用于Java。非常值得我们一起学习。
于是,我找了最新的版本–黄山版。
正所谓,五岳归来不看山,黄山归来不看岳。最新版本——黄山下载地址在文章末尾。
它是以Java开发者为中心视角,划分为编程规约、异常日志、单元测试、安全规约、MySQL 数据库、工程结构、设计规约七个维度。
我就整理一些我目前用到的,推荐各位同学好好阅读,必有收获。
一、编程规约
-
所有编程相关的命名不能以下划线或美元符号开始或结尾。反例:_name / name_ / $first /first$
-
严禁使用中英文混合的方式,不能直接使用中文的方式,杜绝完全不规范的英文缩写
-
代码和注释中避免使用种族歧视性或侮辱性词语,例如blacklist/whitelist。改为blocklist/allowlist
-
类名使用大驼峰例如ForceCode ,方法名/参数名/成员变量/局部变量统一使用小驼峰例如:localValue
-
常量名全部大写,单词用下划线分割。不要嫌弃名字长。
-
抽象类命名使用Abstract或者Base开头,异常类用Exception结尾,测试类以Test结尾
-
类型与中括号紧挨相连来定义数组如String args[]
-
避免在子父类的成员变量之间或不同代码块的局部变量间采用完全相同的命名。
-
在常量与变量命名时,表示类型的名词放词尾,以提升辨识度。如 nameList,TERMINATED_THREAD_COUNT
-
接口类中的方法和属性不要加任何修饰符号,public也不要加,保持代码的简洁性,并加上相应的注释。
-
枚举类名带上Enum后缀,枚举成员名称需全部大写,单词间用下划线隔开。
-
获取单个对象的方法用get做前缀,多个则用list做前缀,复数结尾,如listObjects
-
获取统计值方法用count做前缀,插入的方法用save/insert做前缀
-
删除的方法用remove/delete做前缀,修改方法用update做前缀
-
不允许用魔法值(即未定义的常量)如if (1= =education){…} 这里的1不知道代表什么意思?替换成
static final Integer UNIVERSITY_EDUCATION = 1;if (UNIVERSITY_EDUCATION==education){...}
-
long或Long赋值时,数值后使用大写L,不能为小写l,小写容易跟数值1混淆。
-
浮点数类型后缀统一为大写的D或F
-
不要使用一个常量类维护所有常量,要按常量功能进行归类,分开维护。
有五层:跨应用共享常量、应用内共享常量、子工程内共享常量、包内共享常量、类内共享常量。 -
如果大括号内为空,简洁地写成{}即可。若不为空则左大括号前不换行,后换行,右大括号前换行,若有else则不换行。若终止必换行
-
左小括号和右相连字符不需空格,右小括号与左字符不需空格。左大前需加空格。
-
if/for/while/switch/do 保留字与左右括号间必须加空格
-
任何二目、三目的左右两边需加一个空格。采用4个空格缩进,禁止tab字符。如使用 Tab 缩进,必须设置 1 个 Tab 为 4 个空格。
-
注释的双斜线与注释内容间有且仅有一个空格,在进行类型强制转换时,右括号与强制转换值间不需任何空格隔开。
-
单行字符数限制不超过120个,超出需换行,第二行相对第一行缩进4个空格,从第三行开始不再继续缩进,运算符与下文一起换行,方法调用的点符合一起换行,方法调用中多个参数时在逗号后进行,在括号前不要换行。
-
方法在定义与传入时,多个参数逗号后必须加空格。 IDE中的文件编码为UTF-8,换行符用Unix,不要使用windows格式
-
避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名访问即可。
-
所有的覆写方法,必须加@Override 注解。
-
相同的参数类型,相同的业务含义,才可以使用可变参数(尽量不用可变参数编程),参数类型避免定义为Object。
-
任何货币金额,均以最小货币单位且为整型类型进行存储。浮点数之间的等值判断,基本数据类型不能使用 == 进行比较,包装数据类型不能使用 equals进行判断。浮点数采用“尾数+阶码”的编码方式,类似于科学计数法的“有效数字+指数”的表示方式。二进制无法精确表示大部分的十进制小数。
-
指定一个误差范围,两个浮点数的差值在此范围之内,则认为是相等的。或者使用BigDecimal 来定义值,再进行浮点数的运算操作。BigDecimal 的等值比较应使用 compareTo() 方法,而不是 equals() 方法。equals() 方法会比较值和精度(1.0 与 1.00 返回结果为 false),而 compareTo() 则会忽略精度。
-
构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init 方法中。当一个类有多个构造方法,或者多个同名方法,这些方法应该按顺序放置在一起类内方法定义的顺序依次是:公有方法或保护方法 > 私有方法 > getter / setter 方法。说明:公有方法是类的调用者和维护者最关心的方法,首屏展示最好;保护方法虽然只是子类关心,也可能是“模板设计模式”下的核心方法;而私有方法外部一般不需要特别关心,是一个黑盒实现;因为承载的信息价值较低,所有Service 和 DAO 的 getter / setter 方法放在类体最后。
-
final 可以声明类、成员变量、方法、以及本地变量,下列情况使用 final 关键字:
1)不允许被继承的类,如:String 类。
2)不允许修改引用的域对象,如:POJO 类的域变量。
3)不允许被覆写的方法,如:POJO 类的 setter 方法。
4)不允许运行过程中重新赋值的局部变量。
5)避免上下文重复使用一个变量,使用 final 关键字可以强制重新定义一个变量,方便更好地进行重构。 -
禁止在程序中写死一年为 365 天,避免在公历闰年时出现日期转换错误或程序逻辑错误。
避免公历闰年 2 月问题。闰年的 2 月份有 29 天,一年后的那一天不可能是 2 月 29 日。 -
判断所有集合内部的元素是否为空,使用 isEmpty() 方法(时间复杂度更低可读写好),而不是 size() == 0 的方式。高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁。在使用阻塞等待获取锁的方式中,必须在 try 代码块之外,并且在加锁方法与 try 代码块之间没有任何可能抛出异常的方法调用,避免加锁成功后,在 finally 中无法解锁。
-
在使用尝试机制来获取锁的方式中,进入业务代码块之前,必须先判断当前线程是否持有锁。锁的释放规则与锁的阻塞等待方式相同。并发修改同一记录时,避免更新丢失,需要加锁。要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观锁,使用 version 作为更新依据。
-
在一个 switch 块内,每个 case 要么通过 continue / break / return 等来终止,要么注释说明
程序将继续执行到哪一个 case 为止;在一个 switch 块内,都必须包含一个 default 语句并且放在最后,即使它什么代码也没有。在 if / else / for / while / do 语句中必须使用大括号。 -
三目运算符 condition ? 表达式 1:表达式 2 中,高度注意表达式 1 和 2 在类型对齐时,可能抛出因自动拆箱导致的 NPE 异常。在高并发场景中,避免使用“等于”判断作为中断或退出的条件。
-
类、类属性、类方法的注释必须使用 Javadoc 规范,使用 /** 内容 */ 格式,不得使用 // xxx
方式。所有的抽象方法(包括接口中的方法)必须要用 Javadoc 注释、除了返回值、参数异常说明外,还必须指出该方法做什么事情,实现什么功能。所有的类都必须添加创建者和创建日期。方法内部单行注释,在被注释语句上方另起一行,使用 // 注释。方法内部多行注释使用 /* */注释,注意与代码对齐。 -
所有的枚举类型字段必须要有注释,说明每个数据项的用途。与其用半吊子英文来注释,不如用中文注释说清楚。专有名词与关键字保持英文原文即可。代码修改的同时,注释也要进行相应的修改,尤其是参数、返回值、异常、核心逻辑等。在类中删除未使用的任何字段和方法、内部类;在方法中删除未使用的参数声明与内部变量。谨慎注释掉代码。在上方详细说明,而不是简单地注释掉。如果无用,则删除。对于注释的要求:第一、能够准确反映设计思想和代码逻辑;第二、能够描述业务含义,使别的程序员能够迅速了解到代码背后的信息。完全没有注释的大段代码对于阅读者形同天书,注释是给自己看的,即使隔很长时间,也能清晰理解当时的思路;注释也是给继任者看的,使其能够快速接替自己的工作。
-
好的命名、代码结构是自解释的,注释力求精简准确、表达到位。避免出现注释的另一个极端:过多过滥的注释,代码的逻辑一旦修改,修改注释又是相当大的负担。特殊注释标记,请注明标记人与标记时间。注意及时处理这些标记,通过标记扫描,经常清理此类标记。
-
前后端交互的 API,需要明确协议、域名、路径、请求方法、请求内容、状态码、响应体。前后端数据列表相关的接口返回,如果为空,则返回空数组[]或空集合{}。服务端发生错误时,返回给前端的响应信息必须包含 HTTP 状态码,errorCode、errorMessage、用户提示信息四个部分。前后端交互的 JSON 格式数据中,所有的 key 必须为小写字母开始的 lowerCamelCase风格,符合英文表达习惯,且表意完整。errorMessage 是前后端错误追踪机制的体现,可以在前端输出到 type=“hidden” 文字类控件中,或者用户端的日志中,帮助我们快速地定位出问题。
-
对于需要使用超大整数的场景,服务端一律使用 String 字符串类型返回,禁止使用 Long 类型。Java 服务端如果直接返回 Long 整型数据给前端,Javascript 会自动转换为 Number 类型(HTTP 请求通过 URL 传递参数时,不能超过 2048 字节。HTTP 请求通过 body 传递内容时,必须控制长度,超出最大长度后,后端解析会出错。
-
在翻页场景中,用户输入参数的小于 1,则前端返回第一页参数给后端;后端发现用户输入的参数大于总页数,直接返回最后一页。服务器内部重定向必须使用 forward;外部重定向地址必须使用 URL 统一代理模块生成,否则会因线上采用 HTTPS 协议而导致浏览器提示“不安全”,并且还会带来 URL 维护不一致的问题。
-
在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度。枚举 enum(括号内)的属性字段必须是私有且不可变。任何数据结构的构造或初始化,都应指定大小,避免数据结构无限增长吃光内存。及时清理不再使用的代码段或配置信息。
-
好的单元测试必须遵守 AIR 原则,单元测试在线上运行时,感觉像空气(AIR)一样感觉不到,但在测试质量的保障上,却是非常关键的。好的单元测试宏观上来说,具有自动化、独立性、可重复执行的特点。
A:Automatic(自动化)
I:Independent(独立性)
R:Repeatable(可重复) -
单元测试应该是全自动执行的,并且非交互式的。输出结果需要人工检查的测试不是一个好的单元测试。不准使用 System.out 来进行人肉验证,单元测试必须使用 assert 来验证。保持单元测试的独立性。为了保证单元测试稳定可靠且便于维护,单元测试用例之间决不能互相调用,也不能依赖执行的先后次序。单元测试是可以重复执行的,不能受到外界环境的影响。核心业务、核心应用、核心模块的增量代码确保单元测试通过。
-
编写单元测试代码遵守 BCDE 原则,以保证被测试模块的交付质量。
B:Border,边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等。
C:Correct,正确的输入,并得到预期的结果。
D:Design,与设计文档相结合,来编写单元测试。
E:Error,强制错误信息输入(如:非法数据、异常流程、业务允许外等),并得到预期的结果。 -
用户输入的 SQL 参数严格使用参数绑定或者 METADATA 字段值限定,防止 SQL 注入,禁止字符串拼接 SQL 访问数据库。隶属于用户个人的页面或者功能必须进行权限控制校验。用户敏感数据禁止直接展示,必须对展示数据进行脱敏。用户请求传入的任何参数必须做有效性验证。禁止向 HTML 页面输出未经安全过滤或未正确转义的用户数据。对于文件上传功能,需要对于文件大小、类型进行严格检查和控制。
-
表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint(1 表示是,0 表示否)。表名、字段名必须使用小写字母或数字,禁止出现数字开头禁止两个下划线中间只出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。表名不使用复数名词。禁用保留字,如 desc、range、match、delayed 等。
-
主键索引名为 pk_字段名;唯一索引名为 uk_字段名;普通索引名则为 idx_字段名。小数类型为 decimal,禁止使用 float 和 double。如果存储的字符串长度几乎相等,使用 char 定长字符串类型。varchar 是可变长字符串,不预先分配存储空间,长度不要超过 5000,如果存储长度大于此值,定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其它字段索引率。
-
表必备三字段:id,create_time,update_time。id 必为主键,类型为 bigint unsigned、单表时自增、步长为 1。create_time,update_time 的类型均为datetime 类型,前者现在时表示主动式创建,后者过去分词表示被动式更新。表的命名最好是遵循“业务名称_表的作用”。库名与应用名称尽量一致。如果修改字段含义或对字段表示的状态追加时,需要及时更新字段注释。字段允许适当冗余,以提高查询性能,但必须考虑数据一致。单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表。合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度。
-
业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引。超过三个表禁止 join。需要 join 的字段,数据类型保持绝对一致;多表关联查询时,保证被关联的字段需要有索引。在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度。页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。
-
创建索引时避免有如下极端误解:
1)索引宁滥勿缺。认为一个查询就需要建一个索引。
2)吝啬索引的创建。认为索引会消耗空间、严重拖慢记录的更新以及行的新增速度。
3)抵制唯一索引。认为唯一索引一律需要在应用层通过“先查后插”方式解决。 -
不要使用 count(列名) 或 count(常量) 来替代 count(),count() 是 SQL92 定义的标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。count(distinct col) 计算该列除 NULL 之外的不重复行数,注意 count(distinct col1 , col2) 如果其中一列全为 NULL,那么即使另一列有不同的值,也返回为 0
-
当某一列的值全是 NULL 时,count(col) 的返回结果为 0;但 sum(col) 的返回结果为 NULL,因此使用 sum() 时需注意 NPE 问题。
使用 ISNULL() 来判断是否为 NULL 值,NULL 与任何值的直接比较都为 NULL。
1)NULL<>NULL 的返回结果是 NULL,而不是 false。
2)NULL=NULL 的返回结果是 NULL,而不是 true。
3)NULL<>1 的返回结果是 NULL,而不是 true。 -
代码中写分页查询逻辑时,若 count 为 0 应直接返回,避免执行后面的分页语句。不得使用外键与级联,一切外键概念必须在应用层解决。禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。数据订正(特别是删除或修改记录操作)时,要先 select,避免出现误删除的情况,确认无误才能执行更新语句。
-
对于数据库中表记录的查询和变更,只要涉及多个表,都需要在列名前加表的别名(或表名)进行限定。在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。不允许直接拿 HashMap 与 Hashtable 作为查询结果集的输出。