最常用的重构指导
參考:http://www.cnblogs.com/KnightsWarrior/archive/2010/06/30/1767981.html,本文示例代碼多來自此處;
參考:《重構:改善既有代碼》;
?
完美而高檔的摩天大廈應至少具備兩個特點:房間內部是清潔的、結構上是無懈可擊的。優秀的代碼也應如此。碼農要負責打掃房間,架構師負責搭建一個經得起考驗的代碼結構。有些人兼顧碼農和架構的角色。如果你既不是碼農,也不是架構師,那么就請離代碼遠點,離重構遠點,要有多遠滾多遠。
?
一:打掃房間
1:避免重復代碼
避免重復代碼在大多數情況下適用,但是我有一個逆觀點是:允許重復代碼,如果它影響到你的架構。
2:提取方法原則,超過30行?
并不一定超過30行的代碼就必須提取為方法,當然,原則上,大部分情況下應該是這樣的。還有,如果提取方法讓你的代碼更清晰,你就應該提取方法,如下:
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:警惕超過300行的類
如果它不是個門面類,那么超過300行的類很多時候過于復雜,俗稱“上帝類”,因為它妄圖做太多事情,可以考慮重構成更小的類;
4:過多的方法參數
方法參數超過5個幾乎總是有問題的,可以把參數提取為一個實體類。當然,越接近于底層我越能容忍這種情況的發生,比如 DAL 類,查詢條件多的情況下,我會允許帶很多參數。
5:沒有必要的注釋
很多人拿微軟的 FCL(基礎類庫) 來舉反例,說 MS 的注釋簡直全面俱到。對不起,你要看清它在開發什么,它在開發 API,供我等小白使用的,所以它必須提供一份全面俱到的 API 說明。大多數情況下,干掉你代碼中的注釋,把代碼寫的讓別人能直接看得懂。如果一定要寫注釋,則一定要按規范格式來,不是兩個反斜杠后面跟一段話就叫做注釋,你給自己身上貼滿創可貼試試。
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));
}
直到某個地方愿意處理地方。
8:方法內的代碼屬于一個層級
穿衣服,穿褲子屬于一個層級。穿衣服,造汽車,就不是同一個層級。
9:Dispose
如果某個東西需要 Close,就應該實現 IDispose。
10:Static Or Not
如果該類需要進入單元測試,則它不應該是 Static 的。如果靜態了,代碼就是在測試的收你得額外增加一個包裝類。
11:Shotgun Surgery(霰彈式修改)
現象:當外部條件發生變化時,每次需要修改多個Class來適應這些變化,影響到很多地方。就像霰彈一樣,發散到多個地方。
重構策略:使用Move Method和Move Field將Class中需要修改的方法及成員變量移植到同一個Class中。如果沒有合適的Class,則創建一個新Class。實現目標是,將需要修改的地方集中到一個Class中進行處理。
12:Feature Envy(依戀情結)
現象:Class中某些方法“身在曹營心在漢”,沒有安心使用Class中的成員變量,而需要大量訪問另外Class中的成員變量。這樣就違反了對象技術的基本定義:將數據和操作行為(方法)包裝在一起。
重構策略:使用Move Method將這些方法移動到對應的Class中,以化解其“相思之苦”,讓其牽手。
13:組合與繼承,你有兩種選擇
這里無所謂說哪種好,哪種壞,看情況,以下是這兩種的表現形式,你可以進行互轉。
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:分解復雜判斷
復雜的判斷基礎總是要分解的,因為它太容易閱讀了,寫注釋?注釋一坨 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:盡快返回
實際上,該條是要求我們,可以在方法內部多使用 return,直到最后才 return,會使得最終結果看起來很復雜;
?
二:蓋房子
1:尋找邊界
架構的第一原則,是尋找邊界,最直觀的成果物就是建立幾個解決方案,解決方案內有多少個項目。劃邊界的最終目的就是要告訴組員:什么樣的代碼應該編寫到哪個解決方案中。
2:建立公共庫
任何解決方案幾乎都需要一個公共庫,用于放置一些 Helper 類。
3:資源可以作為一個單獨的項目
不同的資源可以獨立成為不同的項目,圖片、JS、Style字典、配置文件等,都可以作為資源。另外,第三方的 DLL 也需要作為資源獨立出來,把它們注冊到全局程序集中不如直接作為 Content 包含進項目來的舒爽。
4:客戶端邏輯最小化
.NET 程序最讓人詬病的是:混淆了也可窺測你的源碼。除了這個原因,從解耦的角度看,UI 或者其它客戶端項目,都應該知道更少的邏輯才好。
5:基于測試的與MVC、MVVM、MVP
如果一開始你并不知道什么是 MVC 或者 MVVM,那么沒關系,先試著掌握單元測試,把代碼寫成基于測試的。我有一個激進的觀點是,所有的架構模式,其實目的都是為了代碼可測試。
6:AOP
權限認證是典型的面向切面編程。不是 Attribute 才能帶來 AOP 思想,把要運行的代碼交給一個 Action ,也能實現 AOP。
7:模版模式、繼承與多態
繼承不是多態,繼承的另一個價值叫做:模版模式。如果一件 Case 有多個實現途徑,它就應該是模版的,因為你總能找到一些方法放置到父類中去;
8:工廠模式與工廠
類不是被調用者 new 出來的,而是調用某個類的某個方法后被返回出來的,就叫做工廠模式。這類也叫做對象容器。對象容器也可以很復雜,復雜到叫做一個框架,比如 Unity。
9:觀察者模式、事件通知
事件就是觀察者模式。解耦也可以使用觀察者模式來實現。
10:接口的存在都是有目的的
自從 面向接口編程 這個概念提出來后,接口就開始變得漫天飛。接口的出現不能基于某種假設,而是實際已經發生了作用。
11:避免二轉手的代碼
二轉手的代碼常常來自于所謂三層架構代碼,UI-BLL-DAL,然后 BLL 中的大量方法實際就只有一句話 Dal.Update(model),老實說,我受夠了這樣的代碼。
12:見到條件,就考慮是否使用策略模式
“使用策略類” 是指用設計模式中的策略模式來替換原來的switch case和if else語句,這樣可以解開耦合,同時也使維護性和系統的可擴展性大大增強。
如下面代碼所示,ClientCode 類會更加枚舉State的值來調用ShippingInfo 的不同方法,但是這樣就會產生很多的判斷語句,如果代碼量加大,類變得很大了的話,維護中改動也會變得很大,每次改動一個地方,都要對整個結構進行編譯(假如是多個工程),所以我們想到了對它進行重構,剝開耦合。
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;}} }重構后的代碼如下所示,抽象出一個IShippingCalculation 接口,然后把ShippingInfo 類里面的GetAlaskaShippingAmount、GetNewYorkShippingAmount、GetFloridaShippingAmount三個方法分別提煉成三個類,然后繼承自IShippingCalculation 接口,這樣在調用的時候就可以通過IEnumerable<IShippingCalculation> 來解除之前的switch case語句,這和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;}} }總結:這種重構在設計模式當中把它單獨取了一個名字——策略模式,這樣做的好處就是可以隔開耦合,以注入的形式實現功能,這使增加功能變得更加容易和簡便,同樣也增強了整個系統的穩定性和健壯性。
13:分解依賴
無抽象、靜態類、靜態方法都是不可單元測試的。那么,如果我們要寫出可測試的代碼,又要用到這些靜態類等,該怎么辦,實際上我們需要兩個步驟:
1:為它們寫一個包裝類,讓這個包裝類是抽象的(繼承自接口,或者抽象類,或者方法本身是Virtual的);
2:通知客戶端程序員,使用包裝類來代替原先的靜態類來寫業務邏輯;
FCL 中的典型例子是:HttpResponseWrapper。
轉載于:https://www.cnblogs.com/luminji/p/3289030.html
總結
- 上一篇: 做一个.net 程序员要掌握的知识提纲
- 下一篇: Ajax判断图片类型