如何写好一个函数

536 阅读8分钟

如何写好一个函数

简述

在工作中,经常需要阅读同事的代码,有时候往往会对同事写的代码感到非常困惑,明明以为是这样的,结果却不然。这多半是因为变量命名不规范导致的,当然也有许多其他问题。但我也不是说我写的代码多好,其实同事看我写的代码也挺费劲的,所以我在想如何写出大家都比较容易读懂的代码。

大家在工作中可能更关注功能与需求的完成,其实我认为写好代码也是同等重要的,这不仅仅是出于私心,而是本着对项目负责的态度来考虑的,只有项目代码保持在一定水平之上,才能保障长久稳定的开发与维护,否则就要推翻重构了。

本文的目的就是想要探索如何写好代码,并且以简简单单的写好一个函数或方法为目标展开,不涉及什么高深的理论。

函数命名

函数命名的方式至关重要,通过函数命名我们可以得出许多有用的内容,我自己总结了几个常用的命名范式:

根据项目结构命名

在开发中,我们往往会对项目的代码结构进行分层,通常我会分为: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四个前缀

数组:在名词后+表示复数形式的ses,即使名词本身能表示复数也要加,毕竟我们只是为了方便阅读,不是做英语试题

字符串或数字:命名时要注意最后一个单词能明确表达出这是一个字符串或数字

比如userName这种,name即名称在我们通常理解下为一个字符串

不好的例子比如userPhone,不太确定是要表达为用户手机对象还是用户手机号码,若要表达用户手机号码,可以用userPhoneNumber代替

对象:命名时要区分两种情况,所有值的类型与含义是否相同

如果对象中的值类型相同且含义相同,则需要以复数形式命名,这时候对象可以看作是数据集合的一种形式

如果不满足上述条件,则要确保最后一个单词能明确表达出非简单类型含义

不常用的类型:比如symbol,命名时可以Symbol或以缩写Sym结尾

是否需要声明变量

1.对于只需使用一次的表达式,尽可能不要声明变量

2.对于使用两次以上的表达式,尽可能声明变量,包括用.查找属性

函数的价值

封装函数的做法是将一组代码组织在一起,我认为目的仅有两个:多处调用函数减少代码重复、简化复杂函数内代码量

根据这两个意思,我们需要做的就是:1.将重复代码提取为函数 2.提取复杂函数内的部分代码为函数

函数阅读复杂度

这是我自己提出来的一个概念,用来表达一个我们阅读函数时需要理解的点的个数

计算方式:凡是需要思考和理解的地方都记为1,示例如下:

声明一个变量+1,调用一个函数或方法+1,赋值语句+1,返回语句+1,多数情况下一行代码就+1

但少数情况下,比如多个条件表达式使用`&``||`符号的时候要多+1,比如 conditionA & conditionB 为3

依据此方法可以估计出一个函数的复杂度,在函数内部要尽量以减少函数复杂度为目标,比如不要定义不必要的变量等,但也不要因噎废食,这只是一个参考

我认为可以定义个标准,超过多少复杂度就应该将函数内的逻辑拆分一下,将一部分代码的复杂度化为1来降低复杂函数阅读难度

注释

注释是让我们快速理解函数的兜底方案,可以在以下方面写好注释

  1. 详细说明函数的作用
  2. 说明参数的类型以及含义
  3. 说明返回值类型以及含义,返回布尔值时要说明truefalse表示什么意思
  4. 函数内部有逻辑判断的地方需要用注释说明判断的依据
  5. 函数内部最好使用//符号来注释,这样可以方便用/**/格式注释掉整个函数
/**
 * 计算两个值相加的结果
 * @param {any} a 用于相加的值
 * @param {any} b 用于相加的值
 * @returns {number | string} 相加的结果
 */
function add(a, b) {
    return a + b;
}

总结

我认为可以从四个方向上写好代码,函数命名、变量命名、阅读复杂度和注释,只要从这四个方面入手,相信我们都能写出容易阅读的代码。当然想要更好的写好函数方法,单一性原则与纯函数特性甚至函数式编程思想是值得学习的。