与你一起养成 命名 的好习惯!

2,836 阅读18分钟

专栏介绍

2022年啦,新年快乐呀,小伙伴儿们,通常这种时候呢,总要先立一些个flag啦,至于最后能不能做到,这里就不保证了,哈哈哈。

那对于我自己呢,2021年算是比较波动的一年啦,年初离职跳到字节,最近又来到腾讯,一年连续待了3家公司,好在目前基本算是稳定下来啦,那新的一年,立的第一个flag就是:在新的一年能够多写一些文章,多做一些技术输出呀。

所以2022年第一篇文章就是来啦,【代码整洁之道】命名篇 ,同时收录在专栏【代码整洁之道 - 理论与实践】中啦,该专栏文章目录如下:

  1. 【代码整洁之道】命名篇
  2. 【代码整洁之道】注释篇
  3. 【代码整洁之道】函数篇
  4. 【代码整洁之道】格式篇(待发布)
  5. 【代码整洁之道】对象和数据结构篇(待发布)
  6. 【代码整洁之道】错误处理篇(待发布)
  7. 【代码整洁之道】单元测试篇(待发布)
  8. 【代码整洁之道】迭代篇(待发布)

说明:该专栏【代码整洁之道 - 理论与实践】未来也会持续围绕代码整洁去展开更多更深入的内容,也欢迎大家点赞和关注哦~😊😊😊

命名的重要性

命名的重要性,我们这里就不过多叙述啦,总而言之,命名真的非常的重要,直接关系到整个代码的可读性以及后期的可维护性

因此,要想让自己的代码更加优雅和整洁,要从命名,函数,注释,格式等多个方面去养成良好习惯,因此,本专栏 代码整洁之道-理论与实践 就是从命名,函数,注释等多个方面从理论到实战进行总结,希望可以让大家有一个更加清晰的认识。

还要强调两句话:

  1. 我们写代码是给人的,是给我们程序猿自己看的,不是给机器看的,因此,当我们写代码的时候,要经常思考,我们写下的这段代码,别人如果此时看了是否可以比较清晰的理解代码的含义,如果觉得不太好理解,是否意味着我们的代码可以进一步优化呢,是否意味着我们需要加一些注释呢。 总之,都是为了 代码能够让别人也看得懂。
  2. 代码写的好不好,和技术能力本身不是成正相关的,也就是说代码要写好,更多的还是要养成良好的习惯,并且从态度层面去重视这个事情,和技术能力本身没有强相关的关系,当然技术能力本身也很重要,它可以加成让我们的代码可以使用更好的设计模式等去组织代码。

本节内容主要分为三块:

  • 理论篇: 主要结合了【代码整洁之道】这本书里所讲到的命名思想(推荐大家可以完整的看一下)
  • 命名规范:主要是结合了目前市场常见的第三方代码规范库,以及公司的代码规范。
  • 命名实战:主要是根据实际开发经验,以及各种不同场景下的命名分别进行了总结。

理论篇

一. 使用具有明确含义的单词

这是命名的最重要规则啦,其实就是我们使用的名称要有其实际含义,其他程序员看了能够大概知道这个变量表示的是什么意思,而不是看了一脸懵逼。

说到这儿,就不得不说我们刚开始写代码的时候啦,很容易写出如下代码:

let a = 1;
let b = 2;
let num1 = 1;
let num2 = 2;

很显然,上面这些都是bad case,我们再来体会一个例子:

例如:后端返回一个用户列表,刚开始写代码的时候,很容易写出:

let arr = ['user1', 'user2', 'user3', 'user4'];

此时,其他程序员看了arr,虽然知道它可能是一个数组,但是却不知道它到底存储的是什么数据,也是一个bad case,我们可以优化一下:

let users = ['user1', 'user2', 'user3', 'user4']
let userList = ['user1', 'user2', 'user3', 'user4']; // List在其他语言中可能是一个关键字,不推荐使用,而在JS中其不是关键字,可以使用。

