阿里巴巴Java开发规范
一、编程规约
(一) 命名风格
1.【强制】类名使用UpperCamelCase 风格,但以下情形例外:DO / BO / DTO / VO / AO /
PO / UID 等。
正例 :MarcoPolo / UserDO / XmlService / TcpUdpDeal / TaPromotion
反例:macroPolo / UserDo / XMLService / TCPUDPDeal / TAPromotion
- DO (Data Object):用于表示领域模型中的数据对象,通常包含业务相关的属性和行为。例如,
CustomerDO
,ProductDO
。- BO (Business Object):用于封装业务逻辑和规则的类,可能包含对多个DO的操作。例如,
OrderBO
,InvoiceBO
。- DTO (Data Transfer Object):主要用于在不同层(如表现层与业务层、服务层之间)传递数据的简单对象,只包含数据成员及简单的getter/setter方法。例如,
UserDTO
,AddressDTO
。- VO (Value Object):在领域驱动设计(DDD)中,用于表示一个不变的、具有语义概念的对象,其属性值共同定义了对象的完整状态,如
ColorVO
,MoneyVO
。- AO (Application Object):在某些架构中,用于表示应用程序层的实体,负责协调业务逻辑和数据访问。例如,
UserAO
,RoleAO
。- PO (Plain Old Object):通常指那些没有继承任何特殊框架或库提供的基类、未实现特定接口、未被注解标记、不含有任何门面方法(如getter/setter以外的方法)的简单对象。例如,
CustomerPO
,ItemPO
。- UID (Unique Identifier):用于表示唯一标识符的类或接口,如全局唯一ID生成器
UUIDGenerator
或特定类型ID的类UserId
,OrderId
。
2. 【强制】常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。
正例:MAX_FILE_SIZE
反例:maxFileSize
3. 【强制】抽象类命名使用 Abstract 或Base 开头;异常类命名使用 Exception 结尾;测试类命名以它要测试的类的名称开始,以 Test 结尾。
-
抽象类命名使用
Abstract
或Base
开头 :这个规则建议在命名抽象类时,以
Abstract
或Base
作为前缀,以明确表示此类是一个抽象类,即不能被实例化,主要用于定义通用行为和接口,供子类继承实现。这样命名有助于开发者一眼识别出这是一个抽象类,了解其设计意图和用途。例如:AbstractAnimal
或BaseUserService
。 -
异常类命名使用
Exception
结尾 :异常类通常用于表示程序运行过程中遇到的错误情况。遵循这一规则,将
Exception
作为后缀添加到异常类名中,可以清晰地标识出此类是一个异常类,便于理解和管理程序中的错误处理逻辑。例如:InvalidInputException
或ResourceNotFoundException
。
"抽象类中,可以以
Abstract
结尾吗?"
虽然在技术层面上,以Abstract
结尾命名抽象类并不违反任何编程语言的语法规定,但在实践中,以Abstract
或Base
作为前缀已经是一种广泛接受且易于理解的抽象类命名约定。将Abstract
放在类名末尾可能会导致以下问题:
- 可读性降低 :阅读代码时,开发人员习惯于从左到右扫描类名,将表示类性质的关键字放在开头有助于快速识别。将
Abstract
放在末尾可能需要额外的时间和注意力去判断类的性质。- 一致性受损 :大部分编程社区和项目倾向于遵循将
Abstract
或Base
放在抽象类名开头的约定。若您的项目中出现以Abstract
结尾的抽象类,可能会与团队内的其他代码或业界普遍做法产生不一致,增加理解成本。
综上所述,尽管没有严格禁止以 Abstract
结尾命名抽象类,但从可读性、一致性以及遵循最佳实践的角度考虑,建议遵循将 Abstract
或 Base
放在抽象类名开头的命名规则。
4. 【强制】 POJO 类中布尔类型的变量,都不要加 is 前缀,否则部分框架解析会引起序列化错误。
反例:定义为基本数据类型 Boolean isDeleted 的属性,它的方法也是 isDeleted(),RPC框架在反向解析的时候,"误以为"对应的属性名称是 deleted,导致属性获取不到,进而抛出异常。
正例: 避免使用 is
前缀命名布尔类型的变量 :
直接使用描述状态的词汇作为变量名,如 deleted
、enabled
、visible
等。相应的 getter 方法可以保持 JavaBean 规范,即对于布尔类型使用 is
前缀(如 isDeleted()
),但对于属性名本身,则不带 is
。
问题背景:
在 Java 中,布尔类型的变量通常用来表示某种状态或属性的真假值。按照习惯,对于表示"是否具有某特性"的布尔变量,有时会采用 is
前缀来命名,如 isDeleted
、isEnabled
、isVisible
等。这种命名方式符合自然语言习惯,能够直观地传达变量的意义。
然而,在涉及序列化和反序列化的场景中,特别是与某些框架(如 RPC 框架、ORM 框架等)交互时,这种命名可能会导致问题。这是因为部分框架在自动处理对象的序列化和反序列化时,依赖于特定的命名约定来正确映射对象属性到外部格式(如 JSON、XML 或数据库字段)。
问题原因:
-
JavaBean 规范与框架预期的差异 :
JavaBean 规范规定,布尔类型的 getter 方法可以使用
is
前缀(如isDeleted()
),而其他类型的属性则使用get
前缀(如getName()
)。当框架尝试根据 getter 方法名反推属性名(去掉is
或get
并将首字母小写)时,如果遇到以is
开头的方法,可能会误以为对应的属性名为deleted
而不是isDeleted
。这会导致框架在反序列化时找不到实际存在的isDeleted
属性,从而引发错误。 -
外部格式映射规则 :
在某些框架中,可能直接基于属性名进行序列化或反序列化。如果外部格式(如 JSON)要求属性名遵循特定的命名约定(如全小写、下划线分隔等),那么带有
is
缀的属性名在转换时可能会与预期不符,导致映射失败或错误。
5. 【强制】包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式。
正例:应用工具类包名为 com.alibaba.ai.util
、类名为MessageUtils
(此规则参考spring
的框架结构)
6. 【推荐】如果模块、接口、类、方法使用了设计模式,在命名时需体现出具体模式。
说明:将设计模式体现在名字中,有利于阅读者快速理解架构设计理念。
正例:
public class OrderFactory
;public class LoginProxy
;public class ResourceObserver
;
7. 【推荐】接口类中的方法和属性不要加任何修饰符号(public 也不要加)
保持代码的简洁性,并加上有效的Javadoc 注释。尽量不要在接口里定义变量,如果一定要定义变量,肯定是与接口方法相关,并且是整个应用的基础常量。
正例:
- 接口方法签名 void commit();
- 接口基础常量 String COMPANY = "alibaba";
8. 接口和实现类命名的两套规则:
1)【强制】对于 Service 和DAO 类,基于 SOA 的理念,暴露出来的服务一定是接口,内部
的实现类用Impl 的后缀与接口区别。
正例:CacheServiceImpl
实现CacheService
接口。
2) 【推荐】 如果是形容能力的接口名称,取对应的形容词为接口名 (通常是--able 的形式)。
正例:AbstractTranslator
实现 Translatable
接口。
9. 【参考】枚举类名建议带上 Enum 后缀,枚举成员名称需要全大写,单词间用下划线隔开。
说明:枚举其实就是特殊的类,域成员均为常量,且构造方法被默认强制是私有。
正例:枚举名字为ProcessStatusEnum
的成员名称:SUCCESS / UNKNOWN_REASON。
10 .【参考】各层命名规约:
A) Service/DAO 层方法命名规约
1) 获取单个对象的方法用 get 做前缀。
2) 获取多个对象的方法用 list 做前缀,复数形式结尾如:listObjects。
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。
(二) 常量定义
1. 【强制】不允许任何魔法值(即未经预先定义的常量)直接出现在代码中。
反例:
java
String key ="Id#taobao_" + tradeId;
cache.put(key, value);
2. 【强制】在long 或者Long 赋值时,数值后使用大写的L,不能是小写的 l,小写容易跟数字1 混淆,造成误解。
说明:Long a = 2l; 写的是数字的21,还是 Long 型的2?
3. 【推荐】不要使用一个常量类维护所有常量,要按常量功能进行归类,分开维护。
说明: 大而全的常量类, 杂乱无章, 使用查找功能才能定位到修改的常量,不利于理解和维护。
正例:缓存相关常量放在类 CacheConsts 下;系统配置相关常量放在类 ConfigConsts 下
4. 【推荐】常量的复用层次有五层:跨应用共享常量、应用内共享常量、子工程内共享常量、包内共享常量、类内共享常量。
1) 跨应用共享常量:放置在二方库中,通常是 client.jar 中的constant 目录下。
2) 应用内共享常量:放置在一方库中,通常是子模块中的constant 目录下。
反例:易懂变量也要统一定义成应用内共享常量,两位攻城师在两个类中分别定义了表示"是"的变量:
类A 中:public static final String YES = "yes"
类B 中:public static final String YES = "y";
A.YES.equals(B.YES),预期是true,但实际返回为false,导致线上问题。
3) 子工程内部共享常量:即在当前子工程的 constant 目录下。
4) 包内共享常量:即在当前包下单独的 constant 目录下。
5) 类内共享常量:直接在类内部 private static final 定义。
5. 【推荐】如果变量值仅在一个固定范围内变化用 enum 类型来定义。
枚举(Enum)类型的概念与特性:
enum
(枚举)是一种特殊的类型,它允许定义一组命名的常量,这些常量通常是相互独立且互斥的。枚举类型在很多编程语言中都有支持,如 Java、C#、Python(通过枚举模块实现)等。枚举类型的优点包括:
- 类型安全:枚举类型的变量只能被赋予其定义范围内预设的值,不允许赋值为其他未定义的值,增强了代码的健壮性和安全性。
- 可读性强:枚举成员以具名常量的形式存在,使用有意义的名称代替原始值(如整数或字符串),提高了代码的可读性和自文档化能力。
- 易于维护:当枚举成员有所增减或值发生变化时,只需修改枚举定义一处,所有引用该枚举的地方都会自动更新,降低了维护成本。
何时使用枚举来定义变量值范围:
当变量的取值仅在一个固定的、有限的范围内变化,并且这些值具有特定的语义时,非常适合使用枚举类型来定义。以下是一些典型的应用场景:
- 状态标识 :
例如,订单的状态可能只有几个固定选项:PENDING
(待处理)、PROCESSING
(处理中)、COMPLETED
(已完成)、CANCELLED
(已取消)等。使用枚举可以清晰地表示这些状态,并确保状态值不会被错误地设置为无效值。
java
public enum OrderStatus {
PENDING,
PROCESSING,
COMPLETED,
CANCELLED
}
- 星期、月份等预定义集合 :
例如,表示一周中的星期或一年中的月份,这类值都是固定的且具有明确语义。使用枚举可以使代码更具可读性,并防止拼写错误或使用非法值。
java
public enum DayOfWeek {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
}
public enum Month {
JANUARY,
FEBRUARY,
MARCH,
APRIL,
MAY,
JUNE,
JULY,
AUGUST,
SEPTEMBER,
OCTOBER,
NOVEMBER,
DECEMBER
}
- 方向、颜色、角色类型等具有固定选项的分类 :
例如,表示二维平面上的方向(NORTH
,SOUTH
,EAST
,WEST
),用户角色类型(ADMIN
,MODERATOR
,USER
),或者颜色(RED
,GREEN
,BLUE
等)。这些情况都可以使用枚举来限制变量的取值范围。
java
public enum Direction {
NORTH,
SOUTH,
EAST,
WEST
}
public enum UserRole {
ADMIN,
MODERATOR,
USER
}
public enum Color {
RED,
GREEN,
BLUE,
// ...
}
总结来说,当变量的取值范围固定且具有明确语义时,使用枚举类型来定义不仅可以提高代码的可读性和安全性,还能简化维护工作。遵循"如果变量值仅在一个固定范围内变化,用 enum
类型来定义"这一规范,有助于提升代码质量,符合良好的编程实践。