👍
実装仕様を明示するためにローカル関数を活用する
概要
以下のようなコードにおいて、「あるメソッド内のある箇所は処理設計的に意味を持つものなので、そのように定義したいな」という場合があるかなと思います。
その場合のアプローチについてです。
*実際は定数として定義すると思いますが、説明のために直接数値としています。
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 なローカル関数が定義できるようになり、その点でも応用性が高そうです。
Discussion