Codexでリファクタリングをする時は、すぐに直させず、今の動作、変える構造、動作が同じだと示すチェックの3つを先に書かせます。そのうえで、不要なコードの削除や長いファイルの分割など、テーマを1つずつ小さく進めます。この記事では、動作を固定するテストの置き方、依頼文、変更の前後を比べる方法、差分の見方、大きな変更の分け方を教材のコードで説明します。

この記事は、OpenAIの公式資料に基づく解説です。参照したのはRefactor your codebase、Best practices、Prompting、Usage Insights、Modernizing your Codebase with Codexで、いずれも2026年11月2日に確認しました。テストで動作を固定する手順の組み立て、順番、表の整理は編集部がまとめました。教材のコードと依頼文は説明用で、Codexでの実測はしていません。

Codexのリファクタリングは何を変えて、何を変えない作業か

OpenAIのRefactor your codebaseの資料は、この作業を「今の仕組みの構造を良くすること」と説明し、技術スタックの移行とは分けています。外から見た動作は保ち、次の変更を楽にすることが目的です。同じ資料が挙げる対象は次のとおりです。

  • 使われていないコード、古い補助関数やフラグ、もう不要になった互換用のコードを消す
  • 長いモジュールを、補助関数の切り出しや部品の分割で小さくする
  • 古い書き方を、そのリポジトリの今の書き方に置き換える
  • 重複した処理を1つにまとめる

Codexに頼む前に、今回の作業がリファクタリング、バグ修正、機能追加、移行のどれに当たるのかを決めます。作業の種類によって、合格の基準が変わるからです。

作業外から見た動作合格の確かめ方(編集部の整理)
リファクタリング変えない変更前からあるテストが、テストを直さずに通る
バグ修正誤りの部分だけ変える不具合を再現するテストが、修正後に通る
機能追加増やす新しい動作のテストと、既存のテストの両方が通る
依存やフレームワークの移行変わることがある別の作業として計画し、移行の確認項目を決める

Refactor your codebaseの資料も、スタックの変更、依存の移行、アーキテクチャの変更は、そのリファクタリングに欠かせない場合を除いて別の作業に分けるよう案内しています。変更の点検の進め方はコードレビューの解説で扱っているので、ここでは動作を変えない作業に絞ります。

リファクタリングの前に、今の動作をテストで固定する

「動作を変えないで」と書くだけでは、変わったかどうかを誰も確かめられません。先に、今の動作をテストの形で残します。次の教材は、合計金額を計算する関数です。

# invoice.py(変更前)
def total(items, member):
    s = 0
    for i in items:
        if i["qty"] > 0:
            s += i["price"] * i["qty"]
    if member:
        s = s * 0.9
    if s >= 5000:
        return int(s)
    return int(s) + 500

読みにくいのは、小計、会員割引、送料が1つの関数に混ざっているからです。分ける前に、今の答えをテストに書きます。

# test_invoice.py
from invoice import total

def test_shipping_is_added_below_5000():
    assert total([{"price": 1000, "qty": 2}], member=False) == 2500

def test_shipping_is_free_from_5000():
    assert total([{"price": 5000, "qty": 1}], member=False) == 5000

def test_member_discount():
    assert total([{"price": 1000, "qty": 2}], member=True) == 2300

def test_shipping_is_decided_after_discount():
    # 割引前は5000円でも、割引後は4500円なので送料がつく
    assert total([{"price": 5000, "qty": 1}], member=True) == 5000

def test_fraction_is_truncated():
    # 299.7円の端数を切り捨ててから送料を足す
    assert total([{"price": 333, "qty": 1}], member=True) == 799

def test_negative_qty_is_ignored():
    assert total([{"price": 1000, "qty": -1}], member=False) == 500

このテストが変更前のコードで全部通ることを、先に確かめます。特に大事なのはtest_shipping_is_decided_after_discountです。送料を割引の前に判定するように書き換えると、この入力の答えが変わります。分割の時に順番が入れ替わりやすい場所を、先に押さえています。

書くのは「正しい動作」ではなく「今の動作」です。テストを書いていて不具合らしい動きに気づいても、同じ作業では直しません。別の作業として記録します。混ぜると、差分のどこが動作の変更なのかを読めなくなるからです(編集部のすすめ)。

テストがないコードは、先にテストだけを頼む

テストがない場合は、リファクタリングより先に、テストの追加を別の依頼にします。Codexが書いたテストは、通っているだけでは足りません。OpenAIのUsage Insightsの資料は、導入の効果を評価する手順の中で、テストが狙った失敗を検出できるかを担当者が確かめるよう書いています。編集部では、コードを1か所わざと壊して、テストが落ちることを見る方法をすすめます。

invoice.py の total() の今の動作を固定するテストを
test_invoice.py に書いてください。invoice.py は変更しないでください。
送料の境界、割引と送料の判定の順番、端数の扱い、
数量が0以下の時の動きが変わった時に落ちるテストを含めてください。
結果には、各テストが固定している動作を1行ずつ書いてください。

