重構學習筆記 《 第一組重構 》

第一組重構: 

方法簡介

  • 將較難理解的程序段慢慢提煉爲易懂的程序段,這樣可節省往後開發中的閱讀理解時間。

提煉函數、內聯函數、提煉變量、內聯變量

 

  • 隨着你對程序的理解加深,會需要考慮修正原來函數或變量的名稱。改名或修改函數參數列表有利於使代碼更貼合邏輯,易於理解;對於被廣泛調用的變量,先封裝後改名,可使改名變得簡單;將結伴出現的參數組合在一起,不僅整理了相關邏輯,也減少函數參數列表的長度。

改變函數聲明、變量改名、封裝變量、引入參數對象

 

  • 當調用了差不多數據的函數的數目變得多了起來,開始考慮要不要把他們組合成類,還是組合成變化?這樣就將相關聯的數據和操作方式組合成了一個個更高級的模塊。更進一步地進行適當的拆分,可使各個模塊的處理階段界限分明。

       函數組合成類、函數組合成變化、拆分階段

 

細節、重點、感悟

  • 提煉(Extract)

提煉,"將意圖與實現分開"。如果你需要花時間瀏覽一段代碼才能弄清楚它在幹什麼,你就可以把這把這段代碼提煉成一個函數或把這段公式提煉成一個變量了。

function printDetail( aPeople )
{
    console.log('Name is: ${aPeople.name}');
    console.log('Age is: ${aPeople.age}');
    console.log('Sex is: ${aPeople.sex}');
    ......
    //在高層作用域中,我們並不關心這些函數的內部是如何運行的
}

提煉部分邏輯爲函數,會遇見對傳參後的後對該局部變量修改的問題。如果只修改一個參數的局部變量,則可以將其作爲函數返回值返回;如果修改了多個局部變量,我們最好採用其他重構方式減少參數,如拆分變量以查詢取代臨時變量

提煉函數的命名可採用你給的的註釋。

提煉小段邏輯爲變量有助於我們更好的理解也更好管理,但當該變量有更寬的作用域時,我們考慮用以查詢取代臨時變量來處理。

 

  • 內聯(Inline)

內聯,防止你的過渡提煉,你的程序段的意圖已經表現的很明顯了。

function getRating( number )
{
	return moreThanFive( number ) ? 5:10;
}	

function moreThanFive( number )
{
	return number>5;
}

-------------

function getRating( number )
{
	return number>5 ? 5:10;
}	

//上述moreThanFive函數並沒有增加可讀性

 

  • 改變函數聲明(Change Function Declaration)

隨着對代碼的理解加深,你會給原來的函數一個更合適的聲明。關於修改聲明,可分爲 1.修改函數名稱  2.修改函數參數列表。修改手法又可分爲簡單式(用於函數調用點少),和遷移式(調用點多難全修改)。修改函數參數列表往往比較難也比較重要,假設有一個函數用於處理電話格式,將參數從“人”修改成“電話”能使得這個函數被更廣泛的使用,因爲“公司的電話”也能調用這個函數了,但具體情況往往更復雜,如果正確傳參沒有絕對正確答案。遷移式和簡單式的差別是會將老函數名用新函數名包裝一次後才替換調用點,這樣更安全。

//遷移式修改
function circum(radius)                 //原函數名
{
	return circumference(radius);
}

function circumference(radius)          //新函數名
{
	return 2*Math.PI*radius;
}

//原函數被廣泛調用,待調用點被完全被新函數替換時,纔可將原函數刪除
//這樣可保證代碼庫的正常工作即安全

 

  • 封裝變量(Encapsulate Variable)

       如果想要搬移一處被廣泛使用的數據,最好的辦法是以函數形式封裝所有對該數據的訪問。函數相對容易調整一些,因爲函數只有一種用法,就是調用。在改名會或搬移函數過程中,更容易保留舊函數作爲轉發函數(遷移式做法,舊函數調用新函數),這比數據修改要方便的多,因爲數據修改或搬移,需要把所有調用點同時修改一遍才能保證程序的正常運行。對於函數,我可以調用修改好的新函數也可以臨時使用還未修改的舊函數。這樣,重構變得小步而安全(重構安全是本書的核心之一)。

//一個全局變量
let  defaultOwner = { firstName:"Martin" , lastName : "Fowler"  };



