問題
時々、引数の数が可変の関数を書く必要があります。 これは特にライブラリに当てはまります。90%のケースで適切な関数を呼び出す簡単な方法があり、追加の設定やデータを転送する必要がある場合、残りの10%で必要な複雑なものがあります。
例はjQuery.getです。これは、$ .get(url、コールバック)または$ .get(url、データ、コールバック)として呼び出すことができます。
JavaScriptは( Pythonなどとは異なり)引数を操作する手段が特に豊富ではないため、jQuery.getなどのインターフェイスを使用して関数を実装するには、次のように記述する必要があります。
function openTheDoor(door, options, callback) { if (typeof options === 'function') { callback = options options = {} } // var handlePosition = door.getHandlePosition() // ... }
このコードが悪いのはなぜですか?
第一に、関数の先頭は意味的な負荷を持たず、引数を渡す方法の単なるアドオンです。 関数の機能を確認するには、頭の4行のコードを「スクロール」する必要があります。
第二に、ジャグリングとパラメーターを組み合わせてデフォルト値を割り当てる誘惑があり、これは横向きになる可能性があります-次回の初期化は、標準の定型文のように単に「スクロール」されます。
同様のアプローチは、非同期で動作する多くのライブラリのコードで見つけることができます。 この問題には解決策が必要です。
悪い決断
npmレジストリには、 引数オブジェクトを処理することで問題を解決するモジュールがかなりあります。 コードは次のようになります。
var somehowParse = require('some-fancy-args') function openTheDoor() { var args = somehowParse(arguments) // // var handlePosition = args.first.getHandlePosition() // ... }
一部のライブラリは、小さな「パラメータ定義言語」を提供します。
var parseArgs = require('another-fancy-args') function openTheDoor() { var args = parseArgs(['door|obj', 'options||obj', 'callback||func'], arguments) // // , var handlePosition = args.door.getHandlePosition() // ... }
また、元の関数を2回呼び出して引数を正しく設定するマジックライブラリもあります。そのため、匿名関数内でコードを記述し、これをライブラリ呼び出しに渡す必要があります。
var magicArgs = require('oh-so-magic-args') function openTheDoor(door, options, callback) { return magicArgs(this, ['obj', '|obj', '|func'], function () { // // var handlePosition = door.getHandlePosition() // ... }) }
これらの決定の何が悪いのですか? はい、ほとんどすべて。
いずれにしても、関数の先頭はテンプレートのままであり、コードに到達するために「スクロール」する必要があります。 このテンプレートは、追加レベルの抽象化、時には魔法を導入します。これらは個別に理解する必要があります。
非魔法のライブラリーは、関数自体のコードも少し損なわれます。パラメーターは、通常のように個別の変数の形ではなく、オブジェクトの形で提供され、パラメーターは常に名前を持っているわけではありません。
良い解決策への道
機能について話しましょう。
まず、適切に作成された関数にはパラメーターが多すぎてはなりません。 関数に3つ以上のパラメーターがある場合、おそらくリファクタリングが必要です。 複雑な関数はいくつかの個別の関数に分割でき、いくつかのパラメーターを1つのパラメーターオブジェクトにグループ化できます。また、何らかの方法で単純化して書き換えることもできます(トピック「リファクタリング」。既存の Martin Fowler コードの改善 )。
第二に、JavaScriptは欠落しているパラメーターを操作するために単純で論理的なスキームを使用します。 関数の呼び出し時にパラメーターに値が渡されない場合、未定義の値が使用されます。 デフォルトのパラメーター値を指定するには、options = options ||という形式の構成を使用すると便利です。 {}。 [1]
第三に、非同期コールのプログラミングを簡素化する「コールバックが最後になる」という合意があります。 ほとんどの場合、パラメーターを調整する必要があるのはこの合意です。コールバックは常に最後に行く必要があるため、オプションのパラメーターはリストの中央に配置する必要があります。
3つの点すべてを考慮に入れると、かなり単純な解決策が得られます。行うべきことは、引数リストに未定義の値を追加して、コールバックが最後の場所に落ちるようにすることだけです。 これは、私が見つけた解決策を実装するために作成したvargs-callbackモジュールの動作です。
Vargsコールバックモジュール
モジュールは、デコレーターとして使用する必要がある唯一の関数をエクスポートします。
共通(名前付き)関数: [2]
var vargs = require('vargs-callback') function openTheDoor(door, options, callback) { // // options undefined, door callback var handlePosition = door.getHandlePosition() // ... } openTheDoor = vargs(openTheDoor) //
式関数:
var vargs = require('vargs-callback') var openTheDoor = vargs(function (door, options, callback) { // - // // options undefined, door callback var handlePosition = door.getHandlePosition() // ... })
vargsデコレータは、装飾された関数が呼び出されたときに起動し、次のことを行います。
- 渡された引数の数が宣言されたパラメーターの数より少なく、最後に渡された引数が「関数」タイプの場合、引数の数とパラメーターの数が一致するまで、最後の引数の前に未定義の値を配置します。
- 引数を変更して装飾された関数を呼び出します。
- 十分な引数が渡されるか、最後の引数が関数でない場合、何もしません。
おわりに
見つかったソリューションでは、次の利点に注目できます。
追加の抽象化レベルは、関数のパラメーターを決定するために使用されません。 マジックや「パラメーター定義言語」は必要ありません。JavaScript自体に含まれるものだけが使用されます。
コードはよりクリーンになります-宣言されたパラメーターが使用され、関数の先頭を「スキップ」する必要がなくなり、それぞれの場合に適切なメソッドを使用してパラメーターのデフォルト値を設定できます。
同僚のアイデアはどうですか?
Githubソースコード