二. 做有意义的区分

通过第一条规则,相信我们在项目中可以基本保证不同的模块可以使用不同的单词去命名啦,例如:用户模块我们可以用user去命名,权限模块我们可以使用permission去命名,但是还远远不够。

因为在实际项目中,有很多相似的场景,例如:一个用户模块就可以细分为:获取用户基本信息,获取用户列表等,相信我们在不同的项目下经常会看到下面这些命名:

getUser();
getUserData();
getUserInfo();
getUserList();
getUsers();

其他程序员一看用户模块代码,有这么多个方法,但是能分辨清楚各个函数有什么区别吗?很显然不能,比如:getUser()和getUserData(),光从命名上看其实并没有区别,但是我们却用它们写了不同的逻辑,这样就很容造成歧义, 这就是 我们要说的:要做有意义的区分:即需要我们去区分两个子模块或者场景的时候,我们一定要采用两个有明确不同含义的命名去区分它们,而不是使用意思相近,很容易混淆在一起的

类似的常见错误还有:account与accountData, customer与customerInfo, money与moneyAmount等,都其实没有什么区别,在实际项目开发中,我们使用其中一种命名方式即可,不过这里要注意:一定要在整个项目中一以贯之,比如:获取用户信息,我们决定使用ge tUserInfo(),那么其他地方统一用以info结尾的,比如,获取其他详情:getPermissionInfo, getAccountInfo等,我们后面还会讲到!

三. 使用可以读得出来的单词

造成代码不可读,很大的原因是使用了单词缩写,而且还是那些不经常见的缩写,如果多个缩写驼峰拼接在一起,那就更难读了,其他程序员看了也更是一脸懵逼,例如:添加文章

addArt() // bad case 
addArticle() // 推荐

很明显,addArt 即不好读,也很容易引起歧义,我们在实际开发中尽量少使用缩写,如果使用,也是那些经常常见的一些缩写,例如:value可以缩写为val, document可以缩写为doc.

四. 避免使用编码

这里的编码是指:我们要不要把一些关键字,类型 加到变量的命名中,例如:

const phoneString = '123'; // 后面的String没必要
const phoneNumber = 123132; // 这个还可以,因为电话号码本身就是phoneNumber

因为在js中,类型之间可以随意赋值,即使我们定义了phoneString,依然可能被赋值一个number,所以也很容易一起歧义。

五. 使用常见的单词命名

尽可能用大家都熟知的一些单词或者语法去命名,不要去用那些不常见的,或者俗语,俚语的写法。 宁可明确,不搞特殊,不扮可爱。

kill(); // 别用 whack(),不知道什么意思的,可以理解为另外一种不常见的写法
abort(); // 别用 eatMyShorts(), 它是一种俚语的写法。

六. 每个场景对应一个词

这里是指我们在平时开发中,可能固定也就那几种场景,获取数据,更新数据,删除数据等,每个场景我们要给其约定一个固定的词,从而在整个项目中一以贯之,例如:get/set/update/delete等

getUser(); // 获取
updateUser(); // 更新
deleteUser(); // 删除
addUser(); // 添加

而不是在项目中,此处添加用的是add, 其他地方用的又是create等其他单词,此处删除用的delete,其他地方用的又是drop, 这些都是bad case啦。

但是也要注意:用词一定要适合,例如:添加,别什么地方都用add, 很可能其他场景用的insert,append等,我们也要随机应变,不能为了保持一致,而忽略其并不适合当前场景。

七. 使用解决领域方案名词

程序员本身这个职业,可能会有其自身的一些专有名词,比如:队列用queue, 栈用stack, 我们在实际项目中,要尽可能多使用这些名词。

jobQueue // 任务队列
observer // 观察者模式

这里要强调一点,设计模式中有很多特有的名词,我们如果实际开发中有相关的场景,我们要尽可能去使用这些设计模式定义的名词。例如:访问者模式中的visitor, 发布订阅模式中的:publisher, subscriber等。

命名规范

