【実務・中級編】 String.prototype.substr()の非推奨理由と代替案 – JavaScript実践ガイド

「お疲れ様。コードレビューをしていたら、まだ `substr()` を使っている箇所を見つけたよ。動いてはいるけれど、今の設計思想からすると、これは『負債の種』を植え付けているようなものなんだ。

今日は、なぜ僕たちが `substr()` を卒業しなければならないのか、そして代わりに何を使うのが正解なのか、現場のリアルな視点でじっくり解説しよう。コーヒーでも飲みながら読んでくれ。」

—

はじめに:なぜ今さら `substr()` の話を刷るのか

JavaScriptの世界では、古くからあるメソッドが「非推奨(Deprecated)」とされつつも、ブラウザの互換性のために生き残り続けているケースが多々ある。その筆頭が `String.prototype.substr()` だ。

「動くんだからいいじゃないか」と思うかもしれない。しかし、モダンなフロントエンド開発において、非推奨メソッドを放置することは、将来的なブラウザエンジンの最適化から外れたり、予期せぬエッジケースでバグを生むリスクを抱え続けることを意味する。

特に、`substr` は「直感的でない仕様」が原因で、多くのエンジニアを混乱させてきた歴史があるんだ。

1. `substr()` が嫌われる決定的な理由

最大の理由は、「ECMA-262(JavaScriptの標準規格)」の本体に含まれていないことだ。

実は `substr()` は、仕様書の「Annex B(付録B)」という場所に押し込められている。ここには「Webブラウザとの互換性のために残してはいるが、新しいコードでは使うべきではないレガシーな機能」が定義されているんだ。つまり、JavaScriptの正式な将来像には含まれていない機能なんだよ。

さらに、引数の設計が他のメソッドと絶望的に相性が悪い。

  • `substr(start, length)` : 「何文字分」 切り出すか(第2引数が長さ)
  • `substring(start, end)` : 「どこまで」 切り出すか(第2引数がインデックス)
  • `slice(start, end)` : 「どこまで」 切り出すか(第2引数がインデックス)

この「長さ(length)」を指定するという挙動が、配列(Array)操作の標準である `slice` と乖離しているため、スイッチングコストを上げ、バグの温床になってきた。

2. 現場で選ぶべきは `slice()` 一択である理由

「じゃあ `substring` を使えばいいの?」と聞かれることがあるが、僕の答えはノーだ。現代のJavaScriptにおいて、文字列の切り出しは `slice()` を第一選択にするべきだ。

理由は単純。`Array.prototype.slice` と挙動が同じで、一貫性があるからだ。

特に重要なのが「負のインデックス」の扱いだ。

  • `slice(-3)` : 「後ろから3文字目から最後まで」をスマートに取得できる。
  • `substring` : 負の値を `0` として扱うという、今となっては不可解な挙動をする。

ブラウザの内部処理的にも、`slice` は非常に洗練されている。V8エンジンなどのモダンな環境では、文字列をコピーするのではなく、元の文字列への参照を持つ「スライス(内部的なビュー)」として処理することでメモリ効率を高める最適化も行われている。

3. 実践:`substr` から `slice` への書き換えパターン

実際に現場でよく遭遇するパターンを例に、どう書き換えるのが「綺麗」かを見ていこう。

/

  • 文字列操作のマイグレーション・ベストプラクティス

/

const fileName = “awesome-frontend-engineer.js”;

// — パターン1: 特定の長さだけ切り出す —
// [Before] substr(開始位置, 長さ)
const legacyExt = fileName.substr(8, 8); // “frontend”

// [After] slice(開始位置, 終了位置)
// 終了位置は「開始位置 + 長さ」で計算する
const modernExt = fileName.slice(8, 8 + 8);
console.log(modernExt); // “frontend”

// — パターン2: 文字列の「末尾n文字」を取得する —
// [Before] substr は負のインデックスをサポートしているが、仕様が曖昧だった
const legacyLast3 = fileName.substr(-2); // “js”

// [After] slice なら直感的かつ標準的
const modernLast3 = fileName.slice(-2);
console.log(modernLast3); // “js”

// — パターン3: 実務でよくある「IDのプレフィックス除去」 —
const userId = “USER_99821”;

// substr(5) と slice(5) は第2引数を省略すれば同じ動きだが、
// 意味論(セマンティクス)として slice を使うのが現代の標準。
const cleanId = userId.slice(5);
console.log(cleanId); // “99821”

4. 陥りやすい罠:`substring` と `slice` の決定的な違い

ここで、よく後輩から「`substring` でもいいじゃないですか?」と突っ込まれる。だが、以下の挙動を見てほしい。

const text = “JavaScript”;

// 引数が「逆転」した場合
console.log(text.substring(4, 0)); // “Java” (勝手に引数を入れ替えて処理する…お節介すぎる)
console.log(text.slice(4, 0)); // “” (空文字を返す。これが論理的に正しい)

// 負の値の扱い
console.log(text.substring(-3)); // “JavaScript” (負の値を0と見なす)
console.log(text.slice(-3)); // “ipt” (末尾から3文字。これが便利)

この「勝手に引数を入れ替える」挙動や「負の値を無視する」挙動が、予期せぬバグを引き起こすんだ。だからこそ、挙動が予測可能(Predictable)な `slice` を使うのがプロの選択と言える。

まとめ:チーフアーキテクトからのアドバイス

`substr()` を使い続けることは、技術的な怠慢に近い。
もし君が今、レガシーなプロジェクトを保守しているなら、見つけ次第 `slice()` にリファクタリングしてほしい。

今回の学び:
1. `substr()` は Annex B(レガシー)扱い。 新規開発では絶対に使わない。
2. `length` 指定が必要なら、`slice(start, start + length)` で代用する。
3. 負のインデックスを活用して、コードを簡潔に保つ。
4. `substring` ではなく `slice` を選ぶ。 配列操作との一貫性を重視せよ。

「たかが文字列の切り出し」と思うかもしれない。けれど、こうした小さな細部へのこだわりが、数年後に「壊れにくい堅牢なシステム」として実を結ぶんだ。

次は、テンプレートリテラルを使ったタグ付きテンプレートの話でもしようか。あれを使いこなすと、サニタイジングや国際化が劇的に楽になるんだ。また次回、語り合おう!」

コメント

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