//封裝
let defaultOwner =  { firstName:"Martin" , lastName : "Fowler"  };
function getDefaultOwner() { return defaultOwner;}
function setDefaultOwner(arg) { defaultOwner = arg;} 

 

  •  變量改名(Rename Variable)

        好的命名時整潔編程的核心。對廣泛變量改名時應先使用封裝變量,在慢慢內聯回調用點。而對只讀變量來說可以直接用舊變量給新名稱變量賦值,在逐一修改調用點。

 

  • 引入參數對象(Introduce Parameter Object )

       一組數據常常結伴同行,如膠似漆,我喜歡用數據結構代替之。將數據組織成結構能讓數據之間的關係變得更加明晰,使函數參數列表縮短,更重要的使它會催生出更深層次的改變——給於此數據結構以函數捕獲,使得數據結構提升成了新的抽象概念,可以幫助我們更好的理解問題域。

//startData和endData總是形影不離
function amountInvoiced(startData,endData){...}
function amountReceived(startData,endData){...}
function amountOverdue(startData,endData){...}



//理出一個"家"給他們
function amountInvoiced(aDataRange){...}
function amountReceived(aDataRange){...}
function amountOverdue(aDataRange){...}

 

  • 函數組合成類(Combine Functions into Class )

       一組函數都的操作着同一塊數據,將它們組合成類。類,將數據和函數捆綁到一個環境中,將值得暴露的數據或函數給其他抽象使用以供協助。把函數和親密的數據夥伴放在一起,組合成類,內部減少了參數的傳遞,同時簡化了函數的調用。

//aReading是他們的共同的數據
function baseCharge( aReading ) {...}
function taxalbeCharge( aReading ) {...}
function calculateCharge( aReading ) {...}


//組合成類簡化函數
class Reading
{
    function baseCharge( ) {...}
    function taxalbeCharge( ) {...}
    function calculateCharge( ) {...}
}

 

  • 函數組合成變換 (Combine Functions into Transform)

        一組函數都的計算着同一塊數據的派生數據,將它們組合成變化。變化函數,即將源數據作爲輸入,將所有派生數據同時計算出來,這樣我們每次只需要訪問一個變換函數就能找到所需要的任意一個派生數據。當源數據需要更新時,使用函數組合成類來替代函數組合成變換會更好這樣可以保證派生數據也能被實時修改,而不會導致數據不一致。

//計算aReading的派生數據的函數
function baseCharge( aReading ) {...}
function taxalbeCharge( aReading ) {...}
...


//一次性計算aReading的派生數據的變化函數
function enrichReading( argReading )
{
    const aReading = _.cloneDeep(argReading);
    aReading.baseCharge = base(aReading);
    aReading.taxalbeCharge = taxalbeCharge(aReading);
    ...
    return aReading;
}

 

  • 拆分階段

        當一段代碼正處理兩件不同事情,將它們拆分。之後我們便可以單獨處理每個主題,因爲它們之間的界限差異變得清晰。

//難以閱讀一段的代碼
const orderDate = orderString.split(/\s+/);
const productPrice = priceList[orderDate[0].split("-")[1]];
const orderPrice = parseInt(orderDate[1]) * productPrice;



//將解析和計算函數分離,後更便於處理細節
const orderRecord = parseOrder(order);
const orderPrice = price(orderRecord,priceList);

function parseOrder(aString)
{
    const values = aString.split(/\s+/);
    return ({
        productID : values[0].split["-"][1],
        quantity : parseInt(values[1]),
    })
}
function price( order, priceList )
{
    return order.quantity * priceList[order.productID];
}

 

 

 

總結: 

              第一組重構,是Martin認爲最有用的重構手法,也是最基礎的重構的手法。重構的目的之一讓代碼更清晰,讓理解代碼的時間成本縮短,提煉、內聯、改名、拆分無一不是往此處努力。整理出清晰而整潔的代碼加深了對當前邏輯的理解,是深入開發基礎。重構的另一個目的是使代碼更靈活,使修改更靈活,調用更靈活。我們不會希望在修改一處變量名稱時要把全局同名變量都修改一遍,否則程序將無法運行。封裝變量限制可見度,引入參數對象、函數組合成類或變化,將數據和函數組合在一起提高了這種靈活性。靈活性的提高時加數開發的基礎。

本文章內容提煉總結於《重構·改善既有代碼設計(第二版)》--Martin Fowler

基本內容爲記錄本人這周隨學 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章