常見代碼重構(gòu)技巧(非常實用)
點擊上方“碼農(nóng)突圍”,馬上關(guān)注 這里是碼農(nóng)充電第一站,回復(fù)“666”,獲取一份專屬大禮包 真愛,請設(shè)置“星標(biāo)”或點個“在看
關(guān)于重構(gòu)
為什么要重構(gòu)

編碼之前缺乏有效的設(shè)計
成本上的考慮,在原功能堆砌式編程
缺乏有效代碼質(zhì)量監(jiān)督機制
什么是重構(gòu)
重構(gòu)(名詞):對軟件內(nèi)部結(jié)構(gòu)的一種調(diào)整,目的是在不改變軟件可觀察行為的前提下,提高其可理解性,降低其修改成本。
重構(gòu)(動詞):使用一系列重構(gòu)手法,在不改變軟件可觀察行為的前提下,調(diào)整其結(jié)構(gòu)。
代碼的壞味道

實現(xiàn)邏輯相同、執(zhí)行流程相同
方法中的語句不在同一個抽象層級
邏輯難以理解,需要大量的注釋
面向過程編程而非面向?qū)ο?/p>
類做了太多的事情
包含過多的實例變量和方法
類的命名不足以描述所做的事情
發(fā)散式變化:某個類經(jīng)常因為不同的原因在不同的方向上發(fā)生變化
散彈式修改:發(fā)生某種變化時,需要在多個類中做修改
某個類的方法過多的使用其他類的成員
兩個類、方法簽名中包含相同的字段或參數(shù)
應(yīng)該使用類但使用基本類型,比如表示數(shù)值與幣種的Money類、起始值與結(jié)束值的Range類
繼承打破了封裝性,子類依賴其父類中特定功能的實現(xiàn)細節(jié)
子類必須跟著其父類的更新而演變,除非父類是專門為了擴展而設(shè)計,并且有很好的文檔說明
某個實例變量僅為某種特定情況而設(shè)置
將實例變量與相應(yīng)的方法提取到新的類中
僅包含字段和訪問(讀寫)這些字段的方法
此類被稱為數(shù)據(jù)容器,應(yīng)保持最小可變性
命名無法準(zhǔn)確描述做的事情
命名不符合約定俗稱的慣例
壞代碼的問題
難以復(fù)用
系統(tǒng)關(guān)聯(lián)性過多,導(dǎo)致很難分離可重用部分
難于變化
一處變化導(dǎo)致其他很多部分的修改,不利于系統(tǒng)穩(wěn)定
難于理解
命名雜亂,結(jié)構(gòu)混亂,難于閱讀和理解
難以測試
分支、依賴較多,難以覆蓋全面
什么是好代碼

如何重構(gòu)
SOLID原則

單一職責(zé)原則
開放-關(guān)閉原則
里氏替換原則
父類中凡是已經(jīng)實現(xiàn)好的方法(相對于抽象方法而言),實際上是在設(shè)定一系列的規(guī)范和契約,雖然它不強制要求所有的子類必須遵從這些契約,但是如果子類對這些非抽象方法任意修改,就會對整個繼承體系造成破壞。
接口隔離原則
依賴反轉(zhuǎn)原則
迪米特法則
合成復(fù)用原則
設(shè)計模式
設(shè)計模式:軟件開發(fā)人員在軟件開發(fā)過程中面臨的一般問題的解決方案。這些解決方案是眾多軟件開發(fā)人員經(jīng)過相當(dāng)長的一段時間的試驗和錯誤總結(jié)出來的。每種模式都描述了一個在我們周圍不斷重復(fù)發(fā)生的問題,以及該問題的核心解決方案。
創(chuàng)建型:主要解決對象的創(chuàng)建問題,封裝復(fù)雜的創(chuàng)建過程,解耦對象的創(chuàng)建代碼和使用代碼
結(jié)構(gòu)型:主要通過類或?qū)ο蟮牟煌M合,解耦不同功能的耦合
行為型:主要解決的是類或?qū)ο笾g的交互行為的耦合




代碼分層

server_main:配置層,負(fù)責(zé)整個項目的module管理,maven配置管理、資源管理等;
server_application:應(yīng)用接入層,承接外部流量入口,例如:RPC接口實現(xiàn)、消息處理、定時任務(wù)等;不要在此包含業(yè)務(wù)邏輯;
server_biz:核心業(yè)務(wù)層,用例服務(wù)、領(lǐng)域?qū)嶓w、領(lǐng)域事件等
server_irepository:資源接口層,負(fù)責(zé)資源接口的暴露
server_repository:資源層,負(fù)責(zé)資源的proxy訪問,統(tǒng)一外部資源訪問,隔離變化。注意:這里強調(diào)的是弱業(yè)務(wù)性,強數(shù)據(jù)性;
server_common:公共層,vo、工具等
命名規(guī)范
一個好的命名應(yīng)該要滿足以下兩個約束:
準(zhǔn)確描述所做得事情
格式符合通用的慣例
如果你覺得一個類或方法難以命名的時候,可能是其承載的功能太多了,需要進一步拆分。
約定俗稱的慣例

類命名
類名使用大駝峰命名形式,類命通常使用名詞或名詞短語。接口名除了用名詞和名詞短語以外,還可以使用形容詞或形容詞短語,如 Cloneable,Callable 等,表示實現(xiàn)該接口的類有某種功能或能力。

方法命名
方法命名采用小駝峰的形式,首字小寫,往后的每個單詞首字母都要大寫。和類名不同的是,方法命名一般為動詞或動詞短語,與參數(shù)或參數(shù)名共同組成動賓短語,即動詞 + 名詞。一個好的函數(shù)名一般能通過名字直接獲知該函數(shù)實現(xiàn)什么樣的功能。

