減少if else 語句使用的個人集錦

是否你有過這樣的體驗:天天寫着if (){...if(){..}}else{...}的代碼,天天腦海裏一直模擬的程序的運行場景。如果是那請看下面文章:↓

if語句的第一個問題在於,通常出現if語句的代碼很容易越改越糟。我們試着寫個新的if語句:

public void theProblem(boolean someCondition) {
        // SharedState

        if(someCondition) {
            // CodeBlockA
        } else {
            // CodeBlockB
        }
}
這時候還不算太糟,但已經存在一些問題了。在閱讀這段代碼時,我必須得去查看對同一個SharedState來說,CodeBlockA和CodeBlockB有什麼改動。最開始這段代碼很容易閱讀,但隨着CodeBlock越來越多,耦合越來越複雜之後,就會很難讀。
上面這種CodeBlock進一步嵌套if語句與本地return的濫用情況也很常見,很難搞懂業務邏輯是選擇了哪種路徑。
if語句的第二個問題在於:複製時會有問題,也就是說,if語句缺失domain的概念。很容易由於在不需要的情況下,由於將內容放在一起而增加耦合性,造成代碼難讀難改。
而第三個問題在於:開發者必須在頭腦中模擬執行實現情況——你得讓自己變成一臺小型電腦,從而造成腦細胞浪費。開發者的精力應當用來思考如何解決問題,而不是浪費在如何將複雜的代碼分支結構編織在一起之上。
雖然想要直截了當地寫出替代方案,但首先我得強調這句話:
凡事中庸而行,尤其是中庸本身
if語句通常會讓代碼更加複雜,但這不代表我們要完全拋棄if語句。我曾經看到過一些非常糟糕的代碼,只是爲了消除所有的if語句而刻意避開if語句。我們想要繞開這個誤區, 
下面我給出的每種模式,都會給出使用範圍。
單獨的if語句如果不復制到其他地方,也許是不錯的句子。在複製if語句時,我們會希望預知危險的第六感起效。
在代碼庫之外,在與危險的外部世界交流時,我們會想要驗證incoming response,並根據其作出相應的修改。但在自己的代碼庫中,由於有可靠的gatekeeper把關,我覺得這是個很好的機會,我們可以嘗試使用簡單、更爲豐富與強大的替代方案來實現。
 模式1:布爾參數(Boolean Params)

背景: 有方法在修改行爲時使用了boolean。

public void example() {
    FileUtils.createFile("name.txt", "file contents", false);
    FileUtils.createFile("name_temp.txt", "file contents", true);
}

public class FileUtils {
    public static void createFile(String name, String contents, boolean temporary) {
        if(temporary) {
            // save temp file
        } else {
            // save permanent file
        }
    }
}
問題: 在看到這段代碼時,實際上你是將兩個方法捆綁到一起,布爾參數的出現讓你有機會在代碼中定義一個概念。
適用範圍: 通常看到這種情況,如果在編譯時我們可以算出代碼要採用哪種路徑,就可以放心使用這種模式。
解決方案: 將這個方法拆分成兩個新的方法,然後if就不見了。
模式2:使用多態(Polymorphism)
  背景: 根據類型switch時。
public class Bird {

    private enum Species {
        EUROPEAN, AFRICAN, NORWEGIAN_BLUE;
    }

    private boolean isNailed;
    private Species type;

    public double getSpeed() {
        switch (type) {
            case EUROPEAN:
                return getBaseSpeed();
            case AFRICAN:
                return getBaseSpeed() - getLoadFactor();
            case NORWEGIAN_BLUE:
                return isNailed ? 0 : getBaseSpeed();
            default:
                return 0;
        }
    }

    private double getLoadFactor() {
        return 3;
    }

    private double getBaseSpeed() {
        return 10;
    }
}
問題: 在添加新的類型時,我們必須要記得更新switch語句,此外隨着不同bird的概念添加進來,bird類的凝聚力越來越糟。
適用範圍:根據類型做單次切換是可行的,如果switch太多,在添加新類型時如果忘記更新現有隱藏類型中的所有switch,就會導致bug出現,8thlight博客關於這種情況有詳細描述。
解決方案: 使用多態,添加新類型時大家都不會忘記添加相關行爲。 
注意:上例爲了簡潔只寫了一個方法,但在有多個switch時更有用。
public abstract class Bird {

    public abstract double getSpeed();

    protected double getLoadFactor() {
        return 3;
    }

    protected double getBaseSpeed() {
        return 10;
    }
}

public class EuropeanBird extends Bird {
    public double getSpeed() {
        return getBaseSpeed();
    }
}

public class AfricanBird extends Bird {
    public double getSpeed() {
        return getBaseSpeed() - getLoadFactor();
    }
}

public class NorwegianBird extends Bird {
    private boolean isNailed;

    public double getSpeed() {
        return isNailed ? 0 : getBaseSpeed();
    }
}


 模式3:將內聯語句(Inline statements)轉爲表達式 
背景: 在計算布爾表達式時,包含if語句樹。

public boolean horrible(boolean foo, boolean bar, boolean baz) {
    if (foo) {
        if (bar) {
            return true;
        }
    }

    if (baz) {
        return true;
    } else {
        return false;
    }
}
問題:這種代碼會導致開發者必須用大腦來模擬計算機對方法的處理。
適用範圍:很少有不適用的情況,像這樣的代碼可以合成一行,或者拆成不同的部分。
解決方案: 將if語句樹合成單個表達式。

	public boolean horrible(boolean foo, boolean bar, boolean baz) {
    return foo && bar || baz;
}
 模式4:給出應對策略 
背景:在調用一些其他代碼時,無法確保路徑是成功的。

public class Repository {

    public String getRecord(int id) {
        return null; // cannot find the record
    }
}

public class Finder {
    public String displayRecord(Repository repository) {
        String record = repository.getRecord(123);
        if(record == null) {
            return "Not found";
        } else {
            return record;
        }
    }
}


問題: 這類if語句增加了處理同一個對象或者數據結構的時間,其中包含隱藏耦合——null的情況。其它對象可能會返回其他代表沒有結果的magic value。

 適用範圍:最好將這類if語句放在一個地方,由於不會重複,我們就能將爲空對象的magic value刪除。 

   解決方案:針對被調用代碼,給出應對策略。Ruby的Hash#fetch就是很好的案例,Java也用到了類似的方法。這種模式也可以用在刪除例外情況時。

private class Repository {
    public String getRecord(int id, String defaultValue) {
        String result = Db.getRecord(id);

        if (result != null) {
            return result;
        }

        return defaultValue;
    }
}

public class Finder {
    public String displayRecord(Repository repository) {
        return repository.getRecord(123, "Not found");
    }
}





發佈了25 篇原創文章 · 獲贊 18 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章