グローバルなリソースを使うな！

# はじめに
**グローバルなリソースを使うな！**

というキャッチーなタイトルをつけてしまいましたが、この記事で伝えたいのは、

**コンポーネント志向でのフロントエンド開発で、安易にグローバルなリソースの利用を許すと、コードの管理ができなくなってしまうので気をつけよう！**

ということです。

:::note info
この記事の内容は Vue.js での実装を前提としますが、他のフロントエンドフレームワークでも応用可能です。
:::

# グローバルなリソースとは
ここで、グローバルなリソースとは、そのプロジェクト内であればどこからでもアクセス可能な状態変数のことを指します。

例えば以下のようなものです。
- URL
- ローカルストレージ
- ストア

# シンプルなコンポーネントツリー
このようなコンポーネントツリーを考えます。
コンポーネント同士が Prop のみで繋がっている状態です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474068/f5036b26-c3bd-34e5-8dda-65192fb8e7ca.png)



# グローバルなリソースへのアクセスを許可している場合
グローバルなリソースへのアクセスを許可している場合、コンポーネントとリソースの関係は以下のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474068/1ea10322-69ee-25dc-5a58-0d51937edc34.png)

Vue CLI を使うと Vuex がデフォルトのストアとしてインストールされるため、API を叩く処理や取得結果をストアに実装しているプロジェクトは多いのではないでしょうか？

そういったプロジェクトでは、この図のような状態になりがちです。

# 何がつらいか？
## デバッグ
注文履歴 UI でバグがあった場合、OrderHistoryPanel (赤枠) をまずデバッグするでしょう。
OrderHistoryPanel がグローバルなリソースを使っていない場合、
- OrderHistoryPanel 自体
- 先祖/子孫コンポーネント
 
を辿ればバグが見つかる可能性が高いです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474068/7bc92672-19c6-2a3c-ead2-9fda09944d48.png)

しかし、グローバルなリソースへのアクセスを許可している場合、
- OrderHistoryPanel 自体
- 先祖/子孫コンポーネント
- グローバルなリソース
- グローバルなリソースを介して依存関係のあるコンポーネント

の全てにバグがある可能性があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474068/75d82052-6dc2-7ee3-c10f-745a85fa142c.png)

当然、デバッグ作業は大変です。
また、**コンポーネントツリーを辿るのは容易**ですが、**グローバルなリソースを仲介して依存関係のあるコードを探し出すのは非常に時間がかかります。** その点も注意しましょう。

## 認知的負荷の増加
デバッグだけでなく、普段の機能追加や修正のときも、非常に広範囲のロジックを考慮しながら、コードを読む必要があります。

このような状況下では、脳のメモリを非常に多く使う、すなわち**認知的負荷が高い状態になってしまいます。**
本来集中したい機能追加や修正に脳のリソースを割けず、生産性が低下します。

## テスト
Prop だけを利用しているコンポーネントであれば、 Storybook で Story を作ったり、Jest でコンポーネントのテストを書くのは容易です。

一方で、グローバルなリソースに依存しているコンポーネントの場合、**Story やテストを書くときに、前提となっているリソースをモックする必要があります。** URL やストア、ローカルストレージをテストごとに準備するのは非常に面倒です。メンテナンスのコストもかかります。

## コンポーネントの再利用
Prop だけを利用しているコンポーネントの再利用は簡単です。前提となっているデータは Prop に明示されているため、それを用意さえすればどこでも再利用することができます。

一方、グローバルなリソースに依存しているコンポーネントの場合、Prop を見るだけでは前提となっているデータを全て洗い出すことが難しいです。**内部実装を見て、何が必要なのかをコードを呼んで理解する必要があります。**

## マイグレーション
- 新しいバージョンのフレームワークへの移行(Vue2 → Vue3)
- 他のフレームワークへの移行(Vue2 → React)

を行うことを考えます。

Prop だけを利用しているコンポーネントを移行する場合、そのコンポーネントの Story やテストを書いて、移行先でもそれが通ることを確認すれば OK です。

一方、グローバルなリソースに依存しているコンポーネントの場合、前述の通り、**まずテストの作成にコストが掛かります。**
テストを書かずに移行するとなると、**依存している部分全てのコードを移行しないと、移行先での動作確認ができません。**
仮に移行したとしても、**バグの発生可能性のあるコードが広範に及ぶため、移行によるバグの修正に非常に時間がかかる可能性があります。**

## さらなる複雑化
一度グローバルなリソースへのアクセスを許可すると、あらゆる場所でグローバルなリソースに依存したコードが増えたり、ストアが肥大化したりするなどして、コードのさらなる複雑化を招きます。

**コードを読む難易度は指数関数的に高くなり、生産性は著しく低下します。**

このような状態になっていても、グローバルなリソースの怖さを認識していなければ、機能が増えたから仕方ない、と放置されることも多いです。

# じゃあどうすればいいの？
## グローバルなリソースを極力使わない
**そもそもグローバルなリソースを使わないのが最善です。**
例えば前述したような、Vuex で API から得たデータを Store にキャッシュするというのは、本当に必要な時以外はすべきでは有りません。

私がかつて開発していた toB のウェブアプリケーションでは **Vuex を使わない方針にしましたが、その後の開発で困ったことは一度もありませんでした。**

## グローバルなリソースへのアクセスを制限する
とはいっても、URL やローカルストレージなどのグローバルなリソースを使わないと行けない場面もあると思います。
そのような状況では、**特定のレイヤーのコンポーネントからのみグローバルなリソースへのアクセスを許可する**というルールを作るといいでしょう。

例えば、Page コンポーネントからのみ、グローバルなリソースへのアクセスを許可し、それ以下のコンポーネントには、Prop を経由して必要な情報を渡すようにします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474068/01c25758-7963-4fa8-226d-a1f47d5fb2ab.png)

eslint などを用いて仕組み化できるとさらに良いです。

## Provide / Inject を用いる
グローバルなリソースへのアクセスを制限する別の方法として、**Vue.js であれば [Provide / Inject](https://ja.vuejs.org/guide/components/provide-inject) を用いる方法が挙げられます。**

あるコンポーネントから Provide したデータは、その子孫コンポーネントでしか Inject できません。
スコープを制限することで、依存関係の複雑化を抑えることができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474068/819d6ed7-a5fe-36f5-a4d8-d2d6e978feb4.png)

Provide / Inject は、**Vue.js の標準 API を使っているので、Storybook や Jest でモックの仕組みなどを作る必要がない**、というのもメリットとして挙げられます。

https://ja.vuejs.org/guide/components/provide-inject

# まとめ
制限なきグローバルなリソースの使用は
- デバッグ
- コードリーディング
- テスト
- 移行

など、あらゆる場面で**非常に大きなコストが掛かり、プロジェクトの崩壊を招きます。**

- **グローバルなリソースは必要な時以外使わない**
- **グローバルなリソースを使う場合は強い制限を設ける**

などの対策を取ることで、**プロジェクトの複雑性を抑えながらスケールさせる**ことができます。

# さいごに
**グローバルなリソースを使うな！**