成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

踩坑實(shí)戰(zhàn):如何走出“萬劫不復(fù)”的代碼重構(gòu)深淵?

原創(chuàng)
開發(fā) 架構(gòu) 開發(fā)工具
“重構(gòu)是一個改變軟件系統(tǒng)的過程,它旨在不改變代碼外部行為的前提下,改善代碼內(nèi)部的結(jié)構(gòu)。它用一種規(guī)范的方式來清理代碼,從而最大限度地減少錯誤出現(xiàn)的幾率。從本質(zhì)上說,重構(gòu)是在代碼寫完之后對其設(shè)計的改善。”

【51CTO.com原創(chuàng)稿件】 Martin Fowler 在其經(jīng)典著作《重構(gòu):改善既有代碼的設(shè)計》中給出了下述定義。

“重構(gòu)是一個改變軟件系統(tǒng)的過程,它旨在不改變代碼外部行為的前提下,改善代碼內(nèi)部的結(jié)構(gòu)。它用一種規(guī)范的方式來清理代碼,從而***限度地減少錯誤出現(xiàn)的幾率。從本質(zhì)上說,重構(gòu)是在代碼寫完之后對其設(shè)計的改善。”

我最近有幸參與了一個復(fù)雜的代碼重構(gòu)項(xiàng)目,它是由 React 和 Redux 來構(gòu)建的一個銷售預(yù)算管理與分析的應(yīng)用系統(tǒng)。

該系統(tǒng)大約由 300 個文件,共計 13000 行代碼所組成。由于能夠定期地接觸到代碼庫,因此我對于在遵從代碼標(biāo)準(zhǔn)的基礎(chǔ)上如何進(jìn)行代碼改進(jìn)比較熟悉。

以下是我的最終目標(biāo)列表:

  • 更新文件夾結(jié)構(gòu),以集中 Redux 的各種文件(actions、reducers、selectors)。
  • 將 Foundation 框架轉(zhuǎn)換為 Semantic UI React。
  • 實(shí)現(xiàn) CSS 模塊。
  • 升級到 webpack3(一種模塊打包器)。
  • 將 Fetch 替換為 jQuery,以處理各種 HTTP 請求(使用 polyfill)。
  • 刪除不必要的抽象類和“死代碼”。
  • 安裝并配置 Jest 測試框架,編寫各種單元測試。

經(jīng)過一番周折之后,我最終完成了上述目標(biāo)。在此,我將自己從該項(xiàng)目以及其他過往項(xiàng)目中所獲取的寶貴經(jīng)驗(yàn)與技術(shù)分享給大家。

提出正確的問題

[[229785]]

在決定開始進(jìn)行代碼重構(gòu)項(xiàng)目之前,讓我們先理清一些重要的問題。

首先,捫心自問:我需要重構(gòu)嗎?

所有程序員都希望能寫出干凈優(yōu)雅的代碼,但是事實(shí)并非如此。各種截止日期的臨近和需求的變更,往往只是大量問題的開始而已。

面對龐大的代碼庫,您可能早已喪失了重構(gòu)的動力。那么請您在閱讀了下列問題并能夠回答“是”之后,再考慮如何開展重構(gòu)工作吧:

  • 是否存在重大的技術(shù)缺陷而造成了系統(tǒng)的巨大問題?
  • 添加各種新功能是否困難?
  • 對于某部分代碼庫的細(xì)微更改,是否會破壞應(yīng)用中另一部分的某個不相關(guān)的功能?
  • 您是否還使用了那些存在著安全與性能問題的過時依賴關(guān)系?
  • 在 JavaScript 環(huán)境中,ES5 的語法是否能夠被箭頭函數(shù)(arrow functions)和解構(gòu)(destructuring)等新的語言功能所增強(qiáng)?

我建議您多與同事,特別是該領(lǐng)域的高級工程師討論,而不是盲目開始。他們可能會詳細(xì)地說明為什么會以此種方式來編寫代碼,或者給您提供一些能夠影響決斷的有價值的見解。

某位經(jīng)驗(yàn)豐富的工程師甚至還會提醒您他們曾在代碼重構(gòu)中失敗過,因此在某種程度上說這次也不適合再次進(jìn)行重構(gòu)。

接下來,問問自己:我能夠重構(gòu)嗎?

