エラーメッセージとは - 読み方 / 原因特定 / 設計のベストプラクティス

エラーメッセージはプログラムが問題を検出した際に原因と場所を伝える通知。スタックトレースの読み方 / ユーザー向けメッセージの設計指針を解説

プログラミング基礎

エラーメッセージとは

エラーメッセージ (Error Message) は、プログラムが問題を検出したときに出力する通知である。何が起きたか、どこで起きたか、なぜ起きたかを開発者に伝え、修正の手がかりを提供する。

エラーの種類

エラーは発生するタイミングで大きく 3 つに分かれる。

種類発生タイミング
構文エラーコードの解析時括弧の閉じ忘れ、引用符の閉じ忘れ
実行時エラープログラムの実行中null 参照、ゼロ除算、型の不一致
論理エラー実行は成功するが結果が間違い条件式の誤り、計算ロジックのバグ

構文エラーと実行時エラーはエラーメッセージが出るが、論理エラーはメッセージが出ないため発見が難しい。論理エラーだけは処理系が気づけないので、期待値を書いたテストか、出力の目視でしか捕まらない。

言語ごとに「どこまでが構文エラーか」も違う。JavaScript には自動セミコロン挿入 (ASI) の規則があり、行末のセミコロンを省いても多くの場合は解析器が補うため、省略そのものは構文エラーにならない。代わりに、改行の入れ方だけで意図しない位置に文の区切りが入る形の事故が起きる。この言語では何が構文エラーになり、何が黙って通るのかを知っているかどうかが、メッセージの読み取り速度を決める。

エラーメッセージの読み方

エラーメッセージには通常、以下の情報が含まれる。

TypeError: Cannot read properties of undefined (reading 'name')
    at getUser (src/user.ts:15:20)
    at main (src/index.ts:8:10)

1 行目がエラーの種類と内容。TypeError が種類、Cannot read properties of undefined が内容である。2 行目以降がスタックトレースで、エラーが発生した場所を示す。src/user.ts:15:20 は「user.ts の 15 行目 20 文字目」を意味する。

よくあるエラーメッセージ

メッセージ原因対処
ReferenceError: x is not defined未宣言の変数を参照変数名のスペルミスを確認
TypeError: x is not a function関数でないものを呼び出し変数の型を確認
SyntaxError: Unexpected token構文の誤り括弧や引用符の対応を確認
RangeError: Maximum call stack無限再帰基底条件を確認
Cannot find moduleモジュールが見つからないパスとインストール状況を確認

エラーメッセージを活用するコツ

要点の 1 行を先に見つける

スタックトレースが長くても、エラーの種類と内容を述べている行は 1 行しかない。まずその行を探す。ただし位置は言語によって違う。JavaScript や Java は先頭に来るが、Python は逆で、Traceback (most recent call last): の後に呼び出しの並びが続き、公式チュートリアルが「エラーメッセージの最後の行が何が起きたかを示す」と説明するとおり、種類と内容は末尾にある。Java で Caused by: が連なる場合も、根本原因は下にある方が近い。

「先頭を読む」と機械的に覚えると、言語が変わった瞬間に呼び出し元のフレームだけを見て悩むことになる。探すべきは位置ではなく、種類名 (TypeError など) と内容が書かれた行である。

ファイル名と行番号を確認する

スタックトレースの中から、自分が書いたコードのファイル名を探す。ライブラリの内部で例外が投げられていても、そのライブラリに渡す値を決めたのは自分のコードなので、原因は自分のフレーム側にあることが多い。node_modules のフレームは「原因の場所」ではなく「どの経路で呼ばれたか」として読み、最も内側にある自分のコードの行を起点にする。

ビルドやバンドルを通したコードでは、行番号が変換後のファイルを指してしまう。ソースマップを有効にしておくと、元のファイルと行へ読み替えられる。

エラーメッセージで検索する

エラーメッセージをそのまま検索エンジンに貼り付けると、同じ問題に遭遇した人の解決策が見つかることが多い。貼るのは種類名と定型の文面までにし、ファイルパス・ホスト名・トークン・利用者のデータが混ざった部分は削る。エラーメッセージは環境の内情をそのまま含むため、貼り付け先が外部サービスであることを忘れやすい。

良いエラーメッセージの設計

自分でエラーメッセージを書く場合は、以下を心がける。

// ❌ 曖昧なメッセージ
throw new Error("エラーが発生しました");

