アダコテック技術ブログ

アダコテックのエンジニアが発信する技術ブログです

「勢いで作って後で整える」Vibe Coding と Spec Coding の両立法

はじめに

「品質とスピード、どっちを取る?」 開発者なら、一度は考えたことがあると思います。 最近では

みたいな記事をよく目にします。

そんな中、「Make with Notion Showcase Tokyo 2025」で Notionの共同創業者 Simon Last さんが話していたことが とても印象的でした。

「LLMを知って、最初の機能は3ヶ月でリリースした」

「Notion AIは、今まで5回作り直してる」

——いや、それ完全にVibe Codingじゃないですか。 そこで思いました。 「Vibeで作って、Specで磨けばいいじゃん。」 というわけで、実際に試してみました。


Vibe Codingとは?

一言でいうと

設計とか構成よりも「勢い」と「ノリ」を重視して、 とにかく動くものを最短で作るコーディングスタイル。 仕様より感覚、思考より実装。 迷ったらとりあえず書く。

良いところ

  • とにかくすぐ動くものができる。
  • 試行錯誤の回転が速い。
  • プロトタイプがすぐできて、議論が進む。

ダメなところ

  • 後から見返すとカオスなコード
  • テスト何それおいしいの?
  • だいたい1ファイルが肥大化していく

Spec Codingとは?

一言でいうと

設計・要件・構成をきっちり固めてから実装するスタイル。 LLM(生成AI)に頼むなら、このモードが得意です。

良いところ

  • チームでの再利用や保守がしやすい
  • 仕様の抜け漏れが減る
  • 「なぜこれを作ったか」が残る

ダメなところ

  • 初動が遅い
  • 設計ドキュメントを作るのに時間がかかる
  • 設計ドキュメントの変更で、テンションが下がりがち(←ここ大事)

Vibe Codingで作ってみた:異常検知プログラム

まずは「とにかく動くやつ」を作りました。 疎結合?何それ?という感じで、こんなプログラム作りました。👇

  • 教師なしでPaDimライクな画像の異常検知
  • トレーニングデータの異常値分布をベースにヒートマップを出力する
  • AUCとサイクルタイムベンチマーク情報として出力する

確かに動きます。めちゃくちゃ簡単です。 が、ひとつのファイルのすべてが詰め込まれて700行オーバーのコードが誕生です。

よかったこと:1時間で動いた!

ダメだったこと:1週間後、読めない!


Spec Codingで作り直してみた

次は、ちゃんと「設計からやる」モードです。 kiro を使って要件・設計・タスクを全部整理して、Codex(AIアシスタント)に実装をお願いしてみました。 具体的なやり方は下記の記事を参考にしました。

zenn.dev

まず、.kiro/steering/product.md を作成し、プロジェクトの基本方針と命名規約をまとめます。

---
inclusion: always
---

# 深層学習型教師なし画像異常検知プロジェクト基本原則

深層学習ベースの画像異常検知プログラムを開発するための要件です。

## 実装方針
### サンプルコード
カレントディレクトリ内の、binフォルダをプロジェクト実行にあたり必要なpydファイルを格納しています。
base_libとbase.pyは開発にあたって用意したサンプルコードです。
こちらをベースの設計を行って下さい。

### 拡張性
基本的にコードは疎結合を心掛け再利用な形を意識する。
異常検知処理は今後の要件変更にともない大きく変わる事を意識する。
プロダクションコード化においてはRustへの移植が想定されるため、移植作業を意識した実装を心掛ける

### ファイル命名規約
* ファイル名は **スネークケース(snake\_case)** を使用する。\
  例:`data_loader.py`, `image_utils.py`
* モジュール(.py)名は短く、内容を明示する。\
  × `processingscript.py` → ○ `data_processor.py`
* クラス定義のみを含む場合は、クラス名と揃える。\
  例:`image_matcher.py` に `ImageMatcher` クラスを定義。
* テストファイルは `test_` プレフィックスを付与。\
  例:`test_data_loader.py`
* 設定/スクリプト類は目的を明示。\
  例:`config_dev.py`, `train_model.py`, `run_inference.py`

### ⚙️ コード規約(PEP8準拠+α)
* インデントは **スペース4つ**(タブ禁止)
* 1行の長さは **原則79文字以内**(コメント・docstringは72文字以内推奨)
* 空行で論理ブロックを区切る
  * 関数・クラス定義の前後に **2行**
  * 関数内の論理区分には **1行**
* importは以下の順でグルーピングし、各ブロック間に空行を1つ入れる:
  1. 標準ライブラリ
  2. サードパーティライブラリ
  3. 自作モジュール
* ワイルドカードimportは禁止(`from module import *`)

### コメント規約
#### 一般コメント
* コードの意図や理由を説明するコメントを中心に記述する。\
  「何をしているか」ではなく「なぜそうしているか」を書く。
* コメントは英語 or 日本語どちらでも良いが、**プロジェクト内で統一する**。
* 行コメントは対象行の上に置く。