現(xiàn)在您面臨著下一個障礙是:確定重構(gòu)是否可行。可行的條件包括如下限制因素:

  • 根據(jù)我目前的技能組合,我能勝任重構(gòu)嗎?
  • 根據(jù)我的時間安排,我能勝任重構(gòu)嗎?
  • 重構(gòu)時,我可以添加新的功能嗎?
  • 根據(jù)我當(dāng)前的預(yù)算,我能勝任重構(gòu)嗎?

如果您不能對上述所有的問題回答“Yes”,那么重構(gòu)可能對您來說就是自尋煩惱了。

我的經(jīng)驗(yàn)是:您需要得到軟件產(chǎn)品所有者的批準(zhǔn)與支持,因?yàn)樗麄儠?jīng)常與客戶直接合作并能管理預(yù)算,切忌擅自行事!

***,自問自答:我愿意重構(gòu)嗎?

代碼重構(gòu)向來是一項(xiàng)巨大的工程。那些嚴(yán)苛的預(yù)算和時間表往往會給人們帶來難以想象的壓力。

因此,您可能會時常詢問自己如下的問題:

為什么代碼不是在一開始就以正確的方式被編寫呢?

如果應(yīng)用能夠正常工作的話,何必要重構(gòu)它們呢?

如果它沒那么糟糕的話,就不能只是添加點(diǎn)新的功能嗎?

這樣做能夠增加企業(yè)的價值嗎?

面對上述問題,代碼重構(gòu)常被人們誤解為一項(xiàng)吃力不討好的任務(wù)。而如果一些現(xiàn)有的功能因?yàn)榇a重構(gòu)而產(chǎn)生中斷的話,那么重構(gòu)工作就會變得更加“萬劫不復(fù)”了。

盡管困難重重、弊端多多,但是在完成了上述“盡職調(diào)查”之后,您會發(fā)現(xiàn)代碼重構(gòu)還是非常值得我們投入寶貴的時間去開展的。

在清理代碼庫的同時,添加良好的單元測試會有益于新功能的輕松添加,以及回歸類錯誤的(regression bug)大幅降低。

制定計劃

“如果沒有計劃,您就是在計劃失敗。”

--本杰明富蘭克林如是說。

一旦開始決定重構(gòu)某個應(yīng)用程序,您的***本能應(yīng)該是:深挖代碼并清理不適合的代碼。然而事實(shí)證明:如果沒有一個事先的計劃,您將難逃“劫難”。

在按照時間和預(yù)算的范圍制定出行動計劃之前,您先別急著修改代碼。代碼重構(gòu)的目的是為了讓整個團(tuán)隊(duì)受益于代碼價值的***化。

如果代碼中的某些部分雖然顯得特別混亂,但是能夠提供正常服務(wù)的話,那么就沒有必要迅速對它們進(jìn)行重構(gòu)。

我們稍后會討論區(qū)分優(yōu)先級的問題,而當(dāng)前您至少應(yīng)該先對代碼的潛在價值有所預(yù)判。

以下列出了能夠保證您的重構(gòu)項(xiàng)目正確開始的若干步驟。即使您的重構(gòu)項(xiàng)目相對較小,我仍然建議您參考并使用下列技術(shù)。

選擇一種項(xiàng)目管理工具

無論代碼重構(gòu)的范圍是大還是小,您都需要用一種工具來跟蹤項(xiàng)目進(jìn)度。我是 Trello 的忠實(shí)粉絲,并認(rèn)為其“看板(Kanban)”風(fēng)格界面非常適合于項(xiàng)目管理。

當(dāng)然,您完全可以挑選自己喜歡的工具,而且***具有對任務(wù)進(jìn)行排序、分組、添加注釋、以及說明的功能。同時,如果它能夠添加附件或創(chuàng)建標(biāo)簽的話,那就更是錦上添花了。

以我的項(xiàng)目為例,我創(chuàng)建了一個 Trello 看板,并通過如下列表名稱來跟蹤各項(xiàng)任務(wù):

  • 積壓工作
  • 下一步
  • 進(jìn)行中
  • 拉取請求(PullRequest)
  • 關(guān)閉

我還創(chuàng)建了一個標(biāo)簽專門用來表示某個任務(wù)是否涉及到 React 組件、Redux元素(如 actions、reducers、selectors)、以及應(yīng)用配置等方面。

如果您和我一樣屬于“視覺系”的,則可以通過給標(biāo)簽添加顏色,來迅速了解其表征的特性,而不需仔細(xì)閱讀其標(biāo)題或相關(guān)說明。

