Edited at

深いネストを減らすには?

深いネストに困ってませんか?

例えば次のようなコードです。


Program.cs

foreach (...)

{
if (...)
{
if (...)
{
if (...)
{
if (...)
{
// 本当にやりたかった処理
}
}
}
}
}

foreach 文と if 文のネストが深すぎて、書いた本人でさえ何をやっているコードなのか分からない・・・




今回は深いネストの解消方法をいくつか紹介したいと思います。

※サンプルコードは C# で書いていますが、他の言語でも考え方は同じです。



アーリー・リターン(early return)

「アーリー・リターン(early return)」は「早期リターン」「ガード節」とも呼ばれています。

メソッドの先頭で渡された引数が不正な値でないかチェックして、もし不正な値であれば return で即メソッドを抜けてしまいます。

そうすることで、その後の処理は引数に不正な値でないか気にする必要がなくなるので、コードがスッキリします。


【Before】


Program.cs

using System;

namespace Sample
{
class MainClass
{
public static void Main(string[] args)
{
CheckName("山田太郎");
CheckName("アーノルド・シュワルツェネッガー");
CheckName(null);
}

/// <summary>
/// 引数に指定された名前が、10 文字以内かどうかチェックします。
/// </summary>
/// <returns>引数に指定された名前が、10 文字以内であれば true、そうでなければ false を返します。</returns>
/// <param name="name">名前</param>
public static bool CheckName(string name)
{
if (name != null)
{
if (name.Length <= 10)
{
Console.WriteLine(name + " は 10 文字以内の名前です!");
return true;
}
}

return false;
}
}
}



【After】


Program.cs

using System;

namespace Sample
{
class MainClass
{
public static void Main(string[] args)
{
CheckName("山田太郎");
CheckName("アーノルド・シュワルツェネッガー");
CheckName(null);
}

/// <summary>
/// 引数に指定された名前が、10 文字以内かどうかチェックします。
/// </summary>
/// <returns>引数に指定された名前が、10 文字以内であれば true、そうでなければ false を返します。</returns>
/// <param name="name">名前</param>
public static bool CheckName(string name)
{
if (name == null || name.Length > 10)
return false;

Console.WriteLine(name + " は 10 文字以内の名前です!");

return true;
}
}
}




アーリー・コンティニュー(early continue)

「アーリー・コンティニュー(early continue)」は「アーリー・リターン(early return)」の continue 版です。

考え方は「アーリー・リターン(early return)」と同じなのですが、意外と使われていないような気がするので、あえて別物として紹介します。


【Before】


Program.cs

using System;

using System.Collections.Generic;

namespace Sample
{
class MainClass
{
public static void Main(string[] args)
{
var names = new List<string>
{
"山田太郎",
null,
"川田花子",
"アーノルド・シュワルツェネッガー"
};

PrintNames(names);
}

/// <summary>
/// 10文字以内の名前のみを出力します。
/// </summary>
/// <param name="names">名前配列</param>
public static void PrintNames(List<string> names)
{
foreach (string name in names)
{
if (name != null)
{
if (name.Length <= 10)
{
Console.WriteLine(name + " は 10 文字以内の名前です!");
}
}
}
}
}
}



【After】


Program.cs

using System;

using System.Collections.Generic;

namespace Sample
{
class MainClass
{
public static void Main(string[] args)
{
var names = new List<string>
{
"山田太郎",
null,
"川田花子",
"アーノルド・シュワルツェネッガー"
};

PrintNames(names);
}

/// <summary>
/// 10文字以内の名前のみを出力します。
/// </summary>
/// <param name="names">名前配列</param>
public static void PrintNames(List<string> names)
{
foreach (string name in names)
{
if (name == null || name.Length > 10)
continue;

Console.WriteLine(name + " は 10 文字以内の名前です!");
}
}
}
}




if 文の代わりに boolean 値をそのまま使う

次のサンプルコードを見てください。

【Before】の IsEvenNumber メソッドでは、 まず if 文で条件判定して、その結果を一旦変数「result」に代入してから戻り値として返しています。

一方、【After】の IsEvenNumber メソッドでは、if 文も変数「result」もなく、かなりスッキリしていますね。

