当サイトのリンクには広告が含まれています。

プログラミングのマジックナンバーとは?3つの問題点と解消法

当ページのリンクには広告が含まれています。
Mirai

上司にコードレビューを依頼したら「マジックナンバーってわかる?調べてほしい。」といわれました。マジックナンバーとはなんでしょうか?

Zetto

マジックナンバーは、コードの可読性や保守性を下げる典型的な問題の1つですね。定数化という方法で解消できますよ。

この記事では、プログラミングにおけるマジックナンバーの問題点と、具体的な解消法を解説します。

本記事の専門性
6年目現役エンジニアのZettoです。実務でコードレビューや保守性を意識した開発を続けてきました。Java GoldやJava Silverの資格を保有しています。

この記事を読めば、マジックナンバーとは何か・なぜNGなのか・どう定数化すれば解決できるかまで一通り理解できます。

ぜひ参考にしてみてください。

目次

プログラミングにおけるマジックナンバーとは

マジックナンバーとは

マジックナンバーとは、コードの中に直接書かれた「意味がわからない数値」のことです。

  • コードに埋め込まれた「意味不明な数値」の正体
  • マジックナンバーが生まれる典型的なシーン
  • なぜマジックナンバーはNGとされるのか

それぞれ解説します。

コードに埋め込まれた「意味不明な数値」の正体

コードに埋め込まれた「意味不明な数値」の正体

マジックナンバーとは、コードの中に突然登場する数値のことです。

ブラウザでも動かしやすい JavaScript で示します。

たとえば、こんなコードを見たことがあるかもしれません。

if (user.age >= 18) {
  allowAccess();
}

この `18` という数字、一見して何を意味するかわかりますか?

「成人年齢」なのか「システムの制限年齢」なのか「特定のプランの利用条件」なのか、コードを見ただけでは判断できませんよね。

このように、数値だけ見ても意図が読み取れない値のことをマジックナンバーと呼びます。

Zetto

プログラミングの世界では「魔法の数字」という言葉のとおり、どこから来たのか・なぜこの値なのかが不明な数値が、コードのあちこちに出現するんですよね。

マジックナンバーが生まれる典型的なシーン

マジックナンバーが生まれる典型的なシーン

マジックナンバーは、特定の状況で生まれやすいです。

よくあるシーンは以下のとおりです。

  • 期限を区切るとき:`if (days > 30)` の `30`(30日なのか、30回なのか不明)
  • 料金計算をするとき:`price * 1.1` の `1.1`(消費税なのか手数料なのか不明)
  • ステータス管理をするとき:`if (status === 2)` の `2`(承認済みなのか処理中なのか不明)
  • バリデーション(入力チェック)をするとき:`if (name.length > 50)` の `50`(なぜ50文字なのか不明)

特に「急いで実装したとき」や「とりあえず動けばいい」という判断で書いたコードに、マジックナンバーは集中しがちです。

Zetto

僕自身、エンジニア1年目のころは「あとで直せばいい」と思いながらベタ書きしていました。でも、それが数ヶ月後に自分の首を絞めることになるんですよね。

なぜマジックナンバーはNGとされるのか

マジックナンバーがNGとされる理由は、大きく3つあります。

  • コードの意図が伝わらない:数値だけではなぜその値なのかが読み取れない
  • 修正漏れが起きやすい:同じ数値が複数箇所に散らばると、1箇所直しても別の場所が残ってしまう
  • レビューで指摘される:現場ではコードレビューで必ず突っ込まれる箇所になる

特に2つ目の「修正漏れ」は実務で深刻な問題になります。

たとえば消費税率が変わったとき、コード中に `1.1` が10箇所あったとしたら、全部を探し出して直す必要があります。1箇所でも見落とすとバグになるんですよね。

Zetto

マジックナンバーって一見小さな問題に見えるんですが、チームで開発していると本当に困るんですよね。「この数字、なんで86400なんだっけ?」って議論が始まって時間をロスする、みたいなことが実務でもあります。

プログラミングでは「読みやすく、直しやすいコード」を書くことが非常に大切です。コードの品質についてはこちらの記事でも詳しく解説しています。あわせて読んでみてください。

マジックナンバーを定数化で解消するコードの書き方

定数化でマジックナンバーを解消

