コールチェーンのいくつかのレベルでのみ使用される(パラメータを渡す)パターンの名前はありますか?


209

私はいくつかのレガシーコードでグローバル変数を使用する代替案を見つけようとしていました。しかし、この質問は技術的な選択肢に関するものではなく、私は主に用語について心配しています

明らかな解決策は、グローバルを使用する代わりに関数にパラメーターを渡すことです。このレガシーコードベースでは、値が最終的に使用されるポイントと最初にパラメーターを受け取る関数の間で、長い呼び出しチェーン内のすべての関数を変更する必要があることを意味します。

higherlevel(newParam)->level1(newParam)->level2(newParam)->level3(newParam)

どこnewParam私の例では、グローバル変数以前だったが、それは代わりに、以前にハードコード値だったかもしれません。ポイントは、newParamの値がで取得されhigherlevel()、で「移動」する必要があることlevel3()です。

値を変更せずに「渡す」多くの関数にパラメータを追加する必要があるこのような状況/パターンの名前があるかどうか疑問に思っていまし

うまくいけば、適切な用語を使用することで、再設計のソリューションに関するリソースをさらに見つけ、同僚にこの状況を説明できるようになります。


94
これは、グローバル変数の使用よりも改善されています。これにより、各関数がどの状態に依存しているかが明確になります(純粋な関数への道の一歩です)。パラメータの「スレッド化」と呼ばれると聞きましたが、この用語がどれほど一般的かはわかりません。
ガーデンヘッド

8
これは、特定の答えを得るには広すぎるスペクトルです。このレベルでは、これを単に「コーディング」と呼びます。
マチャド

38
「問題」は単なる単純さだと思います。これは基本的に依存性注入です。より深くネストされたメンバーが関数のパラメーターリストを肥大化させることなく、自動的にチェーンを介して依存関係を挿入するメカニズムがあると思います。高度なレベルの異なる依存性注入ストラテジーを調べると、用語がある場合はそれを探しているかもしれません。
nullの

7
それが良いパターン/アンチパターン/コンセプト/ソリューションであるかどうかについての議論に感謝しますが、私が本当に知りたいのは、その名前があるかどうかです。
ecerulm

3
私もそれが呼ばれる聞いたスレッドの最も一般的でなく、配管の低下のように、鉛直線をコールスタック全体。
wchargin

回答:


202

データ自体は「トランプデータ」と呼ばれます。これは「コードのにおい」であり、あるコードが他のコードと距離を置いて仲介者を介して通信していることを示します。

  • 特に呼び出しチェーンで、コードの剛性を高めます。呼び出しチェーン内のメソッドをリファクタリングする方法については、さらに制約があります。
  • データ/メソッド/アーキテクチャに関する知識を、あまり気にしない場所に配布します。ただ通過するデータを宣言する必要があり、その宣言に新しいインポートが必要な場合、名前空間が汚染されています。

グローバル変数を削除するためのリファクタリングは困難であり、トランプデータはその1つの方法であり、多くの場合最も安価な方法です。コストがかかります。


73
「トランプデータ」を検索することで、Safariサブスクリプションで「Code Complete」ブックを見つけることができました。この本には「グローバルデータを使用する理由」というセクションがあり、その理由の1つは「グローバルデータを使用すると不法データを排除できる」ことです。:)。「トランプデータ」によって、グローバルの取り扱いに関するより多くの文献を見つけることができると思います。ありがとう!
ecerulm

9
@JimmyJames、これらの関数はもちろん何かをします。以前は単にグローバルであった特定の新しいパラメーターではありません。
ecerulm

174
プログラミングの20年で、私は文字通りこの用語を聞いたことがなく、その意味がすぐに明らかになることもなかったでしょう。私は答えについて文句を言っているのではなく、この用語がそれほど広く使われていない/知られているわけではないことを示唆しているだけです。たぶんそれは私だけです。
デレクエルキンズ

6
一部のグローバルデータは問題ありません。「グローバルデータ」と呼ぶ代わりに、「環境」と呼ぶことができます。環境には、たとえば、appdataの文字列パス(Windows)、または現在のプロジェクトでは、すべてのコンポーネントで使用されるGDI +ブラシ、ペン、フォントなどのセット全体が含まれます。
ロビンソン

