世界を動かす技術を、日本語で。

検証済み仕様駆動開発 (VSDD)

概要

  • VSDD は、 Spec-Driven DevelopmentTest-Driven DevelopmentVerification-Driven Development を統合したAI主導のソフトウェア開発手法
  • 各フェーズは「何を作るか」「どう作るか」「何も見落としがないか」を順に担保
  • 仕様、テスト、検証の各工程にAIと人間開発者が役割分担
  • 形式検証やアドバーサリアルレビューを通じて高信頼性を実現
  • ツールチェーンやパイプラインの各ステップで厳密なプロセス管理

VSDD(Verified Spec-Driven Development)とは

  • VSDD は、 SDD(Spec-Driven Development)TDD(Test-Driven Development)VDD(Verification-Driven Development) の3手法をAI中心に融合した開発メソッド
  • 仕様策定→テスト駆動実装→アドバーサリアル検証→形式検証の シーケンシャルなパイプライン 構成
  • AI が各工程をオーケストレーションし、人間開発者は 意思決定最終承認 を担当
  • 仕様が「何を作るか」、テストが「どう作るか」、検証が「何も見落としがないか」を分担
  • それぞれの工程で 独立した品質担保 を実現

VSDDツールチェーンの役割

  • The Architect(人間開発者)

    • 戦略的ビジョン、ドメイン知識、最終承認権限
    • 仕様の承認やBuilderとAdversary間の調停
  • The Builder(Claude等AI)

    • 仕様作成、テスト生成、実装、リファクタリング
    • TDD原則に厳格に従う
  • The Tracker(Chainlink等)

    • 課題の階層分解(エピック→イシュー→サブイシュー)
    • 仕様・テスト・実装の全てを「ビーズ」として追跡
  • The Adversary(Sarcasmotron/Gemini Gem等AI)

    • 超批判的レビューア
    • 各工程で新たなコンテキストでレビューし、瑕疵を徹底指摘

VSDDパイプライン概要

フェーズ1:仕様結晶化(Spec Crystallization)

  • 何も実装しない段階で、仕様と検証アーキテクチャを厳密に設計
    • 機能仕様(事前条件・事後条件・不変条件)
    • インターフェース定義(型・エラー型・スキーマ)
    • エッジケース(空、null、最大値、負値、Unicode、同時実行等)
    • 非機能要件(性能、メモリ、安全性等)
  • 検証アーキテクチャ
    • 数学的に証明すべき性質のカタログ化
    • 「純粋コア」と「副作用シェル」の明確な分離
    • 検証ツール(Kani, CBMC, Dafny, TLA+等)選定および制約反映
    • 形式的な性質定義のドラフト作成
  • 仕様レビューゲート
    • 人間とAdversaryによる徹底レビュー
    • 曖昧表現、抜け漏れ、暗黙の前提、矛盾、検証境界の妥当性を精査
    • Chainlinkで仕様ごとにイシュー・サブイシュー化し、進捗を可視化

フェーズ2:テストファースト実装(TDDコア)

  • テストのみを先に作成し、実装はテスト失敗を確認後に最小限で着手
    • 仕様項目ごとにユニットテスト化
    • エッジケースも全てテスト化
    • インテグレーションテスト、プロパティベーステストも生成
    • Red Gate :全テストが最初は失敗することを確認
  • 最小実装→全テストパス後にリファクタ
    • リファクタ時もテスト網で安全性担保
    • 人間による「仕様の精神」との整合性チェック

フェーズ3:アドバーサリアル精緻化(VDD Roast)

  • テスト合格後、Adversaryによる徹底レビュー
    • 仕様忠実性、テスト品質、コード品質、セキュリティ面、未カバー仕様を精査
    • 各指摘に具体的な修正提案を付与
    • 毎回新しいコンテキストでバイアスなしにレビュー

