iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 27
0
Software Development

在Kata中尋找Clean Code是否搞錯了什麼系列 第 27

Day 27 參數過多的問題

有些時候你可能會注意到方法參數很多,每次要使用時都得花一些時間確認方法參數有哪些,參數順序又是什麼,導致沒辦法很流暢的使用該方法,必須一點時間讓畫面反覆在使用端和方法間切換並確認,這也是代碼壞味道之一:過長參數

當方法參數過長的時候,可以透幾個方法來改善

  1. 共有的參數放到類別欄位中

    例如在下面這個例子中,單看這個方法會發現有三個參數,在方法中也是每個參數都會使用到。

    private CarpakPoint StepsToLastLevel(int[,] carpark, CarpakPoint nextStart, List<string> result)
    {
        ...
    }
    

    但是如果再觀察類別中的其他方法,就會發現幾乎每個方法都需要carpark且不會對carkpark進行修改,再者類別名稱也是Carpark與有絕對carpark參數的相關而不是Helper之類的類別。

    public class CarPark
    {
        ...
    
        public string[] Escape(int[,] carpark)
        {
            ...
        }
    
        private CarpakPoint StepsToLastLevel(int[,] carpark, CarpakPoint nextStart, List<string> result)
        {
            ...
        }
    
        private CarpakPoint FindStart(int[,] carPark)
        {
            ...
        }
    
        private int FindStart(int[,] carPark, int level)
        {
            ...
        }
    
        private string CalculateToExit(int startIdx)
        {
            ...
        }
    }
    

    在這種情況下,我們可以把carkPark參數放心的放到類別欄位中,讓每個方法可以直接讀取carpark,而不需要透過方法參數一層一層傳下去。

  2. 拆解方法職責

    假設我們在設計一個使用者登入系統,當使用者登入時,Login(LoginRequest request)會被呼叫,並透過呼叫遠端_loginApi來完成登入。不難發現DoLogin的參數多達五個,如果其他人需要閱讀或重複使用這個方法時,就會變得不易使用。

    public void Login(LoginRequest request, UserStatus status, string country)
    {
        ....
    
        TryLogin(request.UserId, request.Password, status.IsSuspend, status.IsVip, country);
    
        ...
    }
    
    private void TryLogin(int userId, string password, bool isSuspend, bool isVip, string country)
    {
        if (isSuspend)
        {
            throw new AccountSuspendException();
        }
        else if (isVip)
        {
            _loginApi.VipLogin(userId, password, country);
        }
        else
        {
            _loginApi.Login(userId, password, country);
        }
    }
    

    如果仔細觀察一下,DoLogin的職責包含確認使用者是否可以登入與呼叫遠端API執行登入。此時我們應該讓DoLogin只包含一個職責,並把另一個職責拆分出去。

    public void Login(LoginRequest request, UserStatus status, string country)
    {
        ...
    
        CheckUserCanLogin(status.IsSuspend);
        TryLogin(request.UserId, request.Password, status.IsSuspend, status.IsVip, country);
    
        ...
    }
    
    private void CheckUserCanLogin(bool isSuspend)
    {
        if (isSuspend)
        {
            throw new AccountSuspendException();
        }
    }
    
    private void TryLogin(int userId, string password, bool isVip, string country)
    {
        if (isVip)
        {
            _loginApi.VipLogin(userId, password, country);
        }
        else
        {
            _loginApi.Login(userId, password, country);
        }
    }
    
  3. Parameter Object

    在上面的例子中,可以發現雖然已經少了一個參數,但是參數數量還是有四個,但是這些都是必要的參數。此時我們可以把這些參數放到一個簡單的DTO中,作為執行Login操作的唯一參數。

    public class LoginParameters
    {
        public int UserId { get; set; }
        public string Password { get; set; }
        public bool IsVip { get; set; }
        public string Country { get; set; }
    }
    
    public class LoginService
    {
    		...
    
    		private void TryLogin(LoginParameters loginParameters)
    		{
    				...
    		}
    
    		...
    }
    

    這種做法除了DoLogin的參數長度之外,還能讓參數設定更為自由,一些驗證參數數值是否合法的行為也都能放在這個參數列別中。除此之外,相比從方法參數從左向右一個一個看,從類別屬性可以更快知道這個方法需要哪些參數。

雖然解決參數過長的方法有不少,但都還是得依實際情況來選擇,有三個方法都會用上,有時甚至可能出現上面三個方法都不適用,所以除了發現參數過長的問題之外,還需要透過思考來判斷現在是因為什麼原因導致的,然後選擇較適合的才是有效的作法。


上一篇
Day 26 封裝集合的方法
下一篇
Day 28 反向邏輯敘述
系列文
在Kata中尋找Clean Code是否搞錯了什麼30

尚未有邦友留言

立即登入留言