リファクタリングを始める前に、手元の変更をコミットしておきます。OpenAIのPromptingの資料は、差分に別の変更が混ざらないよう、コミットかスタッシュをしてから計画を頼む手順を示しています。

リファクタリングの範囲を1テーマに絞る

Refactor your codebaseの資料の進め方では、編集させる前にCodexに対象の全体を洗い出してもらい、その中からテーマを1つ選びます。次の順で頼みます。

  1. 対象の場所について、長いモジュール、重複、使われていないコード、テスト、外部に公開している関数やAPIの仕様、古い書き方を一覧にしてもらいます。ここでは編集させません。
  2. 一覧から今回のテーマを1つ選びます。
  3. 変更の前に、今の動作、変える構造、動作が同じだと示す最小のチェックを書いてもらいます。

編集させずに洗い出してもらう時は、Planモードが使えます。OpenAIのBest practicesによると、/planかShift + Tabで切り替えられます。計画の読み方はPlanモードの解説を参照してください。

テーマごとに、差分で特に見る場所が違います。

テーマ差分で見ること(編集部の整理)
使われていないコードを消す文字列や設定、動的な読み込みから呼ばれていないか
関数やファイルを分ける処理の順番が変わっていないか。外から呼ぶ名前が残っているか
重複をまとめる似ているだけで条件が違う処理を、1つにしていないか
古い書き方を置き換える置き換え漏れがないか。例外や空の値の扱いが同じか

動作を変えないリファクタリングの依頼文

Best practicesは、依頼に4つの要素を入れることを勧めています。目的、文脈、制約、完了の条件です。編集部では、リファクタリングでは特に制約と完了の条件が重要だと考えています。次は上の教材に合わせた例で、ファイル名は説明用です。

invoice.py の total() を読みやすく分けてください。

目的: 小計、会員割引、送料の計算を別の関数にする
文脈: 今の動作は test_invoice.py に固定してある
制約:
- total(items, member) の引数と戻り値は変えない
- test_invoice.py は変更しない
- 新しい依存を追加しない
- 不具合らしい動きを見つけたら、直さずに報告する
完了の条件:
- 編集の前に、今の動作、変える構造、動作が同じだと示すチェックを書く
- pytest test_invoice.py が通る
- 結果に、実行したコマンドと出力を書く

テストファイルを変更しないよう書くのは、テストの側を合わせて通してしまう動きを防ぐためです。直した後の形は、たとえば次のようになります。これも説明用の例で、Codexの出力ではありません。

# invoice.py(変更後の例)
SHIPPING_FREE_FROM = 5000
SHIPPING_FEE = 500
MEMBER_RATE = 0.9

def subtotal(items):
    return sum(i["price"] * i["qty"] for i in items if i["qty"] > 0)

def apply_member_discount(amount, member):
    return amount * MEMBER_RATE if member else amount

def shipping_fee(amount):
    return 0 if amount >= SHIPPING_FREE_FROM else SHIPPING_FEE

def total(items, member):
    discounted = apply_member_discount(subtotal(items), member)
    return int(discounted) + shipping_fee(discounted)

最後の行でshipping_fee(discounted)と割引後の金額を渡している点が、test_shipping_is_decided_after_discountで守られている部分です。subtotal(items)を渡すように間違えると、そのテストが落ちます。

リファクタリング1回の流れ。今の動作と変える構造を書き、テーマを1つだけ変え、同じテストを直さず実行し、差分を読んでコミットする。
図:リファクタリング1回の流れ。今の動作と変える構造を書き、テーマを1つだけ変え、同じテストを直さず実行し、差分を読んでコミットする。編集部の説明図です。製品の画面ではありません。画像を開くと拡大できます。

リファクタリングの結果を差分とチェックで確かめる

Refactor your codebaseの資料は、変更を1つの大きな差分にまとめず、小さな単位に区切って進め、そのたびに必要最小限のチェックを実行するよう案内しています。Usage Insightsの資料も、評価の手順の中で、差分を読み、関係するチェックを動かし、置き換え漏れや動作の変化を探すよう書いています。編集部では次の順に見ることをすすめます。

  1. 結果に書かれたコマンドと出力を読み、自分の手元でも同じテストを動かします。
  2. 変更されたファイルの一覧を見て、頼んでいないファイルがないかを確かめます。
  3. テストのファイルに変更がないことを確かめます。
  4. 差分を読み、下の表に挙げた変化がないかを見ます。
  5. 問題がなければコミットしてから、次のテーマへ進みます。
差分に見つかる変化疑うこと(編集部の整理)
テストの期待値が変わっている動作が変わったのを、テストの側で合わせた
公開している関数の引数や戻り値が変わっている呼び出し元が壊れる。今回の範囲を超えている
依存のファイルが変わっている移行が混ざった。別の作業に分ける
条件式の順番や比較の記号が変わっている境界の値で答えが変わる。境界のテストを足す
例外の処理が消えている、増えている失敗した時の動きが変わった

