0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Claude Codeで「仕様駆動開発」を1機能まるごと回す実践ハンズオン — 要件定義→仕様書→実装→レビュー→仕様反映

0
Posted at

株式会社Good Labでエンジニアをしている コータロー です。
日々、Java・SQL・Gitなどの技術情報や、新人エンジニア向けの学習ノウハウ、
AI活用についての情報を発信しています。

Good Labについて気になった方は、コーポレートサイトもぜひご覧ください。
コーポレートサイト

はじめに

Claude Codeを使い始めると、最初の数日は感動します。「ログイン機能作って」と頼むだけで動くコードが出てくるからです。

ところが慣れてくると、別の壁にぶつかります。

  • 出てきた実装が「自分が頭の中で思っていた仕様」と微妙に違う
  • 後から「あ、この条件も必要だった」と気づいて何度も作り直す
  • 数日後に見返すと「結局この機能の正しい仕様って何だっけ?」がどこにも残っていない

私はSES・受託開発を本業にしつつ、副業でiOSアプリを複数本Claude Codeで開発しています。両方の現場で痛感したのは、AIの能力ではなく「仕様の渡し方」が成果物の質を決めるということでした。

この記事では、抽象論ではなく 1つの機能(パスワード強度バリデーション)を題材に、要件定義 → 仕様書化 → 実装 → レビュー → 仕様への反映 までを一気通貫で回す手順を、実際に動くコードとともに解説します。

別記事(仕様駆動開発の考え方とCLAUDE.md設計)では「なぜ仕様が重要か」という概念を扱いましたが、本記事は 手を動かして1周回す実践編 です。

この記事の対象者

  • Claude Code(あるいは他のAIコーディングツール)を使い始めた1〜5年目のエンジニア
  • 「AIに頼むと毎回ブレる・手戻りする」悩みがある人
  • 受託・個人開発で再現性のあるワークフローが欲しい人

仕様駆動開発(Spec-Driven Development)の全体像

仕様駆動開発とは、実装の前に「何を作るか」を検証可能な形で確定させ、その仕様を起点にAIに実装させる進め方です。Claude Codeで回す場合のループは次の5ステップになります。

[1] 要件定義     … ふわっとした要望をClaudeと壁打ちして言語化
       ↓
[2] 仕様書化     … 受入条件(Acceptance Criteria)まで落とす
       ↓
[3] 実装         … 仕様をコンテキストとして渡してコード生成
       ↓
[4] レビュー     … 仕様 vs 実装の差分をチェック(コミット前)
       ↓
[5] 仕様への反映 … 実装で判明した変更点を仕様書に書き戻す
       ↑__________________________________________|
              (次の変更でまた[1]へ戻る)

ポイントは [5] で終わらず [1] に戻るループであることです。実装中に必ず「仕様の穴」が見つかります。それを仕様書に書き戻すことで、仕様書が常に「現在の正」を保ちます。

以下、各ステップを題材に沿って進めます。


ステップ1:要件定義 — ふわっとした要望をClaudeと言語化する

題材は「ユーザー登録時のパスワード強度チェック」です。最初の要望はこの程度の解像度しかないことがほとんどです。

パスワードが弱すぎたら登録させたくない。

このままClaude Codeに「パスワードチェック作って」と頼むと、Claudeが勝手に「8文字以上、記号必須、…」と仕様を補完してしまい、後で「記号は必須にしたくなかった」と手戻りします。そこで 実装させる前に、壁打ちで要件を引き出します

実際に使うプロンプト例です。

パスワード強度チェック機能を作りたい。実装の前に、仕様を確定させたい。
いきなりコードは書かず、決めるべき論点を質問形式で挙げてほしい。
- 抜け漏れがちなエッジケースも論点に含めること
- 各論点に「よくある選択肢」を添えること

「いきなりコードは書かず」「質問形式で」と明示するのがコツです。Claudeは次のような論点を返してきます(抜粋)。

  • 最小・最大文字数は?(最大を設けないと長大入力でハッシュ計算が重くなる)
  • 文字種の要件は?(大文字・小文字・数字・記号のうちどれを必須にする)
  • 空白文字の扱いは?(許可するか、トリムするか、拒否するか)
  • null・空文字の扱いは?
  • エラーは「最初の1件」か「違反全部」を返すか?

ここで初めて、自分の頭の中の要件が明確になります。


ステップ2:仕様書化 — 受入条件まで落とす

壁打ちで決まった内容を 検証可能な受入条件(Acceptance Criteria) に落とします。受入条件とは「この条件を満たせば仕様どおり」と機械的に判定できる粒度の記述です。曖昧さが残っていると、ステップ4のレビューとステップ5の反映が回らなくなります。

今回の題材で確定した仕様書(Markdown)の例です。

# 仕様: パスワード強度バリデーション

## 目的
ユーザー登録時に脆弱なパスワードを拒否する。

## 受入条件(Acceptance Criteria)
- AC1: null または空文字は不合格とし「未入力」を返す
- AC2: 8文字以上 64文字以下であること(範囲外は不合格)
- AC3: 英大文字・英小文字・数字をそれぞれ1文字以上含むこと
- AC4: 半角スペースを含む場合は不合格
- AC5: 不合格時は、違反した条件すべてを理由のリストとして返す
       (ただしAC1に該当する場合は他を評価せず未入力のみ返す)

