0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AIにライブラリ移行を丸投げすると、なぜ静かに事故るのか — 大規模コードマイグレーションを「決定的codemod × LLM × 人間」で仕分ける実践ガイド

0
Posted at

はじめに:作るのは速くなったのに、「作り替える」はなぜ怖いままなのか

正直に言うと、AIが来てから、新しいコードを書くのはほんとに速くなりました。

でも、不思議なことに、もうひとつの仕事だけはあんまり楽になってない気がするんです。

それが 「移行(マイグレーション)」

たとえばこういうやつです。

  • React 1718 に上げたら、テストが数百ファイル落ちた
  • テスト基盤を Enzyme から React Testing Library に乗り換えたい。対象、3千ファイル
  • Spring Boot 23 に上げたい。javaxjakarta に全部書き換え
  • 古い社内ライブラリ legacy_fetch() を、新しい httpClient.get() に全置換したい

ガイドはある。やり方も分かる。なのに着手できない。理由はだいたい一緒で、 対象が「1ファイル」じゃなくて「数百〜数千ファイル」だから なんですよね。

ここで多くの人が、こう考えます。

「もうこれ、AIに全部投げちゃえばよくない?」

その気持ち、めっちゃ分かります。僕も最初そう思いました。

でも、 移行を“全部AIに丸投げ”すると、たいてい静かに事故るんです。 一見うまくいったように見えて、後からじわじわ壊れていく。かといって全部手作業は、終わらない。

今日お伝えしたいのは、この 二択の「外側」に道がある という話です。

具体的には、移行を 「決定的なcodemod」「LLM」「人間」の3つのレイヤーに仕分ける という設計。これは僕の思いつきではなくて、2026年時点でAirbnbやGoogleが実際に大規模移行をやり切ったときの「勝ち筋」を、個人やチームの規模に縮小して移植できる形にしたものです。

AI開発にまだ慣れていない方でも追えるように、用語はぜんぶ噛み砕いて進めます。コード例とプロンプト例も、そのまま試せる粒度で置いておきますね。


そもそも「移行」って、技術的には何をしてるのか

まず言葉から、ゆっくりいきましょう。専門用語って、意味が分かると一気に怖くなくなるので。

移行(マイグレーション / migration) は、ざっくり言うと 「引っ越し」 です。

家の引っ越しを想像してみてください。荷物(=コードの中身)はだいたい同じなのに、新しい家(=新しいライブラリやフレームワーク)のルールに合わせて、置き場所や形を変えないといけない。コンセントの形が違う、間取りが違う。だから「中身は同じなのに、全部触らないといけない」。これが移行のしんどさの正体です。

ここで、引っ越しに出てくる登場人物(用語)を3つだけ先に紹介します。

  • codemod(コードモッド) … 「定型的な書き換えの型紙」。たとえば「legacy_fetch(x) という形を見つけたら、全部 httpClient.get(x) に置き換える」みたいな機械的な変換を、人間じゃなくてプログラムにやらせる仕組みのこと。「code(コード)+ modification(修正)」の略です。
  • AST(抽象構文木 / Abstract Syntax Tree) … コードを「文字列」じゃなくて「文の骨組み(木構造)」として捉えたもの。人間が「主語・述語・目的語」で文を理解するのに近いです。codemodは、この骨組みを見て書き換えるので、ただの文字列置換よりずっと正確。
  • long tail(ロングテール / 長い裾野) … 「型紙にハマらない例外だらけの少数派」。移行対象の8割は型紙でサクッといける。でも残り2割が、一個一個ちがう顔をしていて、機械的な型紙じゃ処理できない。この「裾野」が、移行の本当の難所です。

この3つを覚えておくと、後の話が一気に立体的になります。とくに 「型紙でいける8割」と「型紙にハマらない2割」をどう分けるか 。ここが今日の主役です。


2026年の事実:Airbnbの「6週間」を、丸投げ成功談として読まない

