ガード節より読みやすい!?途中リターンせずにネストを浅くする「ガードレール構文」の紹介

プログラムで値のチェック処理を書くとき、普通にif文を使っているとネストが深くなっていってしまいます。

function alertOverweight(user) {
  let result, height, weight;

  // 身長と体重が数値であることを確認
  if (!(isNaN(user.height) || isNaN(user.weight))) {
    height = user.height / 100; // 身長を取得
    weight = user.weight; // 体重を取得

    // 身長が0より大きいことを確認
    if (height > 0) {
      user.bmi = weight / height ** 2; // BMIを計算

      // BMIが25以上であることを確認
      if (user.bmi >= 25) {
        alert('体重を減らしましょう。');

        result = '過体重でした。';
      } else {
        result = 'BMIが25未満のため、過体重ではありませんでした。';
      }
    } else {
      result = '身長が0以下のため、BMIが計算できませんでした。';
    }
  } else {
    result = '身長もしくは体重が数値でないため、BMIが計算できませんでした。';
  }
  return result;
}

このようにネストが深くなると、コードの可読性が低下してしまいます。

ガード節のデメリット

ガード節は、このようなネストを解消するための手法として広く使われています。

function alertOverweight(user) {
  let height, weight;

  // 身長もしくは体重が数値でない場合は結果を返す
  if (isNaN(user.height) || isNaN(user.weight)) {
    return '身長もしくは体重が数値でないため、BMIが計算できませんでした。';
  }
  height = user.height / 100; // 身長を取得
  weight = user.weight; // 体重を取得

  // 身長が0以下の場合は結果を返す
  if (height <= 0) {
    return '身長が0以下のため、BMIが計算できませんでした。';
  }
  user.bmi = weight / height ** 2; // BMIを計算

  // BMIが25未満の場合は結果を返す
  if (user.bmi < 25) {
    return 'BMIが25未満のため、過体重ではありませんでした。';
  }
  alert('体重を減らしましょう。');

  return '過体重でした。';
}

このように途中リターンを使って、チェック処理で関数から抜け出すことでネストを無くしているのです。これは一見するとコードがフラットになりますが、構造化プログラミングの観点ではあまり好ましくありません。例えば、この関数は最終的に「過体重でした。」という結果を返していますが、様々な条件を満たした結果であることが分かり難いといった問題が生じます。

途中リターンしなくてもネストは浅くできる

この問題を解決するために生まれたのが、今回紹介するガードレール構文です。先ほどのコードを、ガードレール構文を使って書き直すと以下のようになります。

function alertOverweight(user) {
  let result, proceed, height, weight;
  
  result = '身長もしくは体重が数値でないため、BMIが計算できませんでした。';
  proceed = !(isNaN(user.height) || isNaN(user.weight)); // 身長と体重が数値であることを確認
  if (proceed) {
    height = user.height / 100; // 身長を取得
    weight = user.weight; // 体重を取得

    result = '身長が0以下のため、BMIが計算できませんでした。';
    proceed = height > 0; // 身長が0より大きいことを確認
  } if (proceed) {
    user.bmi = weight / height ** 2; // BMIを計算

    result = 'BMIが25未満のため、過体重ではありませんでした。';
    proceed = user.bmi >= 25; // BMIが25以上であることを確認
  } if (proceed) {
    alert('体重を減らしましょう。');

    result = '過体重でした。';
  }
  return result;
}

このコードでは、result変数とproceed変数を段階的に更新することで、チェック処理を進めていきます。一度条件を外れると、それ以降のif文はすべてスルーして、最後に設定された結果を返す仕組みです。

if文が処理の流れに沿って直線状に連なっている様子から、これを「ガードレール構文」と呼ぶことにしました。

これならネストを減らしつつ、2つの変数から一連のチェック処理を芋づる式に辿っていくこともできるため、コードの見通しがとても良くなります。

まとめ

途中リターンは再帰関数で使われることもありますが、通常の制御構造(if文やfor文等)で実装できる場合は、極力使わないようにしましょう。

以上、途中リターンせずにネストを浅くする「ガードレール構文」の紹介でした。是非皆さんもガード節の代わりに使ってみてください。

参考記事

https://memorandumrail.com/program-conditional-branch

https://marycore.jp/coding/why-goto-statement-is-bad

https://www.ninton.co.jp/archives/8065

関連記事

Laravelにおける存在チェックの書き方

Laravel

Laravelでビューに渡されたEloquentコレクションの中身が存在しているか判別するプログラムの書き方をまとめます。

データをビューへ渡す

まず、データを表示するためにEloquentでコレクションを取得してビューへ渡します。

public function index()
{
    $users = User::all();

    return view('users', ['users' => $users]);
}

単にデータを表示する

ビューに渡されたデータはループで表示できます。

<html>
    <body>
        <h1>User</h1>

        @foreach($users as $user)
            <p>{{ $user->name }}</p>
        @endforeach
    </body>
</html>

データの存在を判別する

データの存在を判別するには、isNotEmptyメソッドを使います。

<html>
    <body>
        <h1>User</h1>

        @if ($users->isNotEmpty())
            <ul>
                @foreach($users as $user)
                    <li>{{ $user->name }}</li>
                @endforeach
            </ul>
        @else
            Nothing Found
        @endif
    </body>
</html>

逆の動作である、isEmptyメソッドを使うこともできます。

<html>
    <body>
        <h1>User</h1>

        @if ($users->isEmpty())
            Nothing Found
        @else
            <ul>
                @foreach($users as $user)
                    <li>{{ $user->name }}</li>
                @endforeach
            </ul>
        @endif
    </body>
</html>

その他の判別方法

より単純なケースでは、@forelse()が使えるかもしれません。

<html>
    <body>
        <h1>User</h1>

        @forelse($users as $user)
            <p>{{ $user->name }}</p>
        @empty
            Nothing Found
        @endforelse
    </body>
</html>

ちなみに、以下のような書き方では@empty()がうまく判別できませんでした。

<html>
    <body>
        <h1>User</h1>

        @foreach($users as $user)
            <p>{{ $user->name }}</p>
        @endforeach
        @empty($users)
            Nothing Found
        @endempty
    </body>
</html>

リレーションの存在の判定

リレーションの有無を調べるには、existsメソッドとdoesntExistメソッドが使用できます。

<html>
    <body>
        <h1>User</h1>

        @if ($user->posts()->exists())
            The user has some posts
        @endif

        @if ($user->posts()->doesntExist())
            The user doesn't have any posts
        @endif
    </body>
</html>

JavaScript を ES6(ES2015) で書いてイミュータブルなオブジェクトを実装しよう

イミュータブルという言葉を知っていますか?プログラミングにおける重要なキーワードの1つらしいのですが、私は知りませんでした。

イミュータブルとは何か?どうすればそれを実現できるのか?JavaScript を例にとって考察しようと思います。

イミュータブルとは

イミュータブル(immutable)とは、日本語で「不変」という意味の言葉です。イミュータブルなオブジェクトと言ったら、状態が変わらないオブジェクトのことを指します。

イミュータブルなオブジェクトは状態が変えられないため、変数のオブジェクトを更新する場合は、オブジェクトを作り直さなければいけません。

なぜイミュータブルなオブジェクトにするのか

なぜわざわざオブジェクトを作り直すのでしょうか。イミュータブルなオブジェクトを使用する利点は、いくつか考えられます。

以前の状態を簡単に再現できる

イミュータブルなオブジェクトは、状態が変化しないことが保証されているため、そのオブジェクトを保持しておくだけでいつでも元の状態を再現することができます。

データの更新を簡単に検出できる

イミュータブルなオブジェクトは、前と同じオブジェクトを参照している限り、状態も変化していないことがわかります。そのため、オブジェクトが持っているすべての状態を1つずつ取り出して比較する必要がありません。

React ではパフォーマンスを最適化できる

JavaScript ライブラリの React では、イミュータブルな pure component を構築することで、効率的な再レンダーが可能になります。1

JavaScript でオブジェクトをイミュータブルなものとして扱う

JavaScript において、文字列や数値、真偽値等のデータは絶対的にイミュータブルです。そのため、これらのデータを扱う場合はイミュータブルを意識する必要はありません。2

一方で、オブジェクトや配列は中の状態が変えられるため、基本的にイミュータブルなデータではありません。

// オブジェクトの場合
let obj1, obj2;

obj1 = {a: 1};
obj2 = obj1;

obj2["1"] = 2;
obj2.a = "b";

console.log(obj1); // {1: 2, a: "b"}

// 配列の場合
let ary1, ary2;

ary1 = [1, 2];
ary2 = ary1;

ary2.push(3);
ary2[0] = "a";

console.log(ary1); // ["a", 2, 3]

オブジェクトや配列をイミュータブルなデータとして扱いたい場合は、少し工夫が必要です。基となるオブジェクトの状態は変えずに、新しい状態を持った別のオブジェクトを作成し、古いデータと置き換えて使用します。

// オブジェクトの場合
let obj1, obj2;

obj1 = {a: 1};
obj2 = obj1;

obj2 = Object.assign({}, obj2, {"1": 2});
obj2 = Object.assign({}, obj2, {a: "b"});

console.log(obj1); // {a: 1}

// 配列の場合
let ary1, ary2;

ary1 = [1, 2];
ary2 = ary1;

ary2 = ary2.concat(3);
ary2 = ["a"].concat(ary2.slice(1));

console.log(ary1); // [1, 2]

イミュータブルなオブジェクト操作に使えるメソッド

Object.assign() は、ES6 から使えるようになった新しいメソッドです。コピー先として空のオブジェクトを指定することで、コピー元オブジェクトからプロパティをシャローコピー (1段階の深さのコピー)した新しいオブジェクトを返します。

配列では concat() メソッドや slice() メソッドを使うことで、元の配列を変更せずに要素を結合したり、元の配列を変更せずに要素を切り取ったり、非破壊的な配列操作が可能になります。反対に、sort()push() 等のメソッドは元の配列を直接変更してしまうため、イミュータブルな配列には使用できません。

他に、オブジェクトのプロパティや配列の要素に値を直接代入するようなコードも、イミュータブルなデータ更新では使うことができません。

データの変化を伴うメソッドのことを破壊的メソッド、データの変化を伴わないメソッドを非破壊的メソッドといいます。イミュータブルなオブジェクト操作では非破壊的メソッドを使いましょう。

参照の値をコピーする意味

上記のメソッドは、コピー元のプロパティがオブジェクトを参照している場合、参照の値のみをコピーします。また、オブジェクトを別の変数に代入する際も、参照の値をそのまま代入して、同じオブジェクトを参照するようにしています。

let ary1 = [
  {a: 1},
  {b: 2},
  {c: 3},
];

let ary2 = ary1;

ary1 = ary1.slice(1);

console.log(ary1[0] == ary2[1]); // true

このように同じオブジェクトを参照することで、オブジェクトの状態が等しいかどうかを簡単に調べることができます。オブジェクトを1から作り直すわけではないので、メモリが無駄に使用されることもありません。

スプレッド構文でより簡潔に

オブジェクトや配列をイミュータブルなデータとして扱うコードは、スプレッド構文3を使ってより簡潔に書くことができます。

// オブジェクトの場合
let obj1, obj2;

obj1 = {a: 1};
obj2 = obj1;

obj2 = {...obj2, "1": 2};
obj2 = {...obj2, a: "b"};

console.log(obj1); // {a: 1}

// 配列の場合
let ary1, ary2;

ary1 = [1, 2];
ary2 = ary1;

ary2 = [...ary2, 3];
ary2 = ["a", ...ary2.slice(1)];

console.log(ary1); // [1, 2]

オブジェクトのスプレッド構文は ES2018 から、配列のスプレッド構文は ES6 から使えるようになりました。

まとめ

オブジェクトの状態は変えず、新しく作り直すようにすることで、イミュータブルなオブジェクトを実現することができました。

ES6 から使えるようになった新しいメソッドやスプレッド構文を活用して、イミュータブルなオブジェクトを実装しましょう。

詳細情報

  1. パフォーマンス最適化 – React ↩︎
  2. Primitive (プリミティブ) – MDN Web Docs 用語集_ ウェブ関連用語の定義 _ MDN ↩︎
  3. スプレッド構文 – JavaScript _ MDN ↩︎

サイトが妙に遅いときは画像の圧縮に失敗しているかも!

モバイルサイトの読み込み速度をテストできる Google のサービスをご存知でしょうか。

参考

モバイルサイトの読み込み速度とパフォーマンスをテストする – Google

あるイベントサイトを作成して、このサービスで読み込み時間を計測したところ、17秒という非常に悪い結果が出てしまいました。

読み込み速度を優先するために HTML で静的なサイトを作成して、画像の総容量も1MB以下なのになぜ…とずっと悩んでいたのですが、画像を圧縮しなおしてみたところ、すんなり5秒以下に改善しました。

おそらく、画像の圧縮がうまくできていなかっただけのようです。

WordCamp Osaka 2018 に当日スタッフとして参加してきました

6月2日に関西大学梅田キャンパスで開催された WordCamp Osaka 2018 に当日スタッフとして参加してきました。
ついでに、大阪観光もしてきました。

6月1日:大阪観光

松本から高速バスで大阪まできました。
電車で名古屋まで行って乗り継いだ方が早いのですが、私は特急しなのの揺れが苦手で耐えられません。
大阪に着いたらノープランで歩き回ってましたが、結構楽しかったです。

写真撮ってませんけどはなだことかいうお店でたこ焼き食べました

おしゃれなビル

エスカレーターでは皆右側に乗っていました

梅田

東通りの商店街にあるお店の半数以上は無料案内所

一蘭のラーメンは2回替え玉したが…

ホテルに戻って少ししたらお腹がピーピーでした

6月2日:WordCamp

WordCampはとても盛り上がりました!

お世話になった方やはじめましての方、多くの方とお話できました

グッズもたくさんもらいました

スタッフとしての参加ですがセッションもいくつか見られました

懇親会の写真は撮るの忘れました

二次会もあったみたいですが、前日に調子こき過ぎてお腹の調子が悪いのと、WordCampでも調子こき過ぎて周りに無礼を振り撒いてしまったので、自重して寝ます。

ありがとうございました。

おやすみなさい。