しかし、過去20年間で、多くが変化しました。 ダグラス・クロックフォードがJavaScriptで作業していたときに見たように、ビルはJavaScriptを見るようになりました。 強み」:美しく、エレガントで表現力豊かな動的プログラミング言語。
この記事では、Billはしばらくの間喜んで使用してきたすばらしい小さなパターンについて話をしたいと考えています。 彼は、この設計パターンが他のプログラマにも役立つことを望んでいます。 ビルは、自分自身をこのパターンの発見者とは見なしていないと言います。むしろ、他の誰かのコードに似たものを見つけ、それを自分のニーズに合わせたという事実です。
ROROパターン
RORO(オブジェクトを受け取る、オブジェクトを返す-オブジェクトを受け取る、オブジェクトを返す)、これは私の機能のほとんどが
object
型の単一のパラメーターを受け入れるパターンであり、それらの多くも
object
型の値を返すか、同様の値によって解決されます。
ES2015に登場した破壊機能のせいもあり、ROROは強力で有用なパターンであることに気付きました。 おそらく特別な方法で強調するために、ROROという名前を付けました。
破壊は、現代のJavaScriptの私のお気に入りの機能の1つであるという事実に注目したいと思います。 このメカニズムの長所については、後ほど説明します。そのため、構造化に慣れていない場合は、このビデオを見て、より興味深い内容を読むことができます。
ROROパターンを好む理由は次のとおりです。
- 名前付き関数パラメーターを使用します。
- 関数のデフォルトのパラメーターをより明確に強調することができます。
- 関数から複雑な値のセットを返すのに役立ちます。
- 関数の構成を簡素化します。
これらのRORO機能のそれぞれを詳しく見てみましょう。
名前付きパラメーター
特定のロール(ロール)を持つユーザー(ユーザー)のリストを返す関数があり、連絡先情報(連絡先情報)に各ユーザーを含めるために必要な引数をこの関数に提供する必要があるとします。非アクティブ(非アクティブ)ユーザーの配信に含めるためのもう1つの引数。 従来のアプローチでは、このような関数の宣言は次のようになります。
function findUsersByRole ( role, withContactInfo, includeInactive ) {...}
この関数の呼び出しは次のようになります。
findUsersByRole( 'admin', true, true )
コードを読むとき、この呼び出しの最後の2つの引数の役割は完全に理解できないことに注意してください。 2つの
true
値はどういう意味ですか?
アプリケーションがユーザーの連絡先情報をほとんど必要とせず、ほとんどの場合、非アクティブなユーザーのデータを必要とする場合はどうなりますか? ほとんど変わらないにもかかわらず、常に同じ議論に対処する必要があります(これについては後で詳しく説明します)。
一言で言えば、この従来のアプローチは、理解するのが難しく、書くのが難しい不必要な詳細コードでオーバーロードされる可能性がある、理解できない可能性があります。
引数の通常のリストの代わりに、関数の入力で引数を持つ単一のオブジェクトが期待される場合に何が起こるか見てみましょう:
function findUsersByRole ({ role, withContactInfo, includeInactive }) {...}
関数宣言は前の例とほとんど同じに見えることに注意してください。唯一の違いは、パラメーターが中括弧に含まれていることです。 これは、関数が3つの個別の引数を受け取る代わりに、プロパティ
role
、
withContactInfo
、および
includeInactive
持つ単一のオブジェクトを期待することを示しています。
このメカニズムは、再構築のおかげで機能します-ES2015に登場した機会です。
これで、次のような関数を呼び出すことができます。
findUsersByRole({ role: 'admin', withContactInfo: true, includeInactive: true })
そのため、コードにはあいまいさがはるかに少なくなり、読みやすくなり、はるかに明確になりました。 さらに、パラメータがオブジェクトの名前付きプロパティによって表されるようになったため、引数をスキップしたり、順序を変更したりしても問題は発生しなくなりました。
たとえば、次のような関数を呼び出すことができます。
findUsersByRole({ withContactInfo: true, role: 'admin', includeInactive: true })
そして、これを行うことができます。
findUsersByRole({ role: 'admin', includeInactive: true })
さらに、新しいパラメーターを追加して、それらの外観が以前に記述されたコードを壊さないようにすることができます。
ここで、引数なしで関数を呼び出す可能性に関連する、つまり関数のすべてのパラメーターがオプションになる可能性に関連する1つの重要な点に注目する価値があります。 同様の関数の呼び出しは次のようになります。
findUsersByRole()
引数なしで関数を呼び出すには、引数のデフォルト値を設定する必要があります。
function findUsersByRole ({ role, withContactInfo, includeInactive } = {}) {...}
パラメータを持つオブジェクトに対して構造化を使用することの追加の利点は、このアプローチが免疫力を促進することです。 関数に入るときにオブジェクトを破棄するとき、オブジェクトのプロパティを新しい変数に割り当てます。 これらの変数の値を変更しても、元のオブジェクトは変更されません。
次の例を考えてみましょう。
const options = { role: 'Admin', includeInactive: true } findUsersByRole(options) function findUsersByRole ({ role, withContactInfo, includeInactive } = {}) { role = role.toLowerCase() console.log(role) // 'admin' ... } console.log(options.role) // 'Admin'
ここでは、
role
変数の値を変更したという事実にもかかわらず、
options.role
オブジェクトのプロパティの値は変更されませんでした。
引数を持つオブジェクトのプロパティのいずれかがオブジェクト(たとえば、
array
または
object
のタイプ)である場合、その結果、構造化によりオブジェクトの浅いコピーが作成されることに注意してください。その後、このプロパティを変更すると、別の変数に割り当てられても、元のオブジェクトが変更されます これに注意を引いてくれたYuri Khomyakovに感謝します。
デフォルトのパラメーター値
ES2015の革新により、JS関数を宣言するときに、パラメーターのデフォルト値を設定できるようになりました。 実際、オブジェクトをパラメーターで記述した後、宣言に
={}
を追加することで、デフォルトパラメーターの使用を既にここで示しました。
従来のパラメーターを使用する場合、デフォルトのパラメーター値を追加すると次のようになります。
function findUsersByRole ( role, withContactInfo = true, includeInactive ) {...}
includeInactive
パラメーターを
true
に設定する必要がある場合、デフォルト値を保持するために
withContactInfo
の値として
undefined
を明示的に渡す必要があります。
findUsersByRole( 'Admin', undefined, true )
これはすべて非常に神秘的に見えます。
これを、関数宣言のパラメーターを持つオブジェクトの使用と比較してください。
function findUsersByRole ({ role, withContactInfo = true, includeInactive } = {}) {...}
この関数は次のように呼び出すことができます:
findUsersByRole({ role: 'Admin', includeInactive: true })
同時に、
withContactInfo
設定されたデフォルト値は消えません。
必要な関数パラメーターについて
ここでは、適切な操作に絶対に必要な関数パラメーターを宣言するときの指示に関する1つの有用なトリックを検討するために、メイントピックから少し逸脱します。
以下のコードに似た何かを頻繁に書く必要がありますか?
function findUsersByRole ({ role, withContactInfo, includeInactive } = {}) { if (role == null) { throw Error(...) } ... }
ここでは、二重の等号
==
を使用して、
null
と
undefined
両方の値を確認します。
このすべての代わりに、デフォルトのパラメーターを使用できることを伝えた場合、関数内の必要なデータの受信の検証が実装されるため、どうなりますか?
このようなチェックを実行するには、まず
requiredParam()
関数を宣言する
requiredParam()
ありますが、これはエラーを返します。
function requiredParam (param) { const requiredParamError = new Error( `Required parameter, "${param}" is missing.` ) // - if (typeof Error.captureStackTrace === 'function') { Error.captureStackTrace( requiredParamError, requiredParam ) } throw requiredParamError }
ちなみに、この関数はROROを使用しないことを知っていますが、そのため最初の段階では、すべての関数がこのパターンを使用するとは言いませんでした。
これで、
requiredParam
関数の呼び出しを
role
デフォルト値として設定できます。
function findUsersByRole ({ role = requiredParam('role'), withContactInfo, includeInactive } = {}) {...}
その結果、誰かが
role
を指定せずに
findUserByRole
関数を呼び出すと、必要な
role
パラメーターを関数に渡すのを忘れたことを示すエラーメッセージが表示され
Required parameter, "role" is missing
ます。
技術的な観点から、通常のデフォルトパラメータでこのアプローチを使用できます。 ここでのオブジェクトは任意です。 ただし、これは非常に便利なため、ここで説明しました。
複雑な値のセットの関数から戻る
JavaScriptの関数は1つの値のみを返すことができます。 この値が
object
型の
object
、多くの興味深いものを含めることができます。
データベースに
User
オブジェクトを保存する関数を想像してください。 この関数がオブジェクトを返す場合、このアプローチを使用しない場合よりも、呼び出し元の場所にはるかに多くの情報を渡すことができます。
たとえば、通常、対応する関数でデータベースに情報を保存するときに、新しい行をテーブルに挿入するアプローチが使用されます。新しい行がまだ存在しない場合、対応する行が存在する場合、更新されます。
そのような場合、データストレージ関数が実行したデータベース操作の種類
INSERT
または
UPDATE
を知ることは有用です。 データベースに保存されたものの正確な説明を取得することは良いことであり、操作の結果に関する詳細は害になりません。 たとえば、正常に完了したり、より大きなトランザクションと組み合わせてキューに入れたりすることができます。操作のタイムアウトが期限切れになったため、書き込みの試行が失敗する場合があります。
関数からオブジェクトを返すと、必要な情報をすべて簡単に入力できます。
async saveUser({ upsert = true, transaction, ...userInfo }) { // return { operation, // 'INSERT' status, // 'Success' saved: userInfo } }
技術的には、次の構造は
Promise
オブジェクトを返します。これは、タイプ
object
通常のオブジェクトによって解決され
object
、この例はここで考えられているアイデアをよく示していると思います。
機能の構成を簡素化する
関数構成は、2つ以上の関数を組み合わせて新しい関数を作成するプロセスです。 関数の構成は、データの受け渡しが計画されているいくつかのパイプのシステムを組み立てることに似ています。
エリック・エリオット
関数の構成は、次のような宣言の特別な
pipe
関数を使用して実行できます。
function pipe(...fns) { return param => fns.reduce( (result, fn) => fn(result), param ) }
この関数は、関数のリストを取得し、これらの関数を左から右に実行できる1つの関数を返します。これらの関数の最初の関数を
pipe
に渡される引数に渡し、2番目の関数を最初の関数に返します。
おそらくこれは少しわかりにくいので、今度はすべてをその場所に配置する例を見てみましょう。 このアプローチの唯一の制限は、リスト内の各関数がパラメーターを1つだけ受け取ることです。 幸い、ROROパターンを使用する場合、これは問題ではありません。
以下に例を示します。 関数
saveUser
があります。これは、データをチェック、正規化、保存する3つの個別の関数を介して
userInfo
オブジェクトを
userInfo
ます。
function saveUser(userInfo) { return pipe( validate, normalize, persist )(userInfo) }
validate
、
normalize
、および
persist
関数でいわゆるrestパラメーターを使用して、各関数に必要な値のみを破棄し、他のすべてを渡します。
わかりやすくするための小さなコードを次に示します。
function validate( id, firstName, lastName, email = requiredParam(), username = requiredParam(), pass = requiredParam(), address, ...rest ) { // - return { id, firstName, lastName, email, username, pass, address, ...rest } } function normalize( email, username, ...rest ) { // return { email, username, ...rest } } async function persist({ upsert = true, ...info }) { // userInfo return { operation, status, saved: info } }
ROか否か-それが問題です
最初に、ほとんどの関数がオブジェクトを受け入れ、それらの多くもオブジェクトを返すという事実について話しました。 多くではあるが、すべてではない。 他のパターンと同様に、ROROは開発者の武器のツールの1つとしてのみ考慮されるべきです。 便利な場所で使用し、関数パラメーターのリストをより理解しやすく柔軟にし、関数によって返される値をより表現力豊かにします。
1つの引数を渡すだけで十分な関数を作成する場合、オブジェクトを渡すことはすでに多すぎます。 同様に、必要なものをすべて明確に表現する呼び出しの場所に単純な値を渡すことができる関数を作成する場合、そのような関数は
object
型の値をまったく返さないことが明らかです。
たとえば、請求確認機能でROROを使用することはほとんどありません。 渡された値が負でない整数かどうかをチェックする
isPositiveInteger
関数があるとします。 このような関数を作成する場合、ROROパターンには意味がありません。 ただし、JavaScriptアプリケーションを開発する場合、このパターンは他の多くの状況で役立ちます。
親愛なる読者! コードでROROパターンを使用する予定はありますか? または、あなたはすでに類似のものを使用していますか?