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

美味しいスイスチーズを作ろう🧀🐭

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on.

 美味しいスイスチーズを作ろう🧀🐭

AI時代に美味しいスイスチーズを作ろう!

Avatar for Taiga Mikami

Taiga Mikami

June 03, 2026

More Decks by Taiga Mikami

Other Decks in Technology

Transcript

  1. 2 © LayerX Inc. tiger/タイガー Yahoo!(PayPayカード) → Shippio(貿易系スタートアップ) → LayerX

    ⾃⼰紹介 バクラク事業部プロダクト開発部 ソフトウェアエンジニア 筋トレ💪 グミ⽇記 🍭 ⽳掘り楽しい 🕳
  2. 6 © LayerX Inc. 🧩 AIには現場特有のビジネスロジックが⽋如している AI生成コードは問題発生率が 1.7倍 高いことが判明 AIコーディングとその問題

    なぜ...?? State of AI vs. Human Code Generation Report 🔍 AIは表⾯的な正しさしか⽣成しない 📝 AIはリポジトリ固有の慣習に完全には従わない
  3. 13 © LayerX Inc. スイスチーズ ガードレールをスイスチーズに当てはめる Format Lint Style Logic

    Design Architecture インデント, 改⾏, 空⽩, セミコロン etc ルールベースでのコード規約 変数命名, 関数の分割, コメント etc Prettier, Biome, OxFormat ESLint, Biome, Oxlint, tsc AI Reviewツール ビジネスロジック AI Reviewツール APIインターフェース, 責務分割 システム境界, データフロー, デプロイ単 位 AI Reviewツール ⼀部, メンバーレビュー AI壁打ち, チームメンバー議論 🧀 🧀 🧀 🧀 🧀 🧀 引用:コードレビューを6段階にしたら、AIと人間の分業が見えた
  4. 15 © LayerX Inc. スイスチーズ Format Lint Style Logic Design

    Architecture 6 2 4 5 1 3 インデント, 改⾏, 空⽩, セミ コロン etc ルールベースでのコード規 約 変数命名, 関数の分割, コメ ント etc ビジネスロジック APIインターフェース, 責務 分割 システム境界, データフ ロー, デプロイ単位 なし なし ⼩ 中 ⾼ 最⾼ ⼈間必要 下に⾏くほど正解が定義しづらく、コンテキストが必要 下のレイヤーほど⽳が⼤きくなりやすい 🐭
  5. 16 © LayerX Inc. スイスチーズ Format Lint Style Logic Design

    Architecture 6 2 4 5 1 3 インデント, 改⾏, 空⽩, セミ コロン etc ルールベースでのコード規 約 変数命名, 関数の分割, コメ ント etc ビジネスロジック APIインターフェース, 責務 分割 システム境界, データフ ロー, デプロイ単位 ⽳ができやすい チーズ部分 🧀 なし なし ⼩ 中 ⾼ 最⾼ ⼈間必要
  6. 18 © LayerX Inc. スイスチーズ Logic Design Architecture 6 4

    5 ビジネスロジック APIインターフェース, 責務 分割 システム境界, データフ ロー, デプロイ単位 ⼈間必要率 Design, Architectureレイヤー • 未来予測や多数のコンテキストが必要なため⼈間がやるべき • 1度決めたら変更頻度は多くない ⽳ができやすい チーズ部分 🧀 Logicレイヤーは⾼頻度で変更があるし、最もバグが出やすい部分 ⽳を⼩さくするならここ! 中 ⾼ 最⾼
  7. 19 © LayerX Inc. Logic部分はテストの質を高めることが穴を小さくする有効な手段 スイスチーズ しかし、AIにテスト書かせると ... • カバレッジだけ増やしている意味のないテスト

    • いちいちAIが書いたテストケースをレビューするのもめんどう コードを実⾏しこととコードの挙動を正しく検証をすることは異なる
  8. 21 © LayerX Inc. ミューテーションテスティング ミューテーションテスティングとは テストコードの品質を測るための手法 コードに意図的なバグ(ミュータント)を埋め込み、テストがそのバグを検知できるかを確認する Stryker ミューテーションテスティングを自動化するツール

    • コードの変更(Mutation) • テスト実行 • 結果分析 • レポート生成 対応言語:JavaScript, TypeScript, C#, Scala ※ LayerXではまだ本格的な運用事例はないはず... 🙇 テストランナー:cucumber, Jasmine, Jest, Vitest… etc
  9. 22 © LayerX Inc. ミューテーションテスティング function calculateDamage(attack: number, defense: number):

    number { if (attack <= 0) return 0; return Math.max(1, attack - defense); } 例:ダメージ計算 • 攻撃力 ≤ 0 ならダメージは 0 • 通常ダメージは attack - defense、ただし 下限 1 にクランプ (防御力が攻撃力を上回っても 1 ダメージは入る) describe("calculateDamage (weak)", () => { test("攻撃力があればダメージは出る ", () => { expect(calculateDamage(100, 20)).toBeGreaterThan(0); }); test("攻撃力0ならダメージ 0", () => { expect(calculateDamage(0, 20)).toBe(0); }); }); ミューテーションスコアが低いテスト
  10. 23 © LayerX Inc. ミューテーションテスティング 例1: Math.max を Math.min に変えても気付かない

    - return Math.max(1, attack - defense); + return Math.min(1, attack - defense); expect(calculateDamage(100, 20)).toBeGreaterThan(0) だけだと、Math.min(1, 80) = 1 でも 0 より大き いので通ってしまう。 例2: 引き算が足し算になっても気付かない - return Math.max(1, attack - defense); + return Math.max(1, attack + defense); これも toBeGreaterThan(0) で素通り。攻撃と防御を足してしまうという致命的バグも検知できない。
  11. 25 © LayerX Inc. ミューテーションテスティング React Componentとかもテストできる export type Rank

    = "S" | "A" | "B"; export function ScoreRank({ score }: { score: number }) { const rank: Rank = score >= 100 ? "S" : score >= 50 ? "A" : "B"; return ( <div data-testid="score-rank"> <span data-testid="score-value">{score}</span> <span data-testid="rank-label">{rank} ランク</span> </div> ); } • 100 以上 で S、50 以上 100 未満 で A、それ未満 で B • ラベルは「{rank} ランク」というフォーマット文字列 • スコア値もそのまま表示する 例:スコア表示コンポーネント
  12. 26 © LayerX Inc. ミューテーションテスティング ミューテーションスコアが低いテスト describe("ScoreRank", () => {

    test("レンダリングされる ", () => { render(<ScoreRank score={120} />); expect(screen.getByTestId("score-rank")).toBeInTheDocument(); }); test("ランクラベルがある ", () => { render(<ScoreRank score={60} />); expect(screen.getByTestId("rank-label")).toBeInTheDocument(); }); test("スコアが表示される ", () => { render(<ScoreRank score={120} />); expect(screen.getByTestId("score-value")).toBeInTheDocument(); }); test("0点でもレンダリングできる ", () => { expect(() => render(<ScoreRank score={0} />)).not.toThrow(); }); });
  13. 27 © LayerX Inc. ミューテーションテスティング 例1: ランク判定の境界 >= 50 が

    < 50 でも気付かない <ScoreRank score={60} /> をレンダリングして rank-label が存在することだけしか見ていないので、ラベルが "A" であっても "B" であってもテストは通る。 例2: ランクの文字列を "" にしても気付かない 「ランクラベルがある」テストは getByTestId("rank-label") の存在だけを見ているので、ランク文字列が空になっていて もラベル要素自体は DOM に残るので素通り。 - const rank: Rank = score >= 100 ? "S" : score >= 50 ? "A" : "B"; + const rank: Rank = score >= 100 ? "S" : score < 50 ? "A" : "B"; - const rank: Rank = score >= 100 ? "S" : score >= 50 ? "A" : "B"; + const rank: Rank = score >= 100 ? "S" : score >= 50 ? "" : "B";
  14. 28 © LayerX Inc. ミューテーションテスティング サポートされているミュータント https://stryker-mutator.io/docs/mutation-testing-elements/supported-mutators/ 1 Arithmetic Operator

    四則演算 + - * / % 2 Array Declaration 配列リテラル/コンストラクタ 3 Block Statement 関数/ブロックの中身 4 Boolean Literal true / false / ! 5 Conditional Expression if / while / 三項の条件部 6 Equality Operator < <= > >= == != === !== 7 Logical Operator && || ?? 8 Method Expression 標準APIメソッド名 9 Object literal オブジェクトリテラル 10 Optional chaining ?. 11 Regex 正規表現リテラル 12 String Literal 文字列リテラル / テンプレート 13 Unary Operator 単項 + - 14 Update Operator ++ / --
  15. 29 © LayerX Inc. ミューテーションテスティング @stryker-mutator/typescript-checker このプラグインを入れると事前に型チェックで弾いてくれる return Math.max(1, attack

    - defense); // mutant: 文字列に置き換え return Math.max(1, "attack" - defense); // TSコンパイルエラー ミュータントによっては成立しないものが混ざる 1. ミュータント⽣成 2. テスト実⾏前に tsc で型チェック 3. 型エラーになるミュータントはCompileErrorとして即座にマーク 4. 残ったものだけでテスト実⾏
  16. 30 © LayerX Inc. ミューテーションテスティング ミューテーションスコアを CIゲートに • デフォルト: {

    high: 80, low: 60, break: null } ◦ score >= high → 🟢 緑 / low <= score < high → 🟡 黄 / score < low → 🔴 赤 ◦ score < break → ❌ exit 1(CI 失敗)、break: null なら落ちない 運用: いきなり 80 にせず 現状値からラチェット。例 : 55% → break: 50 → 改善ごとに 5pt 上げる
  17. 31 © LayerX Inc. ミューテーションテスティング 運用課題 • テストスイート ✕ ミュータント数

    で時間がかかる • E2Eテストなどは現実的でない(Playwright Runnerには対応してない 解決案 🔍 • ドメイン特化の少数ミュータントに絞る • テスト実⾏を絞る ◦ 差分コード実⾏にする(--in-diff / --incremental) ◦ 変更頻度の⾼いモジュールのみで実⾏する ◦ 週次実⾏する • excludedMutations などのoptionで不要なミュータントは除外する • コードを書き換えたのに 振る舞いが元と完全に同じ ため、どん なテストを書いても絶対に殺せないミュータントのこと 実行に時間がかかる 等価ミュータントが発生する return x * 2; // mutant: return x + x; for (let i = 0; i < arr.length; i++) // mutant: for (let i = 0; i !== arr.length; i++)
  18. 32 © LayerX Inc. まとめ まとめ • AIコーディングのガードレールとしてのスイスチーズモデル • 層を作るのも⼤事だが、⽳を⼩さくするのも⼤事

    • Logic層の⽳を⼩さくする⼿法としてミューテーションテスティング ミューテーションテスティングはただの一例なので、他にも各層の穴を小さくする手法はあるはず AI時代に美味しいスイスチーズ🧀 を作っていきましょう! ちなみに、この⽳の⼤きさで美味しさに影響はないらしい