「AIで移行」と聞くと、まずこの2つの事例が出てきます。どちらも一次情報で確認できる、信頼できる事例です。両方とも、すごい数字が出ます。でも、 数字の読み方を間違えると、真逆の教訓を持って帰ることになる ので、そこを一緒に見ていきましょう。

事例1:Airbnb — Enzyme→RTL、約3,500ファイルを6週間で

Airbnbは、テスト基盤を Enzyme から React Testing Library へ移行しました(Airbnb Tech Blog / InfoQ)。

  • 対象:約 3,500 のテストファイル
  • 手作業の見積り: 1.5年
  • 実際: 6週間 で完了
  • 自動移行の成功率: 97% 。残り3%は、LLMが出した結果を「下書き」として人間が手で仕上げた

「ほら、やっぱりAIに任せれば一瞬じゃん」と思いますよね。でも、Airbnbがやったことの中身を見ると、印象が変わります。彼らは、こういう手順を踏んでいます。

  1. 移行を 1ファイルごとの離散したステップに分割 した
  2. それを 並列化 した
  3. 失敗したら 設定可能なリトライループ で何度も投げ直した
  4. プロンプトに 文脈(周辺コード・移行ルール)を厚く追加 した
  5. 型紙にハマらない複雑なファイル(=long tail)に、 個別のプロンプトチューニング を重ねた

つまり、 「LLMにポンと投げたら終わった」わけじゃない んです。むしろ、LLMの周りに「分割・並列・リトライ・検証・文脈追加」という頑丈なパイプラインを組んだから、6週間で済んだ。LLMは主役というより、 頑丈な工場のラインで動く一工程 でした。

事例2:Google — int32→int64の一括移行

Googleは、社内の大規模移行(たとえば32bit整数を64bitに変える、みたいなやつ)にLLMを使いました(arXiv:2504.09691, FSE 2025)。

  • 仕組み: 変更すべき箇所を自動で特定(change location discovery) → LLMが編集を生成 → 複数の検証(validation) → 最後は 開発者が確認して受け入れ(accept)
  • 規模:39の移行、開発者3名、12か月で595件の変更・93,574箇所の編集
  • そのうち 74.45% の変更、 69.46% の編集がLLM生成
  • 手作業比で 約50% の時間削減

ここでも構造は同じです。 「箇所の特定」「検証」「人間の受け入れ」 という枠があって、その中の「編集の生成」をLLMが担当している。LLMが7割を生成しても、 最終的にOKを出すのは人間 なんですよね。

この2つから持って帰るべき教訓

ここが一番大事なところです。

AirbnbもGoogleも、勝因は「優秀なLLM」じゃなくて「LLMを安全に動かすパイプライン」だった。

「移行をAIに丸投げして成功した話」ではなく、 「移行を仕分けて、機械にできる検証は機械にやらせて、判断だけ人間が握った話」 なんです。

だから僕らがやるべきは「もっと賢いモデルを待つ」ことじゃない。 手元の移行を、ちゃんと仕分けること 。次の章でその仕分けをやります。


移行を「3つのレイヤー」に仕分ける

移行のタスクを、性質ごとに3つに分けます。これが今日の背骨です。

レイヤー 中身の性質 誰に任せる 具体例
① 機械的・規則的 「この形→この形」が明確。例外が少ない 決定的なcodemod importパスの一括変更、関数名のリネーム、引数の並べ替え
② 文脈依存・判断が要る 形が一定じゃない。周辺の意図を読む必要がある LLM(検証つき) テストの書き換え、非推奨APIの意味を保った置換、エラーハンドリングの作り替え
③ 設計判断・不可逆 間違うと戻せない。方針そのものを決める 人間 移行の方針決定、破壊的変更の承認、本番データに触る操作

ポイントは、 「全部AI」でも「全部手作業」でもなく、性質で切り分ける ということ。