7
@Robinsonまったくそうではありません。たとえば、コードを書く画像に本当に%AppData%をタッチさせたいのか、それともどこに書くかを引数に取りたいですか?それがグローバル状態と引数の違いです。「環境」は、同様に簡単に注入された依存関係になる可能性があり、環境との対話を担当する人にのみ存在します。GDI +ブラシなどはより合理的ですが、それはあなたのためにそれを行うことができない環境でのリソース管理の場合です-基本的なAPIおよび/またはあなたの言語/ライブラリ/ランタイムのかなりの欠陥。
ルアーン

102

これ自体はアンチパターンだとは思いません。問題は、実際にはそれぞれを独立したブラックボックスと考える必要があるときに、関数をチェーンと考えていることだと思います(:再帰的メソッドは、このアドバイスの顕著な例外です)。

たとえば、2つのカレンダー日付の間の日数を計算する必要があるとしましょう。関数を作成します。

int daysBetween(Day a, Day b)

これを行うために、新しい関数を作成します。

int daysSinceEpoch(Day day)

それから私の最初の機能は簡単になります:

int daysBetween(Day a, Day b)
{
    return daysSinceEpoch(b) - daysSinceEpoch(a);
}

これについてアンチパターンはありません。daysBetweenメソッドのパラメーターは別のメソッドに渡されており、メソッド内で参照されることはありませんが、そのメソッドが必要なことを行うためには引き続き必要です。

私がお勧めするのは、各機能を見て、いくつかの質問から始めることです。

  • この機能には明確で焦点の合った目標がありますか、それとも「何かをする」方法ですか?通常、ここでは関数の名前が役立ちます。名前で記述されていないものがある場合、それは赤旗です。
  • パラメーターが多すぎますか?メソッドが合法的に大量の入力を必要とする場合もありますが、非常に多くのパラメーターがあるため、使用や理解が面倒になります。

メソッドにバンドルされた単一の目的のないごちゃ混ぜのコードを見ている場合、それを解くことから始めるべきです。これは面倒です。最も簡単なものから始めて、別の方法に移動し、一貫性のあるものになるまで繰り返します。

パラメーターが多すぎる場合は、メソッドからオブジェクトへのリファクタリングを検討してください。


2
まあ、私は(反)と物議を醸すつもりはなかった。しかし、多くの関数シグネチャを更新しなければならない「状況」の名前があるのだろうかと思います。アンチパターンというより「コードの匂い」だと思います。グローバルの削除に対応するために6つの関数のシグネチャを更新する必要がある場合、このレガシーコードに修正する必要があることがわかります。しかし、私は通常、パラメータを渡すことは大丈夫だと思うので、根本的な問題にどのように取り組むかのアドバイスを感謝します。
ecerulm

4
@ecerulm私はそれを知りませんが、私の経験から、グローバルをパラメータに変換することは絶対にそれらを排除し始める正しい方法であると言われます。これにより、共有状態が排除されるため、さらにリファクタリングできます。このコードにはさらに多くの問題があると思いますが、説明にはそれらが何であるかを知るのに十分ではありません。
ジミージェームズ

2
私も通常、このアプローチに従いますが、おそらくこの場合も同様です。私はこれについて語彙/用語を改善したかったので、これについてさらに研究し、将来、より焦点を絞った質問をすることができます。
ecerulm

3
@ecerulmこれには一つの名前はないと思います。それは、多くの病気や、「口渇」などの非病気の状態に共通する症状のようなものです。コードの構造の説明を具体化すると、特定の何かを指す場合があります。
ジミージェームズ

@ecerulm修正すべきものがあることを示します-何かが修正されることは、それが代わりにグローバル変数であったときよりもはるかに明白です。
イミビス

61

BobDalgleishは、この(アンチ)パターンが「トランプデータ」と呼ばれることをすでに指摘ています。

私の経験では、過剰なトランプデータの最も一般的な原因は、オブジェクトまたはデータ構造に実際にカプセル化されるリンクされた状態変数の束を持っていることです。場合によっては、データを適切に編成するために、オブジェクトの束をネストする必要さえあります。

簡単な例では、同様の性質を持つカスタマイズ可能なプレイヤーキャラクターを持っているゲームを、検討しplayerNameplayerEyeColorその上と。もちろん、プレーヤーはゲームマップ上の物理的な位置、および現在および最大の健康レベルなどのさまざまな他のプロパティも持っています。

