<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          重構(gòu)指北——《重構(gòu),改善既有代碼設(shè)計》精讀

          共 18644字,需瀏覽 38分鐘

           ·

          2021-08-29 12:33

          本文總結(jié)自筆者的開發(fā)經(jīng)驗以及 Martin Fowler 的《重構(gòu),改善既有代碼設(shè)計》讀書體會,希望能幫助更多的開發(fā)者了解重構(gòu),重構(gòu)并不是想象中的重活,它也可以很簡單。Commit a feature,review and refactor。

           大廠技術(shù)  高級前端  Node進階

          點擊上方 程序員成長指北,關(guān)注公眾號

          回復(fù)1,加入高級Node交流群

          1. 什么是重構(gòu)

          這里先給重構(gòu)下一個定義:改善既有代碼的設(shè)計。

          具體來說就是在不改變代碼功能行為的情況下,對其內(nèi)部結(jié)構(gòu)的一種調(diào)整。需要注意的是,重構(gòu)不是代碼優(yōu)化,重構(gòu)注重的是提高代碼的可理解性與可擴展性,對性能的影響可好可壞。而性能優(yōu)化則讓程序運行的更快,當然,最終的代碼可能更難理解和維護。

          2. 為什么要重構(gòu)

          2.1. 改善程序的內(nèi)部設(shè)計

          如果沒有重構(gòu),在軟件不停的版本迭代中,代碼的設(shè)計只會越來越腐敗,導(dǎo)致軟件開發(fā)寸步難行。

          這里的原因主要有兩點:

          • 人們只為了短期目的而修改代碼時,往往沒有完全理解整體的架構(gòu)設(shè)計(在大項目中常有這種情況,比如在不同的地方,使用完全相同的語句做著同樣的事情),代碼就會失去自己的結(jié)構(gòu),代碼結(jié)構(gòu)的流失具有累積效應(yīng),越難看出代碼所代表的設(shè)計意圖,就越難保護其設(shè)計。
          • 我們幾乎不可能預(yù)先做出完美的設(shè)計,以面對后續(xù)未知的功能開發(fā),只有在實踐中才能找到真理。

          所以想要體面又快速的開發(fā)功能,重構(gòu)必不可少。

          2.2. 使得代碼更容易理解

          在開發(fā)中,我們需要先理解代碼在做什么,才能著手修改,很多時候自己寫的代碼都會忘記其實現(xiàn),更不論別人的代碼??赡茉谶@段代碼中有一段糟糕的條件判斷邏輯,又或者其變量命名實在糟糕又確實注釋,需要花上好一段時間才能明白其真正用意。

          合理的重構(gòu)能讓代碼“自解釋”,以方便理解,無論對于協(xié)同開發(fā),還是維護先前自己實現(xiàn)的功能,對代碼的開發(fā)有著立竿見影的效果。

          2.3. 提高開發(fā)的速度 && 方便定位錯誤

          提高開發(fā)的速度可能有點“反直覺”,因為重構(gòu)在很多時候看來是額外的工作量,并沒有新的功能和特性產(chǎn)出,但是減少代碼的書寫量(復(fù)用模塊),方便定位錯誤(代碼結(jié)構(gòu)優(yōu)良),這些能讓我們在開發(fā)的時候節(jié)省大量的時間,在后續(xù)的開發(fā)中“輕裝上陣”。

          3. 重構(gòu)的原則

          3.1. 保持當下的編程狀態(tài)

          Kent Beck 提出了“兩頂帽子”的比喻,在開發(fā)軟件時,把自己的時間分配給兩種截然不同的行為:添加新功能和重構(gòu),添加新功能的時候,不應(yīng)該修改既有的代碼,只管添加新功能,并讓程序正確運行;在重構(gòu)時就不能添加新功能,只管調(diào)整代碼結(jié)構(gòu),只有在絕對必要時才能修改相關(guān)代碼。

          在開發(fā)過程中,我們可能經(jīng)常變換“帽子”,在新增功能的時候會意識到,如果把程序結(jié)構(gòu)改一下,功能的添加會容易很多,或者實現(xiàn)會更加優(yōu)雅,于是一會換一頂“帽子”,一邊重構(gòu),一邊新增新功能。這很容易讓自己產(chǎn)生混亂,對自己的代碼難以理解。

          任何時候我們都要清楚自己戴的是哪一頂“帽子”,并專注自己的編程狀態(tài),這讓我們的目標清晰且過程可控,能對自己編碼的進度有掌握。

          3.2. 可控制的重構(gòu)

          重構(gòu)的過程并非一蹴而就,如果因為重構(gòu)影響了自己對時間的掌控,對函數(shù)功能的掌控,那么你就應(yīng)該及時停下,思考你的行為是否值得。我們必須保證程序的可用性與時間的可控性,并且要保證我們的步伐要小,確保每一步都有 git 管理和代碼測試,否則你會陷入程序不可用的中間態(tài),更可怕的是你忘記了之前代碼的樣子!

          在本文后續(xù)章節(jié)何時開始重構(gòu)中會有更多這方面的介紹,這里先跳過不談。

          4. 識別代碼的臭味道

          重構(gòu)世界的規(guī)則我們已經(jīng)了解,下面有一份重構(gòu)指北,是時候去回顧代碼里的片段,識別它們身上的臭味并將其消滅!

          當然,如果覺得其中的內(nèi)容過長,可跳過不看,也可匆匆略過,日后回顧也是不錯的選擇。

          4.1. 神秘命名

          我承認,在偵探小說透過神秘的文字去猜測故事情節(jié)是一種很棒的體驗,但在代碼中,這往往讓程序員困擾!需要花費大量時間去探究一個變量的作用和一個函數(shù)的功能,甚者需要在該代碼片段中加入大量注釋。

          這里并不是批評注釋這種行為,而是一個優(yōu)秀的代碼片段和編碼命名,往往能讓代碼自解釋,減少一些不必要的注釋,閱讀代碼如同閱讀文字一樣流暢。

          由此可見,變量命名實在是任何重構(gòu)時都要第一步更正的地方,但也很遺憾的是,命名是編程中最難的幾件事之一。

          • 需要在簡潔性和命名長度中平衡。
          • 需要統(tǒng)一變量命名的風格,特別是一個整個團隊!因為變量命名往往不在代碼風格檢測之內(nèi)!
          • 需要變量的名字既能做到彼此關(guān)聯(lián),又對其信息的識別互不干擾,想象一下,在一個代碼片段中在存在著 cgi  cgiList 等變量,你可以直接從中讀出之間的關(guān)聯(lián),若是cgi  list 呢,它們之間的聯(lián)系就丟失了,又或者同時出現(xiàn)了 people  human 兩個變量,這是不是讓你產(chǎn)生了疑惑?
          • 需要良好的英語水平。

          變量命名并沒有確切細致的教程,也很難強制統(tǒng)一,一般符合以下三點即可。

          • 有意義的
          • 相關(guān)聯(lián)的
          • 不復(fù)用的

          實踐是檢驗質(zhì)量的唯一標準,如果你的變量能夠讓其他同學見名知意,就說明你是正確的!

          4.2. 重復(fù)代碼

          提煉重復(fù)代碼無疑是重構(gòu)中最經(jīng)典的手法,很多時候我們會在不同的地方寫下相似的代碼,又或者拷貝一份副本至當前上下文中,它們之間的差異寥寥無幾。

          這時會出現(xiàn)一個很棘手的問題,當需要去修改其中的功能時,你必須找出所有的副本一一修改,這讓人在閱讀和修改代碼時都很容易出現(xiàn)紕漏。所以我們要拒絕重復(fù)造輪子,盡量實現(xiàn)高可復(fù)用性的代碼。

          我們可以將其抽離成一個公共函數(shù),并以其功能作為命名。

          4.3. 過長函數(shù)

          函數(shù)越長,就越難以理解,與之帶來的還有高耦合性,不利于拆解重組。

          目前普遍認為代碼的行數(shù)不要超出一個屏幕的范圍,因為這樣會造成上下滾動,會增大出錯的概率。根據(jù)騰訊代碼規(guī)范,一個函數(shù)的代碼行數(shù)不要超出 80 行。

          直接看下面這兩份代碼,它們實現(xiàn)的是同樣的功能,不用理解它們的含義(也沒有任何含義),僅僅簡單對比視覺效果,感覺如何?

          // 重構(gòu)前
          function changeList(list) {
            console.log('some operation of list')
            for (let i=0; i<list.length; i++) {
              // do sth
            }
            
            console.log('conditional judgment')
            let result
            if (list.length < 4) {
              result = list.pop()
            } else {
              result = list.shift()
            }
              
              
            const today = new Date(Date.now())
            const dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30);
            
            result.dueDate = dueDate
           
            return result
          }
          // 重構(gòu)后
          function changeList(list) {
            console.log('some operation of list')
            operationOfList(list)
            
            console.log('conditional judgment')
            const result = judgment(list)
            
            result.dueDate = getRecordTime()
            return result
          }

          function operationOfList(list) {
            for (let i=0; i<list.length; i++) {
              // do sth
            }
            return list
          }

          function judgment(list) {
            let result
            if (list.length < 4) {
              result = list.pop()
            } else {
              result = list.shift()
            }
            return result
          }

          function getRecordTime() {
            const today = new Date(Date.now())
            const dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30);
            return dueDate
          }

          事實證明,拆分函數(shù)有利于更好更快地理解代碼,以及降低耦合度,更方便地重新“組裝”新函數(shù)。當然你也可能此時會覺得很麻煩,屬于多此一舉,但是重構(gòu)的目標就是要保證代碼的可讀性。如果有一天你想要修改或增加該函數(shù)的功能,看到重構(gòu)后的代碼你會感謝自己的。

          4.4. 數(shù)據(jù)泥團 && 過長參數(shù)

          數(shù)據(jù)泥團(魔法數(shù)字),顧名思義就是一幫數(shù)據(jù)無規(guī)則的結(jié)合在一起,這讓人對其難以把控。

          如果說有多個參數(shù)互相搭配,又或者說某些數(shù)據(jù)總是成群結(jié)隊出現(xiàn),那我們就該把這團泥塑造成一個具體的形象,將其封裝成一個數(shù)據(jù)對象。

          function addUser(name, gender, age) {
            // some other codes
            ...
            // officeAreaCode 與 officeNumber 成對出現(xiàn),如果缺少 officeNumber,那么 officeAreaCode 也沒有意義,這里應(yīng)該組合起來
            clumps.officeAreaCode = '+86'
            clumps.officeNumber = 13688888888;
            
            return person
          }


          // 重構(gòu)后
          class TelephoneNumber(officeAreaCode, officeNumber) {
            constructor() {
              this.officeAreaCode = officeAreaCode
              this.officeNumber = officeNumber
            }
          }
          // 參數(shù)融合
          function addUser(person) {
            // some other codes
            ...
            // 封裝數(shù)據(jù)
            person.telephone = new TelephoneNumber('+86''13688888888')
          }

          4.5. 全局數(shù)據(jù)

          很多時候我們都不可避免地使用全局數(shù)據(jù),哪怕只有一個變量,全局數(shù)據(jù)對我們的管理提出了更高的要求。因為哪怕一個小小的更改,都可能引起很多地方出現(xiàn)問題,更可怕的是在無意間觸發(fā)了這種更改。

          全局數(shù)據(jù)也阻礙了程序的可預(yù)測性,由于每個函數(shù)都能訪問這些變量,因此越來越難弄清那個函數(shù)實際讀寫這些變量,要理解一個程序的工作方式,幾乎必須考慮修改全局狀態(tài)的每個函數(shù),使得調(diào)試變得困難。

          如果不依靠全局變量,則可以根據(jù)不同函數(shù)之間傳遞的狀態(tài),這樣以來,就能更好的了解每個功能的作用,因為你無需考慮全局變量。

          let globalData = 1

          // bad
          function foo() {
            globalData = 2
          }

          // bad
          function fuu() {
            globalData = {
              a1
            }
          }

          現(xiàn)在,我們要對全局數(shù)據(jù)進行一些封裝,控制對其的訪問。

          // 使用常量 good
          const constantData = 1

          // 封裝變量操作 good
          let globalData = 1
          function getGlobalData() {
            return globalData
          }

          function setGlobalData(newGlobalData){
            if (!isValid(newGlobalData)) {
              throw Error('Illegal input?。。?)
              return
            }
            
            globalData = newGlobalData
          }
          // 暴露方法
          export {
            getGlobalData,
            setGlobalData
          }

          現(xiàn)在,全局變量不會輕易的被“誤觸”,也能很快定義其修改的位置和防止錯誤的修改。

          4.6. 發(fā)散式變化

          當某個函數(shù)會因為不同原因在不同方向上發(fā)生變化時,發(fā)散式變化就誕生了。這聽起來有點迷糊,那么就用代碼來解釋吧。

          function getPrice(order) {
            // 獲取基礎(chǔ)價格
            const basePrice = order.quantity * order.itemPrice
            // 獲取折扣
            const quantityDiscount = Math.max(0, order.quantity - 500) * order.itemPrice * 0.05
            // 獲取運費
            const shipping = Math.min(basePrice * 0.1100)
            // 計算價格
            return basePrice - quantityDiscount + shipping
          }

          const orderPrice = getPrice(order);

          這個函數(shù)用于計算商品的價格,它的計算包含了基礎(chǔ)價格 + 數(shù)量折扣 + 運費,如果基礎(chǔ)價格的計算規(guī)則改變,我們需要修改這個函數(shù);如果折扣規(guī)則發(fā)生改變,我們需要修改這個函數(shù);如果運費計算規(guī)則改變了,我們還是要修改這個函數(shù)。

          這種修改容易造成混亂,我們當然也希望程序一旦需要修改,我們就夠跳到系統(tǒng)的某一點,所以是時候抽離它們了。

          // 計算基礎(chǔ)價格
          function calBasePrice(order) {
              return order.quantity * order.itemPrice
          }
          // 計算折扣
          function calDiscount(order) {
              return Math.max(0, order.quantity - 500) * order.itemPrice * 0.05
          }
          // 計算運費
          function calShipping(basePrice) {
              return Math.min(basePrice * 0.1100)
          }
          // 計算商品價格
          function getPrice(order) {
              return calBasePrice(order) - calDiscount(order) + calShipping(calBasePrice(order))
          }

          const orderPrice = getPrice(order)

          雖然該函數(shù)行數(shù)不多,當其重構(gòu)的過程與先前的過長函數(shù)一致,但是將各個功能抽離處理,有利于更清晰的定位問題與修改。所以過長函數(shù)擁有多重臭味道!需要及時消滅。

          4.7. 霰彈式修改

          霰彈式修改與發(fā)散式變化聽起來差異不大,實則它們是陰陽兩面。霰彈式修改與重復(fù)代碼有點像,當我們需要做出一點小修改時,卻要去四處一個個的修正,你不僅很難找到它們,也很容易錯過某個重要的修改,直至錯誤發(fā)生!

          // File Reading.js
          const reading = {customer"ivan"quantity10month5year2017}
          function acquireReading() return reading }
          function baseRate(month, year) {
              /* */
          }

          // File 1
          const aReading = acquireReading()
          const baseCharge = baseRate(aReading.month, aReading.year) * aReading.quantity

          // File 2
          const aReading = acquireReading()
          const base = (baseRate(aReading.month, aReading.year) * aReading.quantity)
          const taxableCharge = Math.max(0, base - taxThreshold(aReading.year))
          function taxThreshold(year) /* */ }

          // File 3
          const aReading = acquireReading()
          const basicChargeAmount = calculateBaseCharge(aReading)
          function calculateBaseCharge(aReading) {
            return baseRate(aReading.month, aReading.year) * aReading.quantity
          }

          在上面的代碼中,如果 reading 的邏輯發(fā)生了改變,我們需要跨越好幾個文件去調(diào)整它,這很容易造成遺漏的發(fā)生。

          由于每個地方都對 reading 進行了操作,那么我們可以將其封裝起來,統(tǒng)一在一個文件中進行管理。

          // File Reading.js

          class Reading {
           constructor(data) {
            this.customer = data.customer
            this.quantity = data.quantity
            this.month = data.month
            this.year = data.year
           }

           get baseRate() {
            /* ... */
           }

           get baseCharge() {
            return baseRate(this.month, this.year) * this.quantity
           }

           get taxableCharge() {
            return Math.max(0, base - taxThreshold())
           }

           get taxThreshold() {
            /* ... */
           }
          }

          const reading = new Reading({ customer'Evan You'quantity10month8year2021 })

          所有的相關(guān)邏輯在一起,不僅能提供一個共用的環(huán)境,也可以簡化調(diào)用邏輯,更加清晰。

          4.8. for 循環(huán)語句

          很驚訝,循環(huán)一直是程序中的核心要素,在這里重構(gòu)的世界里居然變成了臭味道。這里并不是要將循環(huán)取締,但僅僅使用普通的 for 循環(huán)在當下有些過時,現(xiàn)在我們有很好的替代品。在 JS 的世界里擁有著管道操作(filter,map 等)它們可以幫助我們更好的處理元素以及幫助我們看清處理的動作。

          下面我們將會從人群中挑選出所有的程序員并記錄他們的名字,哪種做法更賞心悅目呢?

          // for
          const programmerNames = []
          for (const item of people) {
            if (item.job === 'programmer') {
              programmerNames.push(item.name)
            }
          }

          // pipeline
          const programmerNames = people
            .filter(item => item.job === 'programmer')
            .map(item => item.name)

          當然,這個時候你可能會提出它們之間性能的差別,不要忘了重構(gòu)的意義是為了代碼更清晰,性能在這里并不是優(yōu)先要考慮的事情。

          不過這里很也很遺憾的告訴你一個點,僅有少數(shù)的管道操作符支持逆序操作(reduce,reduceRight),更多時候必須在之前使用 reverse 來反轉(zhuǎn)數(shù)組。所以是否要取締 for 循環(huán),取決于你自己,也取決于實際場景。

          4.9. 復(fù)雜的條件邏輯 && 合并條件表達式

          復(fù)雜的條件邏輯是導(dǎo)致復(fù)雜度上升的地點之一,代碼會告訴我們會發(fā)生什么事,可我們常常弄不清為什么會發(fā)生這樣的事,這就證明代碼的可讀性大大降低了。是時候?qū)⑺鼈兎庋b成一個帶有說明的函數(shù)了,見文知意,一目了然。

          // bad
          if (!date.isBefore(plan.summberStart) && !date.isAfter(plan.summberEnd)) {
            charge = quantity * plan.summerRate
          else {
            charge = quantity * plan.regularRate + plan.regularServiceCharge
          }


          // good
          if (isSummer()) {
            charge = quantity * plan.summerRate
          else {
            charge = quantity * plan.regularRate + plan.regularServiceCharge
          }

          // perfect
          isSummer() ? summerCharge() : regularCharge()

          如果一串條件檢查,檢查條件各不相同,最終行為卻一致,那么我們就應(yīng)該使用邏輯或和邏輯與將他們合并成為一個條件表達式。然后再做上面代碼的邏輯,封裝!

          if (man.age < 18return 0
          if (man.hasHeartDisease) return 0
          if (!isFull) return 0

          // step 1
          if (man.age < 18 && man.hasHeartDisease && !isFull) return 0

          // step 2
          if (isIlegalEntry(man) && !isFull) return 0

          4.10. 查詢函數(shù)與修改函數(shù)耦合

          如果某個函數(shù)只是提供一個值,沒有任何副作用,這是一個很有價值的東西,我可以任意調(diào)用這個函數(shù)沒有后顧之憂,也可以隨意的搬遷該函數(shù)??偠灾?,需要操心的事情少多了。

          明確的分離“有副作用”和“無副作用”兩種函數(shù)是一個很好的想法,查詢函數(shù)和修改函數(shù)搭配在平常的開發(fā)中也經(jīng)常出現(xiàn),是時候?qū)⑺鼈兎蛛x了!

          // 給 2 鵝歲以下的五星員工發(fā)郵件鼓勵
          function getTotalAdnSendEmail() {
            const emailList = programmerList
              .filter(item => item.occupationalAge <= 2 && item.stars === 5)
              .map(item => item.email)
            return sendEmail(emailList)
          }

          // 分離查詢函數(shù),這里可以通過傳遞參數(shù)進一步控制查詢的語句
          function search() {
            return programmerList
              .filter(item => item.occupationalAge <= 2 && item.stars === 5)
              .map(item => item.email)
          }

          function send() {
            return sendEmail(search())
          }

          這樣可以更好的控制查詢行為以及復(fù)用函數(shù),我們需要在一個函數(shù)內(nèi)操心的事情又少了一些。

          4.11. 以衛(wèi)語句(Guard Clauses)取代嵌套條件表達式

          直接上代碼:

          function getPayAmount() {
            let result
            if (isDead) {
               // do sth and assign to result
            } else {
              if (isSeparated) {
                // do sth and assign to result
              } else {
                if (isRetired) {
                  // do sth and assign to result
                } else {
                  // do sth and assign to result
                }
              }
            }
            
            return result
          }

          在閱讀該函數(shù)時,是否慶幸在 if else 之間的并非代碼而是一段注釋,如果是一段代碼,則讓人目眩眼花。那下面的代碼呢?

          function getPayAmount() {
            if (isDead) return deatAmount()
            if (isSeparated) return serparateAmount()
            if (isRetired) return retiredAmount()
            return normalPayAmount()
          }

          衛(wèi)語句的精髓就是給予某條分支特別的重視,它告訴閱讀者,這種情況并不是本函數(shù)的所關(guān)心的核心邏輯,如果它真的發(fā)生了,會做一些必要的工作然后提前退出。

          我相信每個程序員都會聽過“每個函數(shù)只能有一個入口和一個出口”這個觀念,但“單一出口”原則在這里似乎不起作用,在重構(gòu)的世界中,保證代碼清晰才是最關(guān)鍵的。如果“單一出口”能讓代碼更易讀,那么就使用它吧,否則就不必這么做。

          5. 何時開始重構(gòu)

          5.1. 添加新功能之前

          重構(gòu)的最佳時機是在添加新功能之前

          在動手添加新功能之前,看看現(xiàn)有的代碼庫,此時經(jīng)常會發(fā)現(xiàn),如果對代碼結(jié)構(gòu)做一點微調(diào),自己的工作會輕松很多。比如有個函數(shù)提供了需要的大部分功能,但有幾個字面量的值與自己的需求不同。如果不做重構(gòu),需要復(fù)制整個函數(shù)再進行微調(diào),這導(dǎo)致重復(fù)代碼的產(chǎn)生,這是代碼臭味道的開始。所以需要戴上重構(gòu)的“帽子”,做完這件事后,再輕松的開發(fā)你的功能。

          但這也是在理想情況下的設(shè)想,事實上任務(wù)的安排總有時間限制,多出一段的重構(gòu)的耗時可能會讓你對時間的安排失控,導(dǎo)致延期,所以對于工作中的場景,并不適用。

          5.2. 完成新功能后或 code review 后

          結(jié)合任務(wù)的排期和實際的工作,重構(gòu)的最佳時機是在完成一個功能后和 code review 后。

          在完成功能并測試通過后,此時對任務(wù)的進度是可控的,重構(gòu)不會影響到代碼既有實現(xiàn)的功能,在使用 git 等版本控制系統(tǒng)管理的情況下,回退至功能可用時的代碼片段是非常輕易的,但你無法立即完成你從未實現(xiàn)好的功能。

          在每完成一個功能后重構(gòu),也類似于垃圾回收中的時間分片的思想,不必等到代碼中塞滿“垃圾”時才開始清理,導(dǎo)致“全停頓”的發(fā)生。將重構(gòu)分解為一小步一小步。

          讓一個團隊,特別是共同實現(xiàn)同一項目的團隊來校驗自己的代碼,往往能夠發(fā)現(xiàn)自己難以注意的問題。比如自己寫的一個功能其實另一個同學已經(jīng)實現(xiàn)過了,完全可以抽離出來復(fù)用;比如有經(jīng)驗的同學提出更加優(yōu)雅的實現(xiàn)方案。

          并且自己編寫的代碼往往帶有自己的風格和“壞習慣”,代碼風格并不是一種錯誤,但在一個團隊中,不同代碼風格的混雜會帶來閱讀與合作的困難,而對于“壞習慣”而言,比如極其復(fù)雜的條件判斷語句等,自己難以意識到該做法的不妥,需要群眾的意見加以改正。

          實際上在每完成一個新功能后重構(gòu)還有一些筆者認為很重要的優(yōu)勢,就是你會對自己的代碼有更清晰的了解,你會去做今后不會再做的事情。

          對代碼更清晰,能讓我們更好的定位問題和提高自己的代碼水平,這很好理解。

          那這個今后不會再做的事情是什么呢?沒錯,就是重構(gòu)。當你完成新功能后,如果不立刻進行 review,那么在上線后很可能就從此被封存在某個地方,直到它出現(xiàn)了 bug。久而久之,整個項目變得難以維護,代碼開始發(fā)臭。

          而在完成新功能后重構(gòu),工作量一般也不會很大,是“順手完成的小工作”,屬于一鼓作氣階段,如果打算以后再看,那么往往就沒有這個以后了。

          5.3. 難以添加新功能的時候

          其實并不希望這個狀況發(fā)生,這代表代碼結(jié)構(gòu)已經(jīng)處于混亂中,添加新功能需要翻越好幾個障礙。此時重構(gòu)是個必選項,也必然是個大工程,這會造成項目的“全停頓”。更糟糕的是此時重構(gòu)可能不如直接重寫,這是我們需要避免的情況。

          6. 什么時候不該重構(gòu)

          6.1. 重寫比重構(gòu)容易

          這個無需多言。

          6.2. 不需要理解該代碼片段時

          如果一個功能或者 API 一直以來“兢兢業(yè)業(yè)”,從未出現(xiàn)過 bug,即便其底下隱藏著十分丑陋的代碼,那么我們也可以忍受它繼續(xù)保持丑陋。不要忘了重構(gòu)的初衷,其中之一就是為了讓人更好的理解代碼,當我們不需要理解其時,就讓它安安靜靜地躺在哪兒吧,不要讓不可控制的行為發(fā)生是重構(gòu)的原則之一。

          6.3. 未與合作者商量時

          如果一個功能被多個模塊引用,而這些模塊并非你負責時,你必須提前通知負責人,聲明將要對這部分功能進行修改,哪怕重構(gòu)不會帶來任何使用上的變化,因為這也意味著重構(gòu)行為將會帶來“不可控”。

          7. 重構(gòu)與性能

          關(guān)于重構(gòu)對性能的影響,是被提及最多的問題。畢竟重構(gòu)代碼很多時候都帶來了運行代碼行數(shù)的增加(并不一定是代碼總行數(shù)增加,因為重構(gòu)有提煉函數(shù)的部分,優(yōu)秀的重構(gòu)總會帶來代碼總行數(shù)的下降)。又或者說將一些性能好的代碼變?yōu)榭勺x性更高的代碼,犧牲掉性能優(yōu)勢。

          首先需要回顧一下,代碼重構(gòu)和性能優(yōu)化是兩個不同的概念,重構(gòu)僅僅只考慮代碼的可理解性和可拓展性,對于代碼的執(zhí)行效率是不在乎的,在重構(gòu)時切記不要同時戴著“兩頂帽子”。

          而重構(gòu)對于性能的影響,也很可能沒有你想象中的那么高,在面對大部分的業(yè)務(wù)情況時,重構(gòu)前和重構(gòu)后代碼的性能差別幾乎難以體現(xiàn)。

          大部分情況下,我們不需要極致的“壓榨”計算機,來減少使用的微乎其微的計算機時鐘周期時間,更重要的是,減少自己在開發(fā)中使用的時間。

          如果對于重構(gòu)后的的性能不滿意,可以在完成重構(gòu)后有的放矢的對部分高耗時功能進行代碼優(yōu)化。一件很有趣的事情是:大多數(shù)程序運行的大半時間都在一小部分代碼身上,只要優(yōu)化這部分代碼,就能帶來顯著的性能提高。如果你一視同仁的優(yōu)化所有代碼,就會發(fā)現(xiàn)這是在白費勁,因為被優(yōu)化的代碼不會被經(jīng)常執(zhí)行。

          所以我認為重構(gòu)時大可不必為性能過多擔憂,可以放手去重構(gòu),如有必要再針對個別代碼片段優(yōu)化。短期來看,重構(gòu)的確可能使軟件變慢,但重構(gòu)也使性能調(diào)優(yōu)更容易,最終還是會得到很好的效果。

          8. 完結(jié)撒花

          筆者并非“重構(gòu)大師”,本文也只展現(xiàn)了一些十分常見的重構(gòu)手法以及對重構(gòu)淺略的思考,還有很多經(jīng)典的手法與案例,本文未于展示,讀者如果對重構(gòu)感興趣,想深入了解的話,可以閱讀 Martin Fowler 的經(jīng)典書籍《重構(gòu),改善既有代碼的設(shè)計 第二版》,其中的示例語言選用了 JavaScript,這簡直是前端工程師的福音。

          對于 VSCode 用戶而言,有很多優(yōu)秀的插件幫助你重構(gòu),比如 JavaScript Booster 或 Stepsize,這些插件能提示你如何重構(gòu)且為代碼添加書簽和報告。

          都讀到這了,接下來知道該怎么做了吧。Commit a feature,review and refactor。

          9. 引用

          [0] 《重構(gòu),改善既有代碼的設(shè)計 第二版》Martin Fowler

          [1]   代碼中常見的 24 種壞味道及重構(gòu)手法

          [2]   vscode中6個好用的前端重構(gòu)插件

          Node 社群


          我組建了一個氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對Node.js學習感興趣的話(后續(xù)有計劃也可以),我們可以一起進行Node.js相關(guān)的交流、學習、共建。下方加 考拉 好友回復(fù)「Node」即可。


             “分享、點贊、在看” 支持一波 

          瀏覽 17
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美成人超碰在线 | 国产欧美综合一区二区三区 | 人妻无码第23页 | 日日碰狠狠| 日操逼网|