マジックナンバーの解消策は「定数化(ていすうか)」です。

  • 可読性・保守性が下がる理由
  • 定数化によるリファクタリングのビフォーアフター
  • コードレビューで指摘される典型的な数値の埋め込みパターン

ひとつずつ見ていきましょう。

可読性・保守性が下がる理由

マジックナンバーがあると、コードの「可読性(よみやすさ)」と「保守性(なおしやすさ)」が下がります。

可読性とは、コードを読んだときにすぐ意味が伝わるかどうかのことです。

保守性とは、仕様変更や機能追加があったときに、素早く・安全に修正できるかどうかのことを指します。

マジックナンバーは、この2つを同時に下げます。

// マジックナンバーあり(読んでも意図が伝わらない)
function calcShippingFee(price) {
  if (price >= 5000) {
    return 0;
  }
  return 500;
}

この `5000` と `500`、何を表しているかすぐにわかりますか?

「5000円以上で送料無料」「送料500円」ということがわかれば読めますが、コードだけ見た人には即座に伝わりません。

仕様変更で送料無料ラインが変わったとき、このコードを書いた本人以外が修正すると、どの数字を変えればいいのか迷うことになります。

定数化によるリファクタリングのビフォーアフター

定数化とは、数値に名前をつけて変数として定義することです。

リファクタリング(refactoring)とは、動作は変えずにコードの構造をきれいにする作業のことを指します。

先ほどのコードを定数化すると、こうなります。

// 定数化あり(意図が一目でわかる)
const FREE_SHIPPING_THRESHOLD = 5000; // 送料無料になる金額
const STANDARD_SHIPPING_FEE = 500;    // 通常送料

function calcShippingFee(price) {
  if (price >= FREE_SHIPPING_THRESHOLD) {
    return 0;
  }
  return STANDARD_SHIPPING_FEE;
}

`FREE_SHIPPING_THRESHOLD`(送料無料のしきい値)と `STANDARD_SHIPPING_FEE`(通常送料)という名前を見るだけで、意図がすぐ伝わりますよね。

また、送料無料ラインが8000円に変わっても、定数の定義部分を1箇所直すだけでOKです。コード全体を検索する必要がありません。

JavaScriptでは `const`(コンスト)を使って定数を定義します。定数名はすべて大文字・アンダースコア区切りで書くのが一般的な慣習です(例:`MAX_AGE`、`TAX_RATE`)。

Zetto

定数化したときの「あ、一目でわかる!」という感覚、僕はすごく好きなんですよね。コードって読む時間のほうが書く時間より長いので、命名に1〜2分かけるだけで後の工数がぐっと減ります。

変数・定数の基本的な考え方については、以下の記事でわかりやすく解説しています。参考にしてみてください。

コードレビューで指摘される典型的な数値の埋め込みパターン

現場のコードレビューでよく指摘されるマジックナンバーのパターンをまとめます。

// パターン1:ステータスコードの直書き
if (user.role === 1) { ... }        // 1って何の権限?
if (order.status === 3) { ... }     // 3って何のステータス?

// パターン2:時間・日数の直書き
setTimeout(callback, 3000);         // 3000ミリ秒?なぜ3秒?
if (daysSinceLogin > 90) { ... }    // 90日の根拠は?

// パターン3:計算式の中の直書き
const tax = price * 0.1;            // 0.1は消費税?手数料?
const salary = hours * 1500;        // 1500は時給?

// パターン4:文字数・件数制限の直書き
if (comment.length > 200) { ... }   // なぜ200文字?
const items = list.slice(0, 10);    // なぜ10件?

これらはすべて、定数化することで意図が明確になります。

// 定数化後
const ROLE_ADMIN = 1;
const ORDER_STATUS_SHIPPED = 3;
const NOTIFICATION_DELAY_MS = 3000;
const SESSION_EXPIRY_DAYS = 90;
const TAX_RATE = 0.1;
const HOURLY_WAGE = 1500;
const MAX_COMMENT_LENGTH = 200;
const DEFAULT_LIST_LIMIT = 10;

定数名を見るだけで、コメントがなくても意図が伝わるようになります。

実務でのマジックナンバーを防ぐ具体的なアプローチ

マジックナンバー防止の実務方法

定数化の基本を押さえたら、次は実務レベルの対策を解説します。

  • 許容されるケースの判断基準
  • ESLintのno-magic-numbersで自動検出する手順
  • チームのコーディング規約への落とし込み方

