Sansan Tech Blog

Sansanのものづくりを支えるメンバーの技術やデザイン、プロダクトマネジメントの情報を発信

Vol.13 複雑さに立ち向かう軽量Spec駆動開発

この記事は、Sansan Data Intelligence 開発Unit ブログリレー の第13弾です。

こんにちは、技術本部 Data Intelligence Engineering Unitのしゅん(@MxShun)です。

弊社では「SOCv2」と呼ばれるMaster Data as a Service (MDaaS)の構築を進めています。SOCv2が生まれる背景になった課題やアーキテクチャ全体像は、先日の永井の記事 Vol.03 SOCv2: MasterData as a Service (MDaaS) 10年もののSystemを作り替える をご参照ください。
今回は、複雑なSOCv2のDataflow with Apache Beam pipeline構築において行き着いた軽量Spec駆動開発アプローチについてお話しします。

実装した処理の概要

SOCv2では、多様化する顧客ニーズに柔軟に応えるべく従来のMaster Data Managementと比較して考慮すべき次元が増えています。
ひとつは時間軸で、point-in-timeな情報を返せることが要件にありました。そして時間方向の変化をGraphで表現し管理するData Modelとなっています。

そうした前提のもとに、複数の異なるdata sourceを突合し下記のような属性値を時系列で確定するpipelineを実装する必要がありました。

属性 意味
is_registered_office 登記住所の拠点か
is_headquarter 本社拠点か
is_closed 閉鎖済みか
is_effective レコードが有効か

ロジックが複雑になる主な要因は以下の3点です。

  • data sourceごとに住所等の更新タイミングが異なるため、どの時点でどのdata sourceの情報を優先するかを時系列で解決する必要がある
  • 登記住所の変更など、リレーションの属性値が変わる時点で追加レコードを生成しなければならない
  • 本社の判定・選出には優先順位ロジックがあり、複数候補のタイブレーク処理が必要になる

このような複雑さをどう扱うか、が今回のテーマです。

課題:Vibe Codingでは複雑な仕様に歯が立たなかった

複雑な仕様に対して、ピュアにVibe Coding*1を試みても出力が安定しませんでした。それらしいコードは生成されるものの、data sourceの優先順位が逆転するなど満たすべき条件が暗黙的に破られることがありました。
また、テストを通すためにテストデータを書き換えるなどの意図しない挙動も見られました。

試行錯誤の末に行き着いたのが、軽量Spec駆動開発です。

アプローチ:軽量Spec駆動開発

行き着いたフローはシンプルです。

  1. Spec(仕様書)を書く
  2. Specからテストデータを生成する(Test First)
  3. テストが通るように実装する
  4. Specを削除する

ここで言うSpecとは、システムが満たすべき条件を体系化・明文化した取り決めです。「どう実装するか(How)」を記す設計書(Design)とは異なり、「何を実現しなければならないか(What)」を定義するものです。
Agentic Coding*2においてSpecは特に重要な役割を持ちます。AI Agentは人間と暗黙知を共有できないため、守るべき前提・約束が明文化されていなければ正しいコードを生成できません。Vibe Codingの不安定さの根本は、この守るべき前提・約束の欠如にありました。

Spec駆動開発(Spec Driven Development, SDD)とは、実装に先立ってSpecを定義することでSpecが開発を駆動するアプローチです。
今回のアプローチが「軽量」である理由は、SpecをSSoT(Single Source of Truth)として維持しない点にあります。
Specは、あくまでテストデータ・実装を補助するための中間成果物(およびロングランAgentic Codingにおけるプラン的な外部記憶)として位置づけています。ドキュメントのメンテナンスコストを払わずにSpecを先に明確にする規律だけを取り入れた簡易版のSpec駆動開発です。

1. Spec(仕様書)を書く

まずSpecとして守るべき前提・約束を明文化します。

  • 入力・出力の定義
  • 各属性の決定ルール
  • 本社代表の選出アルゴリズム(優先順位・タイブレーク)
  • 拠点を3区分に分けた出力まとめ

2. Specからテストデータを生成する(Test First)

Specをもとに、完了条件をテストデータの生成により固定します。テストファーストですが、テストとコードを小さなサイクルで交互に育てるTDD(Test Driven Development)とは異なり、全パターンのテストデータをSpecから一括生成してから実装に入ります。

突合ロジックの入力空間は3つの軸で分類できます。

  • 軸1:拠点住所が登記住所といずれかの時点で一致するか
  • 軸2:拠点種別が特定の条件を満たすか
  • 軸3:拠点名称が特定の条件を満たすか

この3軸のベン図を描くと、カバーすべき領域が明確になります。


