代码规范
命名规范
- 代码的命名对代码的表现力和可读性有重要的影响。
- 命名的过程本身就是一个抽象和思考的过程。
- 代码即文档。可读性好的代码应有一定的自明性。
1.有意义的命名
1.1变量名和常量名
- 变量(variable)来源于数学,是计算机语言中能储存计算结果或能表示值抽象概念。
- 变量名应该是名词,能正确地描述业务,有表达力。
- 使用
int elapsedTimeDays;
代替int d;//表示过去的天数
- 使用
- 变量名应该是名词,能正确地描述业务,有表达力。
- 常量(constant)意为不变的量,在程序内始终不变。
- 常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。
- 不允许任何魔法值(即未经预先定义的常量)直接出现在代码中。
- 用
SECONDS_PER_DAY
代替86400
,用PAGE_SIZE
代替10
- 用
- 在long或者Long赋值时,数值后使用大写的L,不能是小写的l,小写容易跟数 字1混淆,造成误解。
- 不要使用一个常量类维护所有常量,要按常量功能进行归类,分开维护。
- 在常量与变量的命名时,表示类型的名词放在词尾,以提升辨识度。
- 正例:startTime / workQueue / nameList / TERMINATED_THREAD_COUNT
- 反例:startedAt / QueueOfWork / listName / COUNT_TERMINATED_THREAD
- 提高代码可搜索性。
1.2函数名
- 函数(function)指一段可以直接被另一段程序引用的程序,也称子程序。
- 函数名要具体,不能过于空泛。
- 函数的命名要体现做什么而不是怎么做。
举例:
validateUserCredentials()
或者eliminateDuplicateRequest()
要优于processDate()
。- 我们将雇员信息存储在一个栈里,要从栈中获取最近存储的一个雇员信息,
getLastestEmployee()
就优于popRecord()
(栈数据结构是底层细节实现,命名应提升抽象层次,体现业务含义)。
1.3类名
- 类(class)是面向对象的核心概念之一,是对一组数据和操作的封装。
- 类一般可以分为三类:即实体类、抽象类和枚举类。
- 实体类
- 承载了核心业务数据和核心业务逻辑,命名要充分体现业务语义,并在团队中达成一致。
- 如Customer、Bank、Employee等。
- 辅助类
- 辅助实体类一起进行完成业务逻辑的,命名通过后缀实现功能。
- 例如:
- CustomerController:为Customer做控制路由的控制类。
- CustomerService:提供Customer服务的服务类。
- CustomerRepository:获取Customer数据存储的仓储类。
- 对辅助类尽量不要使用Helper,Util等后缀,其含义可能过于笼统,,容易破坏SRP(单一职责原则)。
- 例如:CSVParser.parse(String str),CSVBuilder.create(int[] arrays)就优于CSVHelper.parse(String str),CSVHelper.create(int[] arrays)
- 实体类
1.4包名
- 包(Package)代表一组有关系的类的集合,起到分类组合和命名空间的作用。
- 包名反映了一组类在更高抽象层次上的关系。
- 如类Apple,Pear,Orange可以放入一个包中,命名为fruit。
- 包的命名要适中,不能过于抽象或过于具体。
- 如果包名过于具体,比如Apple,那么Pear和Orange放入该包中就不恰当了。
- 如果包名过于抽象,比如Object,而Object无所不包,那么就失去了包限定范围的作用。
1.5模块名
- 模块(Module)主要是指Maven中的Module,相对包来说,模块的粒度更大,通常一个模块中就包含多个包。
- 在Maven中模块就是一个坐标:<groupId,artifactId>。
- 保证了模块在Maven仓库的唯一性。
- 反映了模块在系统中的职责。
1.6枚举类名
- 枚举类(enum),允许用常量来表示特定的数据片断。
- 枚举其实就是特殊的类,域成员均为常量,且构造方法被默认强制是私有。
- 枚举类名带上Enum后缀,枚举成员名称需要全大写,单词间用下划线隔开。
- 枚举名字为ProcessStatusEnum的成员名称:SUCCESS / UNKNOWN_REASON。
- 如果变量值仅在一个固定范围内变化用enum类型来定义。
- 举例:如果存在名称之外的延伸属性应使用 enum类型,下面正例中的数字就是延伸信息,表示一年中的 第几个季节。
public enum SeasonEnum {
SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4);
private int seq;
SeasonEnum(int seq) {
this.seq = seq;
}
public int getSeq() {
return seq;
}
}
1.7各层命名规约
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。
2.保持一致性
保持命名的一致性,可以提高代码可读性,从而简化复杂度。
2.1每个概念一个词
- 每个概念一个词,并一以贯之。
- 比如查询概念可以用fetch、retrieve、get、find、query,如果不加约定给多个类相同查询方法命名,就会出现冲突等问题。
- 比如CRUD操作可以参考下表进行命名,保持命名一致性。
表2-1-1 CRUD方法名约定
CRUD操作 | 方法名约定 |
---|---|
新增 | create |
添加 | add |
删除 | remove |
修改 | update |
查询(单个结果) | get |
查询(多个结果) | list |
分页查询 | page |
统计 | count |
2.2使用对仗词
- 遵守对仗词的命名规则有助于保持一致性,从而提高代码可读性。
- 以下常见对仗词组
- add/remove
- increment/decrement
- open/close
- begin/end
- insert/delete
- show/hide
- create/destroy
- lock/unlock
- source/target
- first/last
- min/max
- start/stop
- get/set
- next/pervious
- up/down
- old/new
2.3使用后置限定词
- 程序中会有很多表示计算结果的变量(总额、平均值、最大值等)。
- 如果要使用Total、Sum、Average、Max、Min这样的限定词修改某个命名时,建议吧限定词放到名字的最后,并在项目中贯彻执行,保持命名风格一致性。
- 变量名赋予主要含义的部分应位于最前面,这样可以突出显示,并会被首先阅读到。
- 可以避免程序中出现totalRevenue和revenueTotal的歧义
- 遵守此规则可以获得比较优雅的命名,如
- revenueTotal(总收入)、expenseTotal(总支出)
- revenueAverage(平均收入)、expenseAverage(平均支出)
2.4统一业务语言
- 统一语言要确保团队在内部的所有交流、模型、代码和文档都要使用同一种语言,统一语言也是领域驱动设计的重要部分
2.5统一技术语言
- 我们应该尽量使用业内人士都能理解的通用的技术语言。
- 如DO,DAO,DTO,Service,ServiceImpl,Component,Repository
3.自明的代码
- 代码本身就应该具有较好的阅读性和自明性,即不借助其他辅助手段情况下向读者清晰地表达自身含义。
3.1中间变量
通过添加中间变量让代码更加自明,将计算过程打散成多个步骤,并用有意义变量名来命名中间变量,从而将隐藏的计算过程显性化表达出来。
- 比如,如果我们要通过Regex来获取字符串中的值,并放在Map中。
Macher mather = headerPattern.matcher(line);
if(matcher.find()){
headers.put(matcher.group(1),matcher.group(2));
}
如果使用中间变量,可以写成如下形式:
Macher mather = headerPattern.matcher(line);
if(matcher.find()){
String key=macther.group(1);
String value=macther.group(2);
headers.put(key,value);
}
3.2设计模式语言
- 使用设计模式语言也是代码自明的重要手段之一。
- 在技术人员之间共享和使用设计模式语言可以极大地提升沟通的效率(前提大家都理解和熟悉这些模式)
- 技术人员有必要在命名是将设计模式显示化出来,这样阅读代码的人就能很快地体会到设计者的意图
- 例如,Spring中ApplicationListener就充分体现他的设计和用途。通过这个命名我们能知道它使用了观察者模式,每一个被注册的ApplicationListener在Application状态发生变化,都会接收到一个notify,这样我们可以在容器初始化完成后进行一些业务操作,如数据加载,初始化缓存。
3.3注释
注释就是“坏味道”之一。
——Martin Fowler《重构:改善既有代码的设计》
3.3.1不要复述功能
当代码本身足以表达其意图时,不要复述其意图。
3.3.2要解释背后的意图
注释要解释代码背后的意图,而不是对功能的简单的重复。
比如:
try{
//在这里等待2秒
Thread.sleep(2000);
} catch (Exception e){
LOGGER.error(e);
}
这里的注释只是对sleep的简单复述,而没阐述sleep背后的原因,可改写成以下形式:
try{
//休息2秒,为等待关联系统处理结果
Thread.sleep(2000);
} catch (Exception e){
LOGGER.error(e);
}
或者可以用一个private方法将其封装起来,用显性化的方法名来展示意图。
private void waitProcessResultFromA(){
try{
Thread.sleep(2000);
} catch (Exception e){
LOGGER.error(e);
}
}
参考资料
1.《代码精进之路:从码农到工匠》,张建飞
2.《重构:改善既有的代码设计》,Martin Fowler
3.《Java开发手册》华山版,孤尽
4.《码出高效:Java开发手册》,杨冠宝、高海慧