<dfn id="hx5t3"><strike id="hx5t3"><em id="hx5t3"></em></strike></dfn>

    <thead id="hx5t3"></thead><nobr id="hx5t3"><font id="hx5t3"><rp id="hx5t3"></rp></font></nobr>

    <listing id="hx5t3"></listing>

    <var id="hx5t3"></var>
    <big id="hx5t3"></big>

      
      

      <output id="hx5t3"><ruby id="hx5t3"></ruby></output>
      <menuitem id="hx5t3"><dfn id="hx5t3"></dfn></menuitem>

      <big id="hx5t3"></big>

        帶你入門前端工程(十):重構

        譚光志

        《重構2》一書中對重構進行了定義:

        所謂重構(refactoring)是這樣一個過程:在不改變代碼外在行為的前提下,對代碼做出修改,以改進程序的內部結構。重構是一種經千錘百煉形成的有條不紊的程序整理方法,可以最大限度地減小整理過程中引入錯誤的概率。本質上說,重構就是在代碼寫好之后改進它的設計。

        重構和性能優化有相同點,也有不同點。

        相同的地方是它們都在不改變程序功能的情況下修改代碼;不同的地方是重構為了讓代碼變得更加容易理解、易于修改,性能優化則是為了讓程序運行得更快。這里還得重點提一句,由于側重點不同,重構可能使程序運行得更快,也可能使程序運行得更慢。

        重構可以一邊寫代碼一邊重構,也可以在程序寫完后,拿出一段時間專門去做重構。沒有說哪個方式更好,視個人情況而定。如果你專門拿一段時間來做重構,則建議在重構一段代碼后,立即進行測試。這樣可以避免修改代碼太多,在出錯時找不到錯誤點。

        重構的原則

        1. 事不過三,三則重構。即不能重復寫同樣的代碼,在這種情況下要去重構。
        2. 如果一段代碼讓人很難看懂,那就該考慮重構了。
        3. 如果已經理解了代碼,但是非常繁瑣或者不夠好,也可以重構。
        4. 過長的函數,需要重構。
        5. 一個函數最好對應一個功能,如果一個函數被塞入多個功能,那就要對它進行重構了。(4 和 5 不沖突)
        6. 重構的關鍵在于運用大量微小且保持軟件行為的步驟,一步步達成大規模的修改。每個單獨的重構要么很小,要么由若干小步驟組合而成。

        重構的手法

        《重構2》這本書中,介紹了多達上百種重構手法。但我覺得以下八種是比較常用的:

        1. 提取重復代碼,封裝成函數
        2. 拆分功能太多的函數
        3. 變量/函數改名
        4. 替換算法
        5. 以函數調用取代內聯代碼
        6. 移動語句
        7. 折分嵌套條件表達式
        8. 將查詢函數和修改函數分離

        提取重復代碼,封裝成函數

        假設有一個查詢數據的接口 /getUserData?age=17&city=beijing?,F在需要做的是把用戶數據:{ age: 17, city: 'beijing' } 轉成 URL 參數的形式:

        let result = ''
        const keys = Object.keys(data)  // { age: 17, city: 'beijing' }
        keys.forEach(key => {
            result += '&' + key + '=' + data[key]
        })
        
        result.substr(1) // age=17&city=beijing

        如果只有這一個接口需要轉換,不封裝成函數是沒問題的。但如果有多個接口都有這種需求,那就得把它封裝成函數了:

        function JSON2Params(data) {
            let result = ''
            const keys = Object.keys(data)
            keys.forEach(key => {
                result += '&' + key + '=' + data[key]
            })
        
            return result.substr(1)
        }

        拆分功能太多的函數

        下面是一個打印賬單的程序:

        function printBill(data = []) {
            // 匯總數據
            const total = {}
            data.forEach(item => {
                if (total[item.department] === undefined) {
                    total[item.department] = 0
                }
        
                total[item.department] += item.value
            })
            // 打印匯總后的數據
            const keys = Object.keys(total)
            keys.forEach(key => {
                console.log(`${key} 部門:${total[key]}`)
            })
        }
        
        printBill([
            {
                department: '銷售部',
                value: 89,
            },
            {
                department: '后勤部',
                value: 132,
            },
            {
                department: '財務部',
                value: 78,
            },
            {
                department: '總經辦',
                value: 90,
            },
            {
                department: '后勤部',
                value: 56,
            },
            {
                department: '總經辦',
                value: 120,
            },
        ])

        可以看到這個 printBill() 函數實際上包含有兩個功能:匯總和打印。我們可以把匯總數據的代碼提取出來,封裝成一個函數。這樣 printBill() 函數就只需要關注打印功能了。

        function printBill(data = []) {
            const total = calculateBillData(data)
            const keys = Object.keys(total)
            keys.forEach(key => {
                console.log(`${key} 部門:${total[key]}`)
            })
        }
        
        function calculateBillData(data) {
            const total = {}
            data.forEach(item => {
                if (total[item.department] === undefined) {
                    total[item.department] = 0
                }
        
                total[item.department] += item.value
            })
        
            return total
        }

        變量/函數改名

        無論是變量命名,還是函數命名,都要盡量讓別人明白你這個變量/函數是干什么的。變量命名的規則著重于描述“是什么”,函數命名的規則著重于描述“做什么”。

        變量

        const a = width * height

        上面這個變量就不太好,a 很難讓人看出來它是什么。

        const area = width * height

        改成這樣就很好理解了,原來這個變量是表示面積。

        函數

        function cache(data) {
            const result = []
            data.forEach(item => {
                if (item.isCache) {
                    result.push(item)
                }
            })
        
            return result
        }

        這個函數名稱會讓人很疑惑,cache 代表什么?是設置緩存還是刪除緩存?再一細看代碼,噢,原來是獲取緩存數據。所以這個函數名稱改成 getCache() 更加合適。

        替換算法

        function foundPersonData(person) {
            if (person == 'Tom') {
                return {
                    name: 'Tom',
                    age: 18,
                    id: 21,
                }
            }
        
            if (person == 'Jim') {
                return {
                    name: 'Jim',
                    age: 20,
                    id: 111,
                }
            }
        
            if (person == 'Lin') {
                return {
                    name: 'Lin',
                    age: 19,
                    id: 10,
                }
            }
        
            return null
        }

        上面這個函數的功能是根據用戶姓名查找用戶的詳細信息,可以看到這個函數做了三次 if 判斷,如果沒找到數據就返回 null。這個函數不利于擴展,每多一個用戶就得多寫一個 if 語句,我們可以用更方便的“查找表”來替換它。

        function foundPersonData(person) {
            const data = {
                'Tom': {
                    name: 'Tom',
                    age: 18,
                    id: 21,
                },
                'Jim': {
                    name: 'Jim',
                    age: 20,
                    id: 111,
                },
                'Lin': {
                    name: 'Lin',
                    age: 19,
                    id: 10,
                },
            }
        
            return data[person] || null
        }

        修改后代碼結構看起來更加清晰,也方便未來做擴展。

        以函數調用取代內聯代碼

        如果一些代碼所做的事情和已有函數的功能重復,那就最好用函數調用來取代這些代碼。

        let hasApple = false
        for (const fruit of fruits) {
            if (fruit == 'apple') {
                hasApple = true
                break
            }
        }

        例如上面的代碼,可以用數組的 includes() 方法代替:

        const hasApple = fruits.includes('apple')

        修改后代碼更加簡潔。

        移動語句

        讓存在關聯的東西一起出現,可以使代碼更容易理解。如果有一些代碼都是作用在一個地方,那么最好是把它們放在一起,而不是夾雜在其他的代碼中間。最簡單的情況下,只需使用移動語句就可以讓它們聚集起來。就像下面的示例一樣:

        const name = getName()
        const age = getAge()
        let revenue
        const address = getAddress()
        // ...
        const name = getName()
        const age = getAge()
        const address = getAddress()
        
        let revenue
        // ...

        由于兩塊數據區域的功能是不同的,所以除了移動語句外,我還在它們之間空了一行,這樣讓人更容易區分它們之間的不同。

        折分嵌套條件表達式

        當很多的條件表達式嵌套在一起時,會讓代碼變得很難閱讀:

        function getPayAmount() {
            if (isDead) {
                return deadAmount()
            } else {
                if (isSeparated) {
                    return separatedAmount()
                } else if (isRetired) {
                    return retireAmount()
                } else {
                    return normalAmount()
                }
            }
        }
        function getPayAmount() {
            if (isDead) return deadAmount()
            if (isSeparated) return separatedAmount()
            if (isRetired) return retireAmount()
            return normalAmount()
        }

        將條件表達式拆分后,代碼的可閱讀性大大增強了。

        將查詢函數和修改函數分離

        一般的查詢函數都是用于取值的,例如 getUserData()、getAget()、getName() 等等。有時候,我們可能為了方便,在查詢函數上附加其他功能。例如下面的函數:

        function getValue() {
            let result = 0
            this.data.forEach(val => result += val)
            // 這里插入了一個奇怪的操作
            sendBill()
            return result
        }

        千萬不要這樣做,函數很重要的功能是職責分離。所以我們要將它們分開:

        function getValue() {
            let result = 0
            this.data.forEach(val => result += val)
            return result
        }
        
        function sendBill() {
            // ...
        }

        這樣函數的功能就很清晰了。

        小結

        古人云:盡信書,不如無書?!吨貥?》也不例外,在看這本書的時候一定要帶著批判性的目光去閱讀它。

        里面介紹的重構手法有很多,多達上百種,但這些手法不一定適用所有人。所以一定要有取舍,將里面有用的手法摘抄下來,時不時的看幾遍。這樣在寫代碼時,重構才能像呼吸一樣自然,即使用了你也不知道。

        參考資料

        帶你入門前端工程 全文目錄:

        1. 技術選型:如何進行技術選型?
        2. 統一規范:如何制訂規范并利用工具保證規范被嚴格執行?
        3. 前端組件化:什么是模塊化、組件化?
        4. 測試:如何寫單元測試和 E2E(端到端) 測試?
        5. 構建工具:構建工具有哪些?都有哪些功能和優勢?
        6. 自動化部署:如何利用 Jenkins、Github Actions 自動化部署項目?
        7. 前端監控:講解前端監控原理及如何利用 sentry 對項目實行監控。
        8. 性能優化(一):如何檢測網站性能?有哪些實用的性能優化規則?
        9. 性能優化(二):如何檢測網站性能?有哪些實用的性能優化規則?
        10. 重構:為什么做重構?重構有哪些手法?
        11. 微服務:微服務是什么?如何搭建微服務項目?
        12. Severless:Severless 是什么?如何使用 Severless?
        閱讀 461
        5.6k 聲望
        10.3k 粉絲
        0 條評論
        你知道嗎?

        5.6k 聲望
        10.3k 粉絲
        宣傳欄
        一本到在线是免费观看_亚洲2020天天堂在线观看_国产欧美亚洲精品第一页_最好看的2018中文字幕