例如,我創(chuàng)建了一個將 webpack 從 1.0 升級到 3.0 的任務(wù),并為 Configuration 標(biāo)簽設(shè)定了特殊顏色,以便我能在面板上快速地識別出來。

查找邏輯上下文

如果代碼量非常大的話,您可以試著將應(yīng)用程序解構(gòu)為多個模塊或上下文的關(guān)系。

此處“上下文”表示為:隸屬于某個特定業(yè)務(wù)實(shí)體或是應(yīng)用程序配置的代碼段。如果您所面對的代碼庫頗為混亂的話,該過程將具有挑戰(zhàn)性。

不過,即使只是粗略地研究與劃分,也會有助于您更進(jìn)一步熟悉代碼庫,甚至讓您受益匪淺。在多數(shù)情況下,您可以根據(jù)應(yīng)用的服務(wù)流程來推斷出上下文關(guān)系。

例如,一個為牙科診所安排日程表的應(yīng)用,就可以被分為如下的上下文關(guān)系:

  • 病人
  • 約診
  • 用戶導(dǎo)航
  • 牙科記錄
  • 用戶管理

在實(shí)踐中,我們經(jīng)常會碰到的棘手部分是:如何正確把控粒度。對于我所開發(fā)過的應(yīng)用而言,我一般會根據(jù) API 的調(diào)用和現(xiàn)有 Redux 的 reducer 來確定上下文關(guān)系。

我通常會定義出:用戶類上下文、超級用戶類上下文(用于各種管理操作)、應(yīng)用類上下文(用于 UI 的狀態(tài))、以及其他類型。

注意:不要過于苛求***的上下文關(guān)系,這一步的目的只是為了簡化任務(wù)創(chuàng)建的過程。

創(chuàng)建任務(wù)

您必須創(chuàng)建出具有明確范圍的任務(wù)。在此,您可以根據(jù)“拉取請求”的方式來考慮范圍。

雖然誰都不想一次性提交具有 5000 行代碼之多的變更,但是在 5000 行代碼中只提交 2 處修改顯然也是遠(yuǎn)遠(yuǎn)不夠的。

以我曾經(jīng)參與過的一個重構(gòu)項(xiàng)目為例,其目標(biāo)是從 Foundation 框架轉(zhuǎn)換到語義 UI 的 React,并且實(shí)現(xiàn) CSS 模塊。

請參考:https://foundation.zurb.com/

我最初創(chuàng)建了單一的任務(wù)來表示這一轉(zhuǎn)化,但是我立即意識到了它所牽扯到的巨大工作量。

在該應(yīng)用中,有著近 100 個 React 組件需要被更新,而我并不想在 Trello 中創(chuàng)建 100 個任務(wù)來代表每一個單獨(dú)的組件。

在此情況下,我定義了一些簡單的上下文關(guān)系:

  • 首先,我在每個上下文中創(chuàng)建了不同的任務(wù),來重構(gòu)與 Redux 相連接的各個容器組件。
  • 接下來,我查看了共享的 /components 目錄,并按照表單控件、圖表等類別對它們進(jìn)行分組。
  • ***,我創(chuàng)建了單獨(dú)的任務(wù)來重構(gòu)每一組共享的組件。

下圖是我的看板界面截圖,上面包含了一些示例:

當(dāng)然,您也需要考慮到自己的變更對于應(yīng)用的影響。如果可能需要恢復(fù)到舊的版本,您一定不想在大段的變更代碼中費(fèi)力地深挖出造成某個 Bug 的原因。

曾經(jīng)有一次,我為了某個 Bug 而不得不撤銷了自己的絕大部分的更改。在此之后我試著用小塊的程序代碼去進(jìn)行重構(gòu)。

為任務(wù)排序

如果您愿意的話,可以考慮為任務(wù)制定排序或優(yōu)先級的一些標(biāo)準(zhǔn)。當(dāng)然,如果您已經(jīng)為每一項(xiàng)任務(wù)創(chuàng)建了明確的范圍,那么完成它們的順序也就一目了然了。

我傾向于將具有相似特定功能的任務(wù)進(jìn)行分組處理,例如根據(jù) Redux 的元素或 API 管理來區(qū)分。

當(dāng)然,如果您是新手的話,我會建議您先去“摘取那些低垂的果實(shí)”,即一些僅付出少量的努力就會效果明顯的任務(wù)。