if 文の中身は必ず boolean 値になるので、if 文は使わず boolean 値をそのまま使うことで、無駄な if 文を削減できます。


【Before】


Program.cs

using System;

using System.Collections.Generic;

namespace Sample
{
class MainClass
{
public static void Main(string[] args)
{
var numbers = new List<int>() { 0, 1, 2, 3, 4, 5 };

foreach (int number in numbers)
{
bool result = IsEvenNumber(number);

if (result)
{
Console.WriteLine(number + "は偶数です!");
}
else
{
Console.WriteLine(number + "は奇数です!");
}
}
}

/// <summary>
/// 数値が偶数かどうかを調べます。
/// </summary>
/// <returns>数値が偶数であれば true、奇数であれば false を返します。</returns>
/// <param name="number">数値が偶数かどうかを返します。</param>
public static bool IsEvenNumber(int number)
{
bool result;

if (number % 2 == 0)
{
result = true;
}
else
{
result = false;
}

return result;
}
}
}





【After】


Program.cs

using System;

using System.Collections.Generic;

namespace Sample
{
class MainClass
{
public static void Main(string[] args)
{
var numbers = new List<int>() { 0, 1, 2, 3, 4, 5 };

foreach (int number in numbers)
{
bool result = IsEvenNumber(number);

if (result)
{
Console.WriteLine(number + "は偶数です!");
}
else
{
Console.WriteLine(number + "は奇数です!");
}
}
}

/// <summary>
/// 数値が偶数かどうかを調べます。
/// </summary>
/// <returns>数値が偶数であれば true、奇数であれば false を返します。</returns>
/// <param name="number">数値が偶数かどうかを返します。</param>
public static bool IsEvenNumber(int number)
{
return number % 2 == 0;
}
}
}




LINQ を使う

C#には LINQ(リンク)という非常に強力なライブラリがあり、これを使うと繰り返し処理がスッキリしたコードになります。

foreach 文は ForEach メソッドに置き換えることができるので、これだけでもネストを一つ減らすことができますね。

LINQ をまだ使ったことないという方は、別記事「はじめての LINQ」を参照してください。

※ Java であれば「Stream API」、Python であれば「内包表記」が LINQ の代わりとして使えます。


【Before】


Program.cs

using System;

using System.Collections.Generic;

namespace Sample
{
class MainClass
{
public static void Main(string[] args)
{
var numbers = new List<int>() { 0, 1, 2, 3, 4, 5 };

List<int> evenNumbers = GetEvenNumber(numbers);

foreach(int number in evenNumbers)
{
Console.WriteLine(number);
}
}

/// <summary>
/// 数値リストに含まれる偶数をリストにして取得します。
/// </summary>
/// <returns>数値リストに含まれる偶数をリストにして返します。</returns>
/// <param name="numbers">数値リスト</param>
public static List<int> GetEvenNumber(List<int> numbers)
{
var resultList = new List<int>();

foreach(int number in numbers)
{
if(number % 2 == 0)
{
resultList.Add(number);
}
}

return resultList;
}
}
}





【After】


Program.cs

using System;

using System.Collections.Generic;

namespace Sample
{
class MainClass
{
public static void Main(string[] args)
{
var numbers = new List<int>() { 0, 1, 2, 3, 4, 5 };

List<int> evenNumbers = GetEvenNumber(numbers);

evenNumbers.ForEach(x => Console.WriteLine(x));
}

/// <summary>
/// 数値リストに含まれる偶数をリストにして取得します。
/// </summary>
/// <returns>数値リストに含まれる偶数をリストにして返します。</returns>
/// <param name="numbers">数値リスト</param>
public static List<int> GetEvenNumber(List<int> numbers)
{
return numbers.FindAll(x => x % 2 == 0);
}
}
}




さいごに

ネストの解消方法として「処理をメソッドに分離する」方法がよく紹介されているのを見ますが、正直おすすめしません。

確かにネスト数は減るのですが、代わりにメソッドの呼び出し階層が増えてしまうので、根本的な解決にはならないからです。

ネスト数が減ったからといって、必ずしも可読性が向上するとは限らないのです。

「処理をメソッドに分離する」はあくまで最終手段だと思いましょう。