はじめに
「あのコードは怖いから触らない」「リリース日まで変更は止めておこう」——そう判断した経験はありませんか。
自分も最初は、変更を抑え込むほどシステムは安定すると信じていました。
ところが、実際には逆でした。
触らない期間が長いコードほど、いざ変更しようとしたときに影響範囲が読めなくなります。
リリースも同じで、止めている間は安全に見えても、次に出す変更の塊はどんどん大きくなっていきます。
AIがコードを書く時代になっても、「その変更をどう流すか」「いつ古いものを消すか」は人間が設計する必要があります。
この記事では、リリース・大規模変更・廃止の3つを、すべて「止めずに小さく動かす」話として整理します。
TL;DR
- 変更を恐れる仕組みではなく、変更を恐れずに済む仕組みを作る。鍵は「動かし続けること自体」にある
- 動かし続ける仕組みは3面で現れる。小さく頻繁に届ける(CD)/大きな変更も小さく分けて流す(大規模変更)/古いものを意図的に動かして消す(廃止)
- CDも、大規模変更も、廃止も、結局は「大きな変化を小さく扱う」ための仕組み
1. 「動かさない」が最大のコスト
「変更しない方が安全」という直感は、実は安全側に倒した判断ではありません。動かさないこと自体が、次に動かすコストを静かに積み上げています。
1-1. 「触らなければ安全」は本当か
リリース頻度を下げると、1回のリリースに溜まる変更量は増えます。
変更量が増えると、テストすべき範囲も広がり、問題が起きたときの原因特定が難しくなります。
結果として「次のリリースが怖い」と感じ、さらにリリース間隔を伸ばしたくなります。
この悪循環は、だいたい次のように回ります。
古いコードの放置も同じ構造です。
誰も触らなくなると、書いた人がいなくなったときに誰も理解できなくなり、変更不能の状態に近づいていきます。
リリースの間隔も、古いコードの放置も、根っこは同じ「動かしていない」という選択です。
1-2. 動かしていない間に何が起きるか
触られなくなったコードは「そこに置いてあるだけ」では済みません。周辺が動き続けるなかで、相対的に壊れていきます。
積み上がるコストは、大きく分けるとこの3つです。
| 蓄積するもの | 何が起きるか |
|---|---|
| 依存関係の固定化 | 周辺ライブラリの更新に追従できず、他のシステムまで動かしにくくなる |
| 知識の喪失 | 書いた人が離れ、コードの意図を読み取れる人がいなくなる |
| 周辺との不整合 | 言語・ライブラリ・OSの世代差が広がり、ビルドや実行が通らなくなっていく |
こうしたコードは、現場では「怖くて触れないコード」と呼ばれることがあります。
中心的な機能を担っているのに、誰も怖くて変更できない状態です。
止めていたつもりの時間に、依存・知識・周辺の3面でコストが積み上がっていた、というのが正体です。
「動いているから触らない」という判断は、その瞬間のコストを最小化しているように見えます。
しかし、将来の自分や他のメンバーに対して「もっと大きなコスト」を先送りしている状態でもあります。
2. 小さく頻繁に届ける(CD)
動かさないとコストが上がる、までは分かりました。では「動かし続ける」とは具体的にどうやるのか。最も身近な「リリース」から見ていきます。
ここで言う 継続的デリバリー(CD) とは、変更をいつでも本番に届けられる状態に保ち、その流れを小さく頻繁に回す仕組みのことです。
なお、厳密には「いつでも本番に出せる状態を保つ」継続的デリバリーと、「自動で本番まで出す」継続的デプロイは区別されます。この記事では、変更を小さく流し続ける考え方として広めに扱います。
2-1. 「より速い方がより安全」という逆説
直感に反するかもしれませんが、リリース頻度を上げること自体が安全なのではなく、1回あたりの変更量を小さくできることが安全につながります。問題が起きても、直近の小さな変更から原因を特定できるからです(もちろん、テストやロールバックの仕組みが整っている前提です)。
リリースを抑える運用と、動かし続ける運用を並べてみると、違いが見えてきます。
| 観点 | リリースを抑える運用 | 動かし続ける運用 |
|---|---|---|
| 1回の変更量 | 大きい | 小さい |
| 問題の原因特定 | 多数の変更から探す | 直近の小さな変更から探せる |
| ロールバックの容易さ | 影響範囲が広く戻しにくい | 戻す対象が明確で戻しやすい |
| 開発者の心理 | リリース直前に緊張が集中する | 日常作業の延長で済む |
「安定するまで待ってからリリースする」という発想は、短期的には安心ですが、長期的には変更量とリスクを溜め込みます。
リリース間隔は安全装置ではなく、リスクの蓄積装置だと捉え直すと、判断が変わってきます。
2-2. リリースと機能公開を分離する(フラグ駆動)
ここで重要なのは、リリースと公開を別物として扱う ことです。
リリースはコードを本番環境に出すこと、公開はユーザーがその機能を使える状態にすることです。
両者を混同していると、「機能が完成するまでリリースしない」状態が生まれ、リリース自体が止まります。
機能フラグを使うと、コードの中で機能のオン/オフを切り替えられます。開発中の機能をユーザーから隠したまま、本体だけ毎日リリースを続けられます。
たとえばToDoアプリで新しい並び替え機能を開発しているとします。
実装中はフラグをOFFにしておけば、本体のリリースは毎日続けられます。
準備が整ったらフラグを切り替えるだけで、ユーザーに公開されます。
リリースを止めなくてよくなるのは、リリースと公開を分離したときです。
2-3. リスクを段階で受け取る(段階的ロールアウト)
完璧なリリースを目指すのではなく、失敗を小さく受け止める仕組みを目指す——これが段階的ロールアウトの発想です。
全ユーザーに一斉に出さず、小さな割合から徐々に拡大します。
問題があれば早い段階で気づき、変更を戻して被害を限定できます。
問題がなければ次の段階へ拡大します。
これは「リリースしてみないと分からないことがある」と認めたうえで、失敗を小さく受け止めるやり方です。
段階的ロールアウトは、2つのバージョンを同時にユーザーに出して効果を比較するA/B実験と組み合わせて使われることもあります。
「データに基づいてリリース判断する」という発想自体は、動かし続ける仕組みを支える重要なピースです。
3. 大きな変更も小さく動かす(大規模変更)
日々の変更は小さく流せると分かりました。
では、Railsのバージョンアップ、共通ライブラリの置き換え、古いAPI呼び出しの一括修正のような、変更範囲が広すぎる作業はどうするのか。
こういう変更は、やりたいこと自体は1つです。
でも、影響するファイルが多すぎるため、1回のコミットや1本のPRで出すには大きすぎます。
ここでは、このような変更を「大規模変更」として扱います。
3-1. なぜ大きな変更を一気にコミットできないのか
大きな変更があるとき、つい「一気にやりきりたい」と感じます。
中途半端な状態を残したくないし、影響範囲を一度に確定したいからです。
ところが、コードベースが大きくなるほど、一度に動かせる量は逆に小さくなります。
| 観点 | 一気に出す場合 | 分割する場合 |
|---|---|---|
| レビュー | 差分が大きすぎて追いにくい | 変更意図を追いやすい |
| マージ競合 | 並行開発と衝突しやすい | 小さく解決できる |
| テスト失敗時の調査 | どの変更が原因か分かりにくい | 原因候補を絞りやすい |
| 戻し方 | 全体を戻す必要がある | 失敗した分だけ戻しやすい |
表の制約はどれも、コードベースが大きくなるほど強く効いてきます。
特にマージ競合は厄介で、自分が変更を作っている間にも他のメンバーが並行で変更を入れてくるため、大きい塊で抱え込むほど競合の解消コストは雪だるま式に増えていきます。
3-2. 大きな変更を小さく分ける(コミットの分割)
解決策は「変更を小さくする」のではなく、「大きな変更を小さく分けて流す」ことです。
論理的には1つの変更を、独立に提出可能な小さなコミットへと分割します。
各コミットは独立にテスト・レビュー・提出されます。問題が起きたときも、原因となるコミットを絞り込みやすくなります。場合によっては、その分割だけを戻す判断もしやすくなります。
たとえば、ある関数のシグニチャー、つまり引数や戻り値の型を変更したいとします。
その関数が1000ファイルから呼ばれているなら、1000ファイルを一気に変更しません。ファイル群を小さなグループに分けて、それぞれ独立に提出していきます。
コミットの粒度を「論理的なまとまり」ではなく「独立にレビューと提出が可能な単位」に下げることが、大規模変更を成立させる入口になります。
3-3. 大規模変更を支える基盤
これだけ大量の変更を機械的に流すには、土台の整備が欠かせません。
- 言語側の支援: 静的型付けがあると、引数や戻り値の不整合を実行前に見つけやすくなる
- 自動書式整形: 生成されたコードが人間の書いたコードに溶け込むように整形される
- インデックスツール: 「この関数の呼び出し元はどこか」を機械的に列挙できる
大規模変更を成立させるには、機械が安全に編集できる土台が要ります。「誰がこの変更を管理するか」という組織面の話は、本記事の最終セクションでまとめて扱います。
4. 古いものを意図的に動かして消す(廃止)
リリースも大規模変更も、「止めずに動かす」話でした。
では、もう使わない古い仕組みはどうすればよいのでしょうか。
古いものは、放っておいても自然には消えません。移行させ、新規利用を止め、最後に撤去するところまで進める必要があります。
ここで言う 廃止 とは、古いシステムから新システムへの移行と、最終的な撤去までを含むプロセスを指します。
4-1. 古いコードを「自然消滅」させようとすると消えない
廃止が難しいのは、放っておくと消えないからです。「警告を付ければそのうち誰かが移行してくれる」と期待しても、現実にはほとんど起こりません。
- 新規利用: 減ることもある
- 既存利用: 基本的にそのまま残る
- 結末: 旧システムが消えずに何年も残り続ける
「希望は戦略ではない」という言い方があります。
廃止も同じで、「そのうち使われなくなるはず」と期待するだけでは終わりません。「使われない状態を能動的に作る」作業が要ります。
4-2. 勧告的廃止と強制的廃止
廃止には大きく2つの形があります。性質と結末がまったく違うので、整理しておきます。
| 観点 | 勧告的廃止 | 強制的廃止 |
|---|---|---|
| 締切 | なし | あり |
| オーナー | 緩やか(移行は利用者任せ) | 専門チームが移行を担当 |
| 対象 | 新しい物を試したい利用者に代替を示す | 既存利用者を計画的に移行させる |
| 典型的な結末 | 旧システムが消えずに残り続ける | 期限までに移行が完了し撤去される |
ここで重要なのは、移行コストを誰が負うかです。
「移行してください。ただし、そのための時間は各チームで捻出してください」という依頼は、たいてい機能しません。
利用側には他に優先したい仕事があり、廃止作業は後回しになります。
廃止が完了するかどうかは、誰がコストを負うかで決まります。
4-3. アラート疲労を起こさない警告
廃止を進めるとき、警告メッセージを大量に出してしまいがちです。
ところが警告が多すぎると、開発者はだんだん見なくなります。いわゆるアラート疲労です。肝心な警告まで読み飛ばされるようになります。
警告が機能するには、2つの条件が必要です。
- 行動可能性: 警告を見た人が、その場で何をすべきか判断できる
- 関連性: 警告がその人の作業に直接関係する
たとえば、ライブラリAがBに、BがCに依存しているとき、Cの廃止警告はAを書く人にも表示されますが、Aの開発者には行動不能です(Cを変える権限も知識もない)。
警告を出すタイミングも同じくらい重要です。
コードを書いている最中の警告は行動可能ですが、リリース後に蓄積された警告ログは無視されます。
警告が無視されるのは、警告そのものではなく、出すタイミングと量が間違っているからです。
4-4. 廃止は1回ではなく継続プロセス
廃止は「撤去の瞬間」だけでは終わりません。撤去できる状態を維持する継続プロセスです。
特に重要なのが「後戻りの防止」です。
廃止対象のシステムに新しい依存が増え続けていては、移行は永遠に終わりません。
たとえば、コードレビュー時に「廃止予定のAPIを新しく使おうとしている」と検出する仕組みや、ビルド設定で新規依存を許可しない設定が役立ちます。
移行と並行して、新規利用を止め続ける。この継続性がなければ、もぐら叩きになって廃止は完了しません。
5. 3つに共通する原則
CD・大規模変更・廃止を見てきました。一見別々の話に見えますが、やっていることはかなり似ています。
まとめると、共通しているのは次の3つです。
- 分離: リリースと公開、変更の作成とコミット、警告と撤去——いずれも「一気にやらない」を技術で実現している
- 自動化: 小さな変更を機械的に流す基盤がないと、人間が手で繰り返すうちに動きが止まる
- オーナーシップ: 「誰が動かし続けるか」が決まらないと、変更も廃止も途中で止まる
変更を怖くしないには、仕組みだけでは足りません。
小さく流せる技術、繰り返しを任せられる自動化、そして「誰が最後まで面倒を見るか」が必要です。
どれか1つが欠けると、変更はまた止まり始めます。
おわりに
「動かさない」という最も静かな選択肢は、実は最もコストを溜め込む選択肢でした。リリース・変更・廃止のどれも、止めた瞬間から次の一手が重くなっていきます。
大事なのは、いきなり大きく変えることではありません。
次のリリースを少し小さくする。廃止予定のAPIを新しく使わせない。大きな修正を1本のPRに詰め込まない。
そのくらいの小さな一手からでも、「怖くて動かせない」は少しずつ減らせます。


