カナエを作る #4:AI 任せだったソースを見直す。企画の前に、構造の方針を決める

前回(#3)の最後に、次は企画の深掘りと書きました。ただ、その前に1つ、検討しておきたいことが出てきました。 (2026年10月時点の内容です)

ソースの構造の話です。実際に考えるのは、環境構築や開発の段階です。#1 の流れにも入っていません。それでも、思いついたものが適切かどうかを、この時点で検討したいと思いました。


気になっていたのは、ソースをあまり見ていないこと

ハコヅミとワダチは、とりあえず作って、公開して、サービスとして運用できるかを試す位置づけでした。スピード重視の、バイブコーディングです。

もちろん、仕事の経験から、基本的なことは踏襲したつもりです。単体テスト(Vitest)や結合テスト(Playwright)の自動化、型チェックと Lint、CI/CD の設定、Issue の受入条件から始める開発、PR → Merge → Deploy という流れです。

気になっていたのは、実際のソースをあまり見ていないことです。最初は少し見ましたが、ソースの構造をこちらから指定することはなく、AI に任せきりになっていました。

バイブコーディングの弊害として、構造をきちんと定義せずに AI に任せきりにした結果、あとから改修を入れるときに問題になる事例は、結構多い印象です。

カナエは個人開発で、サービスの範囲も狭いので、今のやり方でも特に問題は出ないと思います。それでも、エンジニアとしては、綺麗に構造化されたソースを作りたいという思いがあります。

もともと、構造化された綺麗なソースを書くのが好きなタイプです。チームで開発していたときは、ソースがそこから逸脱していくのを見ると、気持ちが沈みました。

その分、若干、綺麗さに傾倒しすぎて、やりすぎるきらいもあります。


まず、ハコヅミとワダチのソースを見てもらった

いきなり理想の構造を考えるのではなく、Claude に今のソースを確認してもらいました。比較のために、SvelteKit の練習で作った家計簿アプリも見てもらっています。すべての行を読んだわけではなく、ファイル構成の集計と、一部のソースでの確認です。

結果は、思ったより整っていました。

観点 ハコヅミ / ワダチ 家計簿
route の .ts から DB を直接触っている 0件 / 0件 18ファイル
ロジックの分け方 純粋なロジックは lib/*.ts、DB は lib/server/ 大半が route の中
テスト ロジックの横に .test.ts 一部のみ

AI に任せきりでも、土台の形はできていました。

一方で、課題も見えました。

  • lib が平らすぎる:機能の違うファイルが1か所に数十個並び、どれがどの機能のものか、見ただけでは分からない
  • 巨大な画面がある:ワダチのタイマーの画面は1,160行で、そのうち974行が script
  • 構造を機械で守っていない:ESLint に、構造についてのルールはない。今の形は、CLAUDE.md と AI の振る舞いに頼っている
  • 共通部分がコピーで分かれている:ハコヅミとワダチで同じパスのファイルが101個あるが、完全に同じものは7個だけ

lib が平らすぎるのは、自分でも同じことを思っていました。


一般的な構造と照らし合わせて、線を引く

SvelteKit の公式ドキュメントは、$lib/server のようなサーバー専用の仕組みは示していますが、層の設計までは決めていません。server という名前のフォルダは、どの階層でもサーバー専用になるので、機能ごとにフォルダを分ける形と両立できます。

ただ、一般的に良いとされている構造を、そのまま持ち込むつもりはありません。小さいサービスでは、過剰になるからです。Claude からも同じ指摘があり、ここは自分の考えと一致しました。

そこで、どこまで構造を守るかを決めました。

守ることは、次の5つです。

  1. route は、入力の確認、呼び出し、結果を返すことだけにする
  2. DB に触るのは、server/ の中だけにする
  3. 純粋なロジックは、Svelte や DB に依存させない
  4. 依存の向きは routes → features → shared にする。機能どうしは index.ts を通してだけ使う
  5. 1ファイルの行数に上限を設ける

やらないことも、決めました。

  • Repository のインターフェース、DI、ドメインクラス
  • DB の差し替えを想定した抽象化
  • 全機能に、同じ層を機械的に作ること

きれいにすること自体が目的になると、本末転倒です。自分はやりすぎるきらいがあるので、なおさら先に線を引いておきます。カナエの規模に見合う、薄い構造にとどめます。


機能ごとのフォルダにする

形は、次のようにします。

src/lib/
  features/
    bucket-items/            機能のフォルダ
      index.ts               他の機能に見せる入口
      bucket-items.ts        純粋なロジック
      bucket-items.test.ts
      server/                この機能の DB アクセス
      components/            この機能の画面部品
    stats/
      index.ts
      stats.ts
      stats.test.ts
      components/
  shared/
    ui/                      ボタンなどの共通部品
    dates/                   日付の共通処理
      dates.ts
      dates.test.ts
    server/
      db/                    DB への接続と、全テーブルの定義
    auth/
      server/
      components/
    billing/
      server/
      components/

DB の定義(Drizzle のスキーマ)だけは、機能ごとに分けず、shared/server/db/ の1か所にまとめます。テーブルは外部キーで機能をまたぎますし、データモデルは全体を1か所で見られるほうが、人が確認するときに読みやすいからです。

shared の直下には、ファイルを置かず、フォルダだけにします。ファイルを直接置いていくと、lib と同じように平らになってしまうからです。utils のような何でも入る名前は使わず、dates のように中身が分かる名前にします。

server や components は、機能のフォルダの中に入ります。ここは、最初に案を見たときに把握できず、聞き返したところです。

勉強してきた経験では、components は機能単位ではなく、共通の扱いでした。カナエでは、本当に共通の部品は shared/ui/ に置き、機能ごとに異なる画面の構成を、機能の components/ に置きます。

分ける基準は、その機能を知っているかどうかです。

  • shared/ui/:機能を知らない部品。ボタン、モーダル、カードの枠など
  • 機能の components/:その機能のデータや言葉を知っている部品。バケットリストのカードや、登録のフォームなど

迷ったら、まず機能の components/ に置きます。別の機能でも必要になったら、そのときに shared/ui/ へ移します。


共通化は、あとあとできればいいなレベル

カナエの案出しでは、「他のサービスと共通にできるところは共通にする」と書きました。ただ、今回いきなりパッケージ化までは考えていません。

認証や決済のように、他のサービスと共通になりそうなものを、試行的に shared にまとめておきます。あとあと共通化できればいいな、というレベルです。


決めたあとのチェックは、人ではなく AI に

ルールを決めても、守られているかを毎回人が見るのでは、続きません。決めたあとのチェックは、人ではなく AI が自立して判定できる仕組みにしたいと考えています。

Claude と整理して、3段階にしました。

段階 確認すること 仕組み
1. 機械の判定 依存の向き、DB の場所、行数 lint か、import を検査するテスト。CI で落とす
2. AI の判定 責務、命名、重複 チェックリストをスキルにして、PR の前に AI が確認して直す
3. 人の確認 データモデル、構造のルールの変更 そこに触れた PR だけ読む

決まった基準で判定できるものは、1段階目に寄せます。2段階目は、Claude Code のプランが Pro に戻ることも考えて、PR ごとの自動実行にはせず、実装のスキルの最後に組み込むことにしました。

まだ考えの段階で、動くかどうかは確かめていません。候補のツールが .svelte ファイルに対応しているかも、未確認です。これは、環境構築のときに試します。


おわりに

コードは AI が書いても、構造の判断は人が持つ。カナエでは、この形を試します。

今回決めたのは、あくまで方針です。実際に作ってみて合わなければ、また考えが変わると思います。その場合も、そのまま記録にしていきます。

次こそ、企画の深掘りです。

← トップページに戻る