フェーズ4:フィードバック統合ループ

  • Adversaryの指摘を起点にパイプライン全体で再フィードバック
    • 仕様レベル→フェーズ1に戻り修正
    • テストレベル→フェーズ2aに戻り修正
    • 実装レベル→フェーズ2cに戻りリファクタ
    • 新規エッジケース→仕様・テスト・実装に反映
  • このループを収束まで繰り返す

フェーズ5:形式的ハードニング(Verification Plan実行)

  • 検証設計に従い形式検証ツールで純粋コアを証明
    • 仕様で定義した性質の証明実行
    • 失敗時はフィードバックループで修正
  • ファジング、セキュリティテスト、ミューテーションテスト等も実施
    • 環境依存のない純粋コアで高効率な検証
    • 検証境界の最終監査も実施

VSDDの意義と利点

  • 仕様・テスト・検証の各段階で独立した品質ゲート
  • AIによる自動化と人間の判断の最適分業
  • 形式検証を前提にした設計で高信頼性・セキュリティを担保
  • エッジケースや抜け漏れを徹底的に排除
  • CI/CDと連携した自動化可能なパイプライン

VSDD導入のポイント

  • 初期設計段階で仕様と検証アーキテクチャを徹底的に詰めることが成功の鍵
  • AIツールの適切な選定と連携が生産性向上に直結
  • 人間開発者のレビューと意思決定が最終品質の保証となる
  • 全ての成果物をChainlink等のトラッカーで一元管理することで進捗・品質を可視化

まとめ

  • VSDDはAI時代の高信頼ソフトウェア開発の新しい標準
  • 仕様→テスト→検証→形式証明の厳密なパイプライン
  • 人間とAIの協調による高効率・高品質な開発体制
  • 複雑なシステムやAIネイティブなプロダクトに最適な手法

Hackerたちの意見

この投稿の内容は、あなたがすでに何をしているのか知っているという前提から来ていると思うけど、過去に作ったものに関してはそれが当てはまるかもね。でも、全く分からないものを仕様化するのは無理だし、問題の範囲を探る前にテストを書くなんてありえないよね。それは完全に不合理だと思う。次に言いたいのは、このアプローチはAIファーストの開発には根本的に間違っているってこと。コードを書くコストがほぼゼロに近づいているなら、一発で完璧なシステムを作るためにリソースを投資する意味はないよ。もっと大事なのは、どれだけ早く境界を探れるかってこと。今なら、5つのエージェントを立ち上げて、作っているものの5つの異なるバージョンを実装して、ベストなものを選べるんだ。うちのチームでは、常に何百ものエージェントがいろんな問題に取り組んでる。大半のコードは捨てられるけど、合格した部分だけをマージするんだ。

あなたの二つ目のポイントに関して質問してもいい?あなたのショップでやったことがここでも使えるんじゃないかな?「5つの異なる方法で開発して、各々のベストな部分を選ぶ」っていうのは、'VSDD'の概念と矛盾する理由はないと思うけど、これを含めてもいいんじゃない?

あなたがここで書いていることは、私のAIに関する経験とは全然違うよ。仕様を書くことは価値があるし(実装よりもずっと多くの時間をかけるべきだと思う)、それはまだコントロールできる部分だから、完全に読んで理解できるしね。一度コードに入ってしまうと、レビューするのがずっと難しくなるし、すべてをレビューしようとすると、あなたのペースが遅くなるよ。> コードを書くコストがほぼゼロに近づいているなら、一発で完璧なシステムを作るためにリソースを投資する意味はない。AIは一発で完璧なシステムを得られないし、遠く及ばないよ!特に、エッジケース(またはそれほどエッジでないケース)が多く残されたままの雑な初期要件からはね。でも、良い要件があれば、AIを修正するチャンスがあるし、正しい方向に進めることができる。戻って他のAIに「この実装は仕様に準拠しているのか、それとも何かを見落としているのか?」って聞けるし。> あなたが作っているものの5つの異なるバージョンを実装して、単にベストなものを選ぶ。問題は、もしベストなものがまだ十分じゃなかったらどうするの?じゃあ50個作る?それらが全部悪かったらどうするの?収束する方法が必要だよ。