このようなゲームの最初のイテレーションでは、これらのすべてのプロパティをグローバル変数にすることは完全に合理的な選択かもしれません。したがって、グローバル状態には次のような変数が含まれる場合があります。

playerName = "Bob"
playerEyeColor = GREEN
playerXPosition = -8
playerYPosition = 136
playerHealth = 100
playerMaxHealth = 100

しかし、ある時点で、おそらくゲームにマルチプレイヤーモードを追加するために、このデザインを変更する必要があることに気付くかもしれません。最初の試みとして、これらすべての変数をローカルにし、それらを必要とする関数に渡すことができます。ただし、ゲーム内の特定のアクションには、次のような関数呼び出しチェーンが含まれる場合があります。

mainGameLoop()
 -> processInputEvent()
     -> doPlayerAction()
         -> movePlayer()
             -> checkCollision()
                 -> interactWithNPC()
                     -> interactWithShopkeeper()

...このinteractWithShopkeeper()機能では、店主がプレーヤーを名前でアドレス指定するため、突然これらすべての機能をplayerName介してトランプデータとして渡す必要があります。そして、もちろん、店主が青い目をしたプレイヤーがナイーブだと思っており、それらに対してより高い価格を請求する場合、機能のチェーン全体を通過する必要があります。playerEyeColor

この場合の適切な解決策は、もちろん、プレイヤーキャラクターの名前、目の色、位置、健康、およびその他のプロパティをカプセル化するプレイヤーオブジェクトを定義することです。そうすれば、その単一のオブジェクトを、何らかの形でプレーヤーに関係するすべての関数に渡すだけで済みます。

また、上記の関数のいくつかは、そのプレーヤーオブジェクトのメソッドに自然に作成できます。これにより、プレーヤーのプロパティに自動的にアクセスできるようになります。ある意味、これは単なるシンタックスシュガーです。オブジェクトのメソッドを呼び出すと、オブジェクトインスタンスがメソッドに隠されたパラメーターとして効果的に渡されますが、適切に使用するとコードがより明確で自然に見えます。

もちろん、典型的なゲームは、単なるプレーヤーよりもはるかに「グローバル」な状態になります。たとえば、ほぼ確実にゲームが行われる何らかのマップがあり、マップ上を移動するノンプレイヤーキャラクターのリストと、おそらくその上に置かれたアイテムがあります。これらすべてをtrampオブジェクトとして渡すこともできますが、それでもメソッドの引数が乱雑になります。

代わりに、解決策は、永続的または一時的な関係を持つ他のオブジェクトへの参照をオブジェクトに保存させることです。したがって、たとえば、プレイヤオブジェクトは、のような方法がなるように、現在のレベル/マップへの参照を持っているでしょう「ゲームの世界」オブジェクトへの参照を格納する必要があるだろう(そしておそらくどのNPCがあまりにもオブジェクト)player.moveTo(x, y)には必要ありません。マップをパラメーターとして明示的に指定します。

同様に、プレイヤーキャラクターがペットの犬を追いかけた場合、犬を記述するすべての状態変数を自然に1つのオブジェクトにグループ化し、プレイヤーオブジェクトに犬への参照を与えます(プレイヤーが、たとえば、名前で犬を呼び出す)、またはその逆(犬がプレーヤーの位置を知るため)。そしてもちろん、プレーヤーと犬のオブジェクトを両方ともより一般的な「俳優」オブジェクトのサブクラスにしたいので、同じコードを再利用して、たとえば両方をマップ上で移動することができます。

追伸 例としてゲームを使用しましたが、そのような問題が発生する他の種類のプログラムもあります。しかし、私の経験では、根本的な問題は常に同じである傾向があります。1つまたは複数の相互リンクされたオブジェクトに本当にまとめたい個別の変数(ローカルまたはグローバル)がたくさんあります。関数に侵入する「トランプデータ」が数値シミュレーションの「グローバル」オプション設定またはキャッシュされたデータベースクエリまたは状態ベクトルで構成されているかどうかにかかわらず、解決策は常にデータが属する自然なコンテキストを識別し、それをオブジェクトにすることです(または選択した言語で最も近いものは何でも)。


