コンテンツにスキップ

技術的負債・コードの臭い・リファクタリング

一言で言うと

  • 技術的負債(technical debt): 「今すぐ動かす」ために「後で直すコスト」を借金すること。借金自体は戦略であり、問題は無自覚な借金と未返済の利息
  • コードの臭い(code smell): バグではないが、設計上の問題を示唆する兆候。
  • リファクタリング(refactoring): 外から見た振る舞いを変えずに、内部構造を改善すること。

解決したい問題

締切のあるゲーム開発では「正しい設計を待ってからリリース」はできません。負債という概念は、意図的に品質を落とす判断を、管理可能な形にするためにあります。

技術的負債

借金のメタファーとその限界

  • 元本 = 雑に書いた部分。利息 = その部分を触るたびに余計にかかる時間。
  • 良い借金: イベント締切前のハードコード(「イベント終了後に消す」と決まっている)。
  • 悪い借金: 無自覚な借金。返す予定のない借金が中核システムに積もった状態。
  • メタファーの限界: 金の借金と違い、技術的負債は「触らないコード」なら利息ゼロです。全負債の返済は目標になりません。よく触る場所の負債だけが高利息です。

実務的な管理

  • 借りるときに記録する(コメント // HACK(期限/理由): やタスク票)。
  • 返済判断は「利息」で決める: そのコードを月に何回触り、毎回何分余計にかかっているか。

コードの臭い(ゲーム開発でよく出るもの)

臭い 兆候 疑うべき問題 対応する道具
神クラス GameManager が3000行 低凝集 責務ごとに分割
ショットガン手術 敵1体追加で7ファイル修正 知識が散乱(DRY違反) 知識の一元化、Type Object
増え続けるswitch 同じ型分岐が複数関数に OCPの出番 ポリモーフィズム
長すぎる引数リスト 引数8個の関数 データのまとまりの見落とし 構造体にまとめる
羨望するメソッド 他クラスのgetterを大量に呼ぶ 処理の置き場所が違う 処理をデータ側へ移す
フラグの増殖 isDying && !isDead && isStunned 状態設計の欠如 State / FSM
コメントだらけ 説明しないと読めないコード 名前と構造の失敗 抽出と改名

臭いは兆候であって判決ではない。神クラスでも、変更が来ないなら放置が正解のこともあります。

リファクタリング

定義の厳密な意味

「振る舞いを変えずに」が肝です。機能追加とリファクタリングを同時にやらない。混ぜると、バグったとき原因が構造変更か機能変更か分からなくなります。

  1. まず現状の振る舞いを固定する(テスト、なければ手動確認手順)
  2. 小さい構造変更を1つ行い、振る舞いが変わっていないことを確認
  3. 繰り返す。機能追加は構造が整ってから別コミットで

問題のある進め方 → 改善した進め方

悪い: 「汚いので全面書き直し」— 3週間ゲームがビルドできない。
      仕様の暗黙知(あのif文は実は不具合回避だった)を失って劣化する。

良い: 動かしたまま少しずつ。
      例: 神クラスGameManagerの解体
      1. スコア関連の関数と変数を洗い出す(変更なし)
      2. ScoreBoardクラスへ移し、GameManagerは委譲で旧APIを維持
      3. 呼び出し側を段階的に新APIへ
      4. 旧APIを削除
      各段階でゲームは常に動く。

いつやるか

  • やる: これから機能追加する場所が汚いとき(「触るついで」が最有効。準備のリファクタリング)。バグの温床になっている場所。
  • やらない: 触る予定のない場所。リリース直前。振る舞いを固定する手段が何もないとき(先に確認手段を作る)。
  • 詳細な判断フロー: 判断ガイド: リファクタリングすべきか

何が改善され、何を失うか

  • 改善: 変更コストの低下、バグ混入率の低下、知識の整理。
  • 失うもの: 直接の機能進捗ゼロの時間。リグレッションのリスク。チームメンバーの「あのコードどこ行った」コスト。リファクタリングは常に善ではなく、投資判断です。

ゲーム開発での例

  • プロトタイプ→製品化の境目が最大の返済ポイント。「プロトタイプのコードを本番に育てる」なら、この時点で中核(ゲームループ、データフロー)だけは返済する。
  • ライブ運用中のタイトルは「動いているものを触るリスク」が高いので、返済は機能追加と抱き合わせで少しずつが定石。

Unity/C# との対応

  • 「Prefab直しも同時にやる」はリファクタリングの原則違反になりがち(振る舞い固定が崩れる)。コードとアセットの変更は分ける。
  • IDEのリネーム・メソッド抽出は振る舞い保存が保証された安全な操作。ただしUnityのSerializeFieldやAnimationEventの文字列参照はIDEが追えないので注意。

Unreal Engine との対応

  • BlueprintからのC++関数参照も文字列的な結合(リダイレクタで緩和)。C++側のリネームはCore Redirects の設定とセットで行います。

よくある誤解

  • 「リファクタリング = 書き直し」ではありません。全面書き直しはリファクタリングの対極にある最高リスクの手段です。
  • 「負債ゼロを目指すべき」でもありません。締切のある開発で負債ゼロは「借金を恐れて家を買わない」のと同じで、機会損失です。

使う場面 / 使わない場面

  • 負債を意図的に借りる場面: 検証したい仮説がある(面白いか分からない機能)、締切、イベント限定コード。
  • 借りない場面: セーブデータ形式・通信プロトコルなど返済不能になりやすい場所。

関連項目

理解度チェック

  1. 技術的負債の「利息」とは具体的に何ですか。利息が高い負債はどんな場所にありますか。
  2. リファクタリングと機能追加を混ぜてはいけない理由は?
  3. コードの臭いが「判決ではない」とはどういう意味ですか。

演習

自分のプロジェクトで最も触る頻度の高いファイルを1つ選び、上の臭い表と照合してください。臭いが見つかったら、「動かしたまま1段階だけ」の改善手順(5ステップ以内)を書いてください。実施は任意です。


前: YAGNI・KISS・DRY | カテゴリ目次 | 次: 演習