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?

第8章「1MBの崩壊をテックリードが救った夜」

0
Posted at

第8章「1MBの崩壊をテックリードが救った夜」― FastAPI 15,000行 → 9,949行のリファクタリング実録

この記事は、個人開発サービス CheckMe(checkme.run) の開発物語です。

状況:1ファイル15,000行・1MB超のバケモノ

$ wc -l app/main.py
15352 app/main.py
$ du -h app/main.py
1.1M    app/main.py

FastAPIで書かれた単一Pythonファイル。中身は:

  • AIプロンプト 54個(ハードコード)
  • ツールロジック 60本分
  • デッキデータ 106個
  • 全設定値

すべてが main.py 一箇所に「詰め込まれていた」。

テックリードの診断

「これ、誰も読めないし直せません」

エラーが出たとき、どのプロンプトが原因か特定するのに5分かかる。新機能を足すたびにどこかが壊れるリスクが上がる。このまま続けると自重で崩壊する。

リファクタリングの方針

「データはコードに書かない。ロジックだけ残す。繰り返しは1回だけ書く。」

3日間の突貫工事で実施した4つ:

① AIプロンプト 54個をSQLiteに移行

# Before(ハードコード)
_SYSTEM = '''あなたは古文の専門家です。...(500文字)...'''

# After(DBから取得)
system = _P['prompt_kobun_system']  # _P はサービス起動時にDBからロード

SQLiteの site_config テーブルに移行することで:

  • deploy不要でプロンプト更新できる
  • プロンプトの変更履歴が残る
  • n8n から自動更新できる

② データを config/*.json に分離

app/config/
├── tool_meta.json        (60ツール分のメタ情報)
├── pokemon_types.json    (ポケモンタイプデータ)
├── kobun_themes.json     (古文テーマ一覧)
├── chat_themes.json      (AI会話テーマ)
└── ...(全14ファイル)
# Before
_MY_THEMES = ["", "", "", ""]

# After
_MY_THEMES = _load_config('my_themes.json', [])

_ai_call() 共通ラッパーへの集約

Before:各エンドポイントで重複していた処理

# 毎回書いていたボイラープレート(60箇所以上)
cached = await asyncio.to_thread(_cache_get, "xxx_cache", key)
if cached is not None:
    if not isinstance(cached, dict):
        await asyncio.to_thread(_cache_delete, "xxx_cache", key)
    else:
        cached["cached"] = True
        return JSONResponse(cached)
_log_ai_call("xxx", "/xxx/xxx")
try:
    msg = await asyncio.to_thread(
        _anthropic.messages.create,
        model="claude-haiku-4-5-20251001",
        max_tokens=1024,
        system=system,
        messages=[{"role": "user", "content": text}]
    )
    data = _extract_json(msg.content[0].text)
    await asyncio.to_thread(_cache_set, "xxx_cache", key, data)
    return JSONResponse(data)
except Exception as e:
    return _handle_ai_error("xxx", e)

After:_ai_call() 1行に集約

return await _ai_call(
    tool="kobun", path="/words/kobun",
    table="kobun_cache", key=_cache_key(text),
    prompt=text, system_key="prompt_kobun_system",
)

④ レスポンスヘルパーの統一

# Before
return JSONResponse({"error": "入力が不正です。"}, status_code=400)
return templates.TemplateResponse(request, "words/kobun.html", {"key": val})

# After
return _bad_request()
return _t(request, "words/kobun.html", {"key": val})

結果

Before: 15,352行 / 1.1MB
After:  9,949行 / 450KB

削減: 5,403行(35%削減)
所要時間: 3日間

この章のエンジニア向けポイント

  • main.py がどんどん肥大化するのはFastAPI個人開発あるある
  • 「動いてるから触らない」は技術的負債が膨らむだけ
  • データとロジックを分離するだけで可読性は劇的に上がる
  • 共通ラッパーへの集約は「同じ処理を3回書いたら関数化」を徹底すれば防げる
  • SQLiteをプロンプトストアとして使うのは個人開発で費用対効果が高い

これで8章完結。次章以降では、このシステムが月間10万PVを目指して動き続ける話を書いていく予定です。

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?