持續(xù)更新您的計劃

隨著對于代碼的“深耕”,您可能會發(fā)現(xiàn)在前期重構(gòu)計劃里不準(zhǔn)確的地方,那么請不要猶豫,盡快修正以免覆水難收。

重構(gòu)代碼庫的好處往往是無形且難以衡量的。哪怕最終不得不終止重構(gòu)計劃,您的這份詳細(xì)的計劃也能為下一次重構(gòu)的“重啟”提供寶貴的資源和“追蹤”的線索。

測試的重要作用

如果連您都不喜歡測試自己的產(chǎn)品,那么很可能您的客戶也不會樂意試用它。

下面我將引導(dǎo)您來建立一個有效的測試架構(gòu)。考慮到方便搭建與運(yùn)行,我使用了Jest 和 Enzyme。

參考:https://facebook.github.io/jest/

http://airbnb.io/enzyme/

您可以根據(jù)如下鏈接的指引,將這兩個庫配置到 React/Redux 的應(yīng)用之中:

  • 使用 Jest 和 Enzyme 實(shí)施基本組件測試

https://hackernoon.com/implementing-basic-component-tests-using-jest-and-enzyme-d1d8788d627a

  • 如何用 Jest 測試 React 的各種組件

https://www.sitepoint.com/test-react-components-jest/

設(shè)置模擬數(shù)據(jù)

如果測試的是現(xiàn)有應(yīng)用,那么您應(yīng)該了解現(xiàn)有數(shù)據(jù)的形態(tài)和使用方式。在大多數(shù)情況下,隨著新功能的添加,API 會被微調(diào)與改進(jìn)。

對于我的重構(gòu)應(yīng)用項(xiàng)目,我創(chuàng)建了兩個帶有數(shù)據(jù)的文件:一個帶有API的各種響應(yīng),而另一個則帶有 Redux 的狀態(tài)。

您可能需要通過稍許的修改和大量的復(fù)制/粘貼來創(chuàng)建這兩個文件,不過這些工作都是一次性的。

產(chǎn)生這兩個數(shù)據(jù)文件的目的有兩個:

  • 首先(也是最明顯的),您需要測試的許多元素都會以某種方式來顯示或操縱數(shù)據(jù)。
  • 其次,通過快速地參考數(shù)據(jù)的形態(tài),以便有效地確定如何撰寫測試程序,并分析代碼可能出現(xiàn)的問題。

如果所測數(shù)據(jù)過于敏感的話,有時候存儲 API 的響應(yīng)與狀態(tài)并不太可行。在此情況下,您可以使用帶有 fakcer 的 json-schema-faker 庫,或 chance 庫來生成隨機(jī)數(shù)據(jù)。

參考:https://www.npmjs.com/package/json-schema-faker

https://www.npmjs.com/package/chance

我建議您一次性生成數(shù)據(jù)并存儲到庫中,而不是每次在運(yùn)行測試時都使用其“種子”來生成新的數(shù)據(jù)。

我將自己的文件存放在__fixtures__文件夾中,目錄結(jié)構(gòu)如下所示:

  1. /src 
  2.   /components 
  3.   /constants 
  4.   /containers 
  5.   /redux 
  6.     /__fixtures__ 
  7.       /state.js 
  8.       /responses.js 
  9.     /app 
  10.       /appActions.js 
  11.       /appReducer.js 
  12.       /appSelectors.js 
  13.   /... 

獲取 Redux 整體狀態(tài)的最簡便方法是使用 Redux DevTools 的擴(kuò)展。

參考:https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=en

您可以在狀態(tài)視圖中選擇 Raw 選項(xiàng)卡,復(fù)制所有內(nèi)容,并將其粘貼到一個帶有 module.exports 聲明的 JavaScript 文件中。

我建議您只從狀態(tài)和 API 的響應(yīng)中獲取小部分的記錄,以減少 Jest 整體快照的大小。而具體保留多少條記錄則完全取決于您自己的判斷。

例如:某個 API 的響應(yīng)返回了一個包含著 400 條記錄的數(shù)組,那么您肯定需要去除其中的絕大部分,以提高測試效率。

值得注意的是:使用有效的數(shù)據(jù)對于防止回歸錯誤的產(chǎn)生是至關(guān)重要的。如果你用到的數(shù)據(jù)并不能代表應(yīng)用中真實(shí)環(huán)境所用到的內(nèi)容,那么測試的效果是無法保證重構(gòu)質(zhì)量的。

