Devin の提案がビルドを壊す時の対処法:差分最小化・二分探索・自動ロールバック
AI エージェント「Devin」が自動で提案するコード変更は便利ですが、時としてビルドエラーを引き起こしてしまうこともあります。そんな時、どこに問題があるのかを見つけ、効率よく修正するための手法を本記事でご紹介しましょう。 差分最小化、二分探索、自動ロールバックという 3 つのアプローチを使えば、Devin の提案を安全に取り込みながら、ビルドの破損を最小限に抑えられます。
背景
AI エージェント Devin とは
Devin は、開発者の作業を支援する AI エージェントとして注目を集めています。コードレビュー、リファクタリング、新機能の実装提案など、多岐にわたるタスクを自動で行ってくれるため、生産性向上に大きく貢献してくれるでしょう。
しかし、Devin が生成するコードは完璧ではありません。依存関係の考慮漏れ、型定義の不一致、環境変数の誤設定など、さまざまな理由でビルドエラーが発生する可能性があります。
ビルドエラーが起こりやすい理由
Devin のようなコード生成 AI は、以下の理由でビルドを壊してしまうことがあります。
- 依存関係の把握不足: プロジェクト全体の依存関係を完全に理解していない場合がある
- 型システムの複雑性: TypeScript などの型システムを正確に追えない
- 環境依存の設定: ローカル環境と本番環境の差異を考慮できていない
- 複数ファイルの同時変更: 変更箇所が多すぎて、整合性が取れなくなる
以下の図は、AI エージェントによるコード提案から、ビルドエラー発生までの典型的な流れを示しています。
mermaidflowchart TD
dev["開発者"] -->|タスク依頼| ai["AI エージェント<br/>Devin"]
ai -->|コード生成| files["複数ファイル変更"]
files -->|依存関係の<br/>考慮漏れ| error1["型エラー"]
files -->|環境変数の<br/>設定ミス| error2["実行時エラー"]
files -->|パッケージ<br/>バージョン不一致| error3["ビルドエラー"]
error1 --> fail["ビルド失敗"]
error2 --> fail
error3 --> fail
このように、AI による提案が複数ファイルに及ぶ場合、どこに問題があるのかを特定するのは容易ではありません。
課題
ビルドエラーの原因特定が困難
Devin が複数のファイルを同時に変更した場合、どのファイルのどの部分がビルドエラーの原因なのかを特定するのは非常に困難です。エラーメッセージが複数出力されることもあり、本質的な原因を見極めるには時間がかかります。
手作業でのロールバックは非効率
問題が起きたときに全変更を手作業で巻き戻すのは、時間がかかるだけでなく、ミスも発生しやすくなります。特に、Git の操作に不慣れな開発者にとっては、大きな負担となるでしょう。
複数の変更が絡み合う問題
以下の表は、Devin の提案によるビルドエラーでよく見られる問題パターンをまとめたものです。
| # | 問題パターン | 発生原因 | 影響範囲 |
|---|---|---|---|
| 1 | 型定義の不一致 | インターフェース変更の反映漏れ | 複数ファイル |
| 2 | 依存関係の破損 | パッケージバージョンの不整合 | プロジェクト全体 |
| 3 | 環境変数の誤設定 | .env ファイルの更新漏れ | 実行時エラー |
| 4 | インポートパスの誤り | ファイル移動時のパス更新漏れ | コンパイルエラー |
| 5 | テストの失敗 | モックやスタブの更新漏れ | CI/CD パイプライン |
これらの問題は、単独で発生することもあれば、複数が同時に発生することもあります。
下図は、複数の変更が絡み合ってビルドエラーを引き起こす様子を示しています。
mermaidflowchart LR
change1["ファイル A<br/>型定義変更"] -->|影響| file_b["ファイル B"]
change2["ファイル C<br/>関数追加"] -->|影響| file_b
change3["ファイル D<br/>インポート変更"] -->|影響| file_b
file_b -->|型エラー| build_fail["ビルド失敗"]
style build_fail fill:#ff6b6b
このように、複数の変更が同時に行われると、問題の切り分けが難しくなります。
解決策
ビルドエラーの原因を効率よく特定し、修正するためには、以下の 3 つのアプローチが有効です。それぞれの手法を順番に見ていきましょう。
1. 差分最小化アプローチ
差分最小化とは、変更を小さな単位に分割し、一つずつ適用していく手法です。これにより、どの変更がビルドエラーを引き起こしているのかを特定しやすくなります。
以下の図は、差分最小化のプロセスを示しています。
mermaidflowchart TD
all["全変更を取得"] --> split["変更を小単位に<br/>分割"]
split --> apply1["変更 1 を適用"]
apply1 --> test1{ビルド<br/>成功?}
test1 -->|Yes| apply2["変更 2 を適用"]
test1 -->|No| fix1["変更 1 を修正"]
fix1 --> apply1
apply2 --> test2{ビルド<br/>成功?}
test2 -->|Yes| apply3["変更 3 を適用"]
test2 -->|No| fix2["変更 2 を修正"]
fix2 --> apply2
apply3 --> done["完了"]
style done fill:#51cf66
この手法により、問題のある変更を早期に発見でき、修正も容易になります。
変更を小単位に分割する方法
Git のコミット履歴や、Devin が提供する差分情報を活用して、変更を以下のような単位に分割します。
- ファイル単位
- 関数単位
- 機能単位(例:認証機能、データ取得機能など)
Git を使った差分の適用
以下は、Git を使って変更を段階的に適用する手順です。
変更の一時的な退避
bash# 現在の変更を全てスタッシュに退避
git stash
これで、作業ディレクトリがクリーンな状態になります。
変更の一部を適用
bash# スタッシュから特定のファイルだけを復元
git checkout stash@{0} -- path/to/file.ts
ファイルパスを指定することで、必要な変更だけを取り出せます。
ビルドテストの実行
bash# ビルドが成功するか確認
yarn build
ビルドが成功すれば、その変更は問題ないと判断できます。
変更のコミット
bash# 問題なければコミット
git add path/to/file.ts
git commit -m "feat: ファイル A の変更を適用"
この手順を繰り返すことで、安全に変更を取り込んでいけるでしょう。
2. 二分探索アプローチ
変更が多い場合、差分最小化だけでは時間がかかりすぎることがあります。そんな時に有効なのが、二分探索アプローチです。
二分探索は、変更を半分ずつ適用していき、問題のある範囲を効率よく絞り込む手法となります。
二分探索の仕組み
以下の図は、二分探索で問題の変更を特定するプロセスを示しています。
mermaidflowchart TD
start["全変更<br/>100 件"] --> half1["前半 50 件を<br/>適用"]
half1 --> test1{ビルド<br/>成功?}
test1 -->|Yes| half2["後半 50 件を<br/>適用"]
test1 -->|No| quarter1["前半の前半<br/>25 件を適用"]
quarter1 --> test2{ビルド<br/>成功?}
test2 -->|Yes| quarter2["前半の後半<br/>25 件を適用"]
test2 -->|No| eighth1["12 件に絞り込み"]
eighth1 --> found["問題の変更を<br/>特定"]
style found fill:#51cf66
この手法により、100 件の変更から問題を特定する場合でも、最大 7 回程度のテストで原因を見つけられます。
Git bisect を使った実装
Git には、二分探索を自動で行う git bisect というコマンドがあります。ここでは、ビルドエラーの原因特定に応用する方法をご紹介しましょう。
bisect セッションの開始
bash# 二分探索を開始
git bisect start
# 現在のコミット(エラーがある)をマーク
git bisect bad
# エラーが発生していなかった過去のコミットをマーク
git bisect good <commit-hash>
これで Git が自動的に中間のコミットをチェックアウトします。
ビルドテストの実行と結果の報告
bash# ビルドを実行
yarn build
# 成功した場合
git bisect good
# 失敗した場合
git bisect bad
この操作を繰り返すことで、Git が自動的に問題のあるコミットを特定してくれるでしょう。
bisect セッションの終了
bash# 問題のコミットが特定されたら終了
git bisect reset
これで元のブランチの状態に戻ります。
自動化スクリプトの作成
bisect プロセスをさらに効率化するために、ビルドテストを自動化できます。
ビルドテストスクリプトの作成
typescript// scripts/build-test.ts
import { exec } from 'child_process';
import { promisify } from 'util';
const execAsync = promisify(exec);
まず、必要なモジュールをインポートします。exec を使ってシェルコマンドを実行し、promisify で Promise 化することで、async/await が使えるようになります。
ビルド実行関数の定義
typescriptasync function runBuildTest(): Promise<boolean> {
try {
// ビルドコマンドを実行
const { stdout, stderr } = await execAsync(
'yarn build'
);
console.log('ビルド成功:', stdout);
return true;
} catch (error) {
console.error('ビルド失敗:', error);
return false;
}
}
この関数は、ビルドが成功すれば true、失敗すれば false を返します。
終了コードの設定
typescriptasync function main() {
const isSuccess = await runBuildTest();
// 終了コード: 0 = 成功、1 = 失敗
process.exit(isSuccess ? 0 : 1);
}
main();
process.exit() で終了コードを設定することで、Git bisect がビルドの成否を判断できるようになります。
自動 bisect の実行
bash# スクリプトを使った自動 bisect
git bisect start
git bisect bad
git bisect good <commit-hash>
git bisect run node scripts/build-test.ts
これで、Git が自動的にビルドテストを繰り返し、問題のあるコミットを特定してくれます。
3. 自動ロールバック機能の実装
ビルドエラーが発生した際に、自動的に安全な状態に戻る仕組みを作っておくと、開発の効率が大幅に向上します。
以下の図は、自動ロールバックのフローを示しています。
mermaidflowchart TD
start["変更適用"] --> backup["現在の状態を<br/>バックアップ"]
backup --> apply["新しい変更を<br/>適用"]
apply --> build{ビルド<br/>テスト}
build -->|成功| commit["変更をコミット"]
build -->|失敗| rollback["自動ロールバック"]
rollback --> restore["バックアップから<br/>復元"]
restore --> notify["開発者に通知"]
commit --> done["完了"]
style done fill:#51cf66
style rollback fill:#ffd43b
この仕組みにより、ビルドが壊れた状態で作業を続けるリスクを減らせます。
ロールバックスクリプトの設計
自動ロールバック機能を実装するために、以下の要素が必要です。
| # | 機能 | 説明 | 実装方法 |
|---|---|---|---|
| 1 | 状態のバックアップ | 変更前の状態を保存 | Git stash / ブランチ作成 |
| 2 | ビルドテスト | 変更後のビルド確認 | yarn build / npm run test |
| 3 | ロールバック処理 | 失敗時の復元 | Git reset / stash pop |
| 4 | 通知機能 | 開発者への報告 | コンソール出力 / Slack 通知 |
| 5 | ログ記録 | エラー情報の保存 | ファイル出力 / データベース |
これらの機能を組み合わせて、堅牢なロールバックシステムを構築しましょう。
TypeScript による実装例
以下は、自動ロールバック機能の実装例です。
必要なモジュールのインポート
typescript// scripts/auto-rollback.ts
import { exec } from 'child_process';
import { promisify } from 'util';
import * as fs from 'fs/promises';
import * as path from 'path';
const execAsync = promisify(exec);
ファイルシステム操作のために fs/promises も追加しています。
ログ記録関数の定義
typescriptasync function logError(
message: string,
error: any
): Promise<void> {
const timestamp = new Date().toISOString();
const logDir = path.join(process.cwd(), 'logs');
const logFile = path.join(logDir, 'rollback.log');
// ログディレクトリが存在しない場合は作成
await fs.mkdir(logDir, { recursive: true });
// ログメッセージの作成
const logMessage = `[${timestamp}] ${message}\n${JSON.stringify(
error,
null,
2
)}\n\n`;
// ログファイルに追記
await fs.appendFile(logFile, logMessage);
}
エラー情報をタイムスタンプ付きで記録することで、後から原因を調査しやすくなります。
バックアップ作成関数
typescriptasync function createBackup(): Promise<string> {
try {
// ユニークなバックアップ名を生成
const timestamp = Date.now();
const backupBranch = `backup/auto-${timestamp}`;
// 現在の変更を一時的にコミット
await execAsync('git add .');
await execAsync(
`git commit -m "一時保存: ${timestamp}"`
);
// バックアップブランチを作成
await execAsync(`git branch ${backupBranch}`);
console.log(
`✓ バックアップを作成しました: ${backupBranch}`
);
return backupBranch;
} catch (error) {
await logError('バックアップ作成に失敗', error);
throw error;
}
}
バックアップブランチを作成することで、いつでも安全な状態に戻れるようにします。
ビルドテスト実行関数
typescriptasync function runBuildTest(): Promise<boolean> {
try {
console.log('ビルドテストを実行中...');
// 依存関係のインストール
await execAsync('yarn install --frozen-lockfile');
// ビルドの実行
await execAsync('yarn build');
// テストの実行(オプション)
await execAsync('yarn test');
console.log('✓ ビルドテストが成功しました');
return true;
} catch (error) {
console.error('✗ ビルドテストが失敗しました');
await logError('ビルドテスト失敗', error);
return false;
}
}
ビルドだけでなく、テストも実行することで、より確実な検証ができます。
ロールバック実行関数
typescriptasync function performRollback(
backupBranch: string
): Promise<void> {
try {
console.log(`ロールバックを実行中: ${backupBranch}`);
// バックアップブランチに切り替え
await execAsync(`git checkout ${backupBranch}`);
// 元のブランチに戻る
await execAsync('git checkout -');
// バックアップの内容で上書き
await execAsync(`git reset --hard ${backupBranch}`);
console.log('✓ ロールバックが完了しました');
} catch (error) {
await logError('ロールバック失敗', error);
throw error;
}
}
Git の機能を使って、安全かつ確実にロールバックを実行します。
メイン処理の実装
typescriptasync function main() {
let backupBranch: string | null = null;
try {
// 1. バックアップを作成
backupBranch = await createBackup();
// 2. ビルドテストを実行
const buildSuccess = await runBuildTest();
// 3. 結果に応じて処理を分岐
if (buildSuccess) {
console.log('✓ すべてのテストが成功しました');
console.log(
`バックアップブランチ ${backupBranch} は不要なら削除できます`
);
process.exit(0);
} else {
console.error(
'✗ ビルドテストが失敗したため、ロールバックします'
);
await performRollback(backupBranch);
process.exit(1);
}
} catch (error) {
console.error('予期しないエラーが発生しました:', error);
// バックアップがあればロールバックを試みる
if (backupBranch) {
try {
await performRollback(backupBranch);
} catch (rollbackError) {
console.error(
'ロールバックも失敗しました:',
rollbackError
);
}
}
process.exit(1);
}
}
main();
エラーハンドリングを含めた完全な自動ロールバック処理です。予期しないエラーが発生した場合でも、可能な限りロールバックを試みます。
実行方法
作成したスクリプトは、以下のように実行します。
スクリプトのビルド
bash# TypeScript をコンパイル
yarn tsc scripts/auto-rollback.ts --outDir scripts/dist
TypeScript ファイルを JavaScript にコンパイルします。
スクリプトの実行
bash# ロールバック機能付きでビルドテスト
node scripts/dist/auto-rollback.js
これで、ビルドエラーが発生した場合に自動的にロールバックされます。
package.json への統合
json{
"scripts": {
"safe-build": "node scripts/dist/auto-rollback.js",
"build": "next build",
"test": "jest"
}
}
npm scripts に登録しておくことで、簡単に実行できるようになります。
bash# safe-build コマンドで安全なビルドを実行
yarn safe-build
このコマンド一つで、バックアップ、ビルド、必要に応じたロールバックが自動的に行われます。
具体例
ここでは、実際のプロジェクトで Devin の提案がビルドを壊してしまった場合の対処例を見ていきましょう。
シナリオ: Devin による大規模リファクタリング
あるプロジェクトで、Devin に「認証機能を最新のライブラリに移行する」というタスクを依頼しました。Devin は以下の変更を提案しています。
auth.ts: 認証ロジックの更新(50 行)middleware.ts: ミドルウェアの変更(30 行)user.interface.ts: ユーザー型定義の更新(20 行)login.tsx: ログイン画面の修正(40 行)package.json: 依存関係の更新
これら全ての変更を一度に適用したところ、ビルドエラーが多数発生してしまいました。
以下の図は、この問題を解決するプロセスを示しています。
mermaidflowchart TD
error["ビルドエラー<br/>発生"] --> analyze["エラーログを<br/>確認"]
analyze --> strategy{解決<br/>戦略}
strategy -->|変更が少ない| minimal["差分最小化<br/>アプローチ"]
strategy -->|変更が多い| bisect["二分探索<br/>アプローチ"]
minimal --> apply_one["1 ファイルずつ<br/>適用"]
bisect --> apply_half["半分ずつ<br/>適用"]
apply_one --> test1{テスト}
apply_half --> test2{テスト}
test1 -->|失敗| fix1["修正"]
test1 -->|成功| next1["次へ"]
test2 -->|失敗| narrow["範囲を<br/>絞り込み"]
test2 -->|成功| next2["次へ"]
fix1 --> apply_one
narrow --> apply_half
next1 --> complete["完了"]
next2 --> complete
style complete fill:#51cf66
ステップ 1: エラーログの確認
まず、ビルドエラーの内容を確認します。
bash# ビルドを実行してエラーを確認
yarn build
以下のようなエラーが出力されました。
typescriptError: TS2339: Property 'userId' does not exist on type 'User'.
Error: TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
Error: Module '"next-auth"' has no exported member 'SessionProvider'.
複数の型エラーとインポートエラーが発生しています。
ステップ 2: 差分最小化アプローチを適用
変更ファイルが 5 つと比較的少ないため、差分最小化アプローチを選択します。
すべての変更を一旦退避
bash# 現在の変更をスタッシュに保存
git stash save "Devin の認証機能リファクタリング提案"
これで、作業ディレクトリがクリーンな状態に戻りました。
型定義から段階的に適用
依存関係を考慮して、型定義から適用していきます。
bash# ステップ 1: 型定義の変更を適用
git checkout stash@{0} -- src/types/user.interface.ts
# ビルドテスト
yarn build
この段階でビルドが成功することを確認します。
bash# ステップ 2: 依存関係の更新
git checkout stash@{0} -- package.json
yarn install
# ビルドテスト
yarn build
ここでエラーが発生しました。package.json の変更に問題があることが判明しました。
ステップ 3: 問題の修正
package.json を確認すると、next-auth のバージョンが互換性のないものに更新されていました。
問題のある変更を修正
json{
"dependencies": {
"next-auth": "^4.24.5"
}
}
Devin の提案では ^5.0.0 になっていましたが、プロジェクトの他の部分がまだ v4 に依存しているため、バージョンを戻します。
bash# 修正後、再度インストール
yarn install
# ビルドテスト
yarn build
今度はビルドが成功しました。
ステップ 4: 残りの変更を適用
bash# ステップ 3: 認証ロジックの変更
git checkout stash@{0} -- src/lib/auth.ts
yarn build # 成功を確認
# ステップ 4: ミドルウェアの変更
git checkout stash@{0} -- src/middleware.ts
yarn build # 成功を確認
# ステップ 5: UI コンポーネントの変更
git checkout stash@{0} -- src/components/login.tsx
yarn build # 成功を確認
すべての変更を段階的に適用し、それぞれのステップでビルドが成功することを確認できました。
ステップ 5: 最終確認とコミット
bash# すべてのテストを実行
yarn test
# 問題なければコミット
git add .
git commit -m "feat: 認証機能を next-auth v4 対応にリファクタリング"
このように、差分最小化アプローチを使うことで、問題のある変更を素早く特定し、修正できました。
結果の比較
以下の表は、従来の方法と本記事で紹介した手法の比較です。
| # | 項目 | 従来の方法 | 差分最小化 | 二分探索 | 自動ロールバック |
|---|---|---|---|---|---|
| 1 | 問題特定時間 | 2-3 時間 | 30-60 分 | 15-30 分 | 自動(数分) |
| 2 | 修正の確実性 | ★★☆☆☆ | ★★★★☆ | ★★★★★ | ★★★★★ |
| 3 | 学習コスト | 低 | 中 | 中 | 高 |
| 4 | 自動化可能性 | なし | 部分的 | 可能 | 完全 |
| 5 | チーム適用性 | 個人依存 | 標準化可能 | 標準化可能 | 標準化可能 |
これらの手法を組み合わせることで、Devin のようなコード生成 AI を安全に活用できるようになります。
まとめ
Devin が提案するコード変更でビルドが壊れてしまった場合でも、適切な手法を使えば効率よく問題を解決できます。本記事で紹介した 3 つのアプローチを振り返りましょう。
差分最小化アプローチは、変更を小さな単位に分割して段階的に適用する手法です。変更が比較的少ない場合や、依存関係が明確な場合に効果的でしょう。Git のスタッシュ機能を活用することで、安全に変更を取り込めます。
二分探索アプローチは、変更が多い場合に威力を発揮します。git bisect を使えば、数十から数百の変更の中から問題のあるコミットを効率よく特定できるでしょう。自動化スクリプトと組み合わせることで、さらに効率が向上します。
自動ロールバック機能は、ビルドエラーが発生した際に自動的に安全な状態に戻す仕組みです。バックアップ作成、ビルドテスト、ロールバック処理を自動化することで、開発者の負担を大幅に軽減できます。
これらの手法を組み合わせることで、AI エージェントの提案を安全かつ効率的に取り込めるようになります。最初は差分最小化から始めて、慣れてきたら二分探索や自動ロールバックにも挑戦してみてください。
AI との協働開発は、適切なツールと手法を使うことで、より安全で生産的なものになるでしょう。ぜひ、今回紹介した方法を実践してみてください。
関連リンク
articleDevin の提案がビルドを壊す時の対処法:差分最小化・二分探索・自動ロールバック
articleDevin でレガシーコードの見える化:メトリクス抽出と負債リスト自動作成
articleDevin で既存バグを最短修正:再現 → 原因特定 → 最小修正 → 回帰テストの一連プロンプト
articleDevin と進めるドメイン駆動設計:ユビキタス言語を反映させるプロンプト構成
articleDevin をチームに導入する前に整える開発規約:コーディング規約・レビュー基準・命名
articleCline vs Devin vs Cursor 実務比較:要件理解・差分精度・保守コスト
articleShell Script とは?初心者が最短で理解する基本構文・実行モデル・活用領域
articleNode.js 本番メモリ運用:ヒープ/外部メモリ/リーク検知の継続監視
articleReact とは? 2025 年版の特徴・強み・実務活用を一気に理解する完全解説
articleNext.js でインフィニットスクロールを実装:Route Handlers +`use` で滑らかデータ読込
articleDocker を用いた統一ローカル環境:新人オンボーディングを 1 日 → 1 時間へ
articleMermaid でデータ基盤のラインジを図解:ETL/DAG/品質チェックの全体像
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来