上面讲到的都是我们平时命名的一些理论知识,总结起来就是:命名一定要明确的意义,其他程序员看了能看懂! ,接下来,我们看一下具体有哪些明确的命名规范。

命名规范也很不同的等级,这里我们分为:【必须】【推荐】【可选】三个等级。

在实际开发中,我们一般需要ESLint等校验工具,去对代码进行校验,但是我们平时写的时候也一定要按照这些规范去写。

这里,我们列出了:四必须,五推荐

【必须】使用小驼峰命名

说明:实际开发中,所有的变量命名,函数命名等全部遵循小驼峰的写法。对应eslint规则是:camelcase

// bad case
const user_list = [];
const userlist = [];
​
// good
const userList = [];

【必须】 只有命名类或者构造器函数时,才使用大驼峰命名

// bad case
function user(name){
  this.name = name;
}
const u = new user('kobe');
​
// good case
function User(name){
  this.name = name;
}
const u = new User('kobe');

【必须】当导出类/构造器/函数库/对象时,使用大驼峰

首先,类和构造器函数,不管是单独命名还是导出的时候,都统一大驼峰即可,这里要注意的是当导出一个对象的时候,我们也要使用大驼峰。

const User = {
  name: 'kobe',
  age: 41
};
export default User;

【必须】当导出默认函数时,必须使用驼峰命名法。

function formatNumber () {
​
}
export default formatNumber;

注意:推荐文件名尽量和默认导出的名称保持一致,当然文件名建议是kebab-case,例如:上面这个case,文件名应该为format-number.js 。

【推荐】不要以下划线开头或者结尾命名变量

// bad case
const _username = 'kobe';
const user_name = 'kobe';
const username_ = 'kobe';

注意:这里,有一个细节要强调一下,大家可能在其他地方看到过,在js中,可能会以下划线开头来命名私有属性,但其实只是这样命名只是为了说明该属性是一个私有属性,但其实依然是公开的,调用方依然可以调用到,所以,反而可能会引起歧义,换句话说,如果需要声明私有属性,就需要明确做到其私有,外部无法访问。

【推荐】不要保存this的引用,推荐使用箭头函数

// bad case
function foo() {
  const self = this;
  return function () {
    console.log(self);
  };
}
​
// bad case
function foo() {
  const that = this;
  return function () {
    console.log(that);
  };
}
​
// good case
function foo() {
  return () => {
    console.log(this);
  };
}

【推荐】文件名推荐使用kebab-case,即用-隔开

命名文件的时候,推荐大家使用 短横线 隔开来命名。

// good case
task-list.vue
task-detail.vue

当然,实际项目开发中,也经常会遇到小驼峰命名,这个大家随时切换,尽量可以保证一个项目中所有文件的命名规范是统一的。

【推荐】缩略词和缩写都必须全部大写或者全部小写

// bad case
const HttpRequests = [];
​
// good case
const httpRequests = [];
const HTTPRequests = [];

【推荐】export导出的常量,统一全部大写命名,多个单词用_隔开。

这块,有几点要注意:

  1. export导出的常量,推荐用大写命名,但是模块内部的常量不需要全大写,正常按照小驼峰即可。
  2. export导出的常量,推荐大写命名,但是其内部属性不必大写。
// bad case
const NAME = 'kobe';
export const userConfig = {};
// good case
const name = 'kobe';
export const USER_CONFIG = {};
​
// bad case
export const MAPPING = {
  KEY: 'value'
}
// good case
export const MAPPING = {
  key: 'value'
};

命名实战

上面讲到了我们推荐使用的一些代码规范,但是在实际开发中,除了遵循这些规范,我们更需要在实际业务中,更复杂的是,面对各种不同的业务场景的命名,如何去使用准确的单词,并且如何把这些单词组合起来,以及单词谁前谁后等问题,都需要我们有一个比较清晰的认知和命名习惯。

多个单词如何组合?