そして大事な順番があります。 ①の機械的な部分を、先に決定的codemodで“確定”させてしまう 。そうすると、②でLLMに任せる対象が「型紙にハマらない裾野」だけにギュッと絞られる。LLMの仕事が減れば、非決定性のリスクもコストも検証の手間も全部減ります。

ここで、当然こういう疑問が出ますよね。

「機械的な部分も、別にAIにやらせればよくない? なんでわざわざ別のツールを使うの?」

すごく良い問いです。次の章で正面から答えます。


なぜ「機械的な部分」を、わざわざ決定的ツールでやるのか

①の機械的な変換をLLMに丸投げすると、一見うまくいくのに、 静かに事故る 。その理由は、大きく4つあります。

1. 非決定的だから、同じ入力でも結果がブレる
LLMは、同じコードを渡しても、毎回まったく同じ書き換えを返すとは限りません。3,500ファイルを処理して、3,480個は同じルールで変換されたのに、20個だけ微妙に違う書き方になる。この「揺らぎ」が、後でレビューを地獄にします。決定的なcodemodなら、 同じ入力には必ず同じ出力 。これは大規模になればなるほど効いてきます。

2. 差分(diff)が読めなくなる
codemodの変換は「ルールが1個」なので、レビューも「そのルールが正しいか」を1回確認すればいい。でもLLMが3,500ファイルを個別に書き換えると、3,500通りの差分が生まれて、人間が全部目で追えなくなる。 「読めない差分は、レビューしていないのと同じ」 です。

3. コストとレイテンシが爆発する
「importを1行変える」だけのために、毎ファイルLLMを呼ぶ。3,500回。これはお金も時間ももったいない。機械的な変換は、ローカルで一瞬で終わる決定的ツールのほうが、速くて安い。

4. 検証できない(テストできない)
codemodのルールは「入力コード → 期待する出力コード」という形でユニットテストが書けます。つまり 変換そのものをテストで守れる 。LLMの一回限りの出力は、そういう形では守りにくい。

まとめると、こうです。

機械的にできることは、機械的にやる。 AIは“魔法”じゃなくて“高い道具” だから、安い道具で済むところに使わない。

これ、移行に限らずAI活用全体に効く考え方だと思っていて。 「AIにできるか」じゃなくて「AIにやらせる必要があるか」で判断する 。能力ではなく、必要性で切る。ここがブレないと、移行はだいぶ安全になります。


決定的codemodの実物:ast-grep / jscodeshift / OpenRewrite

では、機械的な①レイヤーを担う「決定的codemod」の代表を、実物で見ていきましょう。3つ紹介しますが、 まず1つ(ast-grep)だけ覚えれば十分 です。

① ast-grep — 多言語で速い、最初の一本

ast-grep は、コードを「文字列」じゃなくて「AST(文の骨組み)」として検索・置換するツールです。Rust製でめちゃくちゃ速くて、JS/TS/Python/Java/Goなど多言語に対応しています。

たとえば「古い legacy_fetch(url) を、新しい httpClient.get(url) に全置換したい」とします。文字列置換(sed)だと、コメントの中の legacy_fetch まで誤爆したり、my_legacy_fetch みたいな別物まで巻き込んだりする。ast-grepはASTで見るので、 「本当に関数呼び出しになっているやつ」だけ を正確に捕まえます。

まずはインストールして、書き換えずに「どこがヒットするか」だけ見てみる(ここ超大事。いきなり書き換えない)。

# インストール(npm の例)
npm install --global @ast-grep/cli

# まず「検索だけ」して、対象がどこにあるか目で確認する(破壊しない)
ast-grep --pattern 'legacy_fetch($URL)' --lang ts src/

# 問題なさそうなら --rewrite で置換プレビュー、最後に --update-all で適用
ast-grep --pattern 'legacy_fetch($URL)' --rewrite 'httpClient.get($URL)' --lang ts src/