同じ入力で変更の前後を比べる

テストが手薄な場所は、変更前のコードを一時的に残して、同じ入力の答えを比べる方法もあります(編集部の教材です)。変更前のファイルをinvoice_before.pyとして残し、次のスクリプトを動かします。比べ終わったら、一時ファイルは消します。

# compare_total.py
from itertools import product

import invoice
import invoice_before

prices = [0, 1, 333, 999, 1000, 4999, 5000, 5556, 5557, 10000]
qtys = [-1, 0, 1, 2, 3]

for price, qty, member in product(prices, qtys, [False, True]):
    items = [{"price": price, "qty": qty}]
    before = invoice_before.total(items, member)
    after = invoice.total(items, member)
    assert before == after, (items, member, before, after)

print("同じ結果でした")

入力には、境界の前後の値を入れます。入れなかった入力については何も分からないので、一致しても、動作が完全に同じだという証明にはなりません。

もう一つの確認として、/reviewも使えます。Best practicesによると、未コミットの変更やベースブランチとの差分をレビューしてもらえます。独自の観点も指定できます。たとえば「動作が変わる可能性がある箇所だけを挙げて」と頼みます(編集部の例)。使い方の詳細はコードレビューの解説を参照してください。

複数のモジュールにまたがるリファクタリングは計画に分ける

1回で終わらない作業は、マイルストーンに分けた計画を先に作ります。Promptingの資料は、認証の仕組みを整理する例を載せています。要点は次のとおりです。

  • 目的を、責務の分離や循環参照の削減のように具体的に書く
  • 制約に、利用者に見える動作を変えないこと、公開APIを保つことを書く
  • 計画に、マイルストーンごとに移動するファイルと、元に戻す方法を追記させる
  • 実装は「マイルストーン1を実装して」のように1つずつ頼む

複数のモジュールやセッションにまたがる時は、ExecPlanも使えます。Refactor your codebaseの資料によると、ExecPlanは整理の全体像、目指す状態、各回の確認結果を残す文書です。消したもの、置き換えたパターン、維持すると決めた仕様、先送りしたものを記録します。手順は公式のコードのモダナイゼーションのガイドにあります。このガイドはCOBOLの移行を例にしており、新旧を同じ入力で動かして出力を比べる確認も説明しています。言語を変える移行の例なので、そのままリファクタリングに当てはめず、確認の考え方だけを借ります(編集部の判断)。

長い実装をクラウドへ渡すこともできます。Promptingの資料によると、クラウド環境をまだ用意していなければ、先に設定が必要です。IDEの入力欄の下のクラウドのアイコンから環境を選んで続きの依頼を入力すると、チャットの文脈、計画、手元のソースの変更を引き継いだ新しいチャットがクラウドに作られます。作業は隔離された環境で動きます。環境で有効にしない限り、Codexの作業中はインターネットにアクセスできません。結果の差分を確かめ、PRを作るか、手元へ取り込んで仕上げます。

同じ整理を別のリポジトリでも繰り返すなら、うまくいった手順をスキルにまとめる方法もRefactor your codebaseの資料で紹介されています。作り方はスキルの解説を参照してください。

リファクタリングがうまく進まない時の直し方

編集部が公式資料をもとに整理した、症状別の直し方です。

症状試すこと
差分が大きすぎて読めない範囲を狭めて頼み直す。Usage Insightsの資料は、Codexの結果に手直しが多く残る時は、範囲を狭めるか、手本になる動くコードを先に渡すよう案内している
テストが通るのに動作が変わったテストが固定していない動作だった。その入力をテストに足してから直す
頼んでいない修正が混ざる制約に対象のファイルを書く。作業を戻して、テーマを1つに絞って頼み直す
既存のテストが元から落ちている先に別の作業で扱う。落ちたままだと、今回の変更の影響を見分けられない
テストのコマンドを間違えるAGENTS.mdにビルドとテストのコマンドを書く
同じ失敗を繰り返すBest practicesのとおり、振り返りを頼んでAGENTS.mdを更新する
依存の更新まで始める移行は別の作業だと制約に書く

AGENTS.mdの書き方はAGENTS.mdの解説にまとめています。

まとめ:リファクタリングは動作を固定してから、1テーマずつ頼む

Codexでリファクタリングをする時は、今の動作をテストに残してから、テーマを1つに絞って頼みます。依頼文に書くのは、変えない引数と戻り値、テストを変更しないこと、完了の条件です。結果は、自分でもテストを動かし、差分を読んで確かめてください。1回で終わらない作業は、マイルストーンに分けた計画にします。

Codexの全体像から確かめたい場合は、Codexとは?使い方と仕事でできることを読んでください。開発チームで任せる範囲と確認の決まりを整理したい場合は、法人向けのご相談も受け付けています。