それぞれのポイントを解説します。

許容されるケースの判断基準

「すべての数値を定数化しなければいけない」というわけではありません。

現場では、以下の数値は定数化しなくても許容されることが多いです。

  • `0`:初期値・空チェック(`array.length === 0` など)
  • `1`:インクリメント(増減)処理(`i + 1`、`i – 1` など)
  • `-1`:配列のindexOf(インデックスオブ)でヒットしない場合の返り値(`index !== -1`)
  • 数学的な定数(円周率 `Math.PI` のように言語やライブラリで用意されているもの)

判断基準はシンプルで、「この数値を見た人が、コードの文脈だけで意味を理解できるか」です。

// これはOK(文脈から意味が読み取れる)
for (let i = 0; i < array.length; i++) { ... }
if (result.indexOf('error') !== -1) { ... }

// これはNG(文脈から意味が読み取れない)
if (score >= 60) { allowPass(); }       // なぜ60?
if (retryCount > 3) { giveUp(); }       // なぜ3回?

迷ったら「コードを初めて読む人がすぐ理解できるか」で判断するのが確かです。

ESLintのno-magic-numbersで自動検出する手順

ESLint(イーエスリント)とは、JavaScriptのコードに問題がないかを自動でチェックしてくれるツールのことです。

ESLintには `no-magic-numbers`(ノーマジックナンバーズ)というルールがあり、マジックナンバーを自動で検出してくれます。

設定ファイル(`.eslintrc.json`)に以下を追加するだけで有効になります。

{
  "rules": {
    "no-magic-numbers": [
      "warn",
      {
        "ignore": [0, 1, -1],
        "ignoreArrayIndexes": true,
        "enforceConst": true
      }
    ]
  }
}

各オプションの意味は以下のとおりです。

  • `”warn”`:エラーではなく警告として表示する(`”error”` にすると即エラー扱い)
  • `ignore`:許容する数値のリスト(`0`、`1`、`-1` は除外)
  • `ignoreArrayIndexes`:配列のインデックス指定を除外する
  • `enforceConst`:`const` で定義することを強制する

チームに導入する場合は、最初は `”warn”`(警告)で様子を見てから `”error”`(エラー)に格上げするのが無理のない進め方です。

Zetto

ESLintのno-magic-numbersは便利なんですが、厳しくしすぎると既存コードが一気にエラーだらけになることもあります。まず `warn` から始めて、段階的に整備するのが現実的かなと感じています。

チームのコーディング規約への落とし込み方

マジックナンバーをなくすには、個人の心がけだけでなくチームのルール化が大切です。

コーディング規約(こーでぃんぐきやく)とは、チームで統一するコードの書き方のルールブックのことです。

規約に落とし込む際のポイントは以下のとおりです。

  • 定数ファイルを1か所にまとめる:`constants.js` や `config.js` のように定数専用ファイルを作る
  • 命名規則を決める:大文字・アンダースコア区切り(`SNAKE_CASE`)を全員で統一する
  • コードレビューで必ず確認する:レビューチェックリストに「マジックナンバーがないか」を追加する
  • ESLintで機械的に検出する:ルールを整備して、人の目だけに頼らない仕組みを作る

定数ファイルの構造の一例です。

// constants.js(定数をまとめるファイル)
export const TAX_RATE = 0.1;                  // 消費税率
export const FREE_SHIPPING_THRESHOLD = 5000;  // 送料無料のしきい値
export const SESSION_EXPIRY_DAYS = 90;        // セッション有効期限(日数)
export const MAX_COMMENT_LENGTH = 200;        // コメント最大文字数
export const ROLE_ADMIN = 1;                  // 管理者権限
export const ROLE_GENERAL = 2;                // 一般権限

このように一元管理しておくと、仕様変更があっても1ファイルを修正するだけで全体に反映できます。

Zetto

定数ファイルを1つ作って全員でインポートするルール、これだけでコードの見通しがかなり変わります。僕もチーム開発では最初にこの仕組みを整えることが多いです。

マジックナンバーの演習問題

マジックナンバー演習問題

理解を深めるために、演習問題を3つ用意しました。

  • 演習問題1:ショッピングカートの価格計算を修正しよう
  • 演習問題2:ユーザー権限チェックのコードを定数化しよう
  • 演習問題3:バリデーション処理の数値を整理しよう

