JavaScript Tips | 文字列ユーティリティ:業務用 - 識別子生成

JavaScript JavaScript
スポンサーリンク

何をしたいユーティリティか:「識別子生成」

ここでの「識別子生成」は、「一意な ID(識別子)を文字列として作る」処理を、毎回バラバラに書かず、共通ユーティリティにまとめることです。

注文番号、セッション ID、一時ファイル名、ジョブ ID、トレース ID…。
業務システムでは「他と絶対かぶってほしくない文字列」が、あちこちで必要になります。

だからこそ、

generateId();                 // "20240105-093015-AB3F9C"
generateShortId();            // "k9f3a2x1"
generateTraceId();            // "20240105T093015Z-7F3A9C12"
JavaScript

のように、「識別子はこの関数を呼ぶ」と決めてしまうのが大事です。


業務用識別子に求められる条件を整理する

ざっくり、これだけは意識したい

業務で使う識別子には、だいたい次のような要件があります。

一意性(かぶらないこと)。
ある程度の長さ(短すぎず、長すぎず)。
使える文字の制限(英数字だけ、記号なし、など)。
人間が見て扱うかどうか(ログで見やすいか、コピペしやすいか)。

この「条件セット」を満たす形で、いくつかのパターンを用意しておくと便利です。


一番シンプルな「ランダム英数字 ID」

ランダム文字列生成の基本

まずは、純粋なランダム英数字 ID を作る関数から始めます。

function generateRandomId(length = 12) {
  const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  const max = chars.length;
  let result = "";

  for (let i = 0; i < length; i++) {
    const idx = Math.floor(Math.random() * max);
    result += chars[idx];
  }

  return result;
}
JavaScript

重要ポイントをかみ砕いて説明する

chars に「使ってよい文字」を全部並べています。
ここでは大文字・小文字・数字を使っていますが、「大文字だけ」「数字だけ」などに変えることもできます。

Math.random() は 0 以上 1 未満の乱数を返します。
それに max を掛けて Math.floor することで、0 以上 max - 1 以下の整数を作り、
それをインデックスとして chars[idx] を取り出しています。

このループを length 回回すことで、「指定した長さのランダム文字列」ができます。

実際の動き

generateRandomId();       // 例: "aK3f9ZpQ1xY2"
generateRandomId(8);      // 例: "F9a3k2X1"
generateRandomId(20);     // 例: "k3F9aZpQ1xY2LmN7bC0"
JavaScript

同じ長さ・同じ文字集合なら、呼ぶたびに違う ID が生成されます。


「時刻+ランダム」の業務向け ID

なぜ時刻を混ぜるのか

純粋なランダムでも十分強いですが、業務では「いつ生成されたか」が分かると便利なことが多いです。

ログを追うときに、「この ID はいつ作られたのか」が一目で分かる。
日付ごとに集計したいときに、ID から日付をざっくり推測できる。

そこで、「日付文字列+ランダム文字列」をくっつけた ID を作ります。

日付文字列ユーティリティを使う

先に作ったような日付フォーマット関数を使います。

function padZero(value, length = 2) {
  return String(value).padStart(length, "0");
}

function formatDateTimeCompact(date = new Date()) {
  const d = date instanceof Date ? date : new Date(date);

  const y = d.getFullYear();
  const m = padZero(d.getMonth() + 1, 2);
  const day = padZero(d.getDate(), 2);
  const h = padZero(d.getHours(), 2);
  const min = padZero(d.getMinutes(), 2);
  const s = padZero(d.getSeconds(), 2);

  return `${y}${m}${day}${h}${min}${s}`; // 例: "20240105093015"
}
JavaScript

時刻+ランダム ID の実装

function generateBusinessId() {
  const ts = formatDateTimeCompact();      // "20240105093015"
  const rand = generateRandomId(6);        // 例: "AB3F9C"

  return `${ts}-${rand}`;                  // "20240105093015-AB3F9C"
}
JavaScript

実際の動き

generateBusinessId();  // 例: "20240105093015-K9F3A2"
JavaScript

これなら、

いつ作られたか → 先頭の 14 桁で分かる。
同じ秒の中で複数作っても → 後ろのランダム部分で区別できる。

という、業務で扱いやすい ID になります。


カウンタを混ぜた「ほぼ連番」ID

同一プロセス内での衝突をさらに減らす

「同じプロセス内で、同じ秒に大量に ID を発行する」ようなケースでは、
ランダムだけに頼らず、カウンタを混ぜると安心です。

let _idCounter = 0;