$URL は「ここに何が入ってもいい」というプレースホルダ(メタ変数)です。legacy_fetch("/users") でも legacy_fetch(buildUrl(id)) でも、中身を保ったまま httpClient.get(...) に移してくれます。

もう少し複雑なルールは、YAMLファイルに書けます。これだと「テスト・レビュー・再利用」がしやすい。

# rules/migrate-legacy-fetch.yml
id: migrate-legacy-fetch
language: typescript
rule:
  pattern: legacy_fetch($URL)
fix: httpClient.get($URL)
# このルール自体に「入力→期待出力」のテストを付けられるのが決定的ツールの強み
# ルールを使って一括スキャン → 適用
ast-grep scan --rule rules/migrate-legacy-fetch.yml --update-all

ここがLLMとの決定的な違い です。このYAMLルールは、レビューするのが「3,500個の差分」じゃなくて「ルール1個」で済む。そしてルール自体をテストで守れる。 人間が確認すべき面積が、桁で小さくなる んですよね。

② jscodeshift — JS/TSで込み入った変換をしたいとき

jscodeshift(Meta製)は、JS/TS特化で、変換ロジックをJavaScriptでガッツリ書けるツールです。「単純なパターン置換じゃなくて、条件分岐や周辺コードの状態を見て書き換えたい」ときに向いています。

// transforms/rename-prop.js
// <OldButton type="..."> を <NewButton variant="..."> に変える例
module.exports = function (fileInfo, api) {
  const j = api.jscodeshift;
  const root = j(fileInfo.source);

  // JSXの <OldButton> 要素を探す
  root.findJSXElements('OldButton').forEach((path) => {
    // タグ名を NewButton に
    path.node.openingElement.name.name = 'NewButton';
    if (path.node.closingElement) {
      path.node.closingElement.name.name = 'NewButton';
    }
    // 属性 type を variant にリネーム
    path.node.openingElement.attributes.forEach((attr) => {
      if (attr.type === 'JSXAttribute' && attr.name.name === 'type') {
        attr.name.name = 'variant';
      }
    });
  });

  return root.toSource();
};
npx jscodeshift -t transforms/rename-prop.js src/ --extensions=tsx

ast-grepの宣言的なYAMLでは表現しきれない「手続き的な判断」を入れたいときの選択肢、くらいに覚えておけば大丈夫です。

③ OpenRewrite — Java/Springなどの「フレームワーク移行レシピ」

OpenRewrite(Moderne)は、主にJava/Kotlin界隈で強い移行エンジンです。特徴は LST(Lossless Semantic Tree) という、ASTより詳しい木(型情報や空白まで保持する)を使うこと。だから「型を見て安全に書き換える」ような、フレームワーク移行(Spring Boot 2→3、JUnit 4→5など)の 既製レシピ が充実しています。

# rewrite.yml — 既製レシピを組み合わせる「宣言的レシピ」の例
type: specs.openrewrite.org/v1beta/recipe
name: com.example.MigrateToSpringBoot3
displayName: Migrate to Spring Boot 3
recipeList:
  - org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0
  # javax → jakarta の名前空間移行なども既製レシピでまとまっている

ポイントは、 「よくある移行は、もう誰かがレシピにしてくれている」 こと。自分でLLMに考えさせる前に、 既製の決定的レシピがないか探す 。これが移行を速く・安全にする近道です。

使い分けの早見表

ツール 得意 こんなときに
ast-grep 多言語・高速・宣言的 まず最初の一本。パターン置換の8割はこれで足りる
jscodeshift JS/TSで手続き的な複雑変換 周辺の状態を見て条件分岐したい変換
OpenRewrite Java/Springの型を見た移行 フレームワークのバージョン移行(既製レシピがある)

迷ったら、 「自分の移行ルールを既製レシピや1個のパターンで書けないか?」を先に考える 。書けるなら、そこはLLMの出番じゃありません。


