iT邦幫忙

2021 iThome 鐵人賽

DAY 7
2
Modern Web

Javascript 從寫對到寫好系列 第 7

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 6 - Function 時空旅行 (1) - 參數優化
下一篇
Day 8 - Functional Programming 初探 (1) - HoF 與 Side Effects
系列文
Javascript 從寫對到寫好30
0
TD
iT邦新手 4 級 ‧ 2021-09-22 22:53:28

認識一些常見的動詞,對於命名 function 還蠻有幫助的!很不錯的分享 :)

ycchiuuuu iT邦新手 5 級 ‧ 2021-09-23 20:10:19 檢舉

太棒了~ 不過命名在這篇算是一小部分而已,我有點想要拉出去特別寫一篇跟大家討論,覺得真的很重要!

名字取得好,看 code 沒煩惱XD
0
pjchender
iT邦新手 4 級 ‧ 2021-09-23 01:26:50

指關節式命名/images/emoticon/emoticon37.gif
讓我想到了大月小月

ycchiuuuu iT邦新手 5 級 ‧ 2021-09-23 20:07:34 檢舉

真是太巧了!人類的身體果然是宇宙的產物!

0
s941407
iT邦新手 5 級 ‧ 2021-10-05 01:32:19

命名真的很重要~~~
可以去看看《易讀程式之美學》這本書XD

ycchiuuuu iT邦新手 5 級 ‧ 2021-10-05 23:57:33 檢舉

推!還有英文也要好好讀XD

我要留言

立即登入留言