だから、私はAIに作りたいシステムについて書かせて、それを全部レビューするんだ。良さそうなら、それをプロンプトとして使うよ。

でも、作り方が全く分からないものを仕様書にすることはできないってことには同意してほしいな。ええ、もちろんできるよ。やりたいことが分かっていれば、何でも仕様を指定できる。これはシステムエンジニアリング101みたいなもので、人々はそれを成功させているんだから。

ここには本当に緊張感があるね。もしバイブコーディングをしているなら、このアプローチは確実にそのノリを台無しにして、迅速な反復の利点を失わせるだろう。でも、既存の大規模なシステムで作業しているなら、バイブコーディングをコアに取り入れるのは難しい。だから、AIから大きな利益を得るためには、OPのようなもっとフォーマルなものが必要だと思う。

コードの価格がゼロなら、仕様を変更するのもコードの観点からはゼロのコストで済むんだよね。これが以前からの仕様の問題だった。仕様を書いて、プローバーを通して、コードを書いた後に、ビジネスケースを考慮してなかったせいで、全部捨てなきゃいけなくなることが多かった。今は、下位の98%を「見た目が合ってる」以外の明確な成功シグナルを持つロボットに任せられるようになった。

コードは仕様とは独立してるよ。コードと仕様はそれぞれ繰り返し改善できる。仕様は常に一定である必要はなくて、コーディングパイプラインの成果物に対するECCの一形態なんだ。

これからは、5つのエージェントを立ち上げて、作っているものの5つの異なるバージョンを実装し、単純に一番良いものを選ぶことができるよ。そうじゃなければ、5つの異なる平凡な解決策ができて、一番良い部分がランダムに分散しちゃう。

私たちのショップでは、常に数百のエージェントがさまざまな問題に取り組んでいるよ。ほとんどのコードは捨てられる。マージするのは良い部分だけ。君が説明しているのは、非常に高価で非効率的な遺伝アルゴリズムで、人間のレビューがフィットネス関数になっているんだ。思っているほどのフレックスじゃないよ。

その通りだね。具体的に書かないことで得られるものがあるなら、例えばコードを書かないとか、仕様書を書くときに具体的にしたいなら、結局は正確なシステムから不正確なものに切り替えたってことだよね。

ちょっとしたランダムな(役に立つことを願って)考えなんだけど、多くの企業には古いコードベースやデータベースがあって、ある程度は定義されているけど、そうでない部分もあるよね。35年かけて少しずつ進化してきたものには、ドキュメント化されていないエッジケースがたくさんあるかもしれない。だから、エッジケースカタログの前に、入力と出力の動作をカタログ化するための何らかのプロンプトを設けるステップがあった方がいいかも。そしたら、異なる入力と出力を見つけて、Input AとOutput Aで期待通りに動作するか確認できるし。(レガシーシステムには、誰も覚えていないような奇妙なオーケストレーションがあることが多い。)小さなメモとして、個人的にはここで述べられている以上にコードコメントを充実させるのがいいと思ってる。仕様自体はコードに織り込まれるべきで、各要素について少し過剰にコメントを入れるくらいがちょうどいい。コード自体がコンテキストとして機能するべきだし、特にTDDの段階ではね。暗黙のうちに言ってるけど、フェーズ3のコード品質チェックでは、ゼロトラストベースでチェックすることも明記しておくべきだと思うし、ハードコーディングされたキーみたいなものは含めないように。Chainlinkが何かはよくわからないけど(ごめん!)、分解に関するアイデアは好きなんだ。ただ、ここで述べられているように、すべてをエンドツーエンドで繋げるのを欠いている気がする。(各部分を作成するように求めているけど、実際には全体を織り合わせていない。)カバーされていないこととして、作業のシーケンスや分解があるよね。仕様は内部で複数の依存関係を生むことができて、特定の順序で作業を進める必要があるんだ。

