atoms/ や hooks/ のような「分類のための分類」は¶
「小さく分ける」ベストプラクティスを妨害する
技術種別ディレクトリは「分類のための分類」であり、実際の変更境界と一致しないためモジュールの凝集度が低下する。
問題: 技術種別ディレクトリの構造¶
src/
├── atoms/
│ ├── Button.tsx ← UserCard に関係
│ └── Avatar.tsx ← UserCard に関係
├── hooks/
│ ├── useUser.ts ← UserCard に関係
│ └── useCart.ts ← Cart に関係
├── utils/
│ └── formatDate.ts
└── components/
└── UserCard.tsx
UserCard を変更するとき atoms/・hooks/・components/ を行き来しなければならない。変更境界とディレクトリ境界がズレている。
改善: 機能・責務単位のディレクトリ¶
src/
├── user/
│ ├── UserCard.tsx
│ ├── Avatar.tsx ← UserCard に凝集
│ ├── useUser.ts ← UserCard に凝集
│ └── index.ts ← 公開 API を明示
├── cart/
│ ├── CartList.tsx
│ ├── useCart.ts
│ └── index.ts
└── shared/
└── formatDate.ts
機能単位でまとめることで、変更が 1 ディレクトリ内で完結する。
技術種別 vs 機能単位の比較¶
| 観点 | 技術種別(atoms/hooks/) | 機能単位(user/cart/) |
|---|---|---|
| 変更の局所性 | 複数ディレクトリを横断する | 1 ディレクトリ内で完結 |
| 凝集度 | 低い(種類ごとに散らばる) | 高い(関連するものが近い) |
| 削除のしやすさ | どこを消せばいいか不明瞭 | ディレクトリごと削除できる |
| オーナーシップ | 不明確 | 明確(このディレクトリの担当) |
アクセス制御の補強(eslint-plugin-import-access)¶
-
index.tsで公開する API のみをエクスポートし、内部実装を隠す -
eslint-plugin-import-accessで@packageアノテーションによるアクセス制限が可能 -
他の機能から直接内部ファイルを import するのを Lint で検出できる
要点¶
-
「分類のための分類」は変更の局所性を損ない、ファイル間の移動コストを増やす
-
コンポーネント・hooks・ユーティリティを機能単位でまとめることで凝集度が上がる
-
「再利用のために小さく分ける」ではなく「変更境界に合わせて分ける」が正しい指針