1. 程式人生 > >重構改善既有代碼設計--重構手法01:Extract Method (提煉函數)

重構改善既有代碼設計--重構手法01:Extract Method (提煉函數)

設置 都是 覆寫 list() 為什麽 新建 細粒度 align 容易

背景:

你有一段代碼可以被組織在一起並獨立出來。將這段代碼放進一個獨立函數,並讓函數名稱解釋該函數的用途

void PrintOwing(double amount)

{

PrintBanner();

//print details

Console.WriteLine("name:"+_name);

Console.WriteLine("amount:"+_amount);

}

void PrintOwing(double amount)

{

PrintBanner();

//print details

PrintDetails();

}

private void PrintDetails()

{

Console.WriteLine("name:" + _name);

Console.WriteLine("amount:" + _amount);

}

動機:

Extract Method (提煉函數)是最常用的重構手法之一。當看見一個過長的函數或者一段需要註釋才能讓人理解用途的代碼,就應該將這段代碼放進一個獨立函數中。

簡短而命名良好的函數的好處:首先,如果每個函數的粒度都很小,那麽函數被復用的機會就更大;其次,這會使高層函數讀起來就想一系列註釋;再次,如果函數都是細粒度,那麽函數的覆寫也會更容易些。

一個函數多長才算合適?長度不是問題,關鍵在於函數名稱和函數本體之間的語義距離。如果提煉可以強化代碼的清晰度,那就去做,就算函數名稱必提煉出來的代碼還長也無所謂。

做法:

1、創造一個新函數,根據這個函數的意圖對它命名(以它“做什麽“命名,而不是以它“怎樣做”命名)。

即使你想要提煉的代碼非常簡單,例如只是一條消息或一個函數調用,只要新函數的名稱能夠以更好方式昭示代碼意圖,你也應該提煉它。但如果你想不出一個更有意義的名稱,就別動。

2、將提煉出的代碼從源函數復制到新建的明白函數中。

3、仔細檢查提煉出的代碼,看看其中是否引用了“作用域限於源函數”的變量(包括局部變量和源函數參數)。

4、檢查是否有“僅用於被提煉代碼段”的臨時變量。如果有,在目標函數中將它們聲明為臨時變量。

5、檢查被提煉代碼段,看看是否有任何局部變量的值被它改變。如果一個臨時變量值被修改了,看看是否可以將被提煉代碼處理為一個查詢,並將結果賦值給修改變量。如果很難這樣做,或如果被修改的變量不止一個,你就不能僅僅將這段代碼原封不動提煉出來。你可能需要先使用 Split Temporary Variable (分解臨時變量),然後再嘗試提煉。也可以使用 Replace Temp with Query (以查詢取代臨時變量)將臨時變量消滅掉。

6、將被提煉代碼段中需要讀取的局部變量,當做參數傳給目標函數。

7、處理完所有局部變量後,進行編譯。

8、在源函數中,將被提煉代碼段替換給對目標函數的調用。

如果你將如何臨時變量移到目標函數中,請檢查它們原本的聲明式是否在被提煉代碼段的外圍。如果是,現在可以刪除這些聲明式了。

9、編譯,測試。

代碼演示:

實例代碼如下:

1 private string myName;
2 public void printPeople(int Age)
3 {
4 printFamily();
5 //技術分享無數代碼技術分享//
6
7 //打印個人信息
8 Console.WriteLine("Name:" + myName);
9 Console.WriteLine("Age:" + Age);
10 }


重構後的代碼如下:

1 private string myName;
2 public void printPeople(int Age)
3 {
4 printFamily();
5 //技術分享無數代碼技術分享//
6 printMyInfo(Age);
7 }
8
9 void printMyInfo(int Age)
10 {
11 Console.WriteLine("Name:" + myName);
12 Console.WriteLine("Age:" + Age);
13 }


為什麽要這樣重構?當一個函數很大的時候,第一對代碼的修改起來非常的不方便.
第二,會對你讀代碼有障礙,試想一下當你看到一個很多行代碼的方法,你還有心情看下去嗎?
第三,方法與方法之間的復用性會非常的好,方法的重寫也會更容易些.

那麽我們應該怎麽做呢?
看第一個例子:
無局部變量的方法提煉.