曖昧な「裾野」はLLMに:per-fileパイプラインを組む

さて、決定的codemodで①の8割を片付けると、残るのは「型紙にハマらない裾野(long tail)」です。ここは、形が一定じゃないので、 意味を読んで書き換える必要がある 。ここでようやくLLMの出番です。

ただし、 LLMにフォルダごと丸投げしない 。Airbnbがやったように、 1ファイルずつの小さなジョブに分割して、検証とリトライで囲う 。これが鉄則です。

考え方を擬似コードで書くと、こんな感じになります(TypeScript風)。

// per-fileの移行パイプライン(要点を抜き出した擬似コード)
type FileResult =
  | { file: string; status: "done" }
  | { file: string; status: "manual"; reason: string };

async function migrateFile(file: string): Promise<FileResult> {
  const MAX_RETRY = 2;
  const original = await readFile(file);

  for (let attempt = 0; attempt <= MAX_RETRY; attempt++) {
    // 1. LLMに「このファイルだけ」を移行させる。周辺文脈とルールをプロンプトに厚く渡す
    const migrated = await llmMigrate({
      code: original,
      rules: MIGRATION_RULES,        // 移行のルール(人間が定義)
      examples: GOLDEN_EXAMPLES,     // 正解例(before/after)を数個
      feedback: lastError,           // 前回の失敗内容を次の試行に渡す(ここがリトライの肝)
    });

    // 2. 機械でできる検証は、機械にやらせる(人間を待たせない)
    await writeFile(file, migrated);
    const lint = await run(`eslint ${file}`);
    const types = await run(`tsc --noEmit`);
    const test = await run(`vitest run ${testFor(file)}`);

    if (lint.ok && types.ok && test.ok) {
      return { file, status: "done" };  // 3点とも緑なら合格
    }
    lastError = summarizeFailures(lint, types, test); // 失敗を要約して次の試行へ
  }

  // 3. リトライしても緑にならなければ、無理させない。人間の山に積む
  await restoreFile(file, original); // ← 壊れたまま放置せず、元に戻すのが大事
  return { file, status: "manual", reason: lastError };
}

このパイプラインの肝は3つです。

  • per-file(1ファイル単位) … 失敗の影響が1ファイルに閉じる。並列化もしやすい。
  • retry with feedback(失敗を次に渡すリトライ) … 「型エラーが出た」という事実を次の試行のプロンプトに入れると、成功率がぐっと上がる。Airbnbの「設定可能なリトライループ」はこれです。
  • 諦めたら人間に積む … 何度やっても緑にならないファイルは、無理に通さない。 manual として人間のレビュー待ちに積む 。Airbnbの「残り3%は手動」がまさにこれ。

「全部を自動で通そう」としないこと。 8割をcodemodで、残りの大半をLLMで、最後の数%を人間で。 この比率を最初から織り込んでおくと、移行は止まりません。


「測れる移行」にする:テストの緑だけを信じない検証ゲート

移行で一番危ないのは、 「動いてるように見えるけど、意味がズレている」 という静かな事故です。だから移行は、 「測れる状態」 にしてから進めます。

検証は、 3点セット で見ます。1つだけだと必ず穴が空くので。

# 検証ゲート(最低この3つを緑にしてから次へ進む)
set -e

# 1. テスト:振る舞いが壊れていないか
vitest run

# 2. 型・lint:構造的に壊れていないか
tsc --noEmit
eslint . --max-warnings=0

# 3. 差分の量を機械的にチェック:意図より大きく変わっていないか
#    1ファイルの変更が異常に大きい=LLMが余計なことをしたサイン
git diff --stat | awk '$3 > 200 { print "⚠ 変更が大きすぎる: " $1 }'