演習問題1:ショッピングカートの価格計算を修正しよう

問題

以下のコードにはマジックナンバーが複数あります。定数化してリファクタリングしてください。

function calcTotal(price, quantity) {
  const subtotal = price * quantity;
  const tax = subtotal * 0.1;
  const total = subtotal + tax;

  if (total >= 10000) {
    return total - 1000;
  }

  return total;
}

解答例

const TAX_RATE = 0.1;                    // 消費税率(10%)
const DISCOUNT_THRESHOLD = 10000;        // 割引適用の最低金額
const DISCOUNT_AMOUNT = 1000;            // 割引額

function calcTotal(price, quantity) {
  const subtotal = price * quantity;
  const tax = subtotal * TAX_RATE;
  const total = subtotal + tax;

  if (total >= DISCOUNT_THRESHOLD) {
    return total - DISCOUNT_AMOUNT;
  }

  return total;
}

`0.1`・`10000`・`1000` の3つがマジックナンバーです。

特に `0.1` は消費税率なのか手数料率なのか、コードだけでは判断できません。`TAX_RATE` という名前をつけることで、「消費税のための数値」だということが一目でわかるようになります。

また、割引条件が変わったときも `DISCOUNT_THRESHOLD` と `DISCOUNT_AMOUNT` の2箇所だけを変えればOKです。

演習問題2:ユーザー権限チェックのコードを定数化しよう

問題

以下のコードでは、権限を数値で管理しています。定数化してコードの意図を明確にしてください。

function getMenuItems(user) {
  if (user.role === 1) {
    return ['ダッシュボード', 'ユーザー管理', '設定', 'ログ閲覧'];
  }

  if (user.role === 2) {
    return ['ダッシュボード', '設定'];
  }

  return ['ダッシュボード'];
}

解答例

const ROLE_ADMIN = 1;    // 管理者
const ROLE_STAFF = 2;    // スタッフ
const ROLE_GUEST = 3;    // ゲスト(デフォルト)

function getMenuItems(user) {
  if (user.role === ROLE_ADMIN) {
    return ['ダッシュボード', 'ユーザー管理', '設定', 'ログ閲覧'];
  }

  if (user.role === ROLE_STAFF) {
    return ['ダッシュボード', '設定'];
  }

  return ['ダッシュボード'];
}

`1`・`2` というステータスコードは、定数化しないと「何の権限を意味するのか」がコードだけでは読み取れません。

`ROLE_ADMIN`・`ROLE_STAFF` という名前にすることで、条件分岐を読んだ瞬間に「管理者なら〜」「スタッフなら〜」と理解できるようになります。

新しい権限(たとえば `ROLE_MANAGER = 3` など)を追加するときも、定数を1行加えるだけで対応できます。

演習問題3:バリデーション処理の数値を整理しよう

問題

以下のフォームバリデーション(入力チェック)コードを定数化してください。

function validateUserInput(name, email, age, bio) {
  const errors = [];

  if (name.length < 2 || name.length > 50) {
    errors.push('名前は2〜50文字で入力してください。');
  }

  if (age < 18 || age > 120) {
    errors.push('年齢は18〜120の範囲で入力してください。');
  }

  if (bio.length > 500) {
    errors.push('自己紹介は500文字以内で入力してください。');
  }

  return errors;
}

解答例

const NAME_MIN_LENGTH = 2;       // 名前の最小文字数
const NAME_MAX_LENGTH = 50;      // 名前の最大文字数
const AGE_MIN = 18;              // 利用可能な最低年齢
const AGE_MAX = 120;             // 利用可能な最高年齢
const BIO_MAX_LENGTH = 500;      // 自己紹介の最大文字数

function validateUserInput(name, email, age, bio) {
  const errors = [];

  if (name.length < NAME_MIN_LENGTH || name.length > NAME_MAX_LENGTH) {
    errors.push(`名前は${NAME_MIN_LENGTH}〜${NAME_MAX_LENGTH}文字で入力してください。`);
  }

  if (age < AGE_MIN || age > AGE_MAX) {
    errors.push(`年齢は${AGE_MIN}〜${AGE_MAX}の範囲で入力してください。`);
  }

  if (bio.length > BIO_MAX_LENGTH) {
    errors.push(`自己紹介は${BIO_MAX_LENGTH}文字以内で入力してください。`);
  }

  return errors;
}