1 void printOwing()
2 {
3 ArrayList al = myOrders.GetOrderList();
4 double outstanding = 0.0;
5
6 //打印頭部信息
7 Console.WriteLine("*****************");
8 Console.WriteLine("**Customer Owes**");
9 Console.WriteLine("*****************");
10
11 //計算
12 foreach(Object o in al)
13 {
14 Order each = (Order)o;
15 outstanding += each.Amount;
16 }
17
18 //打印具體信息
19 Console.WriteLine("Name:" + myName);
20 Console.WriteLine("Age:" + age);
21 }


好了我們開始先提最簡單的部分.提出後的代碼如下:

1 void printOwing()
2 {
3 ArrayList al = myOrders.GetOrderList();
4 double outstanding = 0.0;
5
6 printBanner();
7
8 //計算
9 foreach(Object o in al)
10 {
11 Order each = (Order)o;
12 outstanding += each.Amount;
13 }
14
15 //打印具體信息
16 Console.WriteLine("Name:" + myName);
17 Console.WriteLine("Age:" + age);
18 }
19
20 void printBanner()
21 {
22 //打印頭部信息
23 Console.WriteLine("*****************");
24 Console.WriteLine("**Customer Owes**");
25 Console.WriteLine("*****************");
26 }


最簡單的提煉方法結束了.
下來我們看有局部變量的方法提煉.就拿上面的的代碼開刀.

1 void printOwing()
2 {
3 ArrayList al = myOrders.GetOrderList();
4 double outstanding = 0.0;
5
6 printBanner();
7
8 //計算
9 foreach(Object o in al)
10 {
11 Order each = (Order)o;
12 outstanding += each.Amount;
13 }
14
15 printInfo(outstanding);
16 }
17
18 void printBanner()
19 {
20 //打印頭部信息
21 Console.WriteLine("*****************");
22 Console.WriteLine("**Customer Owes**");
23 Console.WriteLine("*****************");
24 }
25
26 void printInfo(double OutStanding)
27 {
28 //打印具體信息
29 Console.WriteLine("Name:" + myName);
30 Console.WriteLine("Age:" + age);
31 }


我們再來看下對局部變量再賦值方法的提煉.繼續拿上面代碼開刀.

1 void printOwing()
2 {
3 double outstanding = GetOutStanding();
4
5 printBanner();
6
7 printInfo(outstanding);
8 }
9
10 void printBanner()
11 {
12 //打印頭部信息
13 Console.WriteLine("*****************");
14 Console.WriteLine("**Customer Owes**");
15 Console.WriteLine("*****************");
16 }
17
18 void printInfo(double OutStanding)
19 {
20 //打印具體信息
21 Console.WriteLine("Name:" + myName);
22 Console.WriteLine("Age:" + age);
23 }
24
25 double GetOutStanding()
26 {
27 ArrayList al = myOrders.GetOrderList();
28 double outstanding = 0.0;
29 //計算
30 foreach(Object o in al)
31 {
32 Order each = (Order)o;
33 outstanding += each.Amount;
34 }
35 return outstanding
36 }


Extract Method方法講解玩了.有人會問為什麽要這樣寫?這樣寫的好處我沒有看到啊.
那麽現在有個這樣的需求,我要設置outstanding的初始值,那麽我們只要修改GetOutStanding方法,代碼

如下:

1 double GetOutStanding(double previousAmount)
2 {
3 ArrayList al = myOrders.GetOrderList();
4 double outstanding = previousAmount;
5 //計算
6 foreach(Object o in al)
7 {
8 Order each = (Order)o;
9 outstanding += each.Amount;
10 }
11 return outstanding
12 }


主要方法修改如下:

1 void printOwing()
2 {
3 double outstanding = GetOutStanding(500.5);
4
5 printBanner();
6
7 printInfo(outstanding);
8 }


如果需求繼續增加,我們修改起來是不是方便了許多?

讀後感:

1.如果說沒有任何局部變量,那麽這個函數提煉就非常容易提煉.

2.如果說提煉的時候有局部變量,即用到了提煉函數之外的局部變量,那麽如果僅僅是內部函數使用的,直接放到內部函數中;第二種,如果提煉的函數內部沒有對此變量賦值的情況,僅僅是讀取使用,那麽直接從外面作為參數傳遞進來。

3.如果提煉的函數,不僅僅有局部變量,並且還要對其賦值,那麽同樣要看,這個局部變量是不是只是內部使用,如果只是內部使用,直接放進來,如果不是,那就說外面還要用到,那麽需要經過提煉函數運算後,將值返回去.

重構改善既有代碼設計--重構手法01:Extract Method (提煉函數)