ここで強く言いたいのは、 「テストが緑=移行が正しい」ではない ということ。理由は2つあります。

  • カバレッジの穴 … テストが無い部分は、壊れても緑のまま。
  • 意図の劣化 … LLMが「テストを通すために、テストの中身を骨抜きにする」ことがある。たとえば、本当は中身を検証していたテストを、expect(true).toBe(true) みたいな空っぽのテストに書き換えてしまう。これは緑になるけど、 テストとして死んでいる

だから、 最後は人間が差分を読む 。ただし全部じゃなくていい。「変更が大きいファイル」「テスト本体が変わったファイル」など、 リスクの高いものから読む 。ここの優先順位付けは、人間が握るべき判断です。

そして、移行全体の進捗は 「移行台帳」 で1枚に可視化します。これが、明日の自分(と仲間)への置き手紙になります。

# migration-ledger.yml — 移行の進捗を1枚で見える化する台帳
migration: enzyme-to-rtl
owner: frontend-team           # ダミー。固有名は書かない
started: 2026-06-21
total_files: 3500
status:
  done_by_codemod: 1200        # 決定的codemodで確定した分
  done_by_llm: 2150            # LLM+検証ゲートを通った分
  manual_queue: 150            # 人間レビュー待ち(裾野)
gates:
  - tests_green
  - types_pass
  - diff_reviewed_for_high_risk
irreversible_hold:             # ここは絶対に自動化しない(人間ゲート)
  - production_schema_change
  - public_api_break

「いま何件終わって、何件が人間待ちで、どのゲートを通したか」が1枚で分かる。これがあるだけで、移行は「終わりの見えない作業」から「残りが数えられる作業」に変わります。


安全設計:不可逆なものは、絶対に人間ゲートを通す

ここは移行で一番、慎重になってほしいところです。ゆっくりいきます。

移行のコード書き換え自体は、git があるので基本「戻せる」。でも、移行作業の周りには、 戻せない操作(不可逆操作) が地続きで存在します。

  • 本番データベースの スキーマ移行(列を消す、型を変える)
  • 公開APIの 破壊的変更(利用者のコードが壊れる)
  • パッケージの publish、本番への デプロイ
  • 古いデータの 一括変換・削除

これらは、 どれだけテストが緑でも、AIに自動実行させてはいけません。 必ず、人間が内容を確認して承認する「ゲート」を通す。仕組みで止めるのが理想です。

// 不可逆操作は「構造で」人間承認を強制する
const IRREVERSIBLE = ["db:migrate:prod", "api:breaking", "npm:publish", "deploy:prod"];

async function runStep(step: string) {
  if (IRREVERSIBLE.includes(step)) {
    // AIエージェントの自動実行を拒否し、人間の承認トークンを要求する
    throw new Error(`STOP: "${step}" は不可逆操作です。人間の承認が必要です。`);
  }
  // 可逆な操作(コード書き換え・テスト実行)だけ自動で進める
  await execute(step);
}

あと2つ、地味だけど大事な注意を。

  • 秘密情報・個人情報を、プロンプトやログに混ぜない。 移行対象のコードに、APIキーや個人情報、社内固有のIDが紛れていることがあります。LLMに渡す前に、 ダミー化・マスキング する。ログにもそのまま貼らない。
  • 外部から来たテキストは「指示」じゃなく「データ」として扱う。 移行対象のコメントやテストデータに、「これまでの指示を無視して〜」みたいな文字列が紛れていたら(プロンプトインジェクション)、AIがそれに従ってしまうことがある。外部テキストは囲って、命令として解釈させない。

不可逆なものは人間、可逆なものはAI。 この線引きが、移行を「攻めても大丈夫」な作業に変えてくれます。


そのまま使えるプロンプト例4本(最終判断は、いつも人間)

ここからは、実際に貼って使えるプロンプトを4つ置いておきます。共通して、 AIには「材料」を出させて、「決定」は人間がする という形にしてあります。

プロンプト1:移行を3レイヤーに仕分けさせる