function generateSequentialLikeId() {
  const ts = formatDateTimeCompact(); // "20240105093015"

  _idCounter = (_idCounter + 1) % 1000000; // 0〜999999 でループ
  const counterStr = String(_idCounter).padStart(6, "0"); // "000001" など

  return `${ts}-${counterStr}`;
}
JavaScript

重要ポイント

_idCounter はモジュール内の変数として持っておきます。
関数を呼ぶたびに 1 ずつ増やし、100 万で一周するようにしています。

これで、同じ秒の中で 100 万件までなら、
"20240105093015-000001" のように、秒+カウンタで一意な ID が作れます。

ランダムを混ぜなくても、プロセス内ではほぼ衝突しません。


UUID 風の ID を作る(簡易版)

完全な UUID ではないけれど「それっぽい」もの

ブラウザや Node.js では、crypto.randomUUID() が使える環境もありますが、
ここでは「自前で簡易 UUID 風 ID を作る」例も載せておきます。

function generateHexSegment(length) {
  let result = "";
  for (let i = 0; i < length; i++) {
    const n = Math.floor(Math.random() * 16); // 0〜15
    result += n.toString(16);                 // 16進数文字列
  }
  return result;
}

function generateUuidLike() {
  return (
    generateHexSegment(8) + "-" +
    generateHexSegment(4) + "-" +
    generateHexSegment(4) + "-" +
    generateHexSegment(4) + "-" +
    generateHexSegment(12)
  );
}
JavaScript

実際の動き

generateUuidLike(); // 例: "f3a9c012-7b4e-4d2a-9f01-3c7a9b2e1d4f"
JavaScript

完全な UUID v4 の仕様を満たしているわけではありませんが、
「16 進数+ハイフンの形で、十分ランダムな ID」が欲しいときには、これで足ります。


実務で意識してほしい設計のポイント

「どの用途にどの ID を使うか」を決める

識別子は、用途によって求められる性質が違います。

ログのトレース ID → 人間が目で追うので、時刻が入っていると便利。
DB の主キー → 衝突しないことが最優先。長さや形式は DB の制約次第。
一時ファイル名 → OS のファイル名制約を満たす必要がある。

だからこそ、

トレース ID 用:generateBusinessId
プロセス内連番 ID 用:generateSequentialLikeId
純ランダム ID 用:generateRandomId

のように、「名前で用途が分かる関数」を用意しておくと、後から読んだときに迷いません。

「一意性の保証範囲」を意識する

ここで紹介した ID は、あくまで「かなりかぶりにくい」レベルです。
システム全体で絶対に衝突させたくない場合は、

DB の AUTO INCREMENT やシーケンスを使う。
UUID(crypto.randomUUID() やライブラリ)を使う。

といった、より強い仕組みを使うのが基本です。

今回のユーティリティは、

ログ用のトレース ID。
一時的なクライアント側 ID。
テストデータ用のダミー ID。

など、「多少の衝突リスクを許容できる場面」や、「他の仕組みと組み合わせる場面」で使うのが現実的です。

フォーマットを変えたくなったときのために

ID のフォーマットは、あとから「やっぱり日付はいらない」「ハイフンを抜きたい」などの話が出がちです。

だからこそ、「ID の見た目を決める場所」をユーティリティに閉じ込めておくのが重要です。
画面や API のコードの中で直接 DateMath.random() を組み合わせるのではなく、
必ず generateXXXId を通すようにしておけば、仕様変更に強くなります。


少し手を動かして感覚をつかむ

コンソールで、次のようなコードを実際に打ってみてください。

generateRandomId();
generateRandomId(8);

generateBusinessId();
generateBusinessId();

generateSequentialLikeId();
generateSequentialLikeId();
generateSequentialLikeId();

generateUuidLike();
generateUuidLike();
JavaScript

何度か呼んでみて、

毎回違う値になっているか。
フォーマットが一定になっているか。
「いつ作られたか」が ID から読み取れるか。

を、自分の目で確認してみてください。

そのうえで、自分のプロジェクトに

export function generateRandomId(...) { ... }
export function generateBusinessId(...) { ... }
export function generateSequentialLikeId(...) { ... }
export function generateUuidLike(...) { ... }
JavaScript

のような関数を置き、

「識別子が欲しくなったら、必ずこの“識別子生成ユーティリティ”を通す」

というルールを作ってみてください。
それだけで、あなたのシステムの ID は、場当たり的な Math.random() の寄せ集めから、
意図と一貫性を備えた「業務レベルの識別子生成」に変わっていきます。

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