iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 23
0
Software Development

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

Day 23 方法名稱與參數

我們今天使用之前舉過ReverseExpression的例子,根據前幾天討論的FeatureEnvy,我們可以把_operatorDict、IsValidOperator和Calculator抽成一個類別,讓他們變成一個專門做二元運算的計算機。

public class ReverseExpression
{
    private readonly Dictionary<string, Func<double, double, double>> _operatorDict = new Dictionary<string, Func<double, double, double>>()
    {
        {"+", (operator2, operator1) => operator1 + operator2 },
        {"-", (operator2, operator1) => operator1 - operator2 },
        {"*", (operator2, operator1) => operator1 * operator2 },
        {"/", (operator2, operator1) => operator1 / operator2 },
    };

    public double Evaluate(string expression)
    {
        var operands = new Stack<double>();
        foreach (var term in expression.Split(' '))
        {
            if (IsValidOperator(term))
            {
                operands.Push(Calculate(term, operands.Pop(), operands.Pop()));
            }
            else
            {
                operands.Push(double.Parse(term));
            }
        }

        return operands.First();
    }

    private double Calculate(string @operator, double operand1, double operand2)
    {
        return _operatorDict[@operator](operand1, operand2);
    }

    private bool IsValidOperator(string term)
    {
        return _operatorDict.ContainsKey(term);
    }
}

如果你是手動抽取類別時,你可能會發現有很多種做法。而其中一種做法是,當發現Calculate和IsValidOperator都會使用到term,就把term放到建構子,讓term變成BinaryExpression的欄位,減少方法的參數。

public class BinaryExpression
{
    private readonly string _term;

    private readonly Dictionary<string, Func<double, double, double>> _operatorDict = ...

    public BinaryExpression(string term)
    {
        _term = term;
    }

    public double Calculate(double operand1, double operand2)
    {
        return _operatorDict[_term](operand1, operand2);
    }

    public bool IsValidOperator()
    {
        return _operatorDict.ContainsKey(_term);
    }
}

public class ReverseExpression
{
    public double Evaluate(string expression)
    {
        var operands = new Stack<double>();
        foreach (var term in expression.Split(' '))
        {
            var binaryExpression = new BinaryExpression(term);
            if (binaryExpression.IsValidOperator())
            {
                operands.Push(binaryExpression.Calculate(operands.Pop(), operands.Pop()));
            }
            else
            {
                operands.Push(double.Parse(term));
            }
        }

        return operands.First();
    }
}

但是其實這種做法是比較不推薦的,主要原因是如果把term變成BinaryExpression的欄位後,會發現IsValidOperator完全沒有參數。這樣的作法反而為閱讀代碼帶來麻煩,會讓別人搞不清楚這個方法到底是依照什麼東西來決定true或false。

當我們在閱讀一段代碼時,我們會從方法名稱與參數來想像這個方法的目的和變因。如果方法名稱不好,我們就會很難了解這個方法的目的是什麼。如果方法參數有缺,我們就會很難理解如何操作這個方法來達成目的。

依照這個邏輯,我們閱讀一下下面這段代碼。不難發現我們雖然可以從方法名稱知道這個方法是用來計算某些東西,再看到參數的部分卻只傳入兩個運算元。此時我們就會困惑,那這個兩運算元到底是用什麼運算子來計算的呢?

binaryExpression.Calculate(operands.Pop(), operands.Pop())

如果我們調整一下,讓運算元也出現在參數中,就能比較好的從方法名稱與參數理解整個方法,也有助於理解方法實作。

binaryExpression.Calculate(@operator, operands.Pop(), operands.Pop())

我們應該修改一下BinaryExpression,讓term從IsValidOperator和Calculate的方法參數傳入,而不是從建構子傳入。

public class BinaryExpression
{
    private readonly Dictionary<string, Func<double, double, double>> _operatorDict = new Dictionary<string, Func<double, double, double>>()
    {
        {"+", (operator2, operator1) => operator1 + operator2 },
        {"-", (operator2, operator1) => operator1 - operator2 },
        {"*", (operator2, operator1) => operator1 * operator2 },
        {"/", (operator2, operator1) => operator1 / operator2 },
    };
    
    public double Calculate(string @operator, double operand1, double operand2)
    {
        return _operatorDict[@operator](operand1, operand2);
    }

    public bool IsValidOperator(string term)
    {
        return _operatorDict.ContainsKey(term);
    }
}

思考方法的名稱與參數是否彼此呼應也應該是審查代碼中需要注意的一環,讓好的方法名稱搭配好的方法簽章,也是一個有效提升代碼可讀性的方法。既然重複的參數也不應該放到建構子裡面,那麼建構子應該放什麼東西呢?我們明天就針對這個主題來聊一下。我們今天使用之前舉過ReverseExpression的例子,根據前幾天討論的FeatureEnvy,我們可以把_operatorDict、IsValidOperator和Calculator抽成一個類別,讓他們變成一個專門做二元運算的計算機。