重構(gòu)技巧
提煉方法
方法是代碼復(fù)用的最小粒度,方法過長不利于復(fù)用,可讀性低,提煉方法往往是重構(gòu)工作的第一步。
把一個問題分解為一系列功能性步驟,并假定這些功能步驟已經(jīng)實現(xiàn)
我們只需把把各個函數(shù)組織在一起即可解決這一問題
在組織好整個功能后,我們在分別實現(xiàn)各個方法函數(shù)
/**
* 1、交易信息開始于一串標(biāo)準(zhǔn)ASCII字符串。
* 2、這個信息字符串必須轉(zhuǎn)換成一個字符串的數(shù)組,數(shù)組存放的此次交易的領(lǐng)域語言中所包含的詞匯元素(token)。
* 3、每一個詞匯必須標(biāo)準(zhǔn)化。
* 4、包含超過150個詞匯元素的交易,應(yīng)該采用不同于小型交易的方式(不同的算法)來提交,以提高效率。
* 5、如果提交成功,API返回”true”;失敗,則返回”false”。
*/
public class Transaction {
public Boolean commit(String command) {
Boolean result = true;
String[] tokens = tokenize(command);
normalizeTokens(tokens);
if (isALargeTransaction(tokens)) {
result = processLargeTransaction(tokens);
} else {
result = processSmallTransaction(tokens);
}
return result;
}
}
以函數(shù)對象取代函數(shù)
引入?yún)?shù)對象
移除對參數(shù)的賦值
public int discount(int inputVal, int quantity, int yearToDate) {
if (inputVal > 50) inputVal -= 2;
if (quantity > 100) inputVal -= 1;
if (yearToDate > 10000) inputVal -= 4;
return inputVal;
}
public int discount(int inputVal, int quantity, int yearToDate) {
int result = inputVal;
if (inputVal > 50) result -= 2;
if (quantity > 100) result -= 1;
if (yearToDate > 10000) result -= 4;
return result;
}
將查詢與修改分離
不要在convert中調(diào)用寫操作,避免副作用
常見的例外:將查詢結(jié)果緩存到本地
移除不必要臨時變量
引入解釋性變量
if ((platform.toUpperCase().indexOf("MAC") > -1)
&& (browser.toUpperCase().indexOf("IE") > -1) && wasInitialized() && resize > 0) {
// do something
}
final boolean isMacOs = platform.toUpperCase().indexOf("MAC") > -1;
final boolean isIEBrowser = browser.toUpperCase().indexOf("IE") > -1;
final boolean wasResized = resize > 0;
if (isMacOs && isIEBrowser && wasInitialized() && wasResized) {
// do something
}
使用衛(wèi)語句替代嵌套條件判斷
//未使用衛(wèi)語句
public void getHello(int type) {
if (type == 1) {
return;
} else {
if (type == 2) {
return;
} else {
if (type == 3) {
return;
} else {
setHello();
}
}
}
}
//使用衛(wèi)語句
public void getHello(int type) {
if (type == 1) {
return;
}
if (type == 2) {
return;
}
if (type == 3) {
return;
}
setHello();
}
使用多態(tài)替代條件判斷斷
public int calculate(int a, int b, String operator) {
int result = Integer.MIN_VALUE;
if ("add".equals(operator)) {
result = a + b;
} else if ("multiply".equals(operator)) {
result = a * b;
} else if ("divide".equals(operator)) {
result = a / b;
} else if ("subtract".equals(operator)) {
result = a - b;
}
return result;
}
基于這種場景,我們可以考慮使用“多態(tài)”來代替冗長的條件判斷,將if else(或switch)中的“變化點”封裝到子類中。這樣,就不需要使用if else(或switch)語句了,取而代之的是子類多態(tài)的實例,從而使得提高代碼的可讀性和可擴展性。很多設(shè)計模式使用都是這種套路,比如策略模式、狀態(tài)模式。
public interface Operation {
int apply(int a, int b);
}
public class Addition implements Operation {
@Override
public int apply(int a, int b) {
return a + b;
}
}
public class OperatorFactory {
private final static Map<String, Operation> operationMap = new HashMap<>();
static {
operationMap.put("add", new Addition());
operationMap.put("divide", new Division());
// more operators
}
public static Operation getOperation(String operator) {
return operationMap.get(operator);
}
}
public int calculate(int a, int b, String operator) {
if (OperatorFactory .getOperation == null) {
throw new IllegalArgumentException("Invalid Operator");
}
return OperatorFactory .getOperation(operator).apply(a, b);
}
使用異常替代返回錯誤碼
不要使用異常處理用于正常的業(yè)務(wù)流程控制 異常處理的性能成本非常高 盡量使用標(biāo)準(zhǔn)異常 避免在finally語句塊中拋出異常 如果同時拋出兩個異常,則第一個異常的調(diào)用棧會丟失 finally塊中應(yīng)只做關(guān)閉資源這類的事情
//使用錯誤碼
public boolean withdraw(int amount) {
if (balance < amount) {
return false;
} else {
balance -= amount;
return true;
}
}
//使用異常
public void withdraw(int amount) {
if (amount > balance) {
throw new IllegalArgumentException("amount too large");
}
balance -= amount;
}
引入斷言
不要濫用斷言,不要使用它來檢查“應(yīng)該為真”的條件,只使用它來檢查“一定必須為真”的條件
如果斷言所指示的約束條件不能滿足,代碼是否仍能正常運行?如果可以就去掉斷言
引入Null對象或特殊對象
//空對象的例子
public class OperatorFactory {
static Map<String, Operation> operationMap = new HashMap<>();
static {
operationMap.put("add", new Addition());
operationMap.put("divide", new Division());
// more operators
}
public static Optional<Operation> getOperation(String operator) {
return Optional.ofNullable(operationMap.get(operator));
}
}
public int calculate(int a, int b, String operator) {
Operation targetOperation = OperatorFactory.getOperation(operator)
.orElseThrow(() -> new IllegalArgumentException("Invalid Operator"));
return targetOperation.apply(a, b);
}
//特殊對象的例子
public class InvalidOp implements Operation {
@Override
public int apply(int a, int b) {
throw new IllegalArgumentException("Invalid Operator");
}
}
提煉類
//原始類
public class Person {
private String name;
private String officeAreaCode;
private String officeNumber;
public String getName() {
return name;
}
public String getTelephoneNumber() {
return ("(" + officeAreaCode + ")" + officeNumber);
}
public String getOfficeAreaCode() {
return officeAreaCode;
}
public void setOfficeAreaCode(String arg) {
officeAreaCode = arg;
}
public String getOfficeNumber() {
return officeNumber;
}
public void setOfficeNumber(String arg) {
officeNumber = arg;
}
}
//新提煉的類(以對象替換數(shù)據(jù)值)
public class TelephoneNumber {
private String areaCode;
private String number;
public String getTelephnoeNumber() {
return ("(" + getAreaCode() + ")" + number);
}
String getAreaCode() {
return areaCode;
}
void setAreaCode(String arg) {
areaCode = arg;
}
String getNumber() {
return number;
}
void setNumber(String arg) {
number = arg;
}
}
組合優(yōu)先于繼承
// Inappropriate use of inheritance!
public class InstrumentedHashSet<E> extends HashSet<E> {
// The number of attempted element insertions
private int addCount = 0;
public InstrumentedHashSet() { }
public InstrumentedHashSet(int initCap, float loadFactor) {
super(initCap, loadFactor);
}
@Override
public boolean add(E e) {
addCount++;
return super.add(e);
}
@Override
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c);
}
public int getAddCount() {
return addCount;
}
}
// Reusable forwarding class
public class ForwardingSet<E> implements Set<E> {
private final Set<E> s;
public ForwardingSet(Set<E> s) { this.s = s; }
@Override
public int size() { return s.size(); }
@Override
public boolean isEmpty() { return s.isEmpty(); }
@Override
public boolean contains(Object o) { return s.contains(o); }
@Override
public Iterator<E> iterator() { return s.iterator(); }
@Override
public Object[] toArray() { return s.toArray(); }
@Override
public <T> T[] toArray(T[] a) { return s.toArray(a); }
@Override
public boolean add(E e) { return s.add(e); }
@Override
public boolean remove(Object o) { return s.remove(o); }
@Override
public boolean containsAll(Collection<?> c) { return s.containsAll(c); }
@Override
public boolean addAll(Collection<? extends E> c) { return s.addAll(c); }
@Override
public boolean retainAll(Collection<?> c) { return s.retainAll(c); }
@Override
public boolean removeAll(Collection<?> c) { return s.removeAll(c); }
@Override
public void clear() { s.clear(); }
}
// Wrappter class - uses composition in place of inheritance
public class InstrumentedHashSet<E> extends ForwardingSet<E> {
private int addCount = 0;
public InstrumentedHashSet1(Set<E> s) {
super(s);
}
@Override
public boolean add(E e) {
addCount++;
return super.add(e);
}
@Override
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c);
}
public int getAddCount() {
return addCount;
}
}
只有當(dāng)子類真正是父類的子類型時,才適合繼承。對于兩個類A和B,只有兩者之間確實存在“is-a”關(guān)系的時候,類B才應(yīng)該繼承A;
在包的內(nèi)部使用繼承是非常安全的,子類和父類的實現(xiàn)都處在同一個程序員的控制之下;
對于專門為了繼承而設(shè)計并且具有很好的文檔說明的類來說,使用繼承也是非常安全的;
其他情況就應(yīng)該優(yōu)先考慮組合的方式來實現(xiàn)
接口優(yōu)于抽象類
現(xiàn)有的類可以很容易被更新,以實現(xiàn)新的接口。
接口是定義混合類型(比如Comparable)的理想選擇。
接口允許構(gòu)造非層次結(jié)構(gòu)的類型框架。
接口的變量修飾符只能是public static final的
接口的方法修飾符只能是public的
接口不存在構(gòu)造函數(shù),也不存在this
可以給現(xiàn)有接口增加缺省方法,但不能確保這些方法在之前存在的實現(xiàn)中都能良好運行。
因為這些默認(rèn)方法是被注入到現(xiàn)有實現(xiàn)中的,它們的實現(xiàn)者并不知道,也沒有許可
Java 8 之前我們知道,一個接口的所有方法其子類必須實現(xiàn)(當(dāng)然,這個子類不是一個抽象類),但是 java 8 之后接口的默認(rèn)方法可以選擇不實現(xiàn),如上的操作是可以通過編譯期編譯的。這樣就避免了由 Java 7 升級到 Java 8 時項目編譯報錯了。Java8在核心集合接口中增加了許多新的缺省方法,主要是為了便于使用lambda。
例如在 List 等集合接口中都有一些默認(rèn)方法,List 接口中默認(rèn)提供 replaceAll(UnaryOperator)、sort(Comparator)、、spliterator()等默認(rèn)方法,這些方法在接口內(nèi)部創(chuàng)建,避免了為了這些方法而專門去創(chuàng)建相應(yīng)的工具類。
在 Java 8 之前我們可能需要創(chuàng)建一個基類來實現(xiàn)代碼復(fù)用,而默認(rèn)方法的出現(xiàn),可以不必要去創(chuàng)建基類。