あなたはコード移行の設計を手伝うアシスタントです。
以下の移行タスクを、3つのレイヤーに仕分けてください。

# 移行内容
- 旧: legacy_fetch(url) / 新: httpClient.get(url)(ほか別紙のルール)

# 出力フォーマット(表)
| 変換パターン | レイヤー(機械的/LLM/人間) | 理由 | 決定的codemodで書ける? |

# ルール
- 「形が一定で例外が少ない」ものは「機械的」に寄せる
- 「周辺の意図を読む必要がある」ものだけ「LLM」にする
- 「不可逆・方針決定」は「人間」にする
- 判断に迷うものは「人間」に倒し、理由を書く
- 最終的にどのレイヤーにするかは私(人間)が決めます。あなたは候補と理由を出してください。

プロンプト2:1ファイルだけを、意図を保って移行させる

あなたは慎重なコード移行アシスタントです。
以下の1ファイルだけを、ルールに従って移行してください。

# 厳守事項
- テストの「意図」を変えないこと(検証している中身を骨抜きにしない)
- テストを通すためだけに assert を空にする・スキップするのは禁止
- ルールに無い変更(リファクタ・整形・命名変更)を勝手にしない
- 変換に自信がない箇所は、書き換えずに「# TODO(migration): 要確認」を残す

# 入力
- 移行ルール: {RULES}
- 正解例(before/after): {GOLDEN_EXAMPLES}
- 対象ファイル: {FILE}

# 出力
- 移行後のコード全文
- 末尾に「自信のない箇所」を箇条書きで(無ければ「なし」)

プロンプト3:移行の差分をレビューさせる(ドリフト検知)

以下は、ある移行の before / after の差分です。
「意図しない振る舞いの変化(ドリフト)」が無いかをレビューしてください。

# 観点
- 振る舞いが変わっていないか(条件分岐・境界値・エラー処理)
- テストの検証内容が弱くなっていないか(空の assert 化、skip化)
- ルールの範囲外の変更が混ざっていないか
- 隠れた副作用(順序依存・非同期の待ち漏れ)はないか

# 出力
- リスク: High / Medium / Low
- 具体的な懸念点(該当行を引用して)
- 「人間が必ず目視すべき箇所」のリスト
※ 通す/戻すの最終判断は私がします。あなたは懸念の洗い出しに徹してください。

プロンプト4:失敗ファイルの原因を分類させる

移行パイプラインで「manual(人間送り)」になったファイル群のエラー要約です。
原因をグループに分類し、それぞれに「決定的codemodで吸収できそうか」を判定してください。

# 入力
- 失敗ファイルとエラー要約のリスト: {FAILURES}

# 出力(表)
| 原因グループ | 件数 | codemod化できる? | 次の一手 |
- 「同じ原因が大量にある」グループは、新しいcodemodルールを作る候補として強調してください。
- 個別対応が必要なものと、ルール化で一掃できるものを、はっきり分けてください。

このプロンプト4が地味に効きます。 「人間送りになった裾野」をもう一度分類すると、その中に“実は機械化できる新しいパターン”が眠っている ことがよくある。そこを見つけてcodemod化すると、人間の山がまた小さくなる。 codemod → LLM → 人間 → 分類して codemod に戻す 。このループが、移行を加速させます。


落とし穴6つと、撤退ライン

最後に、つまずきポイントを正直に共有しておきます。

# 落とし穴 どうなるか 対策
1 機械的な変換までLLMに丸投げ 差分が読めない・非決定・コスト爆発 ①は決定的codemodで確定させる
2 フォルダごとLLMに渡す 失敗の影響範囲が広がり、リトライも効かない per-file(1ファイル単位)に分割
3 テストの緑だけを信じる 骨抜きテストで「動くけど死んでる」 検証3点セット+高リスク差分は人間が目視
4 失敗ファイルを無理に自動で通す 静かに壊れたコードが本番へ 緑にならなければ元に戻し、人間送りに
5 不可逆操作を自動化 戻せない事故(DB/公開/デプロイ) 構造で人間承認ゲートを強制
6 既製レシピを探さず自作 車輪の再発明・品質も落ちる ast-grep/OpenRewriteの既製を先に探す

