「お疲れ様。コードレビューをしていたら、まだ `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` を選ぶ。 配列操作との一貫性を重視せよ。
「たかが文字列の切り出し」と思うかもしれない。けれど、こうした小さな細部へのこだわりが、数年後に「壊れにくい堅牢なシステム」として実を結ぶんだ。
次は、テンプレートリテラルを使ったタグ付きテンプレートの話でもしようか。あれを使いこなすと、サニタイジングや国際化が劇的に楽になるんだ。また次回、語り合おう!」

コメント