最常用的重构指导
參考:http://www.cnblogs.com/KnightsWarrior/archive/2010/06/30/1767981.html,本文示例代碼多來(lái)自此處;
參考:《重構(gòu):改善既有代碼》;
?
完美而高檔的摩天大廈應(yīng)至少具備兩個(gè)特點(diǎn):房間內(nèi)部是清潔的、結(jié)構(gòu)上是無(wú)懈可擊的。優(yōu)秀的代碼也應(yīng)如此。碼農(nóng)要負(fù)責(zé)打掃房間,架構(gòu)師負(fù)責(zé)搭建一個(gè)經(jīng)得起考驗(yàn)的代碼結(jié)構(gòu)。有些人兼顧碼農(nóng)和架構(gòu)的角色。如果你既不是碼農(nóng),也不是架構(gòu)師,那么就請(qǐng)離代碼遠(yuǎn)點(diǎn),離重構(gòu)遠(yuǎn)點(diǎn),要有多遠(yuǎn)滾多遠(yuǎn)。
?
一:打掃房間
1:避免重復(fù)代碼
避免重復(fù)代碼在大多數(shù)情況下適用,但是我有一個(gè)逆觀點(diǎn)是:允許重復(fù)代碼,如果它影響到你的架構(gòu)。
2:提取方法原則,超過(guò)30行?
并不一定超過(guò)30行的代碼就必須提取為方法,當(dāng)然,原則上,大部分情況下應(yīng)該是這樣的。還有,如果提取方法讓你的代碼更清晰,你就應(yīng)該提取方法,如下:
namespace LosTechies.DaysOfRefactoring.ExtractMethod.Before
{
??? public class Receipt
??? {
??????? private IList<decimal> Discounts { get; set; }
??????? private IList<decimal> ItemTotals { get; set; }
??????? public decimal CalculateGrandTotal()
??????? {
??????????? decimal subTotal = 0m;
??????????? foreach (decimal itemTotal in ItemTotals)
??????????????? subTotal += itemTotal;
??????????? if (Discounts.Count > 0)
??????????? {
??????????????? foreach (decimal discount in Discounts)
??????????????????? subTotal -= discount;
??????????? }
??????????? decimal tax = subTotal * 0.065m;
??????????? subTotal += tax;
??????????? return subTotal;
??????? }
??? }
}
namespace LosTechies.DaysOfRefactoring.ExtractMethod.After
{
??? public class Receipt
??? {
??????? private IList<decimal> Discounts { get; set; }
??????? private IList<decimal> ItemTotals { get; set; }
??????? public decimal CalculateGrandTotal()
??????? {
??????????? decimal subTotal = CalculateSubTotal();
??????????? subTotal = CalculateDiscounts(subTotal);
??????????? subTotal = CalculateTax(subTotal);
??????????? return subTotal;
??????? }
??????? private decimal CalculateTax(decimal subTotal)
??????? {
??????????? decimal tax = subTotal * 0.065m;
??????????? subTotal += tax;
??????????? return subTotal;
??????? }
??????? private decimal CalculateDiscounts(decimal subTotal)
??????? {
??????????? if (Discounts.Count > 0)
??????????? {
??????????????? foreach (decimal discount in Discounts)
??????????????????? subTotal -= discount;
??????????? }
??????????? return subTotal;
??????? }
??????? private decimal CalculateSubTotal()
??????? {
??????????? decimal subTotal = 0m;
??????????? foreach (decimal itemTotal in ItemTotals)
??????????????? subTotal += itemTotal;
??????????? return subTotal;
??????? }
??? }
}
3:警惕超過(guò)300行的類(lèi)
如果它不是個(gè)門(mén)面類(lèi),那么超過(guò)300行的類(lèi)很多時(shí)候過(guò)于復(fù)雜,俗稱(chēng)“上帝類(lèi)”,因?yàn)樗龍D做太多事情,可以考慮重構(gòu)成更小的類(lèi);
4:過(guò)多的方法參數(shù)
方法參數(shù)超過(guò)5個(gè)幾乎總是有問(wèn)題的,可以把參數(shù)提取為一個(gè)實(shí)體類(lèi)。當(dāng)然,越接近于底層我越能容忍這種情況的發(fā)生,比如 DAL 類(lèi),查詢條件多的情況下,我會(huì)允許帶很多參數(shù)。
5:沒(méi)有必要的注釋
很多人拿微軟的 FCL(基礎(chǔ)類(lèi)庫(kù)) 來(lái)舉反例,說(shuō) MS 的注釋簡(jiǎn)直全面俱到。對(duì)不起,你要看清它在開(kāi)發(fā)什么,它在開(kāi)發(fā) API,供我等小白使用的,所以它必須提供一份全面俱到的 API 說(shuō)明。大多數(shù)情況下,干掉你代碼中的注釋,把代碼寫(xiě)的讓別人能直接看得懂。如果一定要寫(xiě)注釋,則一定要按規(guī)范格式來(lái),不是兩個(gè)反斜杠后面跟一段話就叫做注釋,你給自己身上貼滿創(chuàng)可貼試試。
6:不要用異常
用 Tester-Doer 模式取代異常,不要嘗試總是使用異常。
7:要用異常
不要使用這種代碼:
public bool Insert(Model model)
{
??? //some other code
??? Dal dal = new Dal();
??? if (dal.Insert(model))
??? {
??????? return true;
??? }
??? else
??? {
??????? return false;
??? }
}
直接讓異常往上拋,
public bool Insert(Model model)
{
??? //some other code
??? new Dal(.Insert(model));
}
直到某個(gè)地方愿意處理地方。
8:方法內(nèi)的代碼屬于一個(gè)層級(jí)
穿衣服,穿褲子屬于一個(gè)層級(jí)。穿衣服,造汽車(chē),就不是同一個(gè)層級(jí)。
9:Dispose
如果某個(gè)東西需要 Close,就應(yīng)該實(shí)現(xiàn) IDispose。
10:Static Or Not
如果該類(lèi)需要進(jìn)入單元測(cè)試,則它不應(yīng)該是 Static 的。如果靜態(tài)了,代碼就是在測(cè)試的收你得額外增加一個(gè)包裝類(lèi)。
11:Shotgun Surgery(霰彈式修改)
現(xiàn)象:當(dāng)外部條件發(fā)生變化時(shí),每次需要修改多個(gè)Class來(lái)適應(yīng)這些變化,影響到很多地方。就像霰彈一樣,發(fā)散到多個(gè)地方。
重構(gòu)策略:使用Move Method和Move Field將Class中需要修改的方法及成員變量移植到同一個(gè)Class中。如果沒(méi)有合適的Class,則創(chuàng)建一個(gè)新Class。實(shí)現(xiàn)目標(biāo)是,將需要修改的地方集中到一個(gè)Class中進(jìn)行處理。
12:Feature Envy(依戀情結(jié))
現(xiàn)象:Class中某些方法“身在曹營(yíng)心在漢”,沒(méi)有安心使用Class中的成員變量,而需要大量訪問(wèn)另外Class中的成員變量。這樣就違反了對(duì)象技術(shù)的基本定義:將數(shù)據(jù)和操作行為(方法)包裝在一起。
重構(gòu)策略:使用Move Method將這些方法移動(dòng)到對(duì)應(yīng)的Class中,以化解其“相思之苦”,讓其牽手。
13:組合與繼承,你有兩種選擇
這里無(wú)所謂說(shuō)哪種好,哪種壞,看情況,以下是這兩種的表現(xiàn)形式,你可以進(jìn)行互轉(zhuǎn)。
namespace LosTechies.DaysOfRefactoring.ReplaceInheritance.Before
{
??? public class Sanitation
??? {
??????? public string WashHands()
??????? {
??????????? return "Cleaned!";
??????? }
??? }
??? public class Child : Sanitation
??? {
??? }
}
namespace LosTechies.DaysOfRefactoring.ReplaceInheritance.After
{
??? public class Sanitation
??? {
??????? public string WashHands()
??????? {
??????????? return "Cleaned!";
??????? }
??? }
??? public class Child
??? {
??????? private Sanitation Sanitation { get; set; }
??????? public Child()
??????? {
??????????? Sanitation = new Sanitation();
??????? }
??????? public string WashHands()
??????? {
??????????? return Sanitation.WashHands();
??????? }
??? }
}
14:分解復(fù)雜判斷
復(fù)雜的判斷基礎(chǔ)總是要分解的,因?yàn)樗菀组喿x了,寫(xiě)注釋?注釋一坨 Shit?
namespace LosTechies.DaysOfRefactoring.SampleCode.ArrowheadAntipattern.Before
{
??? public class Security
??? {
??????? public ISecurityChecker SecurityChecker { get; set; }
??????? public Security(ISecurityChecker securityChecker)
??????? {
??????????? SecurityChecker = securityChecker;
??????? }
??????? public bool HasAccess(User user, Permission permission, IEnumerable<Permission> exemptions)
??????? {
??????????? bool hasPermission = false;
??????????? if (user != null)
??????????? {
??????????????? if (permission != null)
??????????????? {
??????????????????? if (exemptions.Count() == 0)
??????????????????? {
??????????????????????? if (SecurityChecker.CheckPermission(user, permission) || exemptions.Contains(permission))
??????????????????????? {
??????????????????????????? hasPermission = true;
??????????????????????? }
??????????????????? }
??????????????? }
??????????? }
??????????? return hasPermission;
??????? }
??? }
}
namespace LosTechies.DaysOfRefactoring.SampleCode.ArrowheadAntipattern.After
{
??? public class Security
??? {
??????? public ISecurityChecker SecurityChecker { get; set; }
??????? public Security(ISecurityChecker securityChecker)
??????? {
??????????? SecurityChecker = securityChecker;
??????? }
??????? public bool HasAccess(User user, Permission permission, IEnumerable<Permission> exemptions)
??????? {
??????????? if (user == null || permission == null)
??????????????? return false;
??????????? if (exemptions.Contains(permission))
??????????????? return true;
??????????? return SecurityChecker.CheckPermission(user, permission);
??????? }
??? }
}
15:盡快返回
實(shí)際上,該條是要求我們,可以在方法內(nèi)部多使用 return,直到最后才 return,會(huì)使得最終結(jié)果看起來(lái)很復(fù)雜;
?
二:蓋房子
1:尋找邊界
架構(gòu)的第一原則,是尋找邊界,最直觀的成果物就是建立幾個(gè)解決方案,解決方案內(nèi)有多少個(gè)項(xiàng)目。劃邊界的最終目的就是要告訴組員:什么樣的代碼應(yīng)該編寫(xiě)到哪個(gè)解決方案中。
2:建立公共庫(kù)
任何解決方案幾乎都需要一個(gè)公共庫(kù),用于放置一些 Helper 類(lèi)。
3:資源可以作為一個(gè)單獨(dú)的項(xiàng)目
不同的資源可以獨(dú)立成為不同的項(xiàng)目,圖片、JS、Style字典、配置文件等,都可以作為資源。另外,第三方的 DLL 也需要作為資源獨(dú)立出來(lái),把它們注冊(cè)到全局程序集中不如直接作為 Content 包含進(jìn)項(xiàng)目來(lái)的舒爽。
4:客戶端邏輯最小化
.NET 程序最讓人詬病的是:混淆了也可窺測(cè)你的源碼。除了這個(gè)原因,從解耦的角度看,UI 或者其它客戶端項(xiàng)目,都應(yīng)該知道更少的邏輯才好。
5:基于測(cè)試的與MVC、MVVM、MVP
如果一開(kāi)始你并不知道什么是 MVC 或者 MVVM,那么沒(méi)關(guān)系,先試著掌握單元測(cè)試,把代碼寫(xiě)成基于測(cè)試的。我有一個(gè)激進(jìn)的觀點(diǎn)是,所有的架構(gòu)模式,其實(shí)目的都是為了代碼可測(cè)試。
6:AOP
權(quán)限認(rèn)證是典型的面向切面編程。不是 Attribute 才能帶來(lái) AOP 思想,把要運(yùn)行的代碼交給一個(gè) Action ,也能實(shí)現(xiàn) AOP。
7:模版模式、繼承與多態(tài)
繼承不是多態(tài),繼承的另一個(gè)價(jià)值叫做:模版模式。如果一件 Case 有多個(gè)實(shí)現(xiàn)途徑,它就應(yīng)該是模版的,因?yàn)槟憧偰苷业揭恍┓椒ǚ胖玫礁割?lèi)中去;
8:工廠模式與工廠
類(lèi)不是被調(diào)用者 new 出來(lái)的,而是調(diào)用某個(gè)類(lèi)的某個(gè)方法后被返回出來(lái)的,就叫做工廠模式。這類(lèi)也叫做對(duì)象容器。對(duì)象容器也可以很復(fù)雜,復(fù)雜到叫做一個(gè)框架,比如 Unity。
9:觀察者模式、事件通知
事件就是觀察者模式。解耦也可以使用觀察者模式來(lái)實(shí)現(xiàn)。
10:接口的存在都是有目的的
自從 面向接口編程 這個(gè)概念提出來(lái)后,接口就開(kāi)始變得漫天飛。接口的出現(xiàn)不能基于某種假設(shè),而是實(shí)際已經(jīng)發(fā)生了作用。
11:避免二轉(zhuǎn)手的代碼
二轉(zhuǎn)手的代碼常常來(lái)自于所謂三層架構(gòu)代碼,UI-BLL-DAL,然后 BLL 中的大量方法實(shí)際就只有一句話 Dal.Update(model),老實(shí)說(shuō),我受夠了這樣的代碼。
12:見(jiàn)到條件,就考慮是否使用策略模式
“使用策略類(lèi)” 是指用設(shè)計(jì)模式中的策略模式來(lái)替換原來(lái)的switch case和if else語(yǔ)句,這樣可以解開(kāi)耦合,同時(shí)也使維護(hù)性和系統(tǒng)的可擴(kuò)展性大大增強(qiáng)。
如下面代碼所示,ClientCode 類(lèi)會(huì)更加枚舉State的值來(lái)調(diào)用ShippingInfo 的不同方法,但是這樣就會(huì)產(chǎn)生很多的判斷語(yǔ)句,如果代碼量加大,類(lèi)變得很大了的話,維護(hù)中改動(dòng)也會(huì)變得很大,每次改動(dòng)一個(gè)地方,都要對(duì)整個(gè)結(jié)構(gòu)進(jìn)行編譯(假如是多個(gè)工程),所以我們想到了對(duì)它進(jìn)行重構(gòu),剝開(kāi)耦合。
namespace LosTechies.DaysOfRefactoring.SwitchToStrategy.Before {public class ClientCode{public decimal CalculateShipping(){ShippingInfo shippingInfo = new ShippingInfo();return shippingInfo.CalculateShippingAmount(State.Alaska);}}public enum State{Alaska,NewYork,Florida}public class ShippingInfo{public decimal CalculateShippingAmount(State shipToState){switch (shipToState){case State.Alaska:return GetAlaskaShippingAmount();case State.NewYork:return GetNewYorkShippingAmount();case State.Florida:return GetFloridaShippingAmount();default:return 0m;}}private decimal GetAlaskaShippingAmount(){return 15m;}private decimal GetNewYorkShippingAmount(){return 10m;}private decimal GetFloridaShippingAmount(){return 3m;}} }重構(gòu)后的代碼如下所示,抽象出一個(gè)IShippingCalculation 接口,然后把ShippingInfo 類(lèi)里面的GetAlaskaShippingAmount、GetNewYorkShippingAmount、GetFloridaShippingAmount三個(gè)方法分別提煉成三個(gè)類(lèi),然后繼承自IShippingCalculation 接口,這樣在調(diào)用的時(shí)候就可以通過(guò)IEnumerable<IShippingCalculation> 來(lái)解除之前的switch case語(yǔ)句,這和IOC的做法頗為相似。
using System; using System.Collections.Generic; using System.Linq;namespace LosTechies.DaysOfRefactoring.SwitchToStrategy.After_WithIoC {public interface IShippingInfo{decimal CalculateShippingAmount(State state);}public class ClientCode{[Inject]public IShippingInfo ShippingInfo { get; set; }public decimal CalculateShipping(){return ShippingInfo.CalculateShippingAmount(State.Alaska);}}public enum State{Alaska,NewYork,Florida}public class ShippingInfo : IShippingInfo{private IDictionary<State, IShippingCalculation> ShippingCalculations { get; set; }public ShippingInfo(IEnumerable<IShippingCalculation> shippingCalculations){ShippingCalculations = shippingCalculations.ToDictionary(calc => calc.State);}public decimal CalculateShippingAmount(State shipToState){return ShippingCalculations[shipToState].Calculate();}}public interface IShippingCalculation{State State { get; }decimal Calculate();}public class AlaskShippingCalculation : IShippingCalculation{public State State { get { return State.Alaska; } }public decimal Calculate(){return 15m;}}public class NewYorkShippingCalculation : IShippingCalculation{public State State { get { return State.NewYork; } }public decimal Calculate(){return 10m;}}public class FloridaShippingCalculation : IShippingCalculation{public State State { get { return State.Florida; } }public decimal Calculate(){return 3m;}} }總結(jié):這種重構(gòu)在設(shè)計(jì)模式當(dāng)中把它單獨(dú)取了一個(gè)名字——策略模式,這樣做的好處就是可以隔開(kāi)耦合,以注入的形式實(shí)現(xiàn)功能,這使增加功能變得更加容易和簡(jiǎn)便,同樣也增強(qiáng)了整個(gè)系統(tǒng)的穩(wěn)定性和健壯性。
13:分解依賴(lài)
無(wú)抽象、靜態(tài)類(lèi)、靜態(tài)方法都是不可單元測(cè)試的。那么,如果我們要寫(xiě)出可測(cè)試的代碼,又要用到這些靜態(tài)類(lèi)等,該怎么辦,實(shí)際上我們需要兩個(gè)步驟:
1:為它們寫(xiě)一個(gè)包裝類(lèi),讓這個(gè)包裝類(lèi)是抽象的(繼承自接口,或者抽象類(lèi),或者方法本身是Virtual的);
2:通知客戶端程序員,使用包裝類(lèi)來(lái)代替原先的靜態(tài)類(lèi)來(lái)寫(xiě)業(yè)務(wù)邏輯;
FCL 中的典型例子是:HttpResponseWrapper。
轉(zhuǎn)載于:https://www.cnblogs.com/luminji/p/3289030.html
總結(jié)
- 上一篇: 做一个.net 程序员要掌握的知识提纲
- 下一篇: OCA读书笔记(1) - 浏览Orac