LLMを使った開発は、トレンド駆動の開発に似ている気がする。技術や異なるプロンプト、目標に対処する際に、特定の技術に対してギャンブラーの誤謬に陥りやすいよね。仕様駆動の開発は、私にはちょっと疑わしい。予測可能な機能作業や以前に行ったことがある場合にはうまくいくと思うけど、じゃあなんでそれに時間を無駄にするの?LLMが登場する前は、動作するものに向けて急速に反復するのが全体の雰囲気だったし、何がうまくいくか、何がうまくいかないかを見られるようにしていた。コードを書くコストが明らかに安くなっているのに、なんでその戦略を業界全体で放棄するの?もし私がLLMを使うとしたら、過去のアートやアイデアの幅広い検索をするために使って、その後はプロトタイプのオニオンを作るような感じで、新しい問題に対してクリーンルームでの試行を重ねて、各試行で学んだことを次のプロンプトに蓄積していくよ。通常、そのプロトタイプを取って、最終バージョンを自分で書くから、アイデアをしっかりと内面化できるんだ。結局、このプロンプト作業は先延ばしのように感じることが多い。これらのツールがどこで役立つか、どこで役立たないかを理解することではなく、作業のすべての側面を消費させようとすることに焦点を当てている気がする。

もしかしたら、コードよりも話すことが好きな人たちが、話すことがソフトウェア開発を食いつぶす可能性にワクワクしているのかもしれないね。でもそれは全然逆だよ。多くのタスクにおいて、フォーマルな言語は英語よりも優れていて、リアルで、美しい。どんなにトークンがあっても、フェルマーやオイラー、ガウスの公式を無視することはできないし、良いコードにも同じことが言える。もちろん、見た目が悪くて実用的なコードもたくさんあるけど、話すことでそれを書くのが少し楽になるかもしれないね。

人々はコードが仕様そのものであることを忘れがちだよね。通常、コメントを維持する方が効果的なんだ。だって、LLMは言語駆動だから、リテラルプログラミングを復活させたらどうかな?クヌースも喜ぶだろうね。

もし「プログラミングを解決する」戦略を思いついたら、それには必ず欠陥があることが確実で、どこで妥協しなければならないかを特定する必要がある。コンピュータサイエンスは内省的な学問で、問題を解決するためのプロセスに関係なく、問題の本質的な難しさを研究するから、プログラミング自体(正確な、または十分に正確なプログラムを生成する問題)は、研究されてきた問題なんだ。プログラムXがある正しさの特性Pを満たすかどうかを学ぶ問題はモデルチェック問題として知られていて、それを確実に答えることは難しい。例えば、あるプログラムに対して真である特性は、検証に10分以上かかることがあるし(検証がどのように行われるかに関係なく)、他の特性は10時間、10ヶ月、10年かかることもある。事前にその特性が真かどうか、そして真であればそのスペクトルのどこに位置するかは分からない。だから、ある特性を完全に証明しなければならないと決めた場合、検証を待つのをどれくらい待つべきか、そして待つのをやめたらどうするかが問題になる。もし100%の確信がないことを受け入れるなら、どのアプローチを取るべきで、実際にどれくらいの確信を持っているのか?その問題は、その質問の答えが実装の深い理解を必要とすることが多いから難しい。つまり、もしXとYという2つのプログラムが同じ関数を計算する場合、1つのプログラムに対して99%の確信を持つアプローチがあっても、もう1つには10%の確信しか持てないことがあるんだ。

もっと、「もしあなたのLLMが『プログラミングを解決する』戦略を思いついたら…」

私は、15年間憧れてきたソフトウェアエンジニアのように振る舞うLLMから、最高の結果を得ているよ。普通の開発のこと。チケットをしっかりスコープして、分解する。テストをきちんとする。正しいドキュメントを書く。LLM特有のことは来週には消えるだろうね。

