👍

実装仕様を明示するためにローカル関数を活用する

2023/03/20に公開

概要

以下のようなコードにおいて、「あるメソッド内のある箇所は処理設計的に意味を持つものなので、そのように定義したいな」という場合があるかなと思います。
その場合のアプローチについてです。
*実際は定数として定義すると思いますが、説明のために直接数値としています。

private void MethodA()
{
    if (isA)
    {
        numB--;
        var baseScore = numA + 2;
        var bonusScore = baseScore + 5;
        MethodB(bonusScore);
    }
    else
    {
        if (isB)
        {
            numA--;
        }
        else
        {
            var bonusScore = numA + 5;
            MethodB(bonusScore);
        }
    }
}

処理設計的に意味を持つ箇所

        var bonusScore = numA + 5;
        MethodB(bonusScore);

トラップになるパターン

private void MethodA()
{
    if (isA)
    {
        numB--;
        var bonusScore = numA + 7;
        MethodB(bonusScore);
    }
    else
    {
        if (isB)
        {
            numA--;
        }
        else
        {
            var bonusScore = numA + 5;
            MethodB(bonusScore);
        }
    }
}

まず、未来に誰かのトラップになるパターンですが、+2、+5 を1行にまとめると、それぞれのコード上の意図が失われることになります。
そのため、後のタイミングで処理変更になり「+5」に対応する仕様が「+6」になった場合、まとめられた行が変更対象なのかどうか(つまり、+7 とまとめられた箇所が処理変更の対応箇所に該当するかどうか)がわからなくなってしまうことになります。

インスタンスメソッドにする

private void MethodC(int num)
{
    var bonusScore = num + 5;
    MethodB(bonusScore);
}

private void MethodA()
{
    if (isA)
    {
        var baseScore = numA + 2;
        MethodC(baseScore);
    }
    else
    {
        if (isB)
        {
            numA--;
        }
        else
        {
            MethodC(numA);
        }
    }
}

まず思いつくのかこの形式かなと思うのですが、MethodC が、MethodA の中においてのみ意味がある場合に、スコープが意図と乖離していて少し違和感を感じてしまいます。

Action にする

private void MethodA()
{
    Action<int> callMethodB = (num) => {
        var bonusScore = num + 5;
        MethodB(bonusScore);
    };
    
    if (isA)
    {
        var baseScore = numA + 2;
        callMethodB.Invoke(baseScore);
    }
    else
    {
        if (isB)
        {
            numA--;
        }
        else
        {
            callMethodB.Invoke(numA);
        }
    }
}

この場合は、意図が損なわれず、また、スコープとしても関数内となっていてよさそうに思います。
ただ、Action はあくまでデリゲートだということから、好みが分かれそうではあります。

ローカル関数にする

private void MethodA()
{
    void CallMethodB(int num)
    {
        var bonusScore = num + 5;
        MethodB(bonusScore);
    }
    
    if (isA)
    {
        var baseScore = numA + 2;
        CallMethodB(baseScore);
    }
    else
    {
        if (isB)
        {
            numA--;
        }
        else
        {
            CallMethodB(numA);
        }
    }
}

意図が損なわれずに、コード仕様としてもコンセプトどおりなのでこの方法はよさそうです。
また、C#8.0 から static なローカル関数が定義できるようになり、その点でも応用性が高そうです。

Happy Elements

Discussion