小酌重構系列[5]——為布爾方法命名


概述

當一個方法包含大量的布爾參數時,方法是很脆弱的,由此還可能會產生兩個問題:

1. 方法不容易被理解
2. 給方法的使用者造成一定困擾,可能會產生一些預期之外的結果

本文要介紹的重構策略“為布爾方法命名”,可以有效地避開這兩個問題。

為布爾方法命名

大量布爾參數帶來的問題

下圖中的SomeClass的SomeMethod包含3個布爾參數,如果沒有注釋,調用者根本不知道3個布爾參數所代表的含義。

image

即使為這個方法提供了詳細的注釋,調用者也很容易在調用時出錯。
調用者一不小心寫錯了其中一個參數,就可能產生非預期的行為。

在實際的業務處理中,也許你僅用這3個參數處理2~3個業務Case。
但這個方法有3個布爾參數,相當於為調用者提供了8種調用Case!
難道你的程序需要提供8種Case嗎?完全沒有必要,因為其他幾種Case可能根本不會發生!

var obj = new SomeClass();
obj.SomeMethod(false, false, false);  // way1
obj.SomeMethod(false, true, false);   // way2
obj.SomeMethod(false, true, true);    // way3
obj.SomeMethod(false, false, true);   // way4
obj.SomeMethod(true, true, true);     // way5
obj.SomeMethod(true, false, false);   // way6
obj.SomeMethod(true, true, false);    // way7
obj.SomeMethod(true, false, true);    // way8

這好比上個世紀生產的電視機遙控器,幾十個按鍵,用戶沒有說明書根本就不知道怎么用!
對於用戶來說,一個遙控器用來調節頻道、音量就足夠了。
給用戶太多地選擇反而不是一件好的事情,用戶會因太多的選擇而徘徊不定,永遠不要給用戶提供繁雜的使用方式!

重構策略

“為布爾方法命名”這種策略,意在將一些布爾參數提取出來,結合適當的方法命名來取代布爾參數。
接下來,進入我們的示例環節,用實際的示例來說明這個重構策略。

示例

重構前

CreateAccount()方法用於創建賬戶,它提供了4個參數,后3個參數都是布爾類型的。

public class BankAccount
{
    public void CreateAccount(Customer customer, bool withChecking, bool withSavings, bool withStocks)
    {
        // do work
    }
}

如果你是這個方法的調用者,在你腦袋中或許會蹦出8種調用方式,但你知道該如何調用嗎?
我不知道你們的答案是什么,我的答案是:不明確。

重構后

仔細分析“創建銀行賬戶”這項業務,我們發現“創建賬戶”只會產生兩種Case。

1. 驗證並創建賬戶
PS: 你去銀行開戶時,需要驗證你的身份信息。

2. 驗證並創建賬戶,同時存入初始儲蓄金額
PS: 你去銀行開戶時,當身份信息驗證通過后,你可以向銀行卡中存入一部分初始金額。


我們提供了2個方法來實現這2種Case,這2個方法使用具有意義的命名,並將原有的CreateAccount()方法用private修飾限制其訪問性。

public class BankAccount
{
    public void CreateAccountWithChecking(Customer customer)
    {
        CreateAccount(customer, true, false);
    }

    public void CreateAccountWithCheckingAndSavings(Customer customer)
    {
        CreateAccount(customer, true, true);
    }

    private void CreateAccount(Customer customer, bool withChecking, bool withSavings)
    {
        // do work
    }
}

重構以后,調用者只能使用CreateAccountWithChecking()和CreateAccountWithCheckingAndSavings()方法,這兩個方法的名稱也很容易讓人知道其含義。

使用Flags

這條建議來自於園友@blackbob。C#的Flags特性允許enum類型使用組合值。

public class BankAccount
{

    private readonly IList<CreateAccountOptions> _createAccountOptionList = new[]
    {
        CreateAccountOptions.WithChecking, 
        CreateAccountOptions.WithChecking | CreateAccountOptions.WithSavings
    };

    public void CreateAccount(Customer customer, CreateAccountOptions option)
    {
        if (!_createAccountOptionList.Contains(option))
        {
            throw new ArgumentException("Invalid create account option", "option");
        }
        // do work
    }
}

[Flags]
public enum CreateAccountOptions
{
    None = 0,
    WithChecking = 1,
    WithSavings = 2,
    WithStocks = 4
}

class Program
{
    static void Main(string[] args)
    {
        BankAccount bankAccount = new BankAccount();

        Customer customer1 = new Customer();
        // 驗證並創建賬戶
        bankAccount.CreateAccount(customer1, CreateAccountOptions.WithChecking);

        // 驗證並創建賬戶,同時存入初始儲蓄金額
        Customer customer2 = new Customer();
        bankAccount.CreateAccount(customer2, CreateAccountOptions.WithChecking | CreateAccountOptions.WithSavings);
    }
}

使用Flags枚舉,除了例子中的兩種Case外,還可能產生一些其他的組合。
為了防止用戶濫傳參數,我們可以在執行CreateAccount操作前先做個參數驗證。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM