JavaScript | ゼロからはじめるプログラミング、30日で基礎を学ぶJavaScript:Webページを操作できるようになる - Day21:関数分割

JavaScript JavaScript
スポンサーリンク

Day21 後半のゴール

前半では「1つの関数に全部書かない」「役割ごとに分ける」という感覚を掴みました。
後半では、それをもう一歩進めて、

少し大きめの処理をどう分解するか
「どこまで分けるか」の判断基準
関数同士の関係をどう整理するか

を、具体的な例を通してじっくり見ていきます。
ここからが「大規模化の基礎」の本番です。


例題の全体像をまず眺める

テーマ:プロフィールフォーム+localStorage

次のような小さなアプリを考えます。

名前とメールアドレスを入力するフォーム
入力チェック(必須・形式チェック)
エラー表示
localStorage への保存
ページ読み込み時の復元

これを「関数分割なし」で書くと、かなりゴチャつきます。
まずは、あえて「よくあるベタ書き版」を見てみましょう。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Day21 関数分割 後半</title>
  </head>
  <body>
    <h1>プロフィール設定</h1>

    <p>名前</p>
    <input id="nameInput" type="text">

    <p>メールアドレス</p>
    <input id="emailInput" type="text">

    <p>
      <button id="saveButton">保存</button>
    </p>

    <p id="message"></p>

    <script>
      const nameInput = document.getElementById("nameInput");
      const emailInput = document.getElementById("emailInput");
      const saveButton = document.getElementById("saveButton");
      const message = document.getElementById("message");

      const savedProfileText = localStorage.getItem("profile");
      if (savedProfileText !== null) {
        const savedProfile = JSON.parse(savedProfileText);
        nameInput.value = savedProfile.name;
        emailInput.value = savedProfile.email;
      }

      saveButton.addEventListener("click", () => {
        const name = nameInput.value.trim();
        const email = emailInput.value.trim();

        if (name === "") {
          message.textContent = "名前を入力してください。";
          message.style.color = "red";
          nameInput.style.border = "2px solid red";
          emailInput.style.border = "1px solid #ccc";
          return;
        }

        if (email === "") {
          message.textContent = "メールアドレスを入力してください。";
          message.style.color = "red";
          nameInput.style.border = "1px solid #ccc";
          emailInput.style.border = "2px solid red";
          return;
        }

        if (!email.includes("@")) {
          message.textContent = "メールアドレスの形式が正しくありません。";
          message.style.color = "red";
          nameInput.style.border = "1px solid #ccc";
          emailInput.style.border = "2px solid red";
          return;
        }

        const profile = { name, email };
        localStorage.setItem("profile", JSON.stringify(profile));

        message.textContent = "プロフィールを保存しました。";
        message.style.color = "black";
        nameInput.style.border = "1px solid #ccc";
        emailInput.style.border = "1px solid #ccc";
      });
    </script>
  </body>
</html>

動きはしますが、
読みやすさ・変更しやすさ・再利用性のどれも微妙です。
ここから「関数分割の視点」で分解していきます。


どこを関数に分けるべきかを見極める

「役割のかたまり」を探す

このコードには、明らかに役割が分かれている部分があります。

保存済みプロフィールの読み込み
入力値の取得
入力チェック(バリデーション)
エラー表示(メッセージ+スタイル)
成功時の表示(メッセージ+スタイル)
localStorage への保存

これらを、順番に関数として切り出していきます。
ポイントは「一気に完璧を目指さない」ことです。
まずは分けやすいところから分けていきます。


ステップ1:バリデーションを関数にする

validateProfile 関数を作る

名前とメールのチェックを、1つの関数にまとめます。

function validateProfile(name, email) {
  if (name === "") {
    return {
      field: "name",
      message: "名前を入力してください。"
    };
  }

  if (email === "") {
    return {
      field: "email",
      message: "メールアドレスを入力してください。"
    };
  }

  if (!email.includes("@")) {
    return {
      field: "email",
      message: "メールアドレスの形式が正しくありません。"
    };
  }

  return null;
}
JavaScript

ここでのポイントは、

エラーがあれば「どの項目か」と「メッセージ」を返す
エラーがなければ null を返す

という形にしていることです。
こうしておくと、後で「どの input を赤くするか」を判断しやすくなります。

イベントハンドラからチェック処理を追い出す

saveButton.addEventListener("click", () => {
  const name = nameInput.value.trim();
  const email = emailInput.value.trim();

  const error = validateProfile(name, email);

  if (error !== null) {
    // ここでエラー表示を行う(後で関数にする)
    return;
  }

  const profile = { name, email };
  localStorage.setItem("profile", JSON.stringify(profile));

  // ここで成功表示を行う(後で関数にする)
});
JavaScript

これだけでも、だいぶ「流れ」が見やすくなりました。


ステップ2:表示処理を関数にする

エラー表示用の関数

function showError(error) {
  message.textContent = error.message;
  message.style.color = "red";

  nameInput.style.border = "1px solid #ccc";
  emailInput.style.border = "1px solid #ccc";

  if (error.field === "name") {
    nameInput.style.border = "2px solid red";
  }

  if (error.field === "email") {
    emailInput.style.border = "2px solid red";
  }
}
JavaScript

ここでは、

一度すべての枠線をリセットする
エラーの対象フィールドだけ赤くする

という流れを関数の中に閉じ込めています。

成功表示用の関数

function showSuccess() {
  message.textContent = "プロフィールを保存しました。";
  message.style.color = "black";
  nameInput.style.border = "1px solid #ccc";
  emailInput.style.border = "1px solid #ccc";
}
JavaScript

これで、イベントハンドラはさらにシンプルにできます。

saveButton.addEventListener("click", () => {
  const name = nameInput.value.trim();
  const email = emailInput.value.trim();

  const error = validateProfile(name, email);

  if (error !== null) {
    showError(error);
    return;
  }

  const profile = { name, email };
  localStorage.setItem("profile", JSON.stringify(profile));

  showSuccess();
});
JavaScript

「何をしているか」が、ほぼ日本語の文章のように読めるはずです。


ステップ3:読み込み処理も関数にする

loadProfile 関数を作る

ページ読み込み時の処理も、関数にしておきます。

function loadProfile() {
  const savedProfileText = localStorage.getItem("profile");
  if (savedProfileText === null) {
    return;
  }

  const savedProfile = JSON.parse(savedProfileText);
  nameInput.value = savedProfile.name;
  emailInput.value = savedProfile.email;
}
JavaScript

これをスクリプトの初期化部分で呼び出します。

loadProfile();
JavaScript

こうしておくと、
「初期化処理はここ」「イベント処理はここ」「バリデーションはここ」
というふうに、コードの役割がはっきり分かれます。


関数同士の関係を意識する

「上から読んで分かる構造」にする

ここまで分割したコードを、関係性で整理してみます。

loadProfile
初期化時に一度だけ呼ばれる
localStorage からデータを読み込んで input に反映する

validateProfile
イベントハンドラから呼ばれる
入力値をチェックして、エラー情報か null を返す

showError
イベントハンドラから呼ばれる
エラー情報をもとにメッセージとスタイルを更新する

showSuccess
イベントハンドラから呼ばれる
成功メッセージとスタイルを更新する

イベントハンドラ(saveButton の click)
入力値を取得する
validateProfile を呼ぶ
エラーなら showError
成功なら localStorage に保存して showSuccess

このように、「誰が誰を呼ぶか」が整理されていると、
コード全体の見通しが一気によくなります。


どこまで分けるかの判断基準

「同じことを2回書きそうなら分ける」

関数分割の一つの目安は、

同じようなコードをコピペしそうになったら、関数にする

です。

例えば、エラー表示のスタイルを変えるコードを、
あちこちにコピペして書き始めたら危険信号です。
showError のような関数にまとめてしまえば、
「見た目を変えたいときはここだけ触ればいい」状態になります。

「名前を付けられるなら分ける」

もう一つの目安は、

この処理に名前を付けるとしたら、何と呼ぶか?

と自分に問いかけてみることです。

「プロフィールを読み込む処理」→ loadProfile
「プロフィールを保存する処理」→ saveProfile
「プロフィールをチェックする処理」→ validateProfile

