JS代碼簡潔之道–函數

函數的參數越少越好

有一個準則是:如果你的函數參數超過兩個,就應該改為對象傳入。

這樣做是合理的,因為當函數參數超過兩個時,參數順序開始變得難以記憶,而且容易出現一種很尷尬的情況:比如我只需要傳入第三個參數,因為其自身順序的原因,不得不補齊前兩個根本用不上的參數,以讓它順利排在第三位。

// bad
const createArticle = (title, author, date, content) => { }
createArticle('震驚,一男子竟偷偷干這事', 'zhangnan', '2020/06/29', '某天深夜,我喝多了點酒...')

// good
const createArticle = ({title, author, date, content}) => { }
createArticle({
    title: '震驚,一男子竟偷偷干這事',
    author: 'zhangnan',
    date: '2020/06/29',
    content: '某天深夜,我喝多了點酒...'
})

保持函數的單一職責原則

這是軟件開發領域亘古不變的一個真理,讓一個函數只專註於一件事情,能夠很好的解耦各個功能之間的聯繫,使得後續對某一個功能進行更改時,不用擔心會影響其他模塊。

假設我們現在有一個需求:現在需要給班裡的每一個同學發放假短訊通知,如果是男生,就用電信主機號來發,如果是女生,則用聯通主機號發,同時額外發送一封愛心郵件。實現如下:

// bad 代碼擠成一堆,很難理清
// 男生女生的通知方式還有所不同,後期如果要改動女生的通知方式,很難保證不會影響到男生
// 因為大家都寫在同一個函數里

const notifyStudents = (studentList) => {
    studentList.forEach(student => {
        if (student.gender === 'male') {
            const sender1 = new SmsSender({ carrier: '電信' });
            sender1.init();
            sender1.sendTo(student)   
        } else {
            const sender2 = new SmsSender({ carrier: '聯通' });
            sender2.init();
            sender2.sendTo(student);
            
            const sender3 = new EmailSender({ type: 'QQ郵箱' });
            sender3.connect();
            sender3.sendTo(student)
        }
    })
}


// good 函數拆分,各司其職,清晰明了
// 雖然看起來代碼量多了一點點
// 但是分工明確,互不影響
const initSmsSender = (carrier) => {
    const sender = new SmsSender({ carrier });
    sender.init();
}

const initEmailSender = (type) => {
    const sender = new EmailSender({ type });
    sender.connect();
}

const notifyMales = (studentList) => {
    const smsSender = initSmsSender('電信');
    const maleList = studentList.filter(student => student.gender === 'male');
    
    maleList.forEach(male => smsSender.sendTo(male));
}

const notifyFemales = (studentList) => {
    const smsSender = initSmsSender('聯通');
    const emailSender = initEmailSender('QQ郵箱');
    
    const femaleList = studentList.filter(student => student.gender === 'female');
    
    femaleList.forEach(female => {
        smsSender.sendTo(female);
        emailSender.sendTo(female);
    })
}

封裝條件語句

像有一些條件語句,可能存在很多與或非邏輯,如果直接寫在函數裏面,每次都需要重新理一遍,費時費力。把一堆條件語句封裝在一個函數裏面,不僅遵循單一職責原則,也將使得閱讀更加方便。

// bad
const shouldIBuyThisPhone = (phone) => {
    const {price, year, brand} = phone;
    if (price > 5000 && year === new Date.getFullYear() && brand === 'huawei') {
        // 馬上剁手
    }
}

// good
const isHuaweiFlagShipThisYear = ({ price, year, brand }) => {
    const HIGH_PRICE = 5000;
    return price > HIGH_PRICE && year === new Date.getFullYear() && brand === 'huawei'
}

const shouldIBuyThisPhone = (phone) => {
    if (isHuaweiFlagShipThisYear(phone)) {
        // 馬上剁手
    }
}

高層函數不要依賴具體實現

在一些動作函數中,常見的一種情況是傳一個flag參數,通過對標誌變量的判斷,做出不同的響應動作。

這樣其實是不太好的,因為這會使這個動作函數內部去維護一些判斷邏輯,如果flag參數比較多,函數內部的區分情況也會很多。

另外這裡也涉及一種思想:具體的差異實現應該由使用者提供,而不是統一執行者去維護。

或者稱之為依賴倒置原則高層模塊(打印)不應該依賴於實現細節(某個人的喜好)。

比如,我現在有一台打印機🖨️,小A喜歡用單面黑白橫向打印,小B喜歡用單面彩色豎向打印,小C喜歡用雙面彩色橫向打印等等等等。作為一台打印機,它需要去維護一個人員喜好列表嗎?如果有一千個人使用它,那它就需要維護一千條數據。

它只是一台打印機!告訴它配置,然後打印,就完事了!打印機只專註於打印這件事本身。

// bad 需要判斷標誌變量,同時做出不同的相應動作
const print = (person) => {
    if (person === 'A') {
        device.print({ 
            page: 1, 
            color: 'gray', 
            orientation: 'landscape' 
            
        })
    }
    
    else if (person === 'B') {
        device.print({ 
            page: 1, 
            color: 'colorful', 
            orientation: 'vertical' 
        })
    }
    
    else if (person === 'C') {
        device.print({ 
            page: 2, 
            color: 'colorful' ,
            orientation: 'landscape'
        })
    }
    
    ......
    
}


// good
const print = (config) => {
    device.print(config)
}

寫在最後

總結:

  • 函數傳參越少越好,多了改為對象傳入
  • 保持函數單一職責原則
  • 封裝條件語句
  • 高層函數不要依賴具體實現

另外,幫大佬發個位元組跳動今日頭條校園招聘宣傳,北廣深均有,Inspire creativity, enrich life。歡迎各位小鮮肉報名加入 今日頭條校招傳送門