# メモ: どこに何を置くか

このリポジトリで、コードやテストをどこに置くと後で分かりやすいか、なぜそうするかのメモ。言語には依らないので、新しい言語の実装 `foo/` を足すときの目安にもなる。例には `moonbit/` を使う（`go/` も `pkg/`、`examples/`、Makefile は同じ形で、`experiments/` はまだない）。

守るべき規則ではない。数か月後の自分が見て、どこに何があるか分かれば十分。作業の途中は散らかっていてかまわない（`moonbit/` も、ブログを移植している間はモジュールの直下にパッケージが並んでいた）。落ち着いたところで、ここに書いた形に寄せる。

## 新しい言語を足すときの目安

```
foo/
  README.md      何がどこにあるか、どう動かすか
  Makefile       言語の道具の外にあるものをつなぐ糊（build、test、check）
  pkg/           ライブラリ。保守対象
  examples/      アプリ（ブログなど）。保守対象
  experiments/   実験。保守対象のモジュールの外に置き、そちらから参照する
```

- 言語の道具がモジュールやパッケージを持つなら、`foo/` を 1 つのモジュールにし、`pkg/` と `examples/` をその中のパッケージにすると分かりやすい。実験は別のモジュールにしておく。
- テストのための部品（スタブのアプリ、フィクスチャ）は、テストされる側の隣にあると探しやすい（後述）。
- Makefile のターゲットの意味が言語ごとにそろっていると、どの実装でも同じつもりで `make check` を実行できる（後述の表）。
- その言語の道具に合わないところは、合わせなくてよい。気づいたことがあれば、この文書に書き足す。

## 置き場所は「誰が、どれくらい長く使うか」で決める

コードを 3 種類に分けて考えると、置き場所が決めやすい。

| 種類 | 置き場所（`moonbit/`） | 保守 | テスト | 公開 API |
| --- | --- | --- | --- | --- |
| ライブラリ | `pkg/`（`el`、`picoel`、`tinymarkdown`、`tinyfrontmatter`、`http`、`http/wasm`） | する | 常に走らせる | ほかから使われるので、必要なものだけに絞る |
| アプリ | `examples/blog/`（`site`、`worker`、`compare` など） | する | 常に走らせる | ほかから使われないので、ほとんど要らない |
| 実験 | `experiments/`（`adapter-abi`、`wasm-fetch`、`http-bench`） | しない | 走らせない。ビルドできることだけを確かめる | 要らない |

- ライブラリとアプリは保守対象で、変更のたびにテストを走らせる。
- 実験は突発的な探索で、計測の数字を再現できるように残しているだけ。古い形のままであることに意味があるので、整えたり書き直したりしない。ただし、ビルドできなくなったら直す。
- 迷ったら「これが壊れたとき、誰が困るか」を考える。誰も困らないなら実験、ほかのコードが困るならライブラリ、使う人が困るならアプリ。

## 依存は一方向にする

依存は、実験 → アプリ → ライブラリの向きにだけ張っておくと、どこを直すと何が壊れうるかが追いやすい。

- `pkg/` の下は `examples/` を参照しない。
- 実験は、保守対象のモジュールの外に別のモジュールとして置き、実験の側から参照する。`moonbit/` では、実験ごとに `moon.mod` を置き、`moon.work` でブログのモジュール（`../..`）をメンバーにしている。保守対象のモジュールは実験の存在を知らない。
- このため、保守対象のモジュールで `moon check` を実行しても、実験は見えない。公開 API を変えると、気づかないうちに実験が壊れることがある。実際、`Router::new` を `Router::Router` にしたときに `wasm-fetch` が壊れ、全体の検証の後まで気づかなかった。そこで `make check` が、実験の各モジュールでも `moon check` を実行する。
- 実験を保守対象と同じワークスペースに入れる（ルートに `moon.work` を置く）と、`moon check` だけで実験も見える。ただし、ふだんの `moon test` やリリースのビルドにも実験が混ざる。依存の向きを優先して、そうしていない。

## テストのための部品は、テスト対象の隣に置く

名前は形ではなく役割で付け、置き場所も役割で決めておくと、後から見たときに何のためのものか分かる。

- `pkg/http/wasm/integration/` は、`http/wasm` の integration test。`echo.mbt` は受け取った要求をそのまま返すスタブのアプリで、`test.mjs` がそれを Node で `adapter.mjs` 越しに動かす。JS と Wasm の線形メモリの間の受け渡しを確かめているのは、このテストだけ。ブログの Worker も同じ経路を通る。
- 最初は `examples/echo` という「使用例」として置いていた。ただ、日常的に使うツールでも、一度きりの例でもなく、実態はテストの部品だった。そこで、テスト対象の `pkg/http/wasm` の下に移した。
- スタブは本番のビルドに入れない。最適化（wasm-opt）も要らない。`make test` は moon の出力をそのまま読む。

## 公開するものは、呼ばれているものだけにする

- パッケージの外からテストしか呼ばない関数は、API ではない。非公開にして、テストは white-box テスト（`_wbtest.mbt`）にする。`http/wasm` の `decode_request` と `encode_head` がこれに当たった。アプリが使うのは `handle` だけ。
- 汎用の部品として外から使われうるものは、今は呼ばれていなくても残す。`http` の `media_type` と `parse_values` は、Go の `mime.ParseMediaType` と `url.ParseQuery` に当たる。
- 一覧は `pkg.generated.mbti` で見る。`make check` は `moon info` の差分で止まるので、公開 API の変更は diff に出る。

## 言語の道具と、それを補う糊

- パッケージとモジュールの扱いは、言語の道具（moon、go）に任せる。一部だけ動かすときは、`moon test -p podhmo/html-sandbox/pkg/http` のように言語の道具を直接使う。
- make は、言語の道具の外にあるものをつなぐ一時的な糊。`moonbit/` では、Static Assets に置く記事と文書のコピーと目録（`content`）と、テスト用の埋め込みの生成（`embed`）、wasm-opt、Node でのテスト、wrangler が当たる。
- make のターゲットは保守対象の全体を扱う。

  | ターゲット | 内容 |
  | --- | --- |
  | `make build` | 配備するものだけ（ブログの Worker）をビルドし、wasm-opt をかけ、Static Assets に置く記事と文書をコピーする |
  | `make test` | 保守対象のテストをすべて走らせる（`moon test` と、Node での integration test） |
  | `make check` | コミットの前の簡易チェック。`make test` に加えて、fmt、check（wasm-gc と native）、実験のビルド、`.mbti` の差分 |

- make で何かを補う必要が出たら、まず構成を疑う。たとえば一時期、`make test` が実験の各モジュールでも `moon test` を実行していたので、ブログのテストが 3 回走った。実験は `moon.work` でブログを含むので、実験の中の `moon test` はブログのテストも走らせる。実験はテストせず、ビルドだけを確かめる、という区別がないまま糊で補おうとした結果だった。

## 名前は、持っているものに合わせる

- モジュールがブログ以外のライブラリも持つようになったので、モジュール名を `podhmo/blog` から `podhmo/html-sandbox` に変えた。パッケージのパスも `podhmo/blog/examples/blog/site` のような重なりがなくなり、`podhmo/html-sandbox/examples/blog/site` になった。

## 見直すきっかけ

次のことが起きたら、置き場所か依存の向きを見直す頃合い。

- make が同じ処理を何度も実行している、あるいは言語の道具でできることを make が代わりにしている。
- 検証を通っているのに、何かが壊れている。
- 「例」や「ツール」と呼んでいるものが、実際にはテストからしか使われていない。
- パッケージの外からテストしか呼ばない `pub` がある。