1
この回答は、存在する可能性のある問題のクラスに対するいくつかの解決策を提供します。別の解決策を示すグローバルが使用される状況があるかもしれません。メソッドをプレーヤークラスの一部にすることは、オブジェクトをメソッドに渡すことと同等であるという考えに問題があります。これは、この方法で簡単に複製できない多型を無視します。たとえば、動きと異なる種類のプロパティに関する異なるルールを持つ異なる種類のプレーヤーを作成する場合、これらのオブジェクトを1つのメソッド実装に渡すだけでは、多くの条件付きロジックが必要になります。
ジミージェームズ

6
@JimmyJames:ポリモーフィズムについてのあなたのポイントは良いものです。私は自分でそれを作ることを考えましたが、答えがさらに長くならないようにそれを省きました。私が(おそらく不十分に)しようとしていたポイントは、純粋にデータフローの面ではとの間にはほとんど違いがfoo.method(bar, baz)ありませんmethod(foo, bar, baz)が、前者を好む他の理由(多型、カプセル化、局所性など)があるということです。
イルマリカロネン

@IlmariKaronen:また、オブジェクト(playerAgeなど)の将来の変更/追加/削除/リファクタリングから関数プロトタイプを将来的に保証するという非常に明白な利点もあります。これだけでも非常に貴重です。
-smci

34

私はこれの特定の名前を知りませんが、あなたが説明する問題はそのようなパラメータの範囲のための最良の妥協点を見つける問題であることに言及する価値があると思います:

  • グローバル変数として、プログラムが特定のサイズに達するとスコープが大きすぎます

  • 純粋にローカルなパラメーターとして、スコープが小さすぎる可能性があります。これは、コールチェーン内に多数のパラメーターリストが繰り返される場合に発生します。

  • そのため、トレードオフとして、このようなパラメーターを1つ以上のクラスのメンバー変数にすることができます。これを適切なクラス設計と呼んでいます


10
適切なクラス設計のために+1。これはオブジェクト指向ソリューションを待っている古典的な問題のように聞こえます。
l0b0

21

あなたが説明しているパターンは、まさに依存性注入です。これはアンチパターンではなく、パターンであると主張するコメント者もいますが、私は同意する傾向があります。

また、@ JimmyJamesの答えにも同意します。彼は、各関数をすべての入力を明示的なパラメーターとして受け取るブラックボックスとして扱うのが良いプログラミング手法であると主張しています。つまり、ピーナッツバターとゼリーのサンドイッチを作る関数を書いている場合、次のように書くことができます。

Sandwich make_sandwich() {
    PeanutButter pb = get_peanut_butter();
    Jelly j = get_jelly();
    return pb + j;
}
extern PhysicalRefrigerator g_refrigerator;
PeanutButter get_peanut_butter() {
    return g_refrigerator.get("peanut butter");
}
Jelly get_jelly() {
    return g_refrigerator.get("jelly");
}

ただし、依存関係の注入を適用して、代わりに次のように記述する適切です

Sandwich make_sandwich(Refrigerator& r) {
    PeanutButter pb = get_peanut_butter(r);
    Jelly j = get_jelly(r);
    return pb + j;
}
PeanutButter get_peanut_butter(Refrigerator& r) {
    return r.get("peanut butter");
}
Jelly get_jelly(Refrigerator& r) {
    return r.get("jelly");
}

これで、関数シグネチャにすべての依存関係を明確に文書化する関数ができました。これは読みやすくするのに最適です。結局のところ、本当のためににていることmake_sandwichへのアクセスを必要としますRefrigerator。そのため、古い関数シグネチャは基本的に、冷蔵庫を入力の一部として受け取らないことで不誠実でした。

ボーナスとして、クラス階層を正しく行い、スライスを避けるなどの場合はmake_sandwichMockRefrigerator!(ユニットテスト環境ではPhysicalRefrigeratorsにアクセスできない可能性があるため、この方法でユニットテストする必要がある場合があります。)

依存関係注入のすべての使用がコールスタックの多くのレベルで同様の名前のパラメーターを配管する必要があるわけではないことを理解していますので、私はあなたが尋ねた質問に正確に答えていません... 「依存性注入」は間違いなくあなたに関連するキーワードです。


10
これは明らかにアンチパターンです。冷蔵庫を渡す必要はまったくありません。さて、汎用のIngredientSourceを渡すことは機能するかもしれませんが、パンをブレッドビンから、マグロをラーダーから、チーズを冷蔵庫から...入手した場合、原料のソースへの依存関係を、それらを形成する無関係な操作に注入することによって材料をサンドイッチにすると、懸念の分離に違反し、コードの匂いがします。
デウィモーガン

