如何写好一个函数
简述
在工作中,经常需要阅读同事的代码,有时候往往会对同事写的代码感到非常困惑,明明以为是这样的,结果却不然。这多半是因为变量命名不规范导致的,当然也有许多其他问题。但我也不是说我写的代码多好,其实同事看我写的代码也挺费劲的,所以我在想如何写出大家都比较容易读懂的代码。
大家在工作中可能更关注功能与需求的完成,其实我认为写好代码也是同等重要的,这不仅仅是出于私心,而是本着对项目负责的态度来考虑的,只有项目代码保持在一定水平之上,才能保障长久稳定的开发与维护,否则就要推翻重构了。
本文的目的就是想要探索如何写好代码,并且以简简单单的写好一个函数或方法为目标展开,不涉及什么高深的理论。
函数命名
函数命名的方式至关重要,通过函数命名我们可以得出许多有用的内容,我自己总结了几个常用的命名范式:
根据项目结构命名
在开发中,我们往往会对项目的代码结构进行分层,通常我会分为:apis、utils、components、pages等
比如apis目录下主要是对接口的定义,包括根据项目定制化的通用接口方法、具体的实现前后端交互的方法;
如果一个api文件中有统一输出的变量来统领其中的所有接口方法,那么就将这个变量命名为xxxApi,若没有则需要给其中每个方法名最后添加Api后缀
但此方案需根据项目结构实际给方法添加后缀名,比如utils中可不加,因为加了之后感觉怪怪的,还是看具体情况决定是否添加
根据返回值命名
根据返回值命名,这个是非常经典的命名方式了,主要是设置方法名前缀,这里列举一些我常用的方法名前缀:
get+名词:表示此方法将返回名词所表示的内容
is+形容词:返回值为true时,形容词描述的状态成立
has+名词:返回值为true时,有名词所指代的内容或状态
not+形容词:返回值为true时,形容词描述的状态不成立
no+名词:返回值为true时,无名词指代的内容或状态
fetch+名词:表示此方法将返回包含有名词所表示内容的Promise实例
根据函数内部实现特征命名
find+名词:此方法与get一样返回名词指代的内容,但强调了其中包含了数组查找或复杂对象中查找的逻辑
filterWith+名词:此方法表示对数据进行了过滤,并将返回与名词相关的数据
filterBy+名词:此方法表示对数据进行了过滤,并表达了以名词所表示的条件进行的过滤
根据实现的功能命名
一般以功能命名的方法都可以没有返回值
set+属性名:该命名用于类实例方法,表示将设置调用类实例的某个属性值
remove+名词:此方法表示移除了名词指代的内容,被移除对象可为参数或调用方法的实例
add+名词:此方法表明添加名词指代的内容,添加项应当通过参数定义
根据业务逻辑命名
一般以动词+名词的方式命名,选择你认为能表达出业务逻辑含义且常见的单词即可,举几个例子:
openAwardDialog:打开奖励对话框
registerUser:注册用户
uploadUserIdFile:上传用户证件资料
事件处理程序命名
在前端中,大部分方法的调用都是由事件驱动产生的,所以在命名事件处理程序时要遵照一定的规范
目前我所见到的有两种方式:handleButtonNameClick,onButtonNameClick两种方式
我比较倾向于使用on+(按钮名称/事件对象名称)+事件名的方式命名,举几个示例:
onPageLoad:页面加载完成后的处理程序
onNameChange:用户名输入修改处理程序
onUploadButtonClick:上传按钮点击事件处理程序
总结
我认为不要误用命名前缀,get与fetch的区别就在于返回值类型的不同,fetch表示返回值为Promise实例,使用get前缀后返回了Promise实例或使用fetch前缀未返回Promise实例都会给阅读者造成困扰
不管如何命名,都要让这个函数名本身能够表达一定的意思,否则和命名为aaa、bbb、ccc等有什么区别
函数内变量
在能够命名有意义的函数名后,对函数内变量的命名也至关重要,如果你将一个user变量赋值为一个数组、一个字符串或一个布尔值等都会让人难以理解
参照函数名命名规则
命名规则大多数时候是通用的,函数命名的部分原则同样适用于变量命名,比如:is not has no四个表示布尔值的前缀
按照变量值类型命名变量
布尔值:可用is not has no四个前缀
数组:在名词后+表示复数形式的s或es,即使名词本身能表示复数也要加,毕竟我们只是为了方便阅读,不是做英语试题
字符串或数字:命名时要注意最后一个单词能明确表达出这是一个字符串或数字
比如userName这种,name即名称在我们通常理解下为一个字符串
不好的例子比如userPhone,不太确定是要表达为用户手机对象还是用户手机号码,若要表达用户手机号码,可以用userPhoneNumber代替
对象:命名时要区分两种情况,所有值的类型与含义是否相同
如果对象中的值类型相同且含义相同,则需要以复数形式命名,这时候对象可以看作是数据集合的一种形式
如果不满足上述条件,则要确保最后一个单词能明确表达出非简单类型含义
不常用的类型:比如symbol,命名时可以Symbol或以缩写Sym结尾
是否需要声明变量
1.对于只需使用一次的表达式,尽可能不要声明变量
2.对于使用两次以上的表达式,尽可能声明变量,包括用.查找属性
函数的价值
封装函数的做法是将一组代码组织在一起,我认为目的仅有两个:多处调用函数减少代码重复、简化复杂函数内代码量
根据这两个意思,我们需要做的就是:1.将重复代码提取为函数 2.提取复杂函数内的部分代码为函数
函数阅读复杂度
这是我自己提出来的一个概念,用来表达一个我们阅读函数时需要理解的点的个数
计算方式:凡是需要思考和理解的地方都记为1,示例如下:
声明一个变量+1,调用一个函数或方法+1,赋值语句+1,返回语句+1,多数情况下一行代码就+1
但少数情况下,比如多个条件表达式使用`&`或`||`符号的时候要多+1,比如 conditionA & conditionB 为3
依据此方法可以估计出一个函数的复杂度,在函数内部要尽量以减少函数复杂度为目标,比如不要定义不必要的变量等,但也不要因噎废食,这只是一个参考
我认为可以定义个标准,超过多少复杂度就应该将函数内的逻辑拆分一下,将一部分代码的复杂度化为1来降低复杂函数阅读难度
注释
注释是让我们快速理解函数的兜底方案,可以在以下方面写好注释
- 详细说明函数的作用
- 说明参数的类型以及含义
- 说明返回值类型以及含义,返回布尔值时要说明
true或false表示什么意思 - 函数内部有逻辑判断的地方需要用注释说明判断的依据
- 函数内部最好使用
//符号来注释,这样可以方便用/**/格式注释掉整个函数
/**
* 计算两个值相加的结果
* @param {any} a 用于相加的值
* @param {any} b 用于相加的值
* @returns {number | string} 相加的结果
*/
function add(a, b) {
return a + b;
}
总结
我认为可以从四个方向上写好代码,函数命名、变量命名、阅读复杂度和注释,只要从这四个方面入手,相信我们都能写出容易阅读的代码。当然想要更好的写好函数方法,单一性原则与纯函数特性甚至函数式编程思想是值得学习的。