標(biāo)準(zhǔn)化您的測試

您在重構(gòu)項(xiàng)目中往往需要編寫大量的測試。在剛開始寫測試的時候,我經(jīng)常會出現(xiàn)命名不統(tǒng)一的情況。

例如:在測試兩個非常相似的組件時,我所用到的 describe 時常不盡相同。同時,標(biāo)準(zhǔn)化您的測試將有利于減少團(tuán)隊(duì)花費(fèi)在理解測試程序上的時間。

確定測試文件的位置

大部分人喜歡復(fù)制整個 src/ 目錄,并將測試文件放在那里。無論您喜歡用 .spec.js 還是用 .test.js 作為測試文件的擴(kuò)展名,都要注意一致性。

Jest 的默認(rèn)配置會指定將測試文件放置在 __tests__ 目錄,并以 .test.js 作為擴(kuò)展名。

一旦您將此作為了標(biāo)準(zhǔn),請務(wù)必添加到自述文件(README)之中,以便將來使用該應(yīng)用的程序員能夠籍此遵守下去。

建立格式

您應(yīng)當(dāng)為每個正在測試的上下文(如:React 組件、Redux 的 selectors 等)建立起“格式/結(jié)構(gòu)”(format/structure)的關(guān)系。

例如,我創(chuàng)建的每個 React 組件和容器的測試文件都會在其頂部有一個如下所示的 setup 函數(shù):

  1. const setup = (propOverrides, renderFn = shallow) => { 
  2.   const props = { 
  3.     propA: 'Some Value'
  4.     propB: false
  5.     onClick: jest.fn(), 
  6.     ...propOverrides, 
  7.   }; 
  8.   const wrapper = renderFn(<AppComponent {...props} />); 
  9.   return { props, wrapper }; 
  10. }; 

這會使得我在測試組件時非常輕松,而無需編寫大量額外的樣板引用。同時,我還為 React 組件建立了一個特定的 describe 塊結(jié)構(gòu)。

  1. describe('Component A', () => { 
  2.   describe('Snapshot validation', () => { 
  3.     it('matches its snapshot with valid props', () => { 
  4.       const { wrapper } = setup(); 
  5.       expect(wrapper).toMatchSnapshot(); 
  6.     }); 
  7.   }); 
  8.   describe('Event validation', () => { 
  9.     it('fires props.onClick when button is clicked', () => { 
  10.       const { wrapper, props } = setup(); 
  11.       wrapper.find('button').simulate('click'); 
  12.       expect(props.onClick).toHaveBeenCalled(); 
  13.     }); 
  14.   }); 
  15.   // Note: This is only for connected components. 
  16.   describe('Redux validation', () => { 
  17.     const store = { 
  18.       getState: () => state, 
  19.       dispatch: jest.fn(), 
  20.       subscribe: () => {}, 
  21.     }; 
  22.     it('renders when connected to Redux state', () => { 
  23.       const wrapper = shallow(<ComponentA store={store} />); 
  24.       expect(wrapper).toHaveLength(1); 
  25.     }); 
  26.   }); 
  27. }); 

對于 Redux 的 actions、reducers 和 selectors,我進(jìn)行了同樣的操作。根據(jù)具體的測試環(huán)境,我還會使用 WebStorm 的文件模板功能,以快速地創(chuàng)建各種測試文件。

參考:https://www.jetbrains.com/help/webstorm/creating-and-editing-file-templates.html

如果您使用的程序編輯器能夠支持代碼片段或文件模板的話,我建議您事先創(chuàng)建好相應(yīng)的模板,以保持格式上的規(guī)范。同樣,請記得在自述文件中留下簡要的概述說明。

編寫測試

假設(shè)您正在著手開始重構(gòu) Redux 的 actions、reducer 和 selectors 的 UI 狀態(tài)。由于您已經(jīng)掌握了相應(yīng)的狀態(tài)數(shù)據(jù),因此編寫測試相對來說會比較簡單。

您只需要使用類似 redux-mock-store 的庫來模擬出用于測試 actions 的狀態(tài)即可。

參考:https://github.com/arnaudbenard/redux-mock-store

請務(wù)必在更改任何代碼之前編寫好了所有的測試。通過對重構(gòu)之后的代碼進(jìn)行測試,我們能夠發(fā)現(xiàn)一些人為的或無意犯下的錯誤。