8
@DewiMorgan:もちろん、さらにリファクタリングしてをに一般化しRefrigeratorたり、IngredientSource「サンドイッチ」の概念を一般化したりすることさえできtemplate<typename... Fillings> StackedElementConstruction<Fillings...> make_sandwich(ElementSource&)ます。これは「ジェネリックプログラミング」と呼ばれ、かなり強力ですが、確かに、OPが現時点で本当に望んでいるよりもはるかに難解です。サンドイッチプログラムの適切な抽象化レベルに関する新しい質問を自由に開いてください。;)
Quuxplusone

11
間違いがないように、特権のないユーザーはにアクセスしてはいけませんmake_sandwich()
-dotancohen

2
@Dewi-XKCD リンク
Gavin Lock

19
コードの最も重大なエラーは、ピーナッツバターを冷蔵庫に入れていることです。
マルヴォリオ

15

これは、ほとんどがカップリングの教科書の定義です。あるモジュールは、別のモジュールに深く影響する依存関係を持ち、変更すると波及効果を生み出します。他のコメントと回答は、これがグローバルよりも改善されていることを示しています。これは、カップリングが破壊的ではなく、より明確になり、プログラマにとって見やすくなったためです。それはそれが修正されるべきではないという意味ではありません。カップリングを削除または削減するためにリファクタリングできる必要がありますが、しばらくそこにあると痛みを伴う場合があります。


3
level3()必要な場合newParam、それは確かに結合ですが、何らかの形でコードの異なる部分が互いに通信する必要があります。その関数がパラメーターを使用する場合、関数パラメーターの悪い結合を必ずしも呼び出すとは限りません。チェーンの問題のある側面は、導入された追加のカップリングでlevel1()あり、level2()それnewParamを渡す以外に役に立たないと思います。良い答え、カップリングに対して+1。
nullの

6
@null 本当に使用しなかった場合、呼び出し元から受け取る代わりに値を作成できます。
Random832

3

この答えはあなたの質問に直接答えるものではありませんが、それを改善する方法について言及せずに通過させることはできません(あなたが言うように、それはアンチパターンかもしれません)。「トランプデータ」を避ける方法に関するこの追加の解説から、あなたや他の読者が価値を得られることを望みます(ボブダルグレイッシュが私たちのために非常に役立つ名前を付けたので)。

この問題を回避するために、オブジェクト指向をさらに行うことを提案する回答に同意します。ただし、単に「多くの引数を渡すために使用したクラスを渡すだけ!」にジャンプすることなく、この引数の受け渡しを深く減らす別の方法は、プロセスの一部のステップが下位ではなく上位で発生するようにリファクタリングすることです1。たとえば、次の変更前のコードを次に示します。

public void PerformReporting(StuffRepository repo, string desiredName) {
   var stuffs = repo.GetStuff(DateTime.Now());
   FilterAndReportStuff(stuffs, desiredName);
}

public void FilterAndReportStuff(IEnumerable<Stuff> stuffs, string desiredName) {
   var filter = CreateStuffFilter(FilterTypes.Name, desiredName);
   ReportStuff(stuffs.Filter(filter));
}

public void ReportStuff(IEnumerable<Stuff> stuffs) {
   stuffs.Report();
}

これはでしなければならないことが多いほど悪化することに注意してくださいReportStuff。使用するレポーターのインスタンスを渡す必要がある場合があります。そして、引き継がなければならないあらゆる種類の依存関係、関数からネストされた関数へ。

私の提案は、すべてをより高いレベルに引き上げることです。ステップの知識は、メソッド呼び出しのチェーン全体に広がるのではなく、単一のメソッドでの生活を必要とします。もちろん、実際のコードではより複雑になりますが、これによりアイデアが得られます。

public void PerformReporting(StuffRepository repo, string desiredName) {
   var stuffs = repo.GetStuff(DateTime.Now());
   var filter = CreateStuffFilter(FilterTypes.Name, desiredName);
   var filteredStuffs = stuffs.Filter(filter)
   filteredStuffs.Report();
}

ここでの大きな違いは、依存関係を長いチェーンで渡す必要がないことです。1レベルだけでなく、数レベルの深さまでフラット化しても、それらのレベルが「フラット化」を達成し、プロセスがそのレベルでの一連のステップと見なされる場合は、改善されています。