// ✅ 具体的なメッセージ
throw new Error(
  `ユーザー ID "${id}" が見つかりません。` +
  `有効な ID は正の整数です。受け取った値: ${id}`
);
原則説明
何が起きたかエラーの種類を明示する
どこで起きたか関連するデータや文脈を含める
どうすればよいか修正のヒントを添える

ただしこの 3 原則は、開発者が読むメッセージの話である。同じ内容を利用者の画面へそのまま出すと、直し方の分からない技術情報を突きつけることになる。宛先で書き分ける。

利用者に見せるエラーメッセージ

Nielsen Norman Group が公開しているエラーメッセージの設計指針は、見せ方・伝え方・手間の削減の 3 系統に整理されている。実装で効くのは次の点である。

指針実装上の意味
発生源の近くに出す画面上部にまとめず、対象の入力欄の隣に出す
色だけに頼らない赤字や枠線に加えて文言とアイコンを添える (色覚特性への配慮)
利用者を責めない落ち度があるかのような言い回しを避け、期待する形式を伝える
入力を保持するエラー後にフォームを空へ戻さない
早すぎる表示をしない入力欄から離れただけで未入力エラーを出さない

同指針は invalidillegalincorrect のような語を、利用者に落ち度があるかのように響く表現として避けるよう述べている。日本語でも「不正な入力です」「エラーが発生しました」だけの文面は同じ問題を抱える。

エラーメッセージとセキュリティ

エラーメッセージは攻撃者にとって偵察の材料になる。OWASP のエラー処理チートシートは、処理されなかった例外がフレームワークやアプリケーションサーバーのバージョンを画面へ出してしまう例と、データベースのエラー文面が設置パスや注入の入口の手がかりを与える例を挙げている。

対処は宛先の分離である。利用者へは一般的な文面と識別子だけを返し、種類・スタックトレース・発行した問い合わせ・内部パスはサーバー側のログにだけ残す。識別子を突き合わせれば、問い合わせ窓口からの連絡と該当ログを結び付けられる。

HTTP API のエラー

HTTP では、状態コードの階級を取り違えないことが出発点になる。RFC 9110 は 4xx を「クライアントが誤ったと思われる」場合、5xx を「サーバー自身が誤った、または要求された処理を行えない」場合と定めている。入力の検証漏れを 500 で返すと、監視する側は自分の障害と利用者側の誤りを区別できなくなる。

同 RFC の 404 の定義には、対象の表現が無い場合だけでなく「存在することを明かす意思がない」場合も含まれる。他人の資源へのアクセスを 403 で返すと存在自体が漏れるため、404 を選ぶ設計の根拠はここにある。

本文の形式は RFC 9457 (Problem Details for HTTP APIs・RFC 7807 を置き換えた) が定めており、application/problem+json として typetitlestatusdetailinstance を返す。

{
  "type": "https://example.com/probs/out-of-credit",
  "title": "You do not have enough credit.",
  "status": 403,
  "detail": "Your current balance is 30, but that costs 50."
}

同 RFC は detail を、デバッグ情報を与えるためではなくクライアントが問題を正せるようにするためのものと位置づけ、スタックダンプのような実装の詳細を HTTP 経由で得られる状態にしないよう促している。status を本文にも書く場合、経路上のプロキシが状態コードを書き換えると本文と食い違い得る点も注意点として挙げられている。

try-catch によるエラー処理

エラーが発生しうる処理は try-catch で囲み、適切に処理する。

try {
  const data = JSON.parse(rawInput);
  processData(data);
} catch (error) {
  if (error instanceof SyntaxError) {
    console.error("JSON の形式が不正です:", error.message);
  } else {
    throw error; // 想定外のエラーは再送出
  }
}

エラーを握りつぶす空の catch ブロックは、問題の発見を遅らせるため避ける。

途中で捕まえて文脈を足したいときは、元のエラーを捨てずに繋ぐ。JavaScript の Error は第 2 引数に cause を取り、より具体的な文面で投げ直しても元のエラーへ辿れる (主要ブラウザでは 2021 年 9 月以降利用できる)。

try {
  await saveOrder(order);
} catch (error) {
  throw new Error(`注文 ${order.id} の保存に失敗しました`, { cause: error });
}

文面を差し替えるだけの再送出は、原因側の行番号と種類を失わせる。読み手が最終的に必要なのは、どの層で何が起きたかの連なりである。層をまたぐたびに文脈を足し、原因は残す。

この記事は役に立ちましたか?

関連用語

関連する記事