## 対象外(やらないこと)
- 記号(!?@など)の必須化はしない(任意で使えるが必須にはしない)
- パスワード使い回し・漏洩チェックは本機能のスコープ外

「対象外(やらないこと)」を明記するのが重要です。Claudeは親切ゆえに頼んでいない機能まで足しがちなので、スコープの境界を仕様書に書いておくと暴走を防げます。

補足:チーム開発であれば、この仕様書をHTMLに変換して仕様書ポータル(社内/個人のGitHub Pages等)に公開しておくと、スマホからでも参照でき、レビュー時の「正」が一意になります。本記事ではMarkdownのままローカルに置く前提で進めます。


ステップ3:実装 — 仕様をコンテキストとして渡してコード生成させる

仕様書が固まったら、それを そのままコンテキストとして渡して実装させます。プロンプトはこうなります。

添付の仕様書(受入条件AC1〜AC5)を満たすJavaクラス PasswordPolicy を実装してください。
- 各ACにコメントで対応を明記すること
- 戻り値は「合否」と「違反理由のリスト」を持つ型にすること
- 外部ライブラリは使わず標準APIのみで実装すること

仕様を起点にすると、生成されるコードは「ACという根拠」を持ちます。レビューで「なぜこの分岐があるのか」を仕様まで辿れるのが利点です。

生成されたコードの例(コンパイル・実行確認済み)です。

import java.util.ArrayList;
import java.util.List;

/**
 * パスワード強度ポリシーの検証クラス。
 * 仕様(受入条件AC1〜AC5)に対応して各ルールを実装している。
 */
public final class PasswordPolicy {

    /** 検証結果。合格可否と違反理由のリストを保持する。 */
    public record Result(boolean valid, List<String> violations) {}

    private static final int MIN_LENGTH = 8;
    private static final int MAX_LENGTH = 64;

    /**
     * パスワードがポリシーを満たすか検証する。
     *
     * @param raw 検証対象のパスワード(null許容。nullは未入力として扱う)
     * @return 検証結果
     */
    public Result validate(String raw) {
        List<String> violations = new ArrayList<>();

        // AC1: 未入力(null/空)は不合格。他は評価しない
        if (raw == null || raw.isEmpty()) {
            violations.add("パスワードが入力されていません");
            return new Result(false, violations);
        }

        // AC2: 8文字以上64文字以下
        if (raw.length() < MIN_LENGTH) {
            violations.add(MIN_LENGTH + "文字以上にしてください");
        }
        if (raw.length() > MAX_LENGTH) {
            violations.add(MAX_LENGTH + "文字以下にしてください");
        }

        // AC3: 英大文字・英小文字・数字をそれぞれ1文字以上含む
        if (raw.chars().noneMatch(Character::isUpperCase)) {
            violations.add("英大文字を1文字以上含めてください");
        }
        if (raw.chars().noneMatch(Character::isLowerCase)) {
            violations.add("英小文字を1文字以上含めてください");
        }
        if (raw.chars().noneMatch(Character::isDigit)) {
            violations.add("数字を1文字以上含めてください");
        }

        // AC4: 半角スペースを含まない
        if (raw.indexOf(' ') >= 0) {
            violations.add("半角スペースは使用できません");
        }

        // AC5: 違反全件をまとめて返す
        return new Result(violations.isEmpty(), violations);
    }

    public static void main(String[] args) {
        PasswordPolicy policy = new PasswordPolicy();

        String[] samples = {
            "Passw0rd",      // 合格: 8文字・大小数字すべて含む
            "short1A",       // 不合格: 7文字(長さ不足)
            "alllowercase1", // 不合格: 大文字なし
            "Pass w0rd",     // 不合格: スペースを含む
            ""               // 不合格: 未入力
        };

        for (String s : samples) {
            Result r = policy.validate(s);
            System.out.println("入力: \"" + s + "\" => "
                + (r.valid() ? "合格" : "不合格 " + r.violations()));
        }
    }
}

main を実行すると、各受入条件に対応した出力が得られます(実行確認済み)。

入力: "Passw0rd" => 合格
入力: "short1A" => 不合格 [8文字以上にしてください]
入力: "alllowercase1" => 不合格 [英大文字を1文字以上含めてください]
入力: "Pass w0rd" => 不合格 [半角スペースは使用できません]
入力: "" => 不合格 [パスワードが入力されていません]

record を使うことで結果の構造が一目で分かり、Character::isUpperCase 等のメソッド参照で各ACが宣言的に読めます。


ステップ4:レビュー — 受入条件をテストに落として「仕様 vs 実装」を突き合わせる

ここが仕様駆動開発の心臓部です。受入条件をそのままテストケースに変換すれば、仕様と実装の整合性を機械的に検証できます。「ACがそのままテスト名になる」のが理想形です。

