Upgrade to Pro — share decks privately, control downloads, hide ads and more …

生成AIを活用したリファクタリング実践 ~コードスメルをなくすためのアプローチ

Avatar for raedion raedion
November 15, 2025

生成AIを活用したリファクタリング実践 ~コードスメルをなくすためのアプローチ

近年、生成AIや大規模言語モデル(LLM)によるコード生成技術が急速に発展しており、エンジニアの役割は「コードを書く」から「生成されたコードの品質をレビューして改善する」へと変化しつつあります。
レビューの観点として、仕様通りであることはもちろん、そのコードの品質を確認する必要があります。そのためには、生成されたコードの品質を評価する基準が必要です。
そこで、本セッションでは、ソフトウェア品質の指標としてマーティン・ファウラー氏の「コードスメル」概念を紹介し、「良いソースコード」の定義を例示します。
さらに、最新のLLMが生成したコードを対象に調査・分析し、人間が行うべきレビューやリファクタリングの必要性を検証します。

Avatar for raedion

raedion

November 15, 2025
Tweet

More Decks by raedion

Other Decks in Programming

Transcript

  1. 実際にAIが生成したソースコードの一部。 
 各機能が単一メソッドに集中しており処理の読み解きが困難 
 // Collisions // Player bullets vs

    enemies bit = playerBullets.iterator(); while (bit.hasNext()) { Bullet b = bit.next(); eit = enemies.iterator(); while (eit.hasNext()) { Enemy en = eit.next(); if (b.getBounds().intersects(en.getBounds())) { en.hp -= b.damage; bit.remove(); if (en.hp <= 0) { score += 10; if (rand.nextDouble() < 0.3) { items.add( new Item(en.x, en.y, rand.nextInt( 3))); } eit.remove(); } break; } } } // Missiles vs ground enemies mit = missiles.iterator(); while (mit.hasNext()) { Missile m = mit.next(); eit = enemies.iterator(); while (eit.hasNext()) { Enemy en = eit.next(); if (en.isGround && m.getBounds().intersects(en.getBounds())) { en.hp -= m.damage; mit.remove(); if (en.hp <= 0) { score += 20; eit.remove(); } break; } } } @Override public void actionPerformed( ActionEvent e) { if (gameOver) return ; frame++; // Update background background.update( 2); // Spawn enemies if (frame % 120 == 0) { int type = rand.nextInt( 3); boolean ground = type == 2; enemies.add( new Enemy(WIDTH, ground ? 480 : rand.nextInt( 400) + 50, ground, type)); } // Update enemies Iterator <Enemy> eit = enemies.iterator(); while (eit.hasNext()) { Enemy en = eit.next(); en.update(); en.shoot(enemyBullets); if (en.x < - 50) eit.remove(); } // Update bullets Iterator <Bullet > bit = playerBullets.iterator(); while (bit.hasNext()) { Bullet b = bit.next(); b.update(); if (b.x > WIDTH) bit.remove(); } Iterator <Bullet > ebit = enemyBullets.iterator(); while (ebit.hasNext()) { Bullet b = ebit.next(); b.update(); if (b.x < 0) ebit.remove(); } // Update missiles Iterator <Missile > mit = missiles.iterator(); while (mit.hasNext()) { Missile m = mit.next(); m.update(enemies); if (m.x > WIDTH) mit.remove(); } // Update items Iterator <Item> iit = items.iterator(); while (iit.hasNext()) { Item i = iit.next(); i.update(); if (i.x < - 50) iit.remove(); } // Update options for (Option o : options) o.update(); // Player movement double accel = 1.0; if (left) player.x -= accel; if (right) player.x += accel; if (up) player.y -= accel; if (down) player.y += accel; if (player.x < 20) player.x = 20; if (player.x > WIDTH - 20) player.x = WIDTH - 20; if (player.y < 20) player.y = 20; if (player.y > HEIGHT - 20) player.y = HEIGHT - 20; // Shooting if (shoot && frame % 10 == 0) { playerBullets.add( new Bullet(player.x + 16, player.y, 5, 0)); for (Option o : options) o.shoot(playerBullets); } if (player.hasMissile && frame % 60 == 0) { missiles.add( new Missile(player.x + 16, player.y)); } // Player vs items iit = items.iterator(); while (iit.hasNext()) { Item i = iit.next(); if (player.getBounds().intersects(i.getBounds())) { if (i.type == 0) player.hasMissile = true; else if (i.type == 1 && options.size() < 2) options.add( new Option(player)); else if (i.type == 2) player.shotLevel = Math.min( 3, player.shotLevel + 1); iit.remove(); } } // Player vs enemies/bullets for (Enemy en : enemies) { if (player.getBounds().intersects(en.getBounds())) { player.lives--; if (player.lives <= 0) gameOver = true; else { player.x = 100; player.y = HEIGHT / 2; } break; } } for (Bullet b : enemyBullets) { if (player.getBounds().intersects(b.getBounds())) { player.lives--; if (player.lives <= 0) gameOver = true; else { player.x = 100; player.y = HEIGHT / 2; } enemyBullets.remove(b); break; } } repaint(); } 10
  2. // Collisions // Player bullets vs enemies bit = playerBullets.iterator();

    while (bit.hasNext()) { Bullet b = bit.next(); eit = enemies.iterator(); while (eit.hasNext()) { Enemy en = eit.next(); if (b.getBounds().intersects(en.getBounds())) { en.hp -= b.damage; bit.remove(); if (en.hp <= 0) { score += 10; if (rand.nextDouble() < 0.3) { items.add( new Item(en.x, en.y, rand.nextInt( 3))); } eit.remove(); } break; } } } // Missiles vs ground enemies mit = missiles.iterator(); while (mit.hasNext()) { Missile m = mit.next(); eit = enemies.iterator(); while (eit.hasNext()) { Enemy en = eit.next(); if (en.isGround && m.getBounds().intersects(en.getBounds())) { en.hp -= m.damage; mit.remove(); if (en.hp <= 0) { score += 20; eit.remove(); } break; } } } @Override public void actionPerformed( ActionEvent e) { if (gameOver) return ; frame++; // Update background background.update( 2); // Spawn enemies if (frame % 120 == 0) { int type = rand.nextInt( 3); boolean ground = type == 2; enemies.add( new Enemy(WIDTH, ground ? 480 : rand.nextInt( 400) + 50, ground, type)); } // Update enemies Iterator <Enemy> eit = enemies.iterator(); while (eit.hasNext()) { Enemy en = eit.next(); en.update(); en.shoot(enemyBullets); if (en.x < - 50) eit.remove(); } // Update bullets Iterator <Bullet > bit = playerBullets.iterator(); while (bit.hasNext()) { Bullet b = bit.next(); b.update(); if (b.x > WIDTH) bit.remove(); } Iterator <Bullet > ebit = enemyBullets.iterator(); while (ebit.hasNext()) { Bullet b = ebit.next(); b.update(); if (b.x < 0) ebit.remove(); } // Update missiles Iterator <Missile > mit = missiles.iterator(); while (mit.hasNext()) { Missile m = mit.next(); m.update(enemies); if (m.x > WIDTH) mit.remove(); } // Update items Iterator <Item> iit = items.iterator(); while (iit.hasNext()) { Item i = iit.next(); i.update(); if (i.x < - 50) iit.remove(); } // Update options for (Option o : options) o.update(); // Player movement double accel = 1.0; if (left) player.x -= accel; if (right) player.x += accel; if (up) player.y -= accel; if (down) player.y += accel; if (player.x < 20) player.x = 20; if (player.x > WIDTH - 20) player.x = WIDTH - 20; if (player.y < 20) player.y = 20; if (player.y > HEIGHT - 20) player.y = HEIGHT - 20; // Shooting if (shoot && frame % 10 == 0) { playerBullets.add( new Bullet(player.x + 16, player.y, 5, 0)); for (Option o : options) o.shoot(playerBullets); } if (player.hasMissile && frame % 60 == 0) { missiles.add( new Missile(player.x + 16, player.y)); } // Player vs items iit = items.iterator(); while (iit.hasNext()) { Item i = iit.next(); if (player.getBounds().intersects(i.getBounds())) { if (i.type == 0) player.hasMissile = true; else if (i.type == 1 && options.size() < 2) options.add( new Option(player)); else if (i.type == 2) player.shotLevel = Math.min( 3, player.shotLevel + 1); iit.remove(); } } // Player vs enemies/bullets for (Enemy en : enemies) { if (player.getBounds().intersects(en.getBounds())) { player.lives--; if (player.lives <= 0) gameOver = true; else { player.x = 100; player.y = HEIGHT / 2; } break; } } for (Bullet b : enemyBullets) { if (player.getBounds().intersects(b.getBounds())) { player.lives--; if (player.lives <= 0) gameOver = true; else { player.x = 100; player.y = HEIGHT / 2; } enemyBullets.remove(b); break; } } repaint(); } 実際にAIが生成したソースコードの一部。 
 各機能が単一メソッドに集中しており処理の読み解きが困難 
 1つのメソッドが長すぎる! 理解しづらい! 11
  3. リファクタリング:外部から見た振る舞いを変えずに 
 ソフトウェアの内部構造を改善する開発プロセス 
 リファクタリングの要否を判断する定量的指標として「コードスメル」 がある。 核心となる原則 小さな変換 (Small Transformation)

    リファクタリングは、一つ一つがごく小さな変更である。 振る舞いの維持 (Behavior Preserving) 外部の機能は一切変えない (システムは常に動作し続ける)。 達成される メリット 間違いの軽減 個々の変更が小さいため、問題が発生する可能性が低い。 リスクの低減 常に動作する状態を保つため、構造変更によるシステム停止・破壊の リスクを減らせる。 13
  4. コードスメル:システム中の何となくまずい気がする表面的 な兆候
 コードスメル自体はバグではないが、保守性の低さや拡張性の欠如、技術的負 債になる可能性のある問題 であるため是正するのが望ましい。 https://martinfowler.com/bliki/CodeSmell.html 主な特徴 1. 嗅ぎ分けられる 🔸

    すぐに気づけること が定義の一部(例:長すぎるメソッド) 2. 必ずしも問題ではない 🔸 臭いがあるからといって必ずしも悪いわけではない (例:問題のな い長いメソッドもある)。 🔸 スメルは問題そのものではなく潜在的な問題のインジケータ である ことが多い。 14
  5. コードスメルの例 
 g.setColor(Color.RED); g.setFont(new Font("Arial", Font.BOLD, 48)); g.drawString("GAME OVER", WIDTH

    / 2 - 150, HEIGHT / 2); g.setFont(new Font("Arial", Font.PLAIN, 24)); g.drawString("Press R to restart" , WIDTH / 2 - 100, HEIGHT / 2 + 50); // アプリで利用するフォント private final String USING_FONT = "Arial" // リファクタリング … g.setColor(Color.RED); g.setFont(new Font(USING_FONT, Font.BOLD, 48)); g.drawString("GAME OVER", WIDTH / 2 - 150, HEIGHT / 2); g.setFont(new Font(USING_FONT, Font.PLAIN, 24)); g.drawString("Press R to restart" , WIDTH / 2 - 100, HEIGHT / 2 + 50); コードスメルに対応するリファクタリングを実施 15
  6. パターン化されたリファクタリング処理を開発工程に組み込む。 アーキテクチャ概要 
 AIエージェント 生成 ソースコード 解析ツール AIエージェント コードスメル 情報

    レビュアー Git リポジトリ リファクタリング済 ソースコード プロンプトに 基づいて コード生成 push 解析結果を 出力 REST API リファクタリング 17
  7. 検証のために生成AIでアプリを作成 
 • JavaのSwingベースのゲームアプリを生成AIで作成 ◦ 開発に用いたAIモデルは Grok Code Fast 1*

    * https://x.ai/news/grok-code-fast-1 ステップ数 クラス数 概要 レーシングゲーム 436 4 2Dでキーボード操作 2Dアクションゲーム 619 7 ステージを進んでアイテム収集 したり敵を倒したりする シューティングゲーム 2,728 10 インベーダーゲームのように NPCと戦う 19
  8. 生成 ソースコード アーキテクチャ概要 
 AIエージェント AIエージェント コードスメル 情報 レビュアー Git

    リポジトリ リファクタリング 済ソースコード 解析ツール プロンプトに 基づいて コード生成 push 解析結果を 出力 REST API リファクタリング 20
  9. 生成 ソースコード アーキテクチャ概要 | プロンプトを与えてコード生成 
 AIエージェント AIエージェント コードスメル 情報

    レビュアー Git リポジトリ リファクタリング 済ソースコード 解析ツール プロンプトに 基づいて コード生成 push 解析結果を 出力 REST API リファクタリング 21
  10. 要件: - 環境:Java 17、Swingベース(外部ゲームエンジンは使用し ない)、Maven - 視点:真上(トップダウン) - 操作:キーボード(←→で旋回、↑で加速、↓でブレーキ/後退) ...(略)...

    納品物: - runnable.jar - ソース(リポジトリ構成) - README(実行・ビルド手順、操作説明) アーキテクチャ概要 | プロンプトを与えてコード生成 
 AIエージェント 生成 ソースコード プロンプトの抜粋 22
  11. 生成 ソースコード アーキテクチャ概要 | コードスメルの情報を取得 
 AIエージェント AIエージェント コードスメル 情報

    レビュアー Git リポジトリ リファクタリング 済ソースコード 解析ツール プロンプトに 基づいて コード生成 push 解析結果を 出力 REST API リファクタリング 23
  12. アーキテクチャ概要 | コードスメルの情報を取得 
 コードスメル 情報 解析ツール { "component": "racing:src/main/java/com/example/Car.java",

    "message": "Declare \"y\" on a separate line.", "project": "racing", "textRange": { "startLine": 6, "endLine": 6, "startOffset": 22, "endOffset": 23 }, "type": "CODE_SMELL", }, 出力されるJSONデータ(抜粋) REST APIで 取得 コードスメルの 発生箇所や 具体的な内容を 取得可能 24
  13. アーキテクチャ概要 | AIがリファクタリングを実行 
 AIエージェント コードスメル 情報 レビュアー Git リポジトリ

    リファクタリング 済ソースコード 解析ツール push 解析結果を 出力 REST API リファクタリング 生成 ソースコード AIエージェント プロンプトに 基づいて コード生成 push 26
  14. アーキテクチャ概要 | AIがリファクタリングを実行 
 リファクタリング 済ソースコード 生成 ソースコード コードスメル 情報

    AIエージェント SonarQubeのAPI を呼び出して JSONを取得 リファクタリング 実行 SonarQubeに データを同期 コードスメル はx件未満か 開始 終了 Yes No 具体的な ロジック 27
  15. リファクタリング実行用の具体的なプロンプト 
 以下のWebAPIは<プロジェクト名>のコードスメルを取得するためのものです。 <呼び出すAPIの情報> また、アクセストークンは以下となります。 <アクセストークンの情報 > このWebAPIを使って、コードスメル情報を JSON形式で取得してください。 取得したコードスメル情報に基づいてリファクタリングを行ってください。

    リファクタリング完了後、以下のコマンドをコマンドプロンプト上で実行してソースコードの変更結果を SonarQubeへ同期してください。 <SonarQubeに情報を同期するためのコマンド > 上記処理を繰り返し実行し、 JSONの"total"の値が5件未満になるようにしてください。 28
  16. パターン化されたリファクタリング処理を開発工程に組み込む。 生成 ソースコード アーキテクチャ概要 
 AIエージェント AIエージェント コードスメル 情報 レビュアー

    Git リポジトリ リファクタリング 済ソースコード 解析ツール プロンプトに 基づいて コード生成 push 解析結果を 出力 REST API リファクタリング 29
  17. コードスメルが検出されたソースコードの箇所 
 // Collisions // Player bullets vs enemies bit

    = playerBullets.iterator(); while (bit.hasNext()) { Bullet b = bit.next(); eit = enemies.iterator(); while (eit.hasNext()) { Enemy en = eit.next(); if (b.getBounds().intersects(en.getBounds())) { en.hp -= b.damage; bit.remove(); if (en.hp <= 0) { score += 10; if (rand.nextDouble() < 0.3) { items.add( new Item(en.x, en.y, rand.nextInt( 3))); } eit.remove(); } break; } } } // Missiles vs ground enemies mit = missiles.iterator(); while (mit.hasNext()) { Missile m = mit.next(); eit = enemies.iterator(); while (eit.hasNext()) { Enemy en = eit.next(); if (en.isGround && m.getBounds().intersects(en.getBounds())) { en.hp -= m.damage; mit.remove(); if (en.hp <= 0) { score += 20; eit.remove(); } break; } } } @Override public void actionPerformed( ActionEvent e) { if (gameOver) return ; frame++; // Update background background.update( 2); // Spawn enemies if (frame % 120 == 0) { int type = rand.nextInt( 3); boolean ground = type == 2; enemies.add( new Enemy(WIDTH, ground ? 480 : rand.nextInt( 400) + 50, ground, type)); } // Update enemies Iterator <Enemy> eit = enemies.iterator(); while (eit.hasNext()) { Enemy en = eit.next(); en.update(); en.shoot(enemyBullets); if (en.x < - 50) eit.remove(); } // Update bullets Iterator <Bullet > bit = playerBullets.iterator(); while (bit.hasNext()) { Bullet b = bit.next(); b.update(); if (b.x > WIDTH) bit.remove(); } Iterator <Bullet > ebit = enemyBullets.iterator(); while (ebit.hasNext()) { Bullet b = ebit.next(); b.update(); if (b.x < 0) ebit.remove(); } // Update missiles Iterator <Missile > mit = missiles.iterator(); while (mit.hasNext()) { Missile m = mit.next(); m.update(enemies); if (m.x > WIDTH) mit.remove(); } // Update items Iterator <Item> iit = items.iterator(); while (iit.hasNext()) { Item i = iit.next(); i.update(); if (i.x < - 50) iit.remove(); } // Update options for (Option o : options) o.update(); // Player movement double accel = 1.0; if (left) player.x -= accel; if (right) player.x += accel; if (up) player.y -= accel; if (down) player.y += accel; if (player.x < 20) player.x = 20; if (player.x > WIDTH - 20) player.x = WIDTH - 20; if (player.y < 20) player.y = 20; if (player.y > HEIGHT - 20) player.y = HEIGHT - 20; // Shooting if (shoot && frame % 10 == 0) { playerBullets.add( new Bullet(player.x + 16, player.y, 5, 0)); for (Option o : options) o.shoot(playerBullets); } if (player.hasMissile && frame % 60 == 0) { missiles.add( new Missile(player.x + 16, player.y)); } // Player vs items iit = items.iterator(); while (iit.hasNext()) { Item i = iit.next(); if (player.getBounds().intersects(i.getBounds())) { if (i.type == 0) player.hasMissile = true; else if (i.type == 1 && options.size() < 2) options.add( new Option(player)); else if (i.type == 2) player.shotLevel = Math.min( 3, player.shotLevel + 1); iit.remove(); } } // Player vs enemies/bullets for (Enemy en : enemies) { if (player.getBounds().intersects(en.getBounds())) { player.lives--; if (player.lives <= 0) gameOver = true; else { player.x = 100; player.y = HEIGHT / 2; } break; } } for (Bullet b : enemyBullets) { if (player.getBounds().intersects(b.getBounds())) { player.lives--; if (player.lives <= 0) gameOver = true; else { player.x = 100; player.y = HEIGHT / 2; } enemyBullets.remove(b); break; } } repaint(); } 31
  18. // オプションの移動を処理 updateOptions (); // キー入力に基づくプレイヤーの移動と境界チェック updatePlayerMovement (); // 射撃ボタン押下時の弾生成

    handleShooting (); // 弾と敵、プレイヤーと敵 /弾の衝突判定とスコア更新 handleCollisions (); repaint(); } コードスメルが修正されたソースコード 
 メソッド中の処理を 小さなメソッドに分割。 十数行読むだけで 処理の概要を理解できるように。 @Override public void actionPerformed (ActionEvent e) { if (gameOver) return; frame++; // delegate to smaller helpers for readability and lower cognitive complexity // 背景の星のスクロールを更新 updateBackground (); // 一定間隔で敵を生成 spawnEnemies (); // 敵の移動、射撃、画面外削除を処理 updateEnemiesList (); // プレイヤーの弾の移動と画面外削除を処理 updatePlayerBullets (); // 敵の弾の移動と画面外削除を処理 updateEnemyBullets (); // ミサイルの移動と敵追尾を処理 updateMissiles (); // アイテムの移動と画面外削除を処理 updateItems (); 32
  19. • 課題: AIが開発したシステムは人間のレビューしやすい構造とは限らない • 解決策: 保守性向上のためのリファクタリングを活用 ◦ 静的分析で「コードスメル」を検出し、 AIが自動的にリファクタリングする。 ▪

    コードスメルの検出には SonarQubeを活用できる。 • 効果: リファクタリング後のクリーンなコードをレビューすることで、 レビュアーの負担が軽減 される。 ◦ AIにコードをチェックさせる工程を設けたほうがチェックしやすくなる。 まとめ
 33