在实际的业务场景中,如果是单个单词的模块,那我们直接使用该单词即可,但是很多时候,在命名的时候,很可能是需要多个词组合起来,此时,我们都知道是使用驼峰命名法进行命名,但是多个单词之间的顺序是什么样呢?如何去组合呢?这里面其实也有很多细节的。

  1. 我们首先想到是根据文件去拆分不同的子模块

    | - user
      | - list.js
      | - detail.js
    
  2. 如果在实际项目中,目录嵌套结构已经比较深,不想再嵌套一层,这是可以直接根据文件名进行区分。

    userList // 用户列表
    userDetail // 用户详情
    
  3. 以上这些都是比较简单的,除此之外,我们主要是想说一下,同一个模块下,不同的子模块,或者类似的模块在一起的时候,如何去更好的命名,从而区分它们。

    例如:任务模块,可能包含以下内容:

    • 任务A列表
    • 任务B列表
    • 任务详情
    • 添加任务A - 弹框
    • 天际任务B - 弹框

此时,我们很可能会写出下面这样的命名:

taskListA
taskBList
taskModalA
taskBModal
taskDetail

尤其是当我们没有明确自己的习惯的时候,可能这个模块 形容词(如这里的A,B)放在了中间,另外一个模块又放到了结尾, 导致整个项目的代码看着很乱,因此,我们需要尽量整个项目都保持一致的命名习惯。

这里,我个人推荐使用:前缀 + 形容词或名词 + 后缀 的模式。当然,这里只是我个人的命名习惯,不是标准。

我们来具体解释一下:

  • 前缀:一般是公共的模块名称:例如:上面讲到的task
  • 形容词或名词:一般模块的两个不同的子模块,或者子内容。例如:上面讲到的A,B
  • 后缀:一般是表示这个模块具体是哪种类型,例如:List一般表示一个列表或者表格。Detail表示详情,Modal表示弹框等。

我们来实际用一个例子体会一下,例如:一个任务模块,包含:任务列表,任务列表筛选栏目,创建单任务,创建多任务,任务详情等模块,我们使用vue或者react开发的时候,一般都会把它们拆分成独立的文件。

taskFilter.vue 
taskList.vue
taskDetail.vue
taskSingleModal.vue
taskMultipleModal.vue

说明:这里说这一点,主要看到很多代码,其实是因为开发者并没有一个自己的一套习惯,导致不同模块之间的命名都不一致。很随机。因此,我们要培养属于自己的一套命名习惯。

项目命名

推荐:统一采用小写,多个单词之间用短斜杠-隔开即可。 例如:

shop-demo
cms-platform 

模块命名

不同的模块主要体现在两方面:

  1. 根据实际业务场景拆分的模块
  2. 项目架构本身拆分的模块

根据实际业务场景拆分的模块,命名很简单,直接根据其实际业务含义翻译一下即可,实在不知道用啥单词,直接用词典翻译一下。

这里,我们主要说一下前端项目架构本身拆分的模块,其实也就是项目目录结构的划分:

这里把常见的一些功能模块的命名罗列一下:

views // 存放所有页面,子目录按具体业务模块划分即可
assets // 存放所有资源,例如:图片等
components // 公共组件
router // 路由
store // 状态
utils // 各种自定义工具函数
services // 存放ajax请求相关

注意:模块命名,本质上也是文件命名,其实推荐的是统一小写,多个单词之间使用短斜杠隔开。

变量命名

推荐:采用小驼峰去命名变量, 实际开发过程中,根据具体变量的含义,采用有明确意义的单词去命名即可,多个单词组合时,采用我们第一点提到的技巧即可。

常量命名

常量命名主要分为两种:

  1. 需要导出的常量:统一全部采用大写,多个词之间用下划线隔开。
  2. 不需要导出,只在文件模块内部使用的常量: 统一小驼峰命名

方法命名

返回布尔类型的方法

前缀-prefix含义例如
is表示是否符合某种状态isValid() isLoaded()
can表示是否可以执行某种操作canRemove()
has表示是否持有某种特性hasOwnProperty()
needs表示是否需要进行某种操作needsUpdate()

