関数の最後でしか return できないと思いこんでいないか

ソフトウェア開発の現場を見てきて、冗長なわかりにくいコードをいくつも見てきました。

そのなかには、 関数の最後でしか return できないと思いこんでいないか と感じるくらい、頑なに「最後に return 」ものも散見されました。

読みにくいコードを改善するのは、ちょっとしたプログラミングのテクニックを知ること と、 手間を惜しまない気持ち だけあれば OK です。

今回はそんなテクニックのひとつ early return について紹介いたします。

early return とは

early return とは、条件を満たす場合にすぐ return することで、コードのネストを減らし、見通しを良くするテクニックです。

コードサンプルを見他方が早そうですね。

どうぞ。

ケース A ループを途中で抜けられない人

改善前

 1/**
 2 * 装備中のイベントリがある場合 true を返す
 3 *
 4 * @param  Collection $inventories 所持品のコレクション
 5 * @return bool
 6 */
 7public function hasEquipments(Collection $inventories): bool
 8{
 9    $flg = false;
10    foreach ($entities as $entity) {
11        if ($entity->isEquipment()) {
12            $flg = true;
13        }
14    }
15    return $flg;
16}

$flg という一時変数を使って、関数の最後で return している例です。

こういうのが本当に、本当に多いんですよね。


関数の中身を理解するためには、

  • 初期化している所
  • 代入している所
  • return している所

と、 3 箇所も確認しなければなりません。

改善後

early return するように修正します。

 1/**
 2 * 装備中のイベントリがある場合 true を返す
 3 *
 4 * @param  Collection $inventories 所持品のコレクション
 5 * @return bool
 6 */
 7public function hasEquipments(Collection $inventories): bool
 8{
 9    foreach ($entities as $entity) {
10        if ($entity->isEquipment()) {
11            return true;
12        }
13    }
14    return false;
15}

わかりやすくないですか ?

条件に一致するものが見つかった瞬間に true を返し、ひとつも条件に一致するものがなければ、仕方がないで最後に false を返します。

変数がひとつ減るだけで、コードもシンプルになり、考えることも減りました。

ケース B そのまま移植する人

修正前

文字列 ID から、所持品の種類を求める処理があり、改修を重ねるうちに分岐が増えてかっこ悪くなっていました。

 1$is_weapon = Inventory::isWeaponFormat($inventory_id);
 2$is_armor  = Inventory::isArmorFormat($inventory_id);
 3
 4if ($is_weapon) {
 5    $inventory_type = InventoryType::WEAPON;
 6} elseif ($is_armor) {
 7    $inventory_type = InventoryType::Armor;
 8} else {
 9    $inventory_type = InventoryType::CONSUMABLE;
10}

都合の悪いことに、これは関数になっておらず、いくつかのクラスに分散していたため、今回関数化をお願いしました。

すると、そのまま関数になったものが提出されました。

 1/**
 2 * 文字列のインベントリIDから、インベントリの種類を取得する
 3 *
 4 * @param  string $inventory_id インベントリID
 5 * @return int
 6 */
 7public function getInventoryType(string $inventory_id): int
 8{
 9    $is_weapon = Inventory::isWeaponFormat($inventory_id);
10    $is_armor  = Inventory::isArmorFormat($inventory_id);
11
12    if ($is_weapon) {
13        $inventory_type = InventoryType::WEAPON;
14    } elseif ($is_armor) {
15        $inventory_type = InventoryType::Armor;
16    } else {
17        $inventory_type = InventoryType::CONSUMABLE;
18    }
19    return $inventory_type;
20}

修正後

early return するように修正します。

 1/**
 2 * 文字列のインベントリIDから、インベントリの種類を取得する
 3 *
 4 * @param  string $inventory_id インベントリID
 5 * @return int
 6 */
 7public function getInventoryType(string $inventory_id): int
 8{
 9    if (Inventory::isWeaponFormat($inventory_id)) {
10        return InventoryType::WEAPON;
11    }
12    if (Inventory::isArmorFormat($inventory_id)) {
13        return InventoryType::Armor;
14    }
15    return InventoryType::CONSUMABLE;
16}

わかりやすくないですか ?

こちらの例も、一時変数をなくしたことで理解しやすくなっていると思います。

また、else を排除して、if ブロックのみで構成するように変更していますが、「見た目」的にも揃っていて、私としてわかりやすいポイントだと思っています。

精神的スタックを小さくする

プログラミングの名著『リーダブルコード』には、 精神的スタック という言葉が出てきます。

プログラムを読む解く時に、それまで考えたことを頭の中にキープしていく様子をうまく表現していますね。

プログラムの読みづらさは

  • 変数がどうなっているか調べるために、エディタを何度も上下させる必要がある
  • ネストが多く、深く、条件のせりに時間がかかる

などに起因していることが多く、これらは 精神的スタック が大量に蓄積されてしまうものであるとわかります。

これに対し、early return は、 条件をひとつずつ排除 して、考えを整理するためにとても有効です。


このあたりは「枯れた古典」で勉強すると良さそうです。

ちょっと意識するだけで、見違えるほど読みやすいコードを生み出せるようになることもあります。

それって快感ですよ。