#### Docstring(関数・クラス・モジュールの説明)
* **三重クォート(""")** で囲み、PEP257準拠の形式を採用。
* 1行目:概要(短く)
* 2行目以降:詳細・引数・戻り値を明記
* **Googleスタイル** 推奨(以下はGoogleスタイル例)

## README 要件

README ファイルには以下の内容を含める:

- プロジェクトの概要
- セットアップ手順(API キーなどの環境変数設定を含む)
- 動かすためのコマンド一覧
- 使用技術スタック(ライブラリ名、概要、バージョンをテーブル形式で記述)

## 開発プロセス

### ブランチ戦略

- **メインブランチ**: `main`
- **開発ブランチ**: `develop`
- **機能ブランチ**: `feature/[機能名]`
- **修正ブランチ**: `hotfix/[修正内容]`

### コミット規約


<type>(<scope>): <subject>

<body>

<footer>


**タイプ定義:**

- `feat`: 新機能
- `fix`: バグ修正
- `docs`: ドキュメント更新
- `style`: コードスタイル修正
- `refactor`: リファクタリング
- `test`: テスト追加・修正
- `chore`: その他の変更

## やりたい事
- base.pyに記載したように位置対応に学習に部分空間方を行ってスコア計算を行うプログラム
- base.pyはViTのトークンを特徴としているが、restnetも選択できるようにしたい。
- そのうえでプログラム実行時に下記を実施したい。
    - ViT or Resnetの選択
    - HLAC特徴の有無
    - 特徴量の正規化方法:L2正則化 or 標準化(L2正則化の場合は、特徴量ごとに行う)
    - スコアのZ値変換:する/しない
- 学習/推論を下記のように行いたい。
    - モード1:画像の長辺がバックボーンの対応解像度になるようにリサイズして短編方向を黒で余白を付けて解析する。)
    - モード2:画像のバックボーンの対応解像度単位に分割して入力して、学習/検査を行う(タイルド・アンサンブルモード)

そこから、

  • 要件定義書(requirements.md)
  • 詳細設計書(design.md)
  • タスク一覧(tasks.md)

を自動生成。


設計書のイメージ

最終的に整理された設計書の一部はこんな感じです👇

## アーキテクチャ
### システム全体構成

┌─────────────────────────────────────────────────────────────┐
│              enhanced_anomaly_detection.py                  │
│                   (Main Application)                        │
│                 Minimal Orchestration                       │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│              lib_anomaly/pipeline/                          │
│           model_pipeline_manager.py                         │
│              (Core Service)                                 │
│  ┌─────────────────┐                        ┌─────────────┐ │
│  │ lib_anomaly/    │                        │lib_anomaly/ │ │
│  │   config/       │                        │ services/   │ │
│  │configuration_   │                        │output_      │ │
│  │ manager.py      │                        │service.py   │ │
│  └─────────────────┘                        └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
           │                                               │
           ▼                                               ▼
┌─────────────────┐  ┌─────────────────────────────────────────┐
│ lib_anomaly/    │  │         Data Processing Layer           │
│   config/       │  │  ┌─────────────────┐  ┌─────────────┐   │
│ defaults.py     │  │  │ lib_anomaly/    │  │lib_anomaly/ │   │
│ validator.py    │  │  │  inference/     │  │ services/   │   │
│                 │  │  │inference_       │  │feature_     │   │
│ • Model Type    │  │  │processor.py     │  │extraction_  │   │
│ • HLAC Enable   │  │  │                 │  │service.py   │   │
│ • Normalization │  │  │ resize_mode.py  │  │             │   │
│ • Z-Transform   │  │  │ split_mode.py   │  │extractors/  │   │
│ • Inference Mode│  │  └─────────────────┘  └─────────────┘   │
└─────────────────┘  └─────────────────────────────────────────┘
                                      │
                                      ▼
                     ┌─────────────────────────────────────────┐
                     │        Anomaly Estimation Layer         │
                     │         lib_anomaly/services/           │
                     │      anomaly_estimation_service.py     │
                     │  ┌─────────────┐  ┌─────────────────┐   │
                     │  │lib_anomaly/ │  │  lib_anomaly/   │   │
                     │  │estimators/  │  │  estimators/    │   │
                     │  │subspace_    │  │  gamma_         │   │
                     │  │estimator.py │  │  estimator.py   │   │
                     │  │             │  │                 │   │
                     │  │ PCA学習     │  │ ガンマ分布Z値   │   │
                     │  │ 残差計算    │  │ 変換            │   │
                     │  └─────────────┘  └─────────────────┘   │
                     └─────────────────────────────────────────┘

ViT/ResNet対応、HLAC特徴量の組み合わせ、 Z値変換・推論モードの切り替えなど様々な機能が 最初に書いたbase.pyの機能をちゃんと分割できる構成になりました。 AI生成とは思えないくらい整理されていて、 これならメンテもチーム共有もいけそうです。


やってみてわかったこと

Vibe Codingのよさ

  • 「とりあえず作って考える」ができる
  • 何が課題なのかを早く掴める
  • LLMにプロンプトを出すとき、具体的に指示できる

Spec Codingのよさ

  • 一度構成を決めると、AIが正確に動いてくれる
  • 他の人が入りやすい
  • 実験ログ・構成管理がしやすい

結論:Vibe × Spec はいい感じだった

「Vibeで作って、Specで磨く」 これが一番ちょうどいいバランスでした。

  • Vibe:価値を最短で形にする
  • Spec:仕組みをしっかり作る

Vibeしかないと燃え尽きるし、 Specしかないと結構疲れます。

両方合わせると、“楽しくて強い開発” ができます。


おわりに

作って、壊して、磨いていく。 そんな「再構築前提の開発サイクル」を もっと自然に取り入れていきたいと思います。 実際のプロダクト開発だとより複雑な取り組みになると思いますが、開発の取り組みの一助になるとうれしいです。