そして、 撤退ライン(やめどき) も先に決めておきます。移行は「やり切る」ことより「安全に止まれる」ことのほうが、ときどき大事なので。

  • 成功率が頭打ちなら、いったん止める。 リトライを増やしても緑率が上がらなくなったら、残りは人間の領域。AIを回し続けない。
  • 裾野が大きすぎたら、移行の範囲を切る。 全部を一度に移行しない。「よく触るファイルから」など、価値の高いところだけ先に。
  • 不可逆操作が絡む段階に来たら、必ず人間に戻す。 ここはスピードより安全。

人間とAIの役割分担表

最後に、今日の話を1枚にまとめます。

工程 人間(What / Why を握る) AI(How を担う)
方針決定 何を・なぜ移行するか、範囲を決める 候補と影響範囲の洗い出し
仕分け 3レイヤーの最終決定 機械的/LLM/人間の仕分け案を出す
機械的変換① codemodルールのレビュー (ここは決定的ツール。AIは補助でルール下書き)
裾野の変換② ゲート設計・正解例の用意 per-fileの書き換え+自己検証
検証 高リスク差分の目視・通す/戻すの判断 テスト・型・差分量の機械チェック
不可逆操作③ 承認(ここだけは絶対に握る) 提案まで(実行はしない)

きれいに言うと、 「何を・なぜ」は人間、「どう」はAI 。移行という大仕事でも、この線は変わりません。


おわりに:今日の検証ログは、明日の自分への「あざっす」

移行って、ずっと「気が重い作業」の代名詞でした。終わらないし、怖いし、誰もやりたがらない。

でも、ここまで見てきたように、移行は 「丸投げ or 手作業」の二択じゃない機械的な8割は決定的codemodで確定させて、曖昧な裾野はLLMに検証つきで任せて、不可逆と判断だけ人間が握る。 こう仕分けるだけで、1.5年の作業が6週間になる世界線が、ちゃんとあるんですよね。

そして、移行の最中に積み上げた 検証ゲート・移行台帳・codemodルール は、消えてなくなりません。それは次の移行でも使えるし、チームの誰かが引き継ぐときの地図になる。 今日きれいに残した検証ログは、半年後の自分が「あの時の自分、あざっす」って言える置き手紙 なんです。これは、過去の自分を責める軸じゃなくて、未来の自分を思いやる軸。

ここがすごく好きなところで。 移行をやり切る力、仕分ける設計力って、AIに奪われないんですよ。 むしろAIが強くなるほど、「どこを機械に、どこを人間に切るか」を決められる人の価値が上がる。コードを書く速さじゃなくて、 作り替える判断の質 。それは積み上がる資産(Code as Capital)だと思います。

最後に、明日からの小さな4ステップを置いておきますね。

  1. いま後回しにしている移行を1つ選び、 その中の「機械的な8割」を見つける
  2. その8割を、 ast-grepで「検索だけ」してみる(まだ書き換えない)
  3. ルールを1個書いて、 1ファイルだけ 変換 → テストが緑か確認する
  4. 残った「裾野」を、 per-fileでLLMに渡す前に、不可逆操作が混ざってないかだけ確認する

全部を一気にやらなくていいです。1ファイルから。 小さく仕分けて、検証して、戻れるようにして、また少し進む。 その積み重ねが、いつのまにか「3千ファイルの移行、終わったわ」に変わっています。

今日もここまで読んでくれて、あざっす。あなたの移行が、静かな事故じゃなくて、静かな前進になりますように。


参考にした一次情報

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?