Day 7 - Function 时空旅行 (2) - 拆解与命名

前言

今天是 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);

这是模拟编辑个人资讯的表单,可以填上姓名、年龄、性别三个栏位,按下送出表单的时候,会做到以下三件事:

  1. 检核必填栏位(有在 Day 4 提到过)
  2. 将前端栏位转换成後端需要的栏位与格式
  3. 发送 API request 到後端

只有三个栏位,其实算是一个相对简单的范例,但可以看到要送出表单时,submitForm 多达 22 行(而且 fetch 还被我偷懒简化过)。

如果同样的目的,我们将 submitForm 拆解,分别用以下的 function 来处理:

  1. validateFormData
  2. prepareSubmitData
  3. 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 才做得到,比较懒人一点做法,可以加上注解:

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
    };
    
    // 发送 API request 到後端
    fetch('my-backend-API', {
        method: 'POST',
        body: JSON.stringify(submitValue)
    });
};

可重用性

这是用注解也办不到的事,是 function 的一大卖点,如果同样或相似的逻辑,出现两次就该考虑是否写成 function 了,出现三次就要写检讨报告了(?)

因为 function 的可重用性,可以大幅减少不必要的重复 code,feature 修改时只要修改一个地方,不会漏掉;要做测试的时候,也能保证结果相同,同时也提升了可维护性。

重点来了,什麽时候会需要放到 function 重用?

其实。。。单纯就是。。。出现太多次的时候((拖走

比方说常用来发送 API request 的 axiosfetch,就非常适合包进 function 里面重用(部分大写变数,不是本次重点,可自行体会XD):

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 命名成 applebanana 之类的名字,肯定也是看不懂的(应该说更加不懂),因此好的命名绝对是非常加分的!

而 function 的命名,某种程度上算是一种团队风格,只要团队中成员都能够好读、读懂。唯一的衡量标准应该就是 predictable,容易预测、容易猜到这个 function 要做什麽,就是好命名。

而我自己遵守的主要是以下几点:

驼峰式命名(camelCase)

这点算是非常好理解也容易上手,驼峰式就像是骆驼的背一样,凹下去凸起来凹下去凸起来,小写大写小写大写,所以比起全小写还容易阅读。

其实照这样说,手握拳的时候,手指根部的四个关节也是凹下去凸起来啊,怎麽不叫指关节式命名(?),是因为多一个字吗(?)

若是遇到缩写,则可以考虑使用底线(_),虽然没有很建议,但也是个办法。

所以大概是这样:

applePie
bananaFish(?)
toDoList
HTML_Parser

动词 + 名词 (+ 修饰词)

function 本身就是用来「执行」一些任务的,所以必然是动词开头,而後面接名词则构成一个基本的语句(主词大概是 user 吧!)。如果 V. + N. 的组合还不够清楚,还可以加上一些修饰词:

validateFormData
getProductList
findUserById

不同动词用於不同意义

确保每个动词都意义一致,尤其是一些翻成中文相似的动词:

patch 用於部分更新
put 用於替换

fetch 用於发送 request
get 取得的万用字(?)

结语

function 经过拆解之後,重新命名给予逻辑意义,虽然功能都一样,但是当程序规模愈大,就愈能看出这样做的好处,下次当你有以下的感觉,不妨好好考虑「拆解+命名」吧!

  1. 这段 code 你觉得有点难懂,想要给它命名比较好理解
  2. 这段 code 你觉得有点冗长,想要拆出去瘦个身
  3. 这段 code 你觉得逻辑相似,想要重用一次解决

离开了熟悉的家乡
改名换姓
在银河的另一端相遇

参考资料

学习有意义的命名


<<:  Day 8 - 使用 Order API 建立测试订单

>>:  Day10-D3 Transition 动画

[Day25] HTB Granny

URL : https://app.hackthebox.eu/machines/14 IP : ...

[Day - 28] - 运用Spring MockMvc 迈向自动化测试之路

Abstract 小编先前每个范例都有提供服务(Service)层级的测试案例,但部分开发者会开发许...

[用 Python 解 LeetCode] (001) 27. Remove Element

题干懒人包 输入一个数组及一个数,最後输出一个数值代表非重复数值的数量,然後以下几点要注意: 只能修...

App 在发布到play商店後 Firebase Authentication 无法登入问题解决

身为一个App的开发新手常常会遇到一些莫名其妙又难以解决的问题,直到找到问题答案才发现根本是自己愚蠢...

Day 26 运用「目标客户比对」与现有客户交流并接触新客户

我们可运用「目标客户比对」来增加曝光机会: 向现有客户交叉销售他们可能喜欢的其他产品或服务 找出与理...