Appearance
在编程中,命名是非常重要的,因为它可以使代码更易于理解和维护,大到项目名、模块名、包名、对外暴露的接口,小到类名、函数名、变量名、参数名,只要是做开发,我们就逃不过“起名字”这一关。命名的好坏,对于代码的可读性来说非常重要,甚至可以说是起决定性作用的
命名的形式
驼峰命名法(CamelCase)指的是使用大小写混合的格式,单词之间不使用空格隔开或者连接字符连接的命名方式。它有两种格式:大驼峰命名法(UpperCamelCase)和小驼峰命名法(lowerCamelCase)。
大驼峰命名法的第一个单词以大写字母开始,其余的和小驼峰命名法相同。 比如:LastName, InputStream。
小驼峰命名法的第一个单词以小写字母开始,其他单词以大写字母开始,其余字母使用小写字母。 比如:firstName, toString
在驼峰命名法中,如果一个单词是缩写的话,可以将其视为一个单词处理。对于一个单词都是大写的缩写单词,可以将其视为一个单词,只需将其首字母大写即可。例如 my IP 使用小驼峰命名法,将其命名为 "myIp"
下面的表格列出了不同例子的正确转换形式,和容易出错的转换形式 (出自“Google Java Style Guide”)
蛇形命名法(snake_case),单词之间通过下划线“_”连接,比如“out_of_range”
串式命名法(kebab-case),单词之间通过连字符“-”连接,比如“background-color”
匈牙利命名法(Hungarian notation)是一种命名规则,它在变量名前面加上一个或多个小写字母前缀,用于表示变量的数据类型或其他属性。这种命名规则最初由微软公司的程序员Charles Simonyi在20世纪80年代提出,因为他来自匈牙利,所以这种命名规则被称为匈牙利命名法。
匈牙利命名法的前缀通常是一个或多个小写字母,后面跟着一个大写字母,用于表示变量的数据类型或其他属性。例如,"iCount"表示一个整数类型的计数器,"strName"表示一个字符串类型的名称,"bEnabled"表示一个布尔类型的开关。
然而,随着编程语言和开发工具的发展,匈牙利命名法已经不再被广泛使用,因为现代的编程语言和开发工具已经提供了更好的类型检查和自动补全功能,使得使用这种命名规则变得不再必要。
取好名字
- 精准的命名,命名不要过于宽泛,不能精准描述,这是很多代码在命名上存在的严重问题,也是代码难以理解的根源所在。data、info、flag、process、handle、build、maintain、manage、modify 等等。这些名字都属于典型的过于宽泛的名字,当这些名字出现在你的代码里,多半是写代码的人当时没有想好用什么名字,命名要能够描述出这段代码在做的事情,一个好的名字应该描述意图,而非细节,下面有个例子
函数的名字叫 processChapter(处理章节),这个函数确实是在处理章节,但是,这个名字太过宽泛,这段代码是做什么的。你就需要调动全部注意力,去认真阅读这段代码,找出其中的逻辑。经过阅读我们发现,这段代码做的就是把一个章节的翻译状态改成翻译中。除了翻译对章节增删改都可以理解为处理章节
java
public void processChapter(long chapterId) {
Chapter chapter = this.repository.findByChapterId(chapterId);
if (chapter == null) {
throw new IllegalArgumentException("Unknown chapter [" + chapterId + "]");
}
chapter.setTranslationState(TranslationState.TRANSLATING);
this.repository.save(chapter);
}
修改后 可以叫做 setChapterTranslationStateToTranslating 或 updateChapterTranslationStateToTranslating,这样可以更准确地描述这个方法的作用,也更容易让其他开发者理解这段代码的意图
关于细节上如果命名暴露的细节过多,只要代码稍微有所改动,就可能需要改动命名,才能匹配实现所以最后也可以命名为 startTranslation 所以,一个好的名字应该描述意图,而非细节。
命名长度上,在足够表达其含义的情况下,命名当然是越短越好。但是,大部分情况下,短的命名都没有长的命名更能达意,对于一些默认的、大家都比较熟知的词,推荐用缩写。这样一方面能让命名短一些,另一方面又不影响阅读理解,比如,sec 表示 second、str 表示 string、num 表示 number、doc 表示 document
用技术术语命名,例如 bookLs Ls 表示是一个list 这种技术命名,其实可以使用books 这种通俗命名
利用上下文简化命名,下面例子中,在 User 类这样一个上下文中,我们没有在成员变量的命名中重复添加“user”这样一个前缀单词,而是直接命名为 name、password、avatarUrl。在使用这些属性时候,我们能借助对象这样一个上下文,表意也足够明确,因此可以完全不用加上前缀
java
public class User {
private String userName;
private String userPassword;
private String userAvatarUrl;
//...
}
对于接口的命名,一般有两种比较常见的方式。一种是加前缀“I”,表示一个 Interface。比如 IUserService,对应的实现类命名为 UserService。另一种是不加前缀,比如 UserService,对应的实现类加后缀“Impl”,比如 UserServiceImpl。
对于抽象类的命名,也有两种方式,一种是带上前缀“Abstract”,比如 AbstractConfiguration;另一种是不带前缀“Abstract”。实际上,对于接口和抽象类,选择哪种命名方式都是可以的,只要项目里能够统一就行。
对于常量,可以使用全大写字母和下划线命名法 MAX_VALUE
枚举的命名应该使用大写字母和下划线命名,枚举值的名称应该是一个名词或名词短语,描述枚举值的含义
java
public enum ColorEnum {
RED,
GREEN,
BLUE
}
类名是一个名词,表示一个对象
方法名则是一个动词,或者是动宾短语,表示一个动作
- 若方法返回boolean类型,可以推荐使用如下的格式。
操作类型 | 前缀词 | 描述 | 例子 | 返回类型 |
---|---|---|---|---|
检查对象状态 | is | 对象是否符合期待的状态 | isValidUser(user) | boolean |
检查对象能力 | can | 对象能否执行所期待的动作 | canUserRemove(user) | boolean |
检查对象推荐性 | should | 调用方执行某个命令或方法是好还是不好应不应该,或者说推荐还是不推荐 | shouldUserMigrate(user) | boolean |
检查对象持有性 | has | 对象/集合是否持有所期待的数据和属性 | hasUserObservers(user) | boolean |
检查对象存在性 | exists | 对象/集合是否存在所期待的数据和属性 | existsUserObservers(user) | boolean |
检查对象包含性 | contains | 判断集合是否保存某个元素 | containsUser(userList, user) | boolean |
检查对象需求性 | needs | 调用方是否需要执行某个命令或方法 | needsUserMigrate(user) | boolean |
对 should 解释,下面代码为例子的"should"并不表示用户必须升级,而是表示根据一些内部逻辑和数据,我们推荐用户升级。最终决定权仍然在于调用该方法的代码。
java
public boolean shouldUpgrade(User user) {
// 检查用户是否满足升级条件
if (user.getLevel() < MAX_LEVEL && user.getPaymentHistory().hasGoodStanding()) {
return true;
} else {
return false;
}
}
- 与数据相关的方法
操作类型 | 前缀词 | 描述 | 例子 | 返回类型 |
---|---|---|---|---|
查询单个对象 | find | 查找单个对象,如果找到则返回该对象,否则返回 null | findUserById(id) | User/null |
查询单个对象 | get | 查找单个对象,如果找到则返回该对象,否则抛出异常 | getUserById(id) | User/Exception |
查询多个对象 | query | 查找多个对象,返回一个集合 | queryUsersByName(name) | List<User> |
查询多个对象 | list | 查找多个对象,返回一个集合 | listAllUsers() | List<User> |
统计对象数量 | count | 统计符合条件的对象数量 | countUsersByAge(age) | int |
添加对象 | add | 添加一个新的对象 | addUser(user) | void/User |
更新对象 | update | 更新一个已存在的对象 | updateUser(user) | void/User |
删除对象 | delete | 删除一个已存在的对象 | deleteUserById(id) | void |
删除所有对象 | deleteAll | 删除所有符合条件的对象 | deleteAllUsers() | void |
保存对象 | save | 保存一个对象,如果对象已存在则更新,否则添加 | saveUser(user) | User |
保存多个对象 | saveAll | 保存多个对象,如果对象已存在则更新,否则添加 | saveAllUsers(users) | List<User> |
- 介词
操作类型 | 前缀词 | 描述 | 例子 | 返回类型 |
---|---|---|---|---|
创建对象 | from | 从...创建 | createUserFromConfig(config) | User |
转化对象 | to | 转化 | toString(user) | String |
获取属性 | of | 从...中获取 | pathOfUser(user) | String |
使用特定工具进行操作 | with | 和 | writeUserWithMessageConverters(user, converters) | void |
通过特定方式获取对象 | by | 通过 | getUserById(id) | User |
检查对象在集合中的存在性 | in | 在...in | isUserNameInUse(name) | boolean |
为特定对象获取特定属性 | for | 为了 | getMappingForMethod(method) | Mapping |
在特定范围内获取属性 | within | 在...内 | getPathWithinApplication(app) | String |
转化对象 | as | 作为 | getUserValuesAsList(user) | List |
"for"在方法名中通常用于表示该方法是为了某个特定对象或目的而设计的,例如,假设我们有一个学生类(Student),并且我们想要获取学生的成绩(Grade)。我们可以创建一个名为"getGradeForStudent"的方法。这个方法的目的就是为了获取特定学生的成绩
实际开发
实际开发业务的时候同事们可以整理建立词汇表,般情况下,我们都可以去和业务方谈,共同确定一个词汇表,包含业务术语的中英文表达。这样在写代码的时候,你就可以参考这个词汇表给变量和函数命名。
实在想不到好名字的时候,可以去 GitHub 上用相关的关键词联想搜索一下,看看类似的代码是怎么命名的。