回调方法

前缀-prefix含义例如
on表示当事件发生时onCompleted() onFinished()
before/after表示事件发生前后beforeUpdate() afterUpdate()
pre/post同上preUpdate() postUpdate()
Did/will同上didUpdate() willUpdate()

与操作相关,即增删改查

单词意义例如
初始化:init/reset/clearInit: 初始化数据 reset: 重置数据,恢复到初始化状态 clear: 清除数据,恢复到初始化状态initData() resetData(); clearData();
读取:get/fetch/loadget: 获取某数据,可以是本地数据,也可以是远程数据 fetch: 获取远程数据 load: 加载某数据getUserList(); fetchUserList(); loadUserList();
添加:add/append/insert/createadd/create: 在一个list添加一个子项 append/insert: 常见于dom操作addUser() appendChild() removeChild()
删除:delete/remove/destorydelete: 在一个list删除某个子项 remove: 常见于dom操作deleteUser()
更新:updateupdate: 更新数据updateUser()
保存:save/apply/commitsave: 保存数据 apply: 应用 commit: 提交saveData() applyChange() commitData();
队列相关:enqueue/dequeueenqueue: 对尾添加一个元素 dequeue: 对首移除一个元素
栈/数组相关:push/pop/peek/find等即数组的常见操作

成对出现的动词

单词与之对应的
get :获取set :设置
add: 添加delete:删除
create: 创建destroy:销毁
start 启动stop 停止
open 打开close 关闭
read 读取write 写入
load 载入save 保存
backup 备份restore 恢复
import 导入export 导出
split 分割merge 合并
inject 注入extract 提取
attach 附着detach 脱离
bind 绑定separate 分离
view 查看browse 浏览
edit 编辑modify 修改
select 选取mark 标记
copy 复制paste 粘贴
undo 撤销redo 重做
insert 插入delete 移除
add 加入append 添加
clean 清理clear 清除
index 索引sort 排序
find 查找search 搜索
increase 增加decrease 减少
play 播放pause 暂停
launch 启动run 运行
compile 编译execute 执行
debug 调试trace 跟踪
observe 观察listen 监听
build 构建publish 发布
input 输入output 输出
encode 编码decode 解码
encrypt 加密decrypt 解密
compress 压缩decompress 解压缩
pack 打包unpack 解包
parse 解析emit 生成
connect 连接disconnect 断开
send 发送receive 接收
download 下载upload 上传
refresh 刷新synchronize 同步
update 更新revert 复原
lock 锁定unlock 解锁
check out 签出check in 签入
submit 提交commit 交付
push 推pull 拉
expand 展开collapse 折叠
begin 起始end 结束
start 开始finish 完成
enter 进入exit 退出
abort 放弃quit 离开
obsolete 废弃depreciate 废旧
collect 收集aggregate 聚集

路由相关

前缀后缀例如
topagetoUserPage()
gopagegoUserPage()
redirectpageredirectUserPage()
BackpagebackHomePage() backUserPage()

推荐:toXxxPage 或者 goXxxPage 或者backXxxPage 格式。

  • 跳转到指定页面:toXxxPage()
  • 重定向到指定页面:redirectXxxPage()
  • 返回上一页: backPrePage();
  • 返回首页:backHomePage();

总结

本节内容,主要就是从理论到实践,围绕一个问题:如何更好的命名?命名确实是我们写代码的第一步,不管是文件还是模块,还是单个模块中的变量,方法等都需要命名,那我们要做的就是结合命名规范,总结出属于我们自己一套命名习惯,并且保证整个项目或者自己所写的所有代码都可以围绕这套规范展开。

另外,上面所提到的一些实战技巧,只是罗列的一部分。后期依然会持续的补充,大家如果有更好的建议,也可以随时评论分享哈。

写作不易,如果觉得还不错的话,欢迎点赞分享哦!😊😊😊