各領域をカバーするテストケースを定義しました。各テストケースには実在する企業データを参考にした入力JSONと期待値JSONを用意しています。

たとえばa-2領域は軸2の条件は満たすが軸3の条件を満たさない拠点のケースで、本社代表として選出されないことを検証します。入力と期待値のイメージは以下のとおりです。

事業所データ(入力)

[
  {
    "日時": "2025-01-01T00:00:00Z",
    "拠点名称": "○○拠点",       // 軸3の条件を満たさない名称
    "拠点種別": "○○",           // 軸2の条件を満たす種別
    "住所": "○○県○○市...",     // 登記住所と一致
    "data source": "事業所データ"
  }
]

登記情報(入力)

[
  {
    "日時": "2020-01-01T00:00:00Z",
    "住所": "○○県○○市...",
    "data source": "登記情報"
  }
]

期待値(出力)

[
  {
    "日時": "2020-01-01T00:00:00Z",
    "本社拠点か": true,            // 登記情報由来の本社にフォールバック
    "登記住所の拠点か": true,
    "有効か": true,
    "data source": "登記情報"
  },
  {
    "日時": "2025-01-01T00:00:00Z",
    "本社拠点か": false,           // 軸3の条件を満たさないため false
    "登記住所の拠点か": true,     // 住所が登記住所と一致するため true
    "有効か": true,
    "data source": "事業所データ"
  }
]

3. テストが通るように実装する

テストが先にあることで、実装中の迷いが大幅に減りました。「このケースはどう動くべきか?」という疑問が生じたら、対応するテストケースを確認するだけで答えが出ます。

また、仕様から生成したテストはAgentic Codingにおけるガードレールとして機能しました。AIがそれらしいコードを出力してもテストが即座に誤りを検知し、意図しない挙動が紛れ込むのを防いでくれます。課題章で述べたVibe Codingの不安定さは、このガードレールによって解消されました。

4. Specを削除する

実装が完了し、テストが全件グリーンになったらSpecの役割は終わりです。Gitの履歴には残りますが、リポジトリからは削除します。

捨てることでドキュメントのメンテナンスコストをゼロにします。SSoTは実装やテストに移譲されており、実装が変われば壊れるテストとして検知できます。

振り返り

このアプローチで良かった点をまとめます。

仕様の曖昧さが早期に発覚する

Specを書く段階で「この条件の組み合わせはどうなる?」という疑問が自然に出てきます。実装前に疑問を解消できるため、手戻りが減りました。今回もSpecを書く過程で曖昧だった条件の扱いをADR(Architecture Decision Record)で確認するきっかけになりました。

テストが仕様の写像になる

テストデータが先にあるため、実装を確認するテストではなく仕様を検証するテストになります。テストケースはそのままドメイン知識のドキュメントとして機能しています。

実装中の判断コストが下がる

「このレコードはどう処理すべきか?」という疑問が生じたとき、コードではなくテストを見れば答えがわかります。コードレビュー時も何番目のテストケースと話せるため、コミュニケーションが円滑になりました。

捨てられる仕様書がちょうどいい

重厚な設計書を維持するコストを払わずに、設計の恩恵だけを受けられます。Specをテストデータ・実装を補助するための思考整理ツールとして消費するものと割り切ることで、書くハードルも下がります。

おわりに

複雑な設計・実装に対し、まずSpecを書きTest firstで進める 軽量Spec駆動開発というアプローチは、一見コストに見えますが実際には手戻りとデバッグの削減で十分ペイします。
今回紹介した手法はTDDやBDDほど厳密なものではなく、設計の複雑さが見えているときの心構えとして気軽に取り入れられるものです。同じような課題に直面している方の参考になれば幸いです。

Sansan技術本部ではカジュアル面談を実施しています

Sansan技術本部では中途の方向けにカジュアル面談を実施しています。Sansan技術本部での働き方、仕事の魅力について、現役エンジニアの視点からお話しします。「実際に働く人の話を直接聞きたい」「どんな人が働いているのかを事前に知っておきたい」とお考えの方は、ぜひエントリーをご検討ください。

さらに、採用説明会を開催します!

3月31日(火)に採用説明会を行います。チームの動き方やAI活用の実際など、現場のエンジニアやProduct Ownerから直接お話しします。興味のある方はぜひご参加ください。

*1:Vibe Coding:人間が自然言語によるプロンプトを記述し、それをLLMに渡して指示を出しながらコーディングを進めていくこと

*2:Agentic Coding:AI Agentが自ら計画を立て人の手助けをそこまで得ずに作業を自律的に進行するコーディングスタイルのこと

© Sansan, Inc.