優(yōu)先考慮泛型
// 比較三個值并返回最大值
public static <T extends Comparable<T>> T maximum(T x, T y, T z) {
T max = x;
// 假設(shè)x是初始最大值
if ( y.compareTo( max ) > 0 ) {
max = y; //y 更大
} if ( z.compareTo( max ) > 0 ) {
max = z; // 現(xiàn)在 z 更大
} return max; // 返回最大對象
}
public static void main( String args[] ) {
System.out.printf( "%d, %d 和 %d 中最大的數(shù)為 %d\n\n", 3, 4, 5, maximum( 3, 4, 5 ));
System.out.printf( "%.1f, %.1f 和 %.1f 中最大的數(shù)為 %.1f\n\n", 6.6, 8.8, 7.7, maximum( 6.6, 8.8, 7.7 ));
System.out.printf( "%s, %s 和 %s 中最大的數(shù)為 %s\n","pear", "apple", "orange", maximum( "pear", "apple", "orange" ) );
}
不要使用原生態(tài)類型
要盡可能地消除每一個非受檢警告
利用有限制通配符來提升API的靈活性
//List<? extends E>
// Number 可以認(rèn)為 是Number 的 "子類"
List<? extends Number> numberArray = new ArrayList<Number>();
// Integer 是 Number 的子類
List<? extends Number> numberArray = new ArrayList<Integer>();
// Double 是 Number 的子類
List<? extends Number> numberArray = new ArrayList<Double>();
//List<? super E>
// Integer 可以認(rèn)為是 Integer 的 "父類"
List<? super Integer> array = new ArrayList<Integer>();、
// Number 是 Integer 的 父類
List<? super Integer> array = new ArrayList<Number>();
// Object 是 Integer 的 父類
List<? super Integer> array = new ArrayList<Object>();
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
int srcSize = src.size();
if (srcSize > dest.size())
throw new IndexOutOfBoundsException("Source does not fit in dest");
if (srcSize < COPY_THRESHOLD || (src instanceof RandomAccess && dest instanceof RandomAccess)) {
for (int i=0; i<srcSize; i++)
dest.set(i, src.get(i));
} else {
ListIterator<? super T> di=dest.listIterator();
ListIterator<? extends T> si=src.listIterator();
for (int i=0; i<srcSize; i++) {
di.next();
di.set(si.next());
}
}
}
靜態(tài)成員類優(yōu)于非靜態(tài)成員類
匿名類(anonymous class)
局部類(local class)
靜態(tài)成員類(static member class)
非靜態(tài)成員類(nonstatic member class)
優(yōu)先使用模板/工具類
分離對象的創(chuàng)建與使用
public class BusinessObject {
public void actionMethond {
//Other things
Service myServiceObj = new Service();
myServiceObj.doService();
//Other things
}
}
public class BusinessObject {
public void actionMethond {
//Other things
Service myServiceObj = new ServiceImpl();
myServiceObj.doService();
//Other things
}
}
public class BusinessObject {
private Service myServiceObj;
public BusinessObject(Service aService) {
myServiceObj = aService;
}
public void actionMethond {
//Other things
myServiceObj.doService();
//Other things
}
}
public class BusinessObject {
private Service myServiceObj;
public BusinessObject() {
myServiceObj = ServiceFactory;
}
public void actionMethond {
//Other things
myServiceObj.doService();
//Other things
}
}
對象的創(chuàng)建者耦合的是對象的具體類型,而對象的使用者耦合的是對象的接口。也就是說,創(chuàng)建者關(guān)心的是這個對象是什么,而使用者關(guān)心的是它能干什么。這兩者應(yīng)該視為獨立的考量,它們往往會因為不同的原因而改變。
可訪問性最小化
私有的(private修飾)--只有在聲明該成員的頂層類內(nèi)部才可以訪問這個成員;
包級私有的(默認(rèn))--聲明該成員的包內(nèi)部的任何類都可以訪問這個成員;
受保護的(protected修飾)--聲明該成員的類的子類可以訪問這個成員,并且聲明該成員的包內(nèi)部的任何類也可以訪問這個成員;
公有的(public修飾)--在任何地方都可以訪問該成員;
如果類或接口能夠做成包級私有的,它就應(yīng)該被做成包級私有的;
如果一個包級私有的頂層類或接口只是在某一個類的內(nèi)部被用到,就應(yīng)該考慮使它成為那個類的私有嵌套類;
公有類不應(yīng)直接暴露實例域,應(yīng)該提供相應(yīng)的方法以保留將來改變該類的內(nèi)部表示法的靈活性;
當(dāng)確定了類的公有API之后,應(yīng)該把其他的成員都變成私有的;
如果同一個包下的類之間存在比較多的訪問時,就要考慮重新設(shè)計以減少這種耦合;
可變性最小化
聲明所有的域都是私有的
聲明所有的域都是final的
如果一個指向新創(chuàng)建實例的引用在缺乏同步機制的情況下,從一個線程被傳遞到另一個線程,就必須確保正確的行為
不提供任何會修改對象狀態(tài)的方法
保證類不會被擴展(防止子類化,類聲明為final)
防止粗心或者惡意的子類假裝對象的狀態(tài)已經(jīng)改變,從而破壞該類的不可變行為
確保對任何可變組件的互斥訪問
如果類具有指向可變對象的域,則必須確保該類的客戶端無法獲得指向這些對象的引用。并且,永遠不要用客戶端提供的對象引用來初始化這樣的域,也不要從任何訪問方法中返回該對象引用。在構(gòu)造器、訪問方法和readObject 方法中使用保護性拷貝技術(shù)
除非有很好的理由要讓類成為可變的類,否則它就應(yīng)該是不可變的;
如果類不能被做成不可變的,仍然應(yīng)該盡可能地限制它的可變性;
除非有令人信服的理由要使域變成非final的,否則要使每個域都是private final的;
構(gòu)造器應(yīng)該創(chuàng)建完全初始化的對象,并建立起所有的約束關(guān)系;
質(zhì)量如何保證
測試驅(qū)動開發(fā)
測試驅(qū)動開發(fā)(TDD)要求以測試作為開發(fā)過程的中心,要求在編寫任何代碼之前,首先編寫用于產(chǎn)碼行為的測試,而編寫的代碼又要以使測試通過為目標(biāo)。TDD要求測試可以完全自動化地運行,并在對代碼重構(gòu)前后必須運行測試。
TDD的開發(fā)周期

兩個基本的原則
僅在測試失敗時才編寫代碼并且只編寫剛好使測試通過的代碼
編寫下一個測試之前消除現(xiàn)有的重復(fù)設(shè)計,優(yōu)化設(shè)計結(jié)構(gòu)
分層測試點

參考資料
重構(gòu)-改善既有代碼的設(shè)計 設(shè)計模式 Effective Java 敏捷軟件開發(fā)與設(shè)計的最佳實踐 實現(xiàn)模式 測試驅(qū)動開發(fā)
- END - 最近熱文
? 如何寫出讓同事無法維護的代碼? ? 86版西游記“紅孩兒”成中科院博士!做CTO身價過億! ? 劉強東的代碼水平到底有多牛? 網(wǎng)友:95年一個晚上賺5萬! ? 一些惡心的代碼片段