のように、自然な名前が浮かぶなら、
それは関数として独立させる価値が高い処理です。


セキュリティの視点から見た関数分割

「危険な処理を一箇所に閉じ込める」

セキュリティの観点でも、関数分割はとても重要です。

例えば、

ユーザー入力を画面に表示する
localStorage に保存する
サーバーに送信する

といった「危険度の高い処理」は、
専用の関数に閉じ込めておくと安全です。

画面表示には必ず textContent を使う関数を用意する
localStorage に保存する前に、必ずバリデーション関数を通す

といったルールを「関数レベル」で決めておくと、
うっかり innerHTML を使って XSS の穴を開ける、
といった事故を防ぎやすくなります。

関数分割は、単に「きれいに書くテクニック」ではなく、
安全なコードを書くための土台にもなります。


Day21 後半のミニ完成コード

関数分割後の全体像

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Day21 関数分割 後半</title>
    <style>
      input {
        border: 1px solid #ccc;
        padding: 4px;
      }
    </style>
  </head>
  <body>
    <h1>プロフィール設定(関数分割版)</h1>

    <p>名前</p>
    <input id="nameInput" type="text">

    <p>メールアドレス</p>
    <input id="emailInput" type="text">

    <p>
      <button id="saveButton">保存</button>
    </p>

    <p id="message"></p>

    <script>
      const nameInput = document.getElementById("nameInput");
      const emailInput = document.getElementById("emailInput");
      const saveButton = document.getElementById("saveButton");
      const message = document.getElementById("message");

      function loadProfile() {
        const savedProfileText = localStorage.getItem("profile");
        if (savedProfileText === null) {
          return;
        }

        const savedProfile = JSON.parse(savedProfileText);
        nameInput.value = savedProfile.name;
        emailInput.value = savedProfile.email;
      }

      function validateProfile(name, email) {
        if (name === "") {
          return { field: "name", message: "名前を入力してください。" };
        }

        if (email === "") {
          return { field: "email", message: "メールアドレスを入力してください。" };
        }

        if (!email.includes("@")) {
          return { field: "email", message: "メールアドレスの形式が正しくありません。" };
        }

        return null;
      }

      function showError(error) {
        message.textContent = error.message;
        message.style.color = "red";

        nameInput.style.border = "1px solid #ccc";
        emailInput.style.border = "1px solid #ccc";

        if (error.field === "name") {
          nameInput.style.border = "2px solid red";
        }

        if (error.field === "email") {
          emailInput.style.border = "2px solid red";
        }
      }

      function showSuccess() {
        message.textContent = "プロフィールを保存しました。";
        message.style.color = "black";
        nameInput.style.border = "1px solid #ccc";
        emailInput.style.border = "1px solid #ccc";
      }

      function saveProfile(name, email) {
        const profile = { name, email };
        localStorage.setItem("profile", JSON.stringify(profile));
      }

      saveButton.addEventListener("click", () => {
        const name = nameInput.value.trim();
        const email = emailInput.value.trim();

        const error = validateProfile(name, email);

        if (error !== null) {
          showError(error);
          return;
        }

        saveProfile(name, email);
        showSuccess();
      });

      loadProfile();
    </script>
  </body>
</html>

このコードは、行数だけ見れば「長くなった」ように見えます。
でも、読むとき・直すとき・拡張するときのストレスは、
最初のベタ書き版とは比べものになりません。


Day21 後半のまとめ

後半では、実際の小さなアプリを題材にして、

どこを関数に分けるかを見極める
バリデーション・表示・保存・読み込みを関数に分ける
関数同士の関係(誰が誰を呼ぶか)を意識する
「同じことを2回書きそうなら関数にする」という判断基準
セキュリティ的に重要な処理を関数に閉じ込める

という、大規模化の基礎を具体的に見てきました。

ここまでの感覚が身についていると、
これからコードが長くなっても「整理しながら書く」ことができます。
Day22 以降は、この関数分割の感覚を前提に、
さらに一段上の設計や機能に進んでいけます。

タイトルとURLをコピーしました