而事先保存好快照,則有助于我們發(fā)現(xiàn)那些字段或?qū)ο箨P(guān)鍵字上的拼寫錯誤。

您應(yīng)當(dāng)對完成了重構(gòu)的代碼部分,和任何直接受到變更影響的代碼編寫測試程序。

雖說深挖程序間的依賴關(guān)系、并編寫出涉及到應(yīng)用各個方面的測試,會是一項(xiàng)比較繁瑣的工程,但是這會給您提供對于整個代碼庫的深入解析,并為代碼的重構(gòu)提供更多的整改機(jī)會。

應(yīng)該測試什么?確定測試內(nèi)容的最簡單和可靠的方式是:代碼覆蓋率。

Jest 具有內(nèi)置的代碼覆蓋率檢查功能,您可以用來生成帶有覆蓋率百分比的 HTML 報告,以顯示代碼中的哪些部分目前未被測試所覆蓋到。

雖然行覆蓋(Line coverage,查看是否每一行都執(zhí)行了?)會讓您頗有成就感,但是分支覆蓋(branch coverage,查看是否每個 if 代碼塊都執(zhí)行了?)才是您需要去關(guān)注的地方。

更多有關(guān)不同覆蓋類型的概念,請參考:http://jasonrudolph.com/blog/2008/06/10/a-brief-discussion-of-code-coverage-types/

如何知道自己已經(jīng)完成了?如前面所述,覆蓋率是評估代碼的某個部分是否通過了測試的***工具。

如果您的某個函數(shù)中包含一個 if 的聲明,而它的 else 條件卻沒被測試所覆蓋到,那么就會被覆蓋率報告所指出。

我經(jīng)常會習(xí)慣性地多看幾次函數(shù)代碼,并理解其邏輯關(guān)系,然后編寫出能夠故意破壞它的測試用例,包括:如果 API 的響應(yīng)中缺少某個字段會怎么樣?如果響應(yīng)為空又會發(fā)生什么?

例如,假設(shè)有個 selector 能夠加總某個特定區(qū)域里每個銷售員所分配到的預(yù)算。而銷售經(jīng)理則擁有該地區(qū)所有可用的預(yù)算總和。

顯然,所有可用預(yù)算的總和應(yīng)當(dāng)始終大于或等于分配出去的總預(yù)算。那么,如果小于的話,會發(fā)生什么?代碼中是否有 if 的聲明來涉及這方面呢?

通過閱讀代碼和編寫測試,您可考慮到更多的極端情況。由此可見,代碼覆蓋率(function coverage)可以反映出每個函數(shù)是否被測試所調(diào)用到的情況。

重寫代碼

通過上述部分,您應(yīng)該已經(jīng)制定出了計劃、選取了相應(yīng)任務(wù)、編寫出了測試,那么是否現(xiàn)在就可以開始打開某個文件、修正變量名稱并清理代碼了呢?

為了確保重構(gòu)的順利進(jìn)行,我建議您先熟悉一些基本的概念。下面,我將向您介紹一些在代碼重構(gòu)過程中的常見錯誤和化繁為簡的技巧。

識別自動化的可能性

您很可能會碰到需要移動并梳理到正確的位置的大量文件。

例如,我曾經(jīng)在重構(gòu)一個應(yīng)用時發(fā)現(xiàn)其 Redux 的 actions、reducers 和 selectors 都分屬于自己單獨(dú)的文件夾,而我需要將它們按照模塊(例如 appActions.js、appReducer.js 和 appSelectors.js)進(jìn)行分類。

因此,我需要運(yùn)行一條 git mv 的命令,將 /actions/app.js 移動到 /redux/app/appActions.js,并且對于 /reducers/app.js 和 /selectors/app.js 要執(zhí)行相同的操作。

由于該應(yīng)用項(xiàng)目中有 11 個模塊,因此我必須輸入 33 次 git mv 命令。另外,我還需要再運(yùn)行 150 次 git mv,以將 React 的容器和組件放置到正確的文件夾位置。

