提前總結就是四招: 一、重覆的代碼提煉成函數 二、把過長的函數變小 三、參數列太長或變化太頻繁,參數對象化 四、大招:類的代碼行數太多,要考慮提煉子類。 ...
我不是個偉大的程式員;我只是個有著一些優秀習慣的好程式員而己
本人比較直接,不說虛的,直接上乾貨。
目錄
Duplicated Code(重覆的代碼)
Long Method(過長函數)
Long Parameter List(過長參數列)
Large Class(過大類)
提前總結就是四招:
一、重覆的代碼提煉成函數
二、把過長的函數變小
三、參數列太長或變化太頻繁,參數對象化
四、大招:類的代碼行數太多,要考慮提煉子類。
一、重覆的代碼提煉成函數
第一種情況是:同一個class內的兩個函數含有相同表達式(expression)。
void printOwing(String _name) { Enumeration e =_orders.elements(); double outstanding = 0.0; // print banner System.out.println ("**************************"); System.out.println ("***** Customer Owes ******"); System.out.println ("**************************"); // calculate outstanding while (e.hasMoreElements()) { Order each = (Order) e.nextElement(); outstanding += each.getAmount(); } //print details System.out.println ("name:" + _name); System.out.println ("amount" + outstanding); }
實際上這三部分都可以提煉。
優化後的結果
void printOwing(String _name) { printBanner(); double outstanding = getOutstanding(); printDetails(_name,outstanding); } void printBanner() { // print banner System.out.println ("**************************"); System.out.println ("***** Customer Owes ******"); System.out.println ("**************************"); } void printDetails (String _name,double outstanding) { System.out.println ("name:" + _name); System.out.println ("amount" + outstanding); } double getOutstanding() { Enumeration e = _orders.elements(); double result = 0.0; while (e.hasMoreElements()) { Order each = (Order) e.nextElement(); result = each.getAmount(); } return result; }
第二種情況:兩個subclasses有相同的表達式,或者是相似的表達式
優化的方法是:抽取相同的表達式(屬性和方法),放在父類里,兩個子類再去繼承。
**********************************************************************************
如果是相似的表達式,好抽出共性的,則用模板函數設計模式來處理。
這裡用到了JAVA的兩個特性,繼承和多態。
優化的思路是:
1、在各個subclass 中分解目標函數,把有差異的部分變成入參,封裝成一個模板函數。
2、把模板函數放到父類中。
3、子類根據需要輸入不同的入參,得到需要的結果。
*********************************************************************************
如果是相似的表達式,差異的地方不好抽共性,則用模板函數設計模式來處理。
這裡用到了JAVA的兩個特性,繼承和覆寫(overrides)。
優化的思路是:
1、在各個subclass 中分解目標函數,使分解後的各個函數要不完全相同,要不完全不同。
2、父類有一個主函數包含完全相同的函數和完全不同的函數:相同的函數,抽到父類中,不相同的函數在父類中定義一個函數。
3、子類繼承父類,然後覆寫完全不同的函數,再調用主函數可得到期望的結果。
二、把過長的函數變小
百分之九十九的場合里,要把函數變小,只需使用Extract Method(第一招)。找到函數中適合集在一起的部分,將它們提煉出來形成一個新函數。
如果函數內有大量的參數和臨時變數,它們會對你的函數提煉形成阻礙。這時就要用Replace Temp with Query來消除這些臨時變數
Replace Temp with Query(以查詢取代臨時變數)
優化思路:
1、找出只被賦值一次的臨時變數。
2、將該臨時變數聲明為final
3、編譯:這可確保該臨時變數的確只被賦值一次。
4、將臨時變數等號右側部分提煉到一個獨立函數中;
5、首先將函數聲明為private。日後你可能會發現有更多class需要使用 它,彼時你可再放鬆對它的保護。
6、編譯,測試:確保提煉出來的函數無任何連帶影響(副作用),結果不變;
7、把臨時變數全替換成獨立出來的函數;
以上,over!
例子:未優化代碼
double getPrice() { int basePrice = _quantity * _itemPrice; double discountFactor; if (basePrice > 1000) discountFactor = 0.95; else discountFactor = 0.98; return basePrice * discountFactor; }
開始優化 1~3步驟
double getPrice() { final int basePrice = _quantity * _itemPrice; final double discountFactor; if (basePrice > 1000) discountFactor = 0.95; else discountFactor = 0.98; return basePrice * discountFactor; }
4~6步驟
double getPrice() { final int basePrice = basePrice(); final double discountFactor; if (basePrice > 1000) discountFactor = 0.95; else discountFactor = 0.98; return basePrice * discountFactor; } private int basePrice() { return _quantity * _itemPrice; }
7步驟
double getPrice() { final double discountFactor; if (basePrice() > 1000) discountFactor = 0.95; else discountFactor = 0.98; return basePrice() * discountFactor; } private int basePrice() { return _quantity * _itemPrice; }
搞定basePrice之後,再以類似辦法提煉出一個discountFactor():
double getPrice() { final double discountFactor = discountFactor(); return basePrice() * discountFactor; } private double discountFactor() { if (basePrice() > 1000) return 0.95; else return 0.98; }
最後的效果
double getPrice() { return basePrice() * discountFactor(); } private double discountFactor() { if (basePrice() > 1000) return 0.95; else return 0.98; } private int basePrice() { return _quantity * _itemPrice; }
通過以上的優化,一個大函數,已經變成了多個小函數,重點是代碼的可讀性提高了,順帶的代碼量變少。
三、參數對象化
當你看到一個函數的入參有四,五個,甚至更多時,且好幾個函數都使用這組入參,這時就要用參數對象化來優化代碼。這些函數可能隸屬同一個class,也可能隸屬不同的classes 。這樣一組參數就是所謂的Date Clump (數據泥團)」。這時用一個對象封裝這些參數,再用對象取代它們。
優化思路:
1、入參有四,五個,甚至更多時,就要著手優化;
2、用一個新的class封裝入參,並把這些參數設置為private嚴格保護起來,寫這些參數的get方法和set方法。
3、原函數的入參變成這個新的class對象,函數里的參數用class對象對應的屬性替換。
4、編譯測試;
5、將原先的參數全部去除之後,觀察有無適當函數可以運用Move Method 搬移到參數對象之中。
例子:未優化的代碼
@Autowired private AddressService addressService; public List inquireAddressListAccount( Integer pageNum ,Integer pageSize,String addressName,String mobile,String zipCode,String consignee){ return addressService.inquireAddressList(pageNum,pageSize,addressName,mobile,zipCode,consignee); }
優化
@Autowired private AddressService addressService; public List inquireAddressListAccount( Integer pageNum ,Integer pageSize,InquireAddressListInput output){ return addressService.inquireAddressList(pageNum,pageSize,output); } public class InquireAddressListInput(){ private String addressName; private String mobile; private String zipCode; private String consignee; public String getConsignee() { return consignee; } public void setConsignee(String consignee) { this.consignee = consignee; } public String getMobile() { return mobile; } public void setMobile(String mobile) { this.mobile = mobile; } public String getZipCode() { return zipCode; } public void setZipCode(String zipCode) { this.zipCode = zipCode; } public String getAddressName() { return addressName; } public void setAddressName(String addressName) { this.addressName = addressName; } }
四:大招-提煉類和提煉子類
如果想利用單一class做太多事情,其內往往就會出現太多instance變數。一旦如此,Duplicated Code也就接踵而至了。
Extract Class 是Extract Subclass 之外的另一種選擇,兩者之間的抉擇其實就是委托(delegation)和繼承(inheritance)之間的抉擇。
情況一:某個class做了應該由兩個classes做的事。(Extract Class)
優化思路:
1、明確每個class所負的責任,該做什麼事情;
2、建立一個新class,用以表現從舊class中分離出來的責任;
3、建立「從舊class訪問新class」的連接關係;
4、每次搬移後,編譯、測試。
5、決定是否讓新的class曝光。
例子:未優化的代碼
class Person{ private String _name; private String _officeAreaCode; private String _officeNumber; public String getName() { return _name; } public String getTelephoneNumber() { return ("(" + _officeAreaCode + ") " + _officeNumber); } String getOfficeAreaCode() { return _officeAreaCode; } void setOfficeAreaCode(String arg) { _officeAreaCode = arg; } String getOfficeNumber() { return _officeNumber; } void setOfficeNumber(String arg) { _officeNumber = arg; } }
優化1~2步驟
可以將「與電話號碼相關」的行為分離到一個獨立class中
class TelephoneNumber{ private String _number; private String _areaCode; public String getTelephoneNumber() { return ("(" + _areaCode + ") " + _number); } String getAreaCode() { return _areaCode; } void setAreaCode(String arg) { _areaCode = arg; } String getNumber() { return _number; } void setNumber(String arg) { _number = arg; } }
優化3步驟
class Person... private String _name; private TelephoneNumber _officeTelephone = new TelephoneNumber(); public String getName() { return _name; } public String getTelephoneNumber(){ return _officeTelephone.getTelephoneNumber(); } TelephoneNumber getOfficeTelephone() { return _officeTelephone; }
情況二:class 中的某些特性(features)只被某些(而非全部)實體(instances)用到。Extract Subclass(提煉子類)
優化思路:
1、為source class 定義一個新的subclass
2、為這個新的subclass 提供構造函數。
簡單的作法是:讓subclass 構造函數接受與superclass 構造函數相同的參數,並通過super 調用superclass 構造函數;
3、找出調用superclass 構造函數的所有地點。如果它們需要的是新建的subclass , 令它們改而調用新構造函數。
如果subclass 構造函數需要的參數和superclass 構造函數的參數不同,可以使用Rename Method 修改其參數列。如果subclass 構造函數不需要superclass 構造函數的某些參數,可以使用Rename Method 將它們去除。
如果不再需要直接實體化(具現化,instantiated)superclass ,就將它聲明為抽象類。
4、逐一使用Push Down Method 和 Push Down Field 將source class 的特性移到subclass 去。
5、每次下移之後,編譯並測試。
例子:未優化代碼
--用來決定當地修車廠的工作報價:
class JobItem ... public JobItem (int unitPrice, int quantity, boolean isLabor, Employee employee) { _unitPrice = unitPrice; _quantity = quantity; _isLabor = isLabor; _employee = employee; } public int getTotalPrice() { return getUnitPrice() * _quantity; } public int getUnitPrice(){ return (_isLabor) ? _employee.getRate(): _unitPrice; } public int getQuantity(){ return _quantity; } public Employee getEmployee() { return _employee; } private int _unitPrice; private int _quantity; private Employee _employee; private boolean _isLabor; class Employee... public Employee (int rate) { _rate = rate; } public int getRate() { return _rate; } private int _rate;
優化1步驟
class LaborItem extends JobItem {}
優化2步驟
class LaborItem extends JobItem { public LaborItem (int unitPrice, int quantity, boolean isLabor, Employee employee) { super (unitPrice, quantity, isLabor, employee); } }
這就足以讓新的subclass 通過編譯了。但是這個構造函數會造成混淆:某些參數是LaborItem 所需要的,另一些不是。稍後我再來解決這個問題。
優化3步驟
清理構造函數參數列
class JobItem... protected JobItem (int unitPrice, int quantity, boolean isLabor, Employee employee) { _unitPrice = unitPrice; _quantity = quantity; _isLabor = isLabor; _employee = employee; } public JobItem (int unitPrice, int quantity) { this (unitPrice, quantity, false, null) }
外部調用應該使用新構造函數:
JobItem j2 = new JobItem (10, 15);
測試通過後,再使用Rename Method 修改subclass 構造函數:
class LaborItem public LaborItem (int quantity, Employee employee) { super (0, quantity, true, employee); }
可以將JobItem 的特性向下搬移。先從函數幵始,我先運用 Push Down Method 對付getEmployee() 函數:
class LaborItem extends JobItem { public LaborItem (int unitPrice, int quantity, boolean isLabor, Employee employee) { super (unitPrice, quantity, isLabor, employee); } public LaborItem (int quantity, Employee employee) { super (0, quantity, true, employee); } public Employee getEmployee() { return _employee; } } //因為_employee 值域也將在稍後被下移到LaborItem ,所以我現在先將它聲明為protected。 class JobItem... protected Employee _employee;
將_employee 值域聲明protected 之後,我可以再次清理構造函數,讓_employee 只在「即將去達的subclass 中」被初始化:
class JobItem... protected Employee _employee; protected JobItem (int unitPrice, int quantity, boolean isLabor) { _unitPrice = unitPrice; _quantity = quantity; _isLabor = isLabor; } class LaborItem ... public LaborItem (int quantity, Employee employee) { super (0, quantity, true); _employee = employee; }
下一個優化_isLabor 值域,_isLabor 在JobItem是值為false,在LaborItem值為true。
可以用多態常量函數。所謂「多態常量函數」會在不同的subclass 實現版本中返回不同的固定值
class JobItem... protected boolean isLabor() { return false; } class LaborItem... protected boolean isLabor() { return true; }
就可以擺脫_isLabor 值域了
通過多態代替條件的方式,重構代碼
class JobItem ... public int getUnitPrice(){ return (isLabor()) ? _employee.getRate(): _unitPrice; }
將它重構為:
class JobItem... public int getUnitPrice(){ return _unitPrice; } class LaborItem... public int getUnitPrice(){ return _employee.getRate(); }
使用某項值域的函數全被下移至subclass 後,我就可以使用 Push Down Field 將值域也下移。
最後的結果就是:
public class JobItem { protected JobItem (int unitPrice, int quantity) { _unitPrice = unitPrice; _quantity = quantity; } public int getTotalPrice() { return getUnitPrice() * _quantity; } public int getUnitPrice(){ return _unitPrice; } public int getQuantity(){ return _quantity; } private int _unitPrice; private int _quantity; } // public class LaborItem extends JobItem { private Employee _employee; public LaborItem(int quantity, Employee employee) { super(0, quantity); _employee = employee; } public Employee getEmployee() { return _employee; } public int getUnitPrice() { return _employee.getRate(); } } //public class Employee { public Employee(int rate) { _rate = rate; } public int getRate() { return _rate; } private int _rate; }
一個class如果擁有太多代碼,也適合使用Extract Class和Extract Subclass。
想重構代碼,直接把以上四招看情況用上,更多精彩內容,請等待後續更新。
此文章也同步到了https://blog.csdn.net/shi_hong_fei_hei/article/details/80357078
作者:小虛竹
歡迎任何形式的轉載,但請務必註明出處。
限於本人水平,如果文章和代碼有表述不當之處,還請不吝賜教。