寫好一個函數(shù)的個人見解

前言
在 JS 中,除了變量,用的最多的應(yīng)該就是函數(shù)了,函數(shù)是 Javascript 的第一公民。
要寫好一個函數(shù),個人認(rèn)為,可以從以下幾點來編寫:
命名準(zhǔn)確函數(shù)注釋函數(shù)參數(shù)函數(shù)的返回
本文對以上幾點做了梳理和總結(jié),希望能對大家有所幫助。
正文
1. 命名準(zhǔn)確
函數(shù)名稱
這應(yīng)該是最基本的要求了,函數(shù)的命名需要明確,語義清晰,簡單概括函數(shù)的功能。我們不要想著代碼簡短而縮短函數(shù)名稱,這并不會提高什么性能或效率,相反,一個函數(shù)名稱若不夠清晰,往往其他人無法理解。
除了一些都所有人知道的名次(我們這邊的 SKU )縮寫外,一些相對來講比較少人知道的專業(yè)名次就盡量不要使用縮寫。
盡量使用動詞,比如:getXxxxx、setXxxxx,動詞在前面,語義就能更加清晰。
參數(shù)命名
參數(shù)的命名同樣重要,我們都強調(diào)語義化,參數(shù)命名讓調(diào)用者更清晰的知道該傳入什么,對應(yīng)什么參數(shù)。當(dāng)然,像一些通用命名還是可接受的,像 callback,fn,即使不看注釋,往往我也知道這里的參數(shù)要做什么,傳什么。
2. 函數(shù)注釋
當(dāng)我們的命名準(zhǔn)確后,我們也不可能讓每一個看代碼的人都通過名稱就知道這個函數(shù)在做什么,這個參數(shù)代表什么。因此,函數(shù)一定要寫注釋,具體的交互代碼不寫,但函數(shù)的功能,參數(shù)至少是不可避免的。
/**
* 時間格式化工具函數(shù)
*
* @param { (Date | number) } date - 時間
* @param { string } unit - 轉(zhuǎn)換格式
*/
export const timeFormat = (date: Date | number | string, unit: string) => {
if (!date) {
return ''
}
if (typeof date === 'string') return date;
if (typeof date === 'number') {
date = new Date(date);
}
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const hour = date.getHours();
const minute = date.getMinutes();
const second = date.getSeconds();
if (unit === 'year') return `${year}`;
if (unit === 'month') return `${year}-${month}`;
if (unit === 'day') return `${year}-${month}-${day}`;
if (unit === 'hour') return `${year}-${month}-${day} ${hour}`;
if (unit === 'minute') return `${year}-${month}-${day} ${hour}:${minute}`;
if (unit === 'second') return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
}
參數(shù)注釋
像上面這個例子,參數(shù)前面都加了一個 @param,表明這個是參數(shù)的注釋。
一般的格式為 @param { type } 參數(shù) - 參數(shù)解釋。
type 表明的是參數(shù)的類型,比如 string,number,當(dāng)有多個參數(shù)類型的時候,可以這么來標(biāo)識 { (string|string[]) },表示這個參數(shù)可以是字符串或者字符串?dāng)?shù)組。
還有其他的參數(shù)注釋可以這么來寫
對象屬性:需要解釋對象的每一個屬性
/**
* Assign the project to an employee.
* @param {Object} employee - The employee who is responsible for the project.
* @param {string} employee.name - The name of the employee.
* @param {string} employee.department - The employee's department.
*/
Project.prototype.assign = function(employee) {
// ...
};
可選參數(shù):
有兩種語法 JSDoc 和 Google Closure Compiler
JSDoc 語法
/**
* 時間格式化工具函數(shù)
*
* @param { (Date | number) } date - 時間
* @param { string } [unit] - 轉(zhuǎn)換格式
*/
export const timeFormat = (date: Date | number | string, unit: string) => {
// ...
}
Google Closure Compiler 語法
/**
* 時間格式化工具函數(shù)
*
* @param { (Date | number) } date - 時間
* @param { string= } unit - 轉(zhuǎn)換格式
*/
export const timeFormat = (date: Date | number | string, unit: string) => {
// ...
}
默認(rèn)值:
/**
* 時間格式化工具函數(shù)
*
* @param { (Date | number) } date - 時間
* @param { string } [unit = 'second'] - 轉(zhuǎn)換格式
*/
export const timeFormat = (date: Date | number | string, unit: string) => {
// ...
}
函數(shù)注釋
函數(shù)的功能點需要表明,一個函數(shù)一個功能,這就是我們說的 單一功能,一個函數(shù)就做一件事,因此注釋也只會說明函數(shù)做了哪一件事。
其他注釋
除了 @param 之外,還有其他的類型
3. 函數(shù)參數(shù)
參數(shù)默認(rèn)值
有時候,當(dāng)一個函數(shù)的參數(shù)不那么必要,或者某一個值相對應(yīng)用較多,就應(yīng)該考慮加上一個默認(rèn)值,比如上面的時間轉(zhuǎn)換工具,參數(shù)就應(yīng)該這么寫。
export const timeFormat = (date: Date, unit = 'second') => {
// ...
}
當(dāng)我們的業(yè)務(wù)中需要 YYYY-MM-DD hh:mm:ss 這樣的格式較多的時候,unit 默認(rèn)就是 second, 這樣,當(dāng)我們調(diào)用函數(shù)的時候,可以只傳一個 date 參數(shù),而不需要在每一處調(diào)用的代碼都寫上 timeFormat(new Date(), 'second')。
對象參數(shù)
在一段打印代碼中,參數(shù)多達十幾個。像下面這個 打印函數(shù)。
async function printer_proxy_print(
html_str: string,
file_path: string,
device: string | undefined,
orientation: number,
printer_mode: string,
width: number,
height: number,
scale: number,
from: number,
to: number,
left_offset: number,
top_offset: number,
pdf_tools: string | undefined,
begin_page = 1,
end_page = 1,
repeat_times = 1,
print_type: string
) {
// ...
}
這個時候,我們可以給參數(shù)默認(rèn)值,這樣可以只傳前面幾個必要的參數(shù),像這樣調(diào)用。
async function printer_proxy_print(
html_str: string,
file_path: string,
device = 'pc',
orientation = 'xxx',
printer_mode = 'xxx',
width = 123,
height = 123,
scale = 123,
from = 123,
to = 123,
left_offset = 123,
top_offset = 123,
pdf_tools = 123,
begin_page = 1,
end_page = 1,
repeat_times = 1,
print_type = 'base64'
) {
// ...
}
await printer_proxy_print(html_str, file_path);
上面的方法看似可行,實際上,當(dāng)我中間某個參數(shù)不一樣的時候,我就需要把這個參數(shù)前面的參數(shù)都傳一遍。這樣顯然不可行。所以當(dāng)參數(shù)多的時候,我們需要用對象解構(gòu)的方式傳參。
async function printer_proxy_print({
html_str,
file_path,
device = 'pc',
orientation = 'xxx',
printer_mode = 'xxx',
width = 123,
height = 123,
scale = 123,
from = 123,
to = 123,
left_offset = 123,
top_offset = 123,
pdf_tools = 123,
begin_page = 1,
end_page = 1,
repeat_times = 1,
print_type = 'base64'
}) {
// ...
}
await printer_proxy_print({html_str, file_path});
解構(gòu)的好處便是我可以隨便傳我想要的某幾個參數(shù),而不用在意順序問題。不過像這么多參數(shù)的函數(shù)往往存在問題(具體問題具體分析)。也就是下面提到的參數(shù)數(shù)量問題。
參數(shù)數(shù)量
一個函數(shù)的參數(shù)越少越好,最多不應(yīng)該超過3個,參數(shù)多往往意味著關(guān)系多,邏輯交叉相對也就多了起來。在進行測試的時候,往往也就很難覆蓋到所有條件,出問題概率也就加大了。
參數(shù)多的時候,有時候也意味著功能多,就違背了 單一功能 的原則。
參數(shù)類型防御
在 TS 開發(fā)前,我們不知道用戶會傳什么東西進來,這時候往往容易產(chǎn)生類型錯誤,又或者,我們想實現(xiàn)兼容,像前面的 timeFormat 函數(shù),我們希望用戶調(diào)用的時候,可以是想對 時間對象 格式化,也可以是對 時間戳 格式化,那我們就需要做一個防御處理。
if (!date) {
return ''
}
if (typeof date === 'string') return date;
if (typeof date === 'number') {
date = new Date(date);
}
不過值得注意的是,即使我們用上了 TS,在大多數(shù)情況下,我們確實可以避免參數(shù)類型問題,但是這并不絕對,因為我們有時候會直接接受 接口 返回的數(shù)據(jù)。
我們常說,永遠不要相信用戶的輸入,同樣,接口返回的數(shù)據(jù)我也不信,我們不能保證,后端不會出錯,約定好的參數(shù)是數(shù)組類型,怎么空的時候,你給我個 null 呢?
當(dāng)然這些情況有時候需要去試錯,有時候我們能想到的可能,不要偷懶,給寫上類型判斷吧。
4. 函數(shù)的返回
冪等
什么叫冪等,簡單來說,輸入什么輸出什么是固定的,入?yún)Q定了出參,不管調(diào)用多少次,只要輸入一樣,結(jié)果應(yīng)該保持一樣。
簡單的例子:
function sum(a: number, b: number) {
return a + b;
}
冪等函數(shù)具有可維護性,相對容易進行單元測試。
純函數(shù)
純函數(shù)在冪等的條件下,還要求沒有副作用。
舉個例子:
const dog = {
name: 'puppy',
age: 2,
weight: 30,
}
if (!dog.color) {
console.log('has no color');
}
function addColor(dog) {
dog.color = 'white';
}
addColor(dog);
console.log(dog); // {name: "puppy", age: 2, weight: 30, color: "white"}
可以看到,addColor 函數(shù)修改了 dog 對象的屬性,也就是產(chǎn)生了副作用,我們應(yīng)該怎么做修改會合理點呢?可以像下面這樣:
function addColor(dog) {
let copyDog = Object.assign({}, dog);
copyDog.color = 'white';
return copyDog;
}
這樣一來,dog 對象的屬性就不會修改,addColor 函數(shù)是純函數(shù)。
return null
null 在進行處理的時候相對麻煩,需要進行判斷,導(dǎo)致了額外的代碼,應(yīng)當(dāng)返回空對象,或者是空數(shù)組,或者拋出異常。
總結(jié)
良好的代碼,自帶注釋。
希望大家都能養(yǎng)成好的習(xí)慣。
謝謝。
