😇

vibe coding による小規模デスクトップアプリ開発の実践と教訓

に公開

はじめに

SREホールディングス株式会社 のデータサイエンティストとして画像認識案件の開発に携わっている池内です。

最近、「vibe coding」という言葉を耳にする機会が増えてきました。
コーディングの細部は AI に任せ、人間は“ノリ”と“雰囲気”でアイデアを伝える――そんな時代が本当に到来したのか。
それを確かめるべく、vibe coding を用いてデスクトップアプリを開発する小さなチャレンジに取り組みました。


目次

  1. vibe coding とは
  2. 短時間で MVP が成立
  3. OCR 統合で直面した課題と試行錯誤
  4. 最終的に人手で補ったロジック
  5. まとめ

1. vibe coding とは

vibe coding とは、コーディングに特化した大規模言語モデル(LLM)に自然言語で要件を提示し、生成されたコードをユーザーが確認・修正しながら完成へ導く開発手法です(2025 年 2 月、A. Karpathy 氏が提唱)。
プログラマは「実装者」から「ガイド兼品質保証者」へ役割が移り、着手から動作確認までの時間を大幅に短縮できる点が特徴とされています。

本稿では、ゲームのステータス画面からステータス名や数値を自動取得し、ダメージ計算に利用するデスクトップアプリの試作を題材とします。

試作したダメージ計算デスクトップアプリ画面(抜粋)
試作したダメージ計算デスクトップアプリ画面(抜粋)


2. 短時間で MVP が成立

2.1 GUI と計算ロジックの骨子

LLM に

「PySide6 で攻撃・防御・結果の 3 カラム UI を作成し、入力値が変わるたびに再計算してください」

と依頼したところ、返ってきたコードをほぼそのまま利用できました。次の関数はその一例です。入力値を変更すると即座に貫通率ラベルが更新されます。

def _update_penetration_display(self):
    calculator = DamageCalculator(
        AttackParameters(penetration_a=self.physical_penetration_a.value()),
        DefenseParameters()
    )
    rate = calculator.calculate_display_penetration(
        self.physical_penetration_a.value()
    )
    self.penetration_display.setText(f"({rate:.2f}%)")

もう 1 つのサンプル:SpinBox の値変更トリガ

# 全ての数値入力に同じスロットを接続し、変更時に再計算
for spin in self.findChildren((QSpinBox, QDoubleSpinBox)):
    spin.valueChanged.connect(self._calculate_damage)

これにより UI 側のイベント配線は 3 行で済み、LLM 生成コードでも可読性が維持されました。

骨子の作成から動作確認までの所要時間:約 30 分

この時点で 「攻撃側パラメータ」「防御側パラメータ」「計算結果」 の 3 カラム UI と基本計算が一通り動作し、vibe coding の効果を実感しました。


3. OCR 統合で直面した課題と試行錯誤

MVP 完成後、入力の手間を省くため 「ゲーム画面から数値を自動で読み取る」 という要件を追加しました。
LLM へ追加プロンプトを投げるたびに複数の OCR ライブラリやアプローチが提示されましたが、精度不足や依存関係の問題で実装は難航しました。

3.1 採用した最終構成(抜粋)

def ocr_and_parse_region(selected_area):
    # 1. EasyOCR でテキスト検出
    ocr_results = reader.readtext(img_bgr, detail=1)

    # 2. CLIP によるラベル領域検出
    region_embeddings = clip_model.get_image_features(**inputs)
    similarities = region_embeddings @ template_embeddings_norm.T

    # 3. 自前 NMS で重複除去
    keep = _non_maximum_suppression(boxes, scores, 0.2)

    # 4. CLIP ラベルと OCR 数値を最近傍マッチング
    parsed = parse_template_and_easyocr_results(found_labels, ocr_results, img_w)

参考:数値クリーニングユーティリティ

def safe_convert_to_float(text: str) -> float | None:
    """
    '1,234%', '+56.7' など OCR が返す文字列を安全に float へ変換します。
    変換不能・無限大・NaN の場合は None を返し、呼び出し側で判定します。
    """
    if not isinstance(text, str):
        return None

    cleaned = text.replace('%', '').replace(',', '.').replace('+', '').strip()
    try:
        value = float(cleaned)
        return value if math.isfinite(value) else None
    except ValueError:
        return None

OCR 出力はブレが大きいため、前処理/後処理の堅牢性が結果精度を左右しました。

  • CLIP でラベル候補を検出し、EasyOCR で数値を抽出
  • 重複領域を自作 NMS(Non‑Maximum Suppression) で除去
  • 結果として約 3,000 行規模の OCR パイプラインへ発展

しかし、

「OCR 精度向上のためライブラリを切り替える」→「新たなエラーが発生」→「LLM が別の提案を出す」

というループが続き、実装より検証・修正に時間を要する場面が多くありました。


4. 最終的に人手で補ったロジック

OCR が返すラベルは「物理攻撃」「魔法攻撃」「魔法政撃」など表記ゆれが多く、AI だけで正規化するのは困難でした。
最終的には、例えば 「物理/魔法のペアを比較し、大きい方を採用する」 というルールを人手で追加し、以下のように実装しています。

def process_physical_magical_pairs(parsed):
    for rep, (phys_key, mag_key) in PARAMETER_PAIRS.items():
        phys = safe_convert_to_float(parsed.get(phys_key))
        mag  = safe_convert_to_float(parsed.get(mag_key))

        if phys is None and mag is None:
            continue

        final_params[rep] = max(phys or 0, mag or 0)

こういったルールベース含め、ひと手間を加えること等で

  • OCR のばらつきをある程度吸収
  • ダメージ計算に必要な最小限の値を安定供給

できるようになりました。LLM の出力とドメイン知識を結合する工程には、現時点でもエンジニアの判断が欠かせません


5. まとめ

観点 得られたメリット 直面した課題
開発速度 GUI と計算ロジックの MVP を短時間で実装 OCR 精度検証とデバッグは人手で対応
堂々巡りが起きると開発が進まないどころか行き詰る
品質 計算結果の正確性は生成したテストで網羅性含め担保可能 様々なアプローチ提案の結果、要リファクタコードとなりうる
提案毎に大きくコードが変わり、差分検証コストが増大

おわりに

vibe coding により 「コードを書く」 という作業は大幅に短縮でき、実装サイクルを劇的に加速できました。課題は残るものの、開発を“ノリ”と“雰囲気”で始めても MVP まで辿り着けるという点で、非常に便利なアプローチだと実感しています。

一方で、成果物の品質を担保するためには、

  • 要件定義や設計思想等の策定
  • アルゴリズムの選定
  • ドメイン知識を伴う前後処理や例外処理
  • 精度検証と運用のマネジメント

といった工程で、依然としてエンジニアリングスキルが求められます。

本記事が、生成 AI を活用したアプリ開発をご検討中の皆さまにとって、ささやかなヒントとなれば幸いです。

SRE Holdings 株式会社

Discussion