因此,面對如此“崩潰”的任務(wù),我并沒有手動地逐條輸入命令,而是使用 JavaScript 和 Node.js 編寫了一個腳本來實(shí)現(xiàn):

  1. const fs = require('fs'); 
  2. const path = require('path'); 
  3. const chalk = require('chalk'); 
  4. const sh = require('shelljs'); 
  5. const _ = require('lodash'); 
  6. const sourcePath = path.resolve(process.cwd(), 'src'); 
  7. // This is the new /src/redux folder that gets created: 
  8. const reduxPath = path.resolve(sourcePath, 'redux'); 
  9. // I used "entities" instead of "modules", but they represent the same thing: 
  10. const entities = [ 
  11.   'app'
  12.   'projects'
  13.   'schedules'
  14.   'users'
  15. ]; 
  16. const createReduxFolders = () => { 
  17.   if (!fs.existsSync(reduxPath)) fs.mkdirSync(reduxPath); 
  18.   // Code to create entities folders in /src/redux... 
  19. }; 
  20. // Executes a `git mv` command (I omitted some additional code that validates 
  21. // if the file already exists for brevity). 
  22. const gitMoveFile = (sourcePath, targetPath) => { 
  23.   console.log(chalk.cyan(`Moving ${sourcePath} to ${targetPath}`)); 
  24.   const command = `git mv ${sourcePath} ${targetPath}`; 
  25.   sh.exec(command); 
  26.   console.log(chalk.green('Move successful.')); 
  27. }; 
  28. const moveReduxFiles = () => { 
  29.   entities.forEach(entity => { 
  30.     ['actions''reducers''selectors'].forEach(reduxType => { 
  31.       // Get the file associated with the specified entity for the specified reduxType, 
  32.       // so the first file might be /src/actions/app.js: 
  33.       const sourceFile = path.resolve(sourcePath, reduxType, `${entity}.js`); 
  34.       if (fs.existsSync(sourceFile)) { 
  35.         // Capitalize the reduxType to append to the file name (e.g. appActions.js): 
  36.         const fileSuffix = _.capitalize(reduxType); 
  37.         // Build the path to the target file, so this would be /src/redux/app/appActions.js: 
  38.         const targetPath = `${reduxPath}/${entity}`; 
  39.         const targetFile = `${targetPath}/${entity}${fileSuffix}.js`; 
  40.         // Execute a `git mv` command for the file: 
  41.         gitMoveFile(sourceFile, targetFile); 
  42.       } 
  43.     }); 
  44.   }); 
  45. }; 
  46. moveReduxFiles(); 

您既可以通過腳本來自動遷移文件的路徑,也可以通過腳本來直接修改路徑的名稱。

當(dāng)然,您需要注意投入產(chǎn)出比,不要花費(fèi)了 20 小時去編寫一個腳本,卻只是節(jié)省了 1 個小時手動工作量。

由于大多數(shù)代碼庫、及其結(jié)構(gòu)都相對獨(dú)特,因此一般您編寫腳本的復(fù)用性都不高。

持續(xù)提交

您所重構(gòu)的應(yīng)用程序越多、時間越長,就越難以記住和追蹤那些在不同文件里的細(xì)微修改。

而對于各種文件的累計且大量更改,勢必給您的應(yīng)用測試帶來失敗的風(fēng)險。因此,無論代碼的修改量大或小、多或少,請記得予以持續(xù)提交。

以某次應(yīng)用重構(gòu)為例,我就進(jìn)行了 1747 次提交,涉及到 659 個文件中的 76080 行代碼,總體占用的存儲空間為 10MB。

另外,在多次且持續(xù)的提交過程中,您可以通過限制每一次更改的內(nèi)容和文件的數(shù)量,以便您能夠隨時按需“跳回”到某一個可靠的“保存點(diǎn)”。

抵御“分心”

請暫時避免清理那些手頭任務(wù)范圍之外的代碼,這也是代碼重構(gòu)過程中最困難的方面之一。

假設(shè)您遇到了一個使用 Object.assign() 的 selector,而您的后續(xù)任務(wù)之一是更新代碼,使用類似 spread syntax 新的 ESNext 功能。

參考:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax

那么請不要偏離當(dāng)前的任務(wù)走向,哪怕只是對該代碼進(jìn)行微不足道的改變,都不要放在現(xiàn)在進(jìn)行。

我的經(jīng)驗(yàn)是:在相應(yīng)的代碼處添加了一條 //REFACTOR: Fix this later 的注釋,然后繼續(xù)自己的當(dāng)前任務(wù)。

有關(guān)拉取請求的方法論