バリデーションのコードはマジックナンバーが集中しやすい場所です。

定数化することで2つのメリットが生まれます。1つ目は「なぜこの数値なのか」がコードを読むだけで伝わること。2つ目は、エラーメッセージの文字数も定数から参照できるため、制限値を変えてもメッセージと条件が自動で一致することです。

たとえば `BIO_MAX_LENGTH` を `500` から `300` に変えるだけで、条件チェックもエラーメッセージも同時に更新されます。修正漏れが起きる余地がなくなるんですよね。

プログラミングの基本構文をしっかり理解したい方には、以下の記事もおすすめです。

よくある質問

よくある質問

よくある質問と回答をまとめました。

  • マジックナンバーと「マジックストリング」は違うのですか?
  • 既存のコードにマジックナンバーが大量にある場合、どこから直せばいいですか?
  • `const` で定義した定数は「マジックナンバーではない」と言い切れますか?

マジックナンバーと「マジックストリング」は違うのですか?

基本的な問題は同じです。

マジックストリング(magic string)とは、数値ではなく「文字列」をコードにベタ書きしたものです。

// マジックストリングの例
if (user.status === 'active') { ... }       // 'active' という文字列が直書き
if (order.type === 'express') { ... }       // 'express' という文字列が直書き

マジックナンバーと同じで、「この文字列はどんな意味?」が読み取りにくく、変更も漏れやすいです。

定数化の考え方はまったく同じで、こう書くと改善できます。

const STATUS_ACTIVE = 'active';
const ORDER_TYPE_EXPRESS = 'express';

数値でも文字列でも、「意図が読み取れない値をベタ書きしない」が原則です。

既存のコードにマジックナンバーが大量にある場合、どこから直せばいいですか?

優先度の高い箇所から少しずつ整備するのが現実的です。

優先度を決める基準は以下のとおりです。

  • 変更頻度が高い箇所から直す:仕様変更のたびに触る場所は定数化の恩恵が大きい
  • 複数箇所に同じ数値が散らばっている箇所から直す:修正漏れリスクが高いため優先
  • 新規機能の実装時は必ず定数化する:新しく書くコードからルールを守る

一気に全部直そうとすると既存の動作を壊すリスクがあります。テストがある箇所から順番に整備するのが確かです。

`const` で定義した定数は「マジックナンバーではない」と言い切れますか?

`const` で定義しても、名前が意味を表していなければマジックナンバーのままです。

// これはマジックナンバーと変わらない(名前から意味が読み取れない)
const NUM = 5000;
const X = 0.1;

// これはOK(名前から意図が読み取れる)
const FREE_SHIPPING_THRESHOLD = 5000;
const TAX_RATE = 0.1;

`const` で定義することと、「意図が伝わる名前をつける」こと、この2つをセットで行うことが大切です。

どちらか片方だけでは意味が半減します。

Zetto

マジックストリングは見落としがちなんですが、実は数値と同じくらいトラブルの原因になります。`status === ‘actve’` のようなタイポ(打ち間違い)が混入したとき、定数化していれば気づきやすいですよね。

プログラミングのマジックナンバーをなくしてコードの質を上げよう

コード品質向上ガイド

この記事では、プログラミングにおけるマジックナンバーの概要と、定数化による解消法を解説しました。

  • マジックナンバーとは、コードに直書きされた「意図が読み取れない数値」のこと
  • 可読性・保守性が下がり、修正漏れやバグの原因になる
  • `const` で意味のある名前をつけて定数化することで解消できる
  • ESLintの `no-magic-numbers` ルールで自動検出できる
  • チームのコーディング規約に落とし込むことでコード全体の品質が上がる

コードの質は、「動くかどうか」だけでなく「読みやすく・直しやすいか」でも評価されます。

マジックナンバーをなくす習慣は、初心者のうちから身につけておくと現場でそのまま通用するスキルになります。

Mirai

マジックナンバーってそういう意味だったんですね。定数化、さっそく試してみます!

Zetto

コードを書いた瞬間は「これでいいか」と思っても、3ヶ月後の自分が見たときに「なんだこの数字」となりやすいのがマジックナンバーなんですよね。名前をつける習慣、ぜひ続けてみてください。

コードの読みやすさを意識できるようになったら、プログラミング全体の考え方も改めて深掘りしてみてください。以下の記事で、現場で使える思考プロセスをまとめています。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次