最近、これを強く感じているよ。エージェントをジュニア開発者だと思って、時間がないことをやらせているんだ。変更を自分の都合で確認するのは、ジュニア開発者をチェックするのと似ているね。一方で、ジュニア開発者が必要とする同じ準備をすることで、より良い結果が得られる気がするから、できるだけ未知のものや依存関係を取り除くようにしている(そうでなければ、明示的にいくつかのものをスタブとして残すように指示する)んだ。

私の勘違いでなければ、ここでの検証は問題があるね。遅すぎるタイミングで行われている。単一のテストを満たすコードは、仕様に従う可能性が低い。さらに悪いことに、全体の仕様は完全に実装されないと正しく実現できない。1つの制約を満たしていくことで反復的に作業することはできない。テストケースにも同じことが言える。つまり、最後のテストを満たしたり、最後の制約をクリアするのは、最初のものよりもずっと多くの作業が必要になるってこと。通過したテストの数は、実装の完了を測る良い指標じゃないよ。

私はもっとカジュアルなことをやっているよ。Riaan Zoetmulderの深層学習と医療画像分析の無料コースを見つけて、仕様駆動開発に関する彼の記事も見つけたんだ。彼は、要件、システム設計、アーキテクチャの3つを最初に指定することでVモデルを適応させている。残りは生成されるんだ。彼は、LLMの支援が大規模なコードベースで経験豊富なオープンソース開発者を遅くさせたという研究を紹介していた。モデルは暗黙のコンテキストを知らないからね。それが私にとってのポイントなんだ!LLMには何らかのインデックスが必要だと思う。だから、自分の関数呼び出しを追跡する静的解析プログラムを作ったんだ。それは、自分で定義した関数のコールグラフを出力して、呼び出しているものの名前(とPythonの型ヒント)を表示する(標準ライブラリの関数は除外して、自分で定義したものだけ)。そのプログラムを実行して、時々差分を送るのがかなり役立っているみたい。

ちょっと分からないんだけど、具体的なワークフローの例を教えてくれる?

短いまとめ:TDDをBDDに置き換えて、DDDをスパイスとして加えるといいかも。そうでなければ、これはかなり良い記事だね。なんでTDDじゃないかって?今、多くの開発者がLLMを使ってテストを作成しているし、トレーニングデータの多くにはその方法が含まれているから。LLMが自分でやり方を見つけるか、ズルをするかのどちらかになる。どちらも良くない。少し物議を醸す意見としては、LLMが自分で生成できるテストを書くのは避けるべきだということ。先週、agents.mdファイルを削除したのと似ているね。

agents.mdを削除することについて、もう少し詳しく教えてもらえる?

現代のテストフレームワークはすでにBDD(ビヘイビア駆動開発)を使ってるよ。これは自然言語でテストを書くことを意味してる。BDDはTDDのスーパーセットで、例えばこんな感じ:import { describe, it, expect } from 'vitest'; describe('ユーザー認証', () => { it('有効な資格情報でログインできるはず', () => { const result = login('user@example.com', 'password123'); expect(result.isAuthenticated).toBe(true); }); これがBDDで、私がキャリアの中で使ってきたテストフレームワークはどれも、書いたテストの95%がこんな感じだよ。

これをrlm-workflowで実装したバージョンがあるよ: https://skills.sh/doubleuuser/rlm-workflow/rlm-workflow

ここでのアイデアのいくつかは、私のTDDフレームワークであるCodeLeashにもっとシンプルにまとめられてるよ(https://codeleash.dev)。大きな違いは、CodeLeashはモデルの外にガードレールを設けているところ。Claude Codeのフックとしてね。忘れたりステップを飛ばしたりすることができないから、プロセスを強制的に通過させるんだ。バイパスもあるけど、それを使うとモデルが再確認する必要があるアラームが鳴る。モデルの外にガードレールを設けることで、決定論的なプロセスの遵守が強制される。十分に能力のあるプロセス(TDDはソフトウェアを構築する強力な方法だよ)を使えば、コーディングエージェントの利用を本当にスケールアップできるんだ。