(jp) =
Laravel 開発者として、私はサービス ロケーター パターンに毎日直面しています。 すべてのファサード呼び出しといくつかのヘルパー関数は、その上に構築されています。
一般的なファサード呼び出しを見てみましょう。 Auth::user()
. の Auth
ファサードは Laravel のサービス コンテナーに到達し、登録されたコンポーネントを取得して、静的呼び出しをそのコンポーネントに転送します。 この場合、ログインしているユーザーが返されます。
同僚との話し合いの中で、コンテナー (サービス ロケーター) から物を取り出すことの正確な問題点を言葉で説明するのが難しいことがわかったので、例を挙げて自分の考えを書き留めることにしました: クラス CreatePostAction
これは、一連のパラメーターに基づいてブログ投稿を作成する役割を果たします。
class CreatePostAction
public function __invoke(
string $title,
string $body
): Post
return Post::create([
'title' => $title,
'body' => $body,
'author_id' => Auth::user()->id,
]);
このアプローチには、サービス ロケーターの使用によって直接引き起こされる 3 つの問題があることを強調したいと思います。
- 実行時エラーが発生する可能性が高くなります。
- コードは外部に対して難読化されています。
- それは認知負荷を増加させます。
これらの問題を 1 つずつ見ていきましょう。
# ランタイム – コンパイル時エラーの代わりに
この最初の問題を検討する前に、1 つの仮定を立てます。 つまり、開発者として、コードのバグをできるだけ早く知り、できるだけ早く修正できるようにしたいということです。
プロダクション プロジェクトが壊れているとクライアントから言われ、いくつかの手順を実行することによってのみ問題を再現できるという状況が気に入らないと仮定します。
名前が示すように、実行時エラーはプログラムを実行することによってのみ発見できます。 本当のことを言うと: PHP、解釈された言語であること; この種のエラーに大きく傾いています。 かどうかを知ることはできません PHP プログラムは、実行する前に動作します。
それは何も悪いことではありませんが、ここでの私の主張は、これらのエラーを回避できるすべての場所で、回避すべきであるということです.
コンパイル時エラーは、コードを実行しなくても検出できるエラーです。 例: IDE または静的分析ツールを使用します。 利点は、コードをテストしなくても、コードが確実に機能することを知っていることです。
それを実践してみましょう。 何が Auth::user()
戻る? ログインしたA User
-ほとんどの時間。
私たちのアクションクラスは、それが存在するシステムについて、私たちが伝えること以外は何も知りません。 これは、呼び出し時に Auth::user()->id
、周囲のシステムにログインしているユーザーがいると仮定します。 id
.
もちろん、あなたの最初の考えは、 知る このアクションは、ログインしているユーザーを必要とするコントローラー内で呼び出されるためです。 その議論については後ほど触れます。
今のところ、数学的な観点から言えば、 Auth::user()->id
実行しなくても動作します。 アクションの観点から、これを修正するには 2 つの方法があります。
実行時チェックを行うことにより:
class CreatePostAction
public function __invoke(
string $title,
string $body
): Post
if (! Auth::user())
throw new Exception('…');
または、実行する前に有効なユーザーを要求することによって:
class CreatePostAction
public function __invoke(
string $title,
string $body,
User $author
): Post
私 知る なぜこれが決して起こらないのかという議論があり、私はそれについて心配するべきではありません。 これらの議論については、すぐに説明します。
# 難読化されたクラス
最大の問題を検討する前に、サービス ロケーターが認知負荷に与える影響について説明します。 難読化されたクラスの問題があります。 アクションの定義を見てみましょう。
class CreatePostAction
public function __invoke(
string $title,
string $body
): Post
これについては、すでにブログで何度も話してきましたが、開発者はコードのすべての行を読むのではなく、スキャンします。
コードを書いている時点では、すべてが明白に見えます。 知る ブログ投稿にはログイン ユーザーが必要です。 ただし、レガシー コードで作業している開発者にとって、その意図は明確ではありません。 彼がコードのすべての行を読んでいる場合を除きます。
その人を想像してみてください。何が起こっているのかを大まかに把握するために、コードのすべての行を読む必要があるレガシー プロジェクトで作業しなければなりません。
投稿がどのように作成されるかについての詳細には関心がないかもしれませんが、そのために何が必要かを知りたいだけです。 この問題を解決するには 2 つの方法があります。
docblock を使用している。 つまり、作成者と読者の両方にとってより多くの作業が必要になり、コードが乱雑になります。
class CreatePostAction
public function __invoke(
string $title,
string $body
): Post
または、ユーザーを注入することによって:
class CreatePostAction
public function __invoke(
string $title,
string $body,
User $author
): Post
あなたはどちらを好みますか? 覚えておいてください: レガシー プロジェクトで作業している人の観点からすると、それは 1 つのクラスだけではなく、何十ものクラスがあります。
# 認知負荷の増加
これはすべて、最終的かつ主要な問題である認知負荷につながります。 このトピックについてはすでに多くの記事を書いています。この投稿の最後にいくつかのリンクを掲載します。
サービスロケーターの賛成論のほとんどに反論する重要な質問。 は、開発者であるあなたが次のような些細な質問に費やす必要がある脳の労力です。
このコードが実際に機能するという確信はありますか?
最も基本的な例を見てみましょう。 Auth::user()->id
. 私は Laravel プロジェクトに取り組んでおり、このコードを何度も使用したことを認めています。 以下は、このコードを書いているときに頭に浮かんだ質問の非網羅的なリストです。
- この時点でユーザーがログインしていることは確かですか?
- 確かに、追加のチェックを追加する必要がありますか?
- このメソッドはどのコンテキストから呼び出されますか?
- プロジェクトの範囲内で考慮しなければならない将来の機能はありますか?
- これが将来壊れないことを確認するためにテストを追加する必要がありますか?
これらはすべてとても些細な質問であり、私はそれらについて考える必要があります 毎日 ファサードを使用するとき。 次のように簡単に言うことができます。
私 必要 ログインしたユーザーがこのアクションを実行し、このアクションを呼び出しているコンテキストがそこからそれを把握できます。
class CreatePostAction
public function __invoke(
string $title,
string $body,
User $author
): Post
確かに、コンパイル時のエラーと少ないコードは良いことですが、私の主な問題はこの認知負荷です。 ファサードを使用するたびに、これらすべての質問をしたくありません。
ベテランの Laravel 開発者は、これがフレームワークの仕組みであり、採用すべきだと教えてくれます。 もちろん、彼らは正しいです。 しかし、「うまくいく」という仮定を立てることは、私には十分ではありません。 少なくとも、周囲の状況について多くの疑問が残るため、認知負荷の増加に対する議論にはなりません。
# 依存性注入で解決
もちろん、依存性注入。 これを修正します。 制御の反転を可能にし、意図を明確にするパターンです。 Laravel で適切な DI を行うことも完全に可能です。 そして、私の意見では、もっとやるべきです。
DI については以前に書いたことがありますので、こちらでお読みください。 また、最近、視覚的な観点からの認知負荷についての講演を行いました。 ここで見つけることができます。