有時候,您花費(fèi)了大量的時間去清理一部分代碼庫,而難以后退到過去的某個時間點(diǎn),或無法對修改后的代碼質(zhì)量進(jìn)行評估。

那么拉取請求往往就能夠幫助您和同事來評審這些修改,以確定是否有益于代碼的優(yōu)化:

  • 當(dāng)您在提交拉取請求時,請?jiān)谡幗o予盡可能詳細(xì)的描述。如果您使用的是 GitHub,那么新的拉取請求會伴隨著一個模板。您可以在其中填寫相應(yīng)的標(biāo)題、總體描述、重要章節(jié)的變更列表等各種審評人員所關(guān)注的信息。
  • 您需要注意的一個指標(biāo)是:與拉取請求相關(guān)的代碼更改數(shù)量。請盡量限制更改的代碼行數(shù)在 500 行左右。由于 Jest 快照文件的添加會使得拉取請求動輒添加數(shù)千行,因此請務(wù)必在摘要中包含與之相關(guān)的注釋。
  • 如果您無法控制改變的數(shù)量,那么就請盡量降低其復(fù)雜性。
  • 如果您只是對重要的聲明語句進(jìn)行重新排序的話,只要此類更改并不太復(fù)雜,超過 2000 行的代碼量還是可以接受的。
  • 請將同類變更盡量限定在同一次拉取請求之中。
  • 您可以大膽地在注釋中寫上“本次并未做邏輯上的修改”,以節(jié)約審閱者的理解時間。

寫在***

我們從代碼重構(gòu)項(xiàng)目的實(shí)施角度向您提供了:盡可能自動化、持續(xù)提交、抵御“分心”,以及善用拉取請求等方面的建議。

【51CTO原創(chuàng)稿件,合作站點(diǎn)轉(zhuǎn)載請注明原文作者和出處為51CTO.com】

責(zé)任編輯:武曉燕 來源: 51CTO技術(shù)棧
相關(guān)推薦

2011-01-13 10:50:42

AMD

2010-03-29 09:39:47

SQL數(shù)據(jù)庫

2019-03-10 15:54:22

5G通信4G

2025-03-04 09:30:00

2017-09-21 12:32:25

內(nèi)存電腦DDR4

2017-11-02 15:07:56

代碼重寫代碼開發(fā)

2009-04-06 09:05:55

IBM收購Sunsolarisjava

2012-08-28 10:30:23

Amazon云時代Borders

2020-11-02 12:49:16

重構(gòu)核心系統(tǒng)

2023-02-20 08:11:04

2022-07-26 09:34:23

監(jiān)控系統(tǒng)

2023-08-16 18:28:54

2024-09-05 10:17:34

2024-04-10 08:39:56

BigDecimal浮點(diǎn)數(shù)二進(jìn)制

2020-09-15 08:46:26

Kubernetes探針服務(wù)端

2023-01-18 23:20:25

編程開發(fā)

2024-04-01 08:05:27

Go開發(fā)Java

2021-11-11 10:41:25

代碼開發(fā)工具

2017-07-17 15:46:20

Oracle并行機(jī)制
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 精品视频一区二区三区在线观看 | 欧美日韩在线精品 | 国产精品久久久久久久久免费高清 | 日本久久久一区二区三区 | 天天想天天干 | 免费在线a视频 | 国产精品视频一二三区 | 一级黄在线观看 | 国产精品一区在线观看 | 国产在线精品一区二区 | 亚洲一区二区视频在线播放 | 超碰免费在 | 91久久| 天天操天天干天天透 | 亚洲一区二区三区福利 | 国产精品入口久久 | 精品视频在线免费观看 | 91精品一区二区三区久久久久久 | 成人激情视频 | 欧美视频二区 | 成人小视频在线观看 | 午夜视频免费在线观看 | 美国一级黄色片 | 欧美日韩在线观看一区二区三区 | 久久机热 | 国产精品久久午夜夜伦鲁鲁 | 国产精品视频一区二区三区 | 伊人成人免费视频 | 久久精品欧美一区二区三区麻豆 | 一区二区三区四区免费在线观看 | 桃色五月| 国产高清免费视频 | 久久99这里只有精品 | 久草电影网| 久久久国产一区二区三区 | 国产欧美精品区一区二区三区 | 国产精品91网站 | 男人视频网站 | 久久国产精品免费一区二区三区 | 亚洲精品一二三 | 亚洲人成人一区二区在线观看 |