前言
今天是 function 时空旅行第二天,昨天我们学会了如何收拾背包 (?),也学会不要让背包里的东西变质 (?)。。。总之就是学会了关于 function 参数的另一种使用方式,提高可维护性。
今天要来谈谈关于 function 的拆解与命名:
✅拆解
跟昨天一样,先来个范例看看吧:
// 送出编辑个人信息的表单
/*
* name : 姓名
* age : 年龄
* gender: 性别
*/
const requiredFields = [
'name',
'age',
'gender'
];
const submitForm = (value) => {
const formFields = Object.keys(value);
const valid = requiredFields.every(key => {
return formFields.includes(key) && typeof value[key] !== 'undefined'
});
if(!valid) {
console.log('尚有必填字段未填');
return;
}
const nameSplitList = value.name.split(' ');
const submitValue = {
firstName: nameSplitList[0],
lastName: nameSplitList[1],
age: Number(value.age),
gender: value.gender
};
fetch('my-backend-API', {
method: 'POST',
body: JSON.stringify(submitValue)
});
};
const formValue = {
name: 'yc chiu',
age: '20',
gender: 'male'
};
submitForm(formValue);
这是模拟编辑个人资讯的表单,可以填上姓名、年龄、性别三个栏位,按下送出表单的时候,会做到以下三件事:
- 检核 必填栏位 (有在 彻底掌握 Object提到过)
- 将前端栏位转换成后端需要的栏位与格式
- 发送API request 到后端
只有三个栏位,其实算是一个相对简单的范例,但可以看到要送出表单时,submitForm
多达 22 行 (而且 fetch
还被我偷懒简化过)。
如果同样的目的,我们将 submitForm
拆解,分别用以下的 function 来处理:
validateFormData
prepareSubmitData
postPersonData
/ 送出编辑个人信息的表单
/*
* name : 姓名
* age : 年龄
* gender: 性别
*/
const requiredFields = [
'name',
'age',
'gender'
];
const validateFormData = (formData) => {
const formFields = Object.keys(formData);
return requiredFields.every(key => {
return formFields.includes(key) && typeof formData[key] !== 'undefined'
});
};
const prepareSubmitData = (formData) => {
const nameSplitList = formData.name.split(' ');
return {
firstName: nameSplitList[0],
lastName: nameSplitList[1],
age: Number(formData.age),
gender: formData.gender
};
};
const postPersonData = (submitData) => {
fetch('my-backend-API', {
method: 'POST',
body: JSON.stringify(submitData)
});
};
const submitForm = (value) => {
const valid = validateFormData(value);
if(!valid) {
console.log('尚有必填字段未填');
return ;
}
const submitData = prepareSubmitData(value);
postPersonData(submitData);
};
const formValue = {
name: 'yc chiu',
age: '20',
gender: 'male'
};
submitForm(formValue);
没错,改完之后程式码更长了 (傻眼),但我们换到什么呢?
✔ 可读性
虽然程式码更多了,但对于刚接手这份 code 的人来说,其实读懂的速度更快了。
重点在于我们将 submitForm
这个 function 里面,原本混杂没有界线的逻辑,使用几个小 function 切割开来,强迫这些逻辑拆散,就不再是一大坨程式要一行一行读。
✔ 可重用性
这是用注解也办不到的事,是 function 的一大卖点,如果同样或相似的逻辑,出现两次就该考虑是否写成 function 了,出现三次就要写检讨报告了 (?)
因为 function 的可重用性,可以大幅减少不必要的重复 code,feature 修改时只要修改一个地方,不会漏掉;要做测试的时候,也能保证结果相同,同时也提升了可维护性。
重点来了,什么时候会需要放到 function 重用?
其实。。。单纯就是。。。出现太多次的时候 ((拖走
比方说常用来发送 API request 的 axios
或fetch
,就非常适合包进 function 里面重用
const callApi = async(endpoint, method = 'get', body) => {
const requestUrl = `${API_URL}/${endpoint}`;
const options = {
headers: {
'content-type': 'application/json',
Authorization: TOKEN,
},
method
};
if (body) {
options.body = JSON.stringify(body);
}
try {
const response = await fetch(requestUrl, options);
if (response.ok) {
const json = await response.json();
return json;
}
return Promise.reject(response);
} catch (error) {
throw new Error(error);
}
};
呼叫时都只要一行
// GET
const productList = await callApi(`/product`);
// POST
const productResult = await callApi(`/product`, 'post', data);
✅命名
当然,光是拆散还不够,如果把这些小 function 命名成apple
、banana
之类的名字,肯定也是看不懂的 (应该说更加不懂),因此好的命名绝对是非常加分的!
而 function 的命名,某种程度上算是一种团队风格,只要团队中成员都能够好读、读懂。唯一的衡量标准应该就是 predictable,容易预测、容易猜到这个 function 要做什么,就是好命名。
而我自己遵守的主要是以下几点:
✔ 驼峰式命名 (camelCase)
这点算是非常好理解也容易上手,驼峰式就像是骆驼的背一样,凹下去凸起来凹下去凸起来,小写大写小写大写,所以比起全小写还容易阅读。
若是遇到缩写,则可以考虑使用底线 (_),虽然没有很建议,但也是个办法。
所以大概是这样:
applePie
bananaFish(?)
toDoList
HTML_Parser
✔ 动词 + 名词 (+ 修饰词)
function 本身就是用来「执行」一些任务的,所以必然是动词开头,而后面接名词则构成一个基本的语句 (主词大概是 user 吧!)。如果 V. + N. 的组合还不够清楚,还可以加上一些修饰词:
validateFormData
getProductList
findUserById
结语
function 经过拆解之后,重新命名给予逻辑意义,虽然功能都一样,但是当程式规模愈大,就愈能看出这样做的好处,下次当你有以下的感觉,不妨好好考虑「拆解 + 命名」吧!
- 这段 code 你觉得有点难懂,想要给它命名比较好理解
- 这段 code 你觉得有点冗长,想要拆出去瘦个身
- 这段 code 你觉得逻辑相似,想要重用一次解决