これはまだ手続き的であり、オブジェクトに変換されたものはまだありませんが、何かをクラスに変換することで達成できるカプセル化の種類を決定するための良いステップです。beforeシナリオの深く連鎖したメソッド呼び出しは、実際何が起こっているかの詳細を隠し、コードを非常に理解しにくくします。あなたはこれをやりすぎて、高レベルのコードにすべきでないことを知らせるか、またはあまりにも多くのことを行い、単一責任の原則に違反するメソッドを作成することができますが、一般的に、物事を平らにすることは少し助けになることがわかりましたより明確なコードとより良いコードに向けて段階的に変更を加えること。

このすべてを実行している間、テスト可能性を考慮する必要があることに注意してください。チェーンメソッド呼び出しは、テストするスライスのアセンブリに適切なエントリポイントと出口ポイントがないため、実際にユニットテストを難しくします。このフラット化を使用すると、メソッドがそれほど多くの依存関係を取得しなくなるため、テストが簡単になり、モックをそれほど必要としません。

私は最近、17個の依存関係のようなものをとるクラスにユニットテストを追加しようとしました(私は書きませんでした)。私はそれがすべてうまくいったわけではありませんが、クラスを3つのクラスに分割し、それぞれが関係する別々の名詞の1つを処理し、最悪の場合は12に、依存関係のリストは約8に下げました一番いいもの。

テスト容易性は、より良いコードを書くことを強制します。ユニットテストを書く前に持っていたバグの数に関係なく、コードについて違った考え方をするようになり、最初からより良いコードを書くことがわかるので、ユニットテストを書くべきです。


2

あなたは文字通りデメテルの法則に違反していませんが、あなたの問題はいくつかの点でそれに似ています。質問のポイントはリソースを見つけることなので、デメテルの法則について読んで、そのアドバイスがあなたの状況にどの程度当てはまるかを確認することをお勧めします。


1
詳細についてはやや弱いが、これはおそらくダウン投票を説明している。しかし、精神的には、この答えはまさに次の点に当てはまります。OPはデメテルの法則を読んでください。これは関連用語です。
コンラッドルドルフ

4
FWIW、デメテルの法則(別名「最小特権」)はまったく関係ないと思います。OPの場合は、関数トランプデータを持っていないと、その機能を実行できません(コールスタックの次の人が必要とするため、次の人がそれを必要とするためなど)。最小特権/デメテルの法則は、パラメーターが実際に使用されていない場合にのみ関連し、その場合、修正は明らかです:未使用のパラメーターを削除してください!
Quuxplusone

2
この質問の状況は、デメテルの法則とはまったく関係ありません...メソッド呼び出しのチェーンに関して表面的な類似性がありますが、それ以外は非常に異なります。
エリックキング

@Quuxplusone可能ですが、この場合、チェーンコールはそのシナリオでは実際には意味をなさないため、説明は非常に混乱します。代わりにネストする必要があります。
コンラッドルドルフ

1
この問題、LoD違反に非常によく似ています。LoD違反に対処するために推奨される通常のリファクタリングは、トランプデータの導入です。私見、これはカップリングを減らすための良い出発点ですが、十分ではありません。
ヨルゲンフォー

1

常にすべてを渡すオーバーヘッドではなく、特定の変数をグローバルとして持つのが(効率、保守性、実装の容易さの点で)最適な場合があります(永続化する必要のある15個ほどの変数があるとします)。そのため、潜在的な混乱(名前空間と改ざんのあるもの)を軽減するために、(C ++のプライベート静的変数として)より有効なスコープをサポートするプログラミング言語を見つけることは理にかなっています。もちろん、これは単なる常識です。

しかし、OPで述べられているアプローチは、関数型プログラミングを行う場合に非常に役立ちます。


0

発信者は以下のこれらすべてのレベルを知らず、気にしないので、ここにはアンチパターンはありません。

誰かがhigherLevel(params)を呼び出しており、higherLevelがその仕事をすることを期待しています。higherLevelがparamsで行うことは、呼び出し側ビジネスではありません。higherLevelは、この場合はparamsをlevel1(params)に渡すことで、できる限り最善の方法で問題を処理します。それは絶対に大丈夫です。

コールチェーンが表示されますが、コールチェーンはありません。最上部には、できる限り最善の方法で仕事をする機能があります。他にも機能があります。各機能はいつでも置き換えることができます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.