JUnit 5でのテスト例(全件パス確認済み)です。

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class PasswordPolicyTest {

    private final PasswordPolicy policy = new PasswordPolicy();

    @Test
    void 受入条件_有効なパスワードは合格() {
        assertTrue(policy.validate("Passw0rd").valid());
    }

    @Test
    void 受入条件_8文字未満は不合格() {  // AC2
        PasswordPolicy.Result r = policy.validate("short1A");
        assertFalse(r.valid());
        assertTrue(r.violations().contains("8文字以上にしてください"));
    }

    @Test
    void 受入条件_大文字なしは不合格() {  // AC3
        assertFalse(policy.validate("alllowercase1").valid());
    }

    @Test
    void 受入条件_スペースを含むと不合格() {  // AC4
        assertFalse(policy.validate("Pass w0rd").valid());
    }

    @Test
    void 受入条件_未入力は不合格() {  // AC1
        assertFalse(policy.validate("").valid());
        assertFalse(policy.validate(null).valid());
    }
}

実行すると次のように全テストが成功します(junit-platform-console-standalone で実行確認済み)。

[         5 tests successful      ]
[         0 tests failed          ]

Claude Codeに対しては、こう依頼するとレビューが自動化できます。

仕様書のAC1〜AC5それぞれに対応するJUnit5テストを書いてください。
テストメソッド名にどのACに対応するかが分かるようにすること。
仕様にあるのにテストが無いACがあれば指摘してください。

最後の一文(仕様にあるのにテストが無いACの指摘)が効きます。これにより「AC2の最大文字数(64文字超)のテストが抜けている」といった仕様カバレッジの穴をClaudeが洗い出してくれます。コミット前にこの突き合わせを習慣にすると、レビューの再現性が一気に上がります。


ステップ5:仕様への反映 — 実装で判明した変更を書き戻す

実装・テスト中には、必ず「仕様の言い忘れ」が見つかります。例えば今回なら、レビュー中にこんな疑問が出るはずです。

全角スペースは? AC4は「半角スペース」しか書いていない。

これは仕様の穴です。ここで実装だけ直して終わらせると、仕様書とコードが乖離し、次に触る人(数週間後の自分を含む)が混乱します。正しい手順は 仕様書に書き戻してから実装を直すことです。

仕様書のACを更新します。

- AC4: 半角スペースおよび全角スペース(U+3000)を含む場合は不合格

そのうえで実装を仕様に追従させます。

// AC4: 半角・全角スペースを含まない
if (raw.indexOf(' ') >= 0 || raw.indexOf(' ') >= 0) {
    violations.add("スペースは使用できません");
}

最後にテストも追加し、「仕様 → テスト → 実装」の三者が常に一致した状態を保ちます。この 三者一致のループこそが仕様駆動開発の本質で、ループが回り続ける限り「結局正しい仕様は何だっけ?」が起きなくなります。


ワークフローを定着させる3つの仕組み

ここまでの手順を毎回手作業でやるのは大変です。私は実務・個人開発で次の3つに仕組み化しています。

1. CLAUDE.md に「規約」を集約する

プロジェクトルートの CLAUDE.md に、毎回守ってほしいルール(命名規約、テストの書き方、「やらないこと」など)を書いておくと、Claude Codeが全リクエストで参照します。仕様書には「その機能固有の受入条件」、CLAUDE.mdには「プロジェクト共通の規約」を置く、と役割を分けるのがコツです。

# CLAUDE.md(抜粋・例)
## コーディング規約
- バリデーション系クラスは検証結果を record で返す
- equals をオーバーライドしたら hashCode も必ず実装する
## レビュー方針
- 機能実装時は対応する受入条件を必ずテストに落とす
- 仕様にあってテストの無いACがあれば実装前に指摘する

2. エージェントで工程を分業する

Claude Codeのサブエージェント機能を使い、「要件を壁打ちするエージェント」「コードレビューするエージェント」のように工程ごとに役割を分けると、各工程で観点がブレません。レビュー専任エージェントには「仕様カバレッジの穴を探す」ことだけに集中させる、といった使い分けができます。

3. 仕様書をHTMLで公開し「正」を一意にする

仕様書をMarkdownのままローカルに置くと埋もれます。チームや複数端末で共有するなら、HTMLに変換してGitHub Pages等に公開し、URLで参照できる状態にしておくと、レビュー時の「正」が一意になります。スマホからも確認でき、仕様の認識ズレが減ります。


まとめ

ステップ やること アウトプット
1. 要件定義 壁打ちで論点を洗い出す 確定した要件
2. 仕様書化 受入条件まで落とす 検証可能な仕様書
3. 実装 仕様を渡してコード生成 ACに紐づくコード
4. レビュー ACをテストに変換し突合 仕様カバレッジ済テスト
5. 仕様反映 判明した変更を書き戻す 三者一致の仕様書

Claude Codeの価値は「速くコードを書くこと」だけではありません。仕様という根拠を起点にすることで、生成物に再現性と説明責任を持たせられる点にあります。「とりあえず作って」をやめて、まず1機能でこのループを回してみてください。手戻りが体感で減るはずです。


参考


@kotaro_ai_lab
AI活用や開発効率化について発信しています。フォローお気軽にどうぞ!

0
1
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
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?