public class ReverseExpression
{
    private readonly Dictionary<string, Func<double, double, double>> _operatorDict = new Dictionary<string, Func<double, double, double>>()
    {
        {"+", (operator2, operator1) => operator1 + operator2 },
        {"-", (operator2, operator1) => operator1 - operator2 },
        {"*", (operator2, operator1) => operator1 * operator2 },
        {"/", (operator2, operator1) => operator1 / operator2 },
    };

    public double Evaluate(string expression)
    {
        var operands = new Stack<double>();
        foreach (var term in expression.Split(' '))
        {
            if (IsValidOperator(term))
            {
                operands.Push(Calculate(term, operands.Pop(), operands.Pop()));
            }
            else
            {
                operands.Push(double.Parse(term));
            }
        }

        return operands.First();
    }

    private double Calculate(string @operator, double operand1, double operand2)
    {
        return _operatorDict[@operator](operand1, operand2);
    }

    private bool IsValidOperator(string term)
    {
        return _operatorDict.ContainsKey(term);
    }
}

如果你是手動抽取類別時,你可能會發現有很多種做法。而其中一種做法是,當發現Calculate和IsValidOperator都會使用到term,就把term放到建構子,讓term變成BinaryExpression的欄位,減少方法的參數。

public class BinaryExpression
{
    private readonly string _term;

    private readonly Dictionary<string, Func<double, double, double>> _operatorDict = ...

    public BinaryExpression(string term)
    {
        _term = term;
    }

    public double Calculate(double operand1, double operand2)
    {
        return _operatorDict[_term](operand1, operand2);
    }

    public bool IsValidOperator()
    {
        return _operatorDict.ContainsKey(_term);
    }
}

public class ReverseExpression
{
    public double Evaluate(string expression)
    {
        var operands = new Stack<double>();
        foreach (var term in expression.Split(' '))
        {
            var binaryExpression = new BinaryExpression(term);
            if (binaryExpression.IsValidOperator())
            {
                operands.Push(binaryExpression.Calculate(operands.Pop(), operands.Pop()));
            }
            else
            {
                operands.Push(double.Parse(term));
            }
        }

        return operands.First();
    }
}

但是其實這種做法是比較不推薦的,主要原因是如果把term變成BinaryExpression的欄位後,會發現IsValidOperator完全沒有參數。這樣的作法反而為閱讀代碼帶來麻煩,會讓別人搞不清楚這個方法到底是依照什麼東西來決定true或false。

當我們在閱讀一段代碼時,我們會從方法名稱與參數來想像這個方法的目的和變因。如果方法名稱不好,我們就會很難了解這個方法的目的是什麼。如果方法參數有缺,我們就會很難理解如何操作這個方法來達成目的。

依照這個邏輯,我們閱讀一下下面這段代碼。不難發現我們雖然可以從方法名稱知道這個方法是用來計算某些東西,再看到參數的部分卻只傳入兩個運算元。此時我們就會困惑,那這個兩運算元到底是用什麼運算子來計算的呢?

binaryExpression.Calculate(operands.Pop(), operands.Pop())

如果我們調整一下,讓運算元也出現在參數中,就能比較好的從方法名稱與參數理解整個方法,也有助於理解方法實作。

binaryExpression.Calculate(@operator, operands.Pop(), operands.Pop())

我們應該修改一下BinaryExpression,讓term從IsValidOperator和Calculate的方法參數傳入,而不是從建構子傳入。

public class BinaryExpression
{
    private readonly Dictionary<string, Func<double, double, double>> _operatorDict = new Dictionary<string, Func<double, double, double>>()
    {
        {"+", (operator2, operator1) => operator1 + operator2 },
        {"-", (operator2, operator1) => operator1 - operator2 },
        {"*", (operator2, operator1) => operator1 * operator2 },
        {"/", (operator2, operator1) => operator1 / operator2 },
    };
    
    public double Calculate(string @operator, double operand1, double operand2)
    {
        return _operatorDict[@operator](operand1, operand2);
    }

    public bool IsValidOperator(string term)
    {
        return _operatorDict.ContainsKey(term);
    }
}

思考方法的名稱與參數是否彼此呼應也應該是審查代碼中需要注意的一環,讓好的方法名稱搭配好的方法簽章,也是一個有效提升代碼可讀性的方法。既然重複的參數也不應該放到建構子裡面,那麼建構子應該放什麼東西呢?我們明天就針對這個主題來聊一下。我們今天使用之前舉過ReverseExpression的例子,根據前幾天討論的FeatureEnvy,我們可以把_operatorDict、IsValidOperator和Calculator抽成一個類別,讓他們變成一個專門做二元運算的計算機。

public class ReverseExpression
{
    private readonly Dictionary<string, Func<double, double, double>> _operatorDict = new Dictionary<string, Func<double, double, double>>()
    {
        {"+", (operator2, operator1) => operator1 + operator2 },
        {"-", (operator2, operator1) => operator1 - operator2 },
        {"*", (operator2, operator1) => operator1 * operator2 },
        {"/", (operator2, operator1) => operator1 / operator2 },
    };

    public double Evaluate(string expression)
    {
        var operands = new Stack<double>();
        foreach (var term in expression.Split(' '))
        {
            if (IsValidOperator(term))
            {
                operands.Push(Calculate(term, operands.Pop(), operands.Pop()));
            }
            else
            {
                operands.Push(double.Parse(term));
            }
        }

        return operands.First();
    }

    private double Calculate(string @operator, double operand1, double operand2)
    {
        return _operatorDict[@operator](operand1, operand2);
    }

    private bool IsValidOperator(string term)
    {
        return _operatorDict.ContainsKey(term);
    }
}

如果你是手動抽取類別時,你可能會發現有很多種做法。而其中一種做法是,當發現Calculate和IsValidOperator都會使用到term,就把term放到建構子,讓term變成BinaryExpression的欄位,減少方法的參數。

public class BinaryExpression
{
    private readonly string _term;

    private readonly Dictionary<string, Func<double, double, double>> _operatorDict = ...

    public BinaryExpression(string term)
    {
        _term = term;
    }

    public double Calculate(double operand1, double operand2)
    {
        return _operatorDict[_term](operand1, operand2);
    }

    public bool IsValidOperator()
    {
        return _operatorDict.ContainsKey(_term);
    }
}

public class ReverseExpression
{
    public double Evaluate(string expression)
    {
        var operands = new Stack<double>();
        foreach (var term in expression.Split(' '))
        {
            var binaryExpression = new BinaryExpression(term);
            if (binaryExpression.IsValidOperator())
            {
                operands.Push(binaryExpression.Calculate(operands.Pop(), operands.Pop()));
            }
            else
            {
                operands.Push(double.Parse(term));
            }
        }

        return operands.First();
    }
}

但是其實這種做法是比較不推薦的,主要原因是如果把term變成BinaryExpression的欄位後,會發現IsValidOperator完全沒有參數。這樣的作法反而為閱讀代碼帶來麻煩,會讓別人搞不清楚這個方法到底是依照什麼東西來決定true或false。

當我們在閱讀一段代碼時,我們會從方法名稱與參數來想像這個方法的目的和變因。如果方法名稱不好,我們就會很難了解這個方法的目的是什麼。如果方法參數有缺,我們就會很難理解如何操作這個方法來達成目的。

依照這個邏輯,我們閱讀一下下面這段代碼。不難發現我們雖然可以從方法名稱知道這個方法是用來計算某些東西,再看到參數的部分卻只傳入兩個運算元。此時我們就會困惑,那這個兩運算元到底是用什麼運算子來計算的呢?

binaryExpression.Calculate(operands.Pop(), operands.Pop())

如果我們調整一下,讓運算元也出現在參數中,就能比較好的從方法名稱與參數理解整個方法,也有助於理解方法實作。

binaryExpression.Calculate(@operator, operands.Pop(), operands.Pop())

我們應該修改一下BinaryExpression,讓term從IsValidOperator和Calculate的方法參數傳入,而不是從建構子傳入。

public class BinaryExpression
{
    private readonly Dictionary<string, Func<double, double, double>> _operatorDict = new Dictionary<string, Func<double, double, double>>()
    {
        {"+", (operator2, operator1) => operator1 + operator2 },
        {"-", (operator2, operator1) => operator1 - operator2 },
        {"*", (operator2, operator1) => operator1 * operator2 },
        {"/", (operator2, operator1) => operator1 / operator2 },
    };
    
    public double Calculate(string @operator, double operand1, double operand2)
    {
        return _operatorDict[@operator](operand1, operand2);
    }

    public bool IsValidOperator(string term)
    {
        return _operatorDict.ContainsKey(term);
    }
}

思考方法的名稱與參數是否彼此呼應也應該是審查代碼中需要注意的一環,讓好的方法名稱搭配好的方法簽章,也是一個有效提升代碼可讀性的方法。既然重複的參數也不應該放到建構子裡面,那麼建構子應該放什麼東西呢?我們明天就針對這個主題來聊一下。


上一篇
Day 22 覆寫operator
下一篇
Day 24 建構子的職責
系列文
在Kata中尋找Clean Code是否搞錯了什麼30

尚未有邦友留言

立即登入留言