params options args parameters の使い分け
はじめに
株式会社ドワンゴのニコニコ生放送でフロントエンドを担当している misuken です。
今回はTypeScriptを使った開発においてブレやすい params
options
args
parameters
の使い分けについて解説します。
これらの名前は統一されていないとコードの可読性を下げる原因にもなり、自身が携わるプロジェクトでもレビュー中にコードのブレが気になることから、うまく使い分けるコツを記事として公開することにしました。
それぞれの名前の意味や役割を理解し、適切に使い分けることでコード品質の向上やレビューの負担軽減に繋げていただけると幸いです。
前提
この記事は予約語などの関係で TypeScript を前提に書いていますが、考え方自体は他の言語でも利用できると思うので、もしよければ参考にしてみてください。
結論
時間の無い方のために結論から先に書くと次のようになります。
-
params
は名前付き引数に使用するオブジェクトです -
options
は省略可能な値、主に処理の動作をカスタマイズするときに使用するオブジェクトです -
args
は引数自体を引数の意味で使用する配列です -
parameters
の利用は避け、代わりにparams
やoptions
を使用しましょう - 名称の問題を避けるために、単数と複数の使い分けを意識しましょう
- 単一の単位では組と名前と値の関係を意識しましょう
- 単数、複数、コード、口頭、ドキュメントでブレのない名称を使用しましょう
名称 | 利用 | 型 | コード上の読み | ドキュメント上の記述 |
---|---|---|---|---|
param | ✅ | 値に準ずる | パラム | パラメータ |
params | ✅ | オブジェクト | パラムス | パラメータオブジェクト or パラメータ群 |
option | ✅ | 値に準ずる | オプション | オプション |
options | ✅ | オブジェクト | オプションズ | オプションオブジェクト or オプション群 |
arg | ✅ | 値に準ずる | アーグ | 引数 |
args | ✅ | 配列 | アーグス | 引数リスト |
parameter | ❌ | - | - | - |
parameters | ❌ | - | - | - |
※ param
と option
は組の単位でのみ使うようにすることで、コード上ではほとんど現れないようにできるはずです
各単語の意味
まずは params
options
args
parameters
の意味や、どのように扱うとうまく使い分けられるのかを例を交えて説明します。
params
-
params
は名前付き引数に使用するオブジェクトです - クエリパラメータなど、元々 "パラメータ" と呼ばれるものにも使います
-
URLSearchParams など、
params
という単語が使われることが多いです
-
URLSearchParams など、
- 引数の順序がわかりにくい場合のオブジェクト渡しに有効です
- なんでもかんでも
params
で済ませてしまわないよう、より具体的な文脈(config
やcontext
など)を持つ場合はそちらを優先します - オブジェクトに渡す値の用途として、データ的な意味合いの値にも対応しています
引数の順序問題を改善する例
// ❌ 引数の順序がわかりにくく、間違いやすい
function sendMessage(to: string, from: string, message: string, subject?: string) { /* ... */ }
// 呼び出し側のコードを見るだけでは各文字列が何に使われるか不明確
sendMessage("@foo", "@bar", "test", "A");
// ✅ オブジェクト渡しのほうが明示的でわかりやすい
// 実際には分割代入で受け取る可能性もあるが、オブジェクトとして受け取る場合は params に相当する
function sendMessage(params: { to: string, from: string, subject?: string, message: string }) { /* ... */ }
// 呼び出し側のコードで各文字列が何に使われるか明確
sendMessage({
to: "@foo",
from: "@bar",
message: "test",
subject: "A",
});
メインの引数は残して改善する例
特定のURLに所定のクエリパラメータを追加するようなインタフェースの場合。
URLはメインの引数として残して、クエリパラメータを params
にまとめるとキレイにまとまります。
// ❌ 省略可能なものを最後に置く必要がある
function generateXxxUrl(url: string, paramA: number, paramB?: number) { /* ... */ }
function generateXxxUrl(url: string, paramA: number, paramC: number, paramB?: number) { /* ... */ }
// ✅ メインの引数とオブジェクト渡しの構成にすると、わかりやすくなる
function generateXxxUrl(url: string, params: { paramA: number, paramB?: number }) { /* ... */ }
function generateXxxUrl(url: string, params: { paramA: number, paramB?: number, paramC: number }) { /* ... */ }
// ❌ params内にメインの引数まで含めると、使いにくくなることもあるので注意
// 呼び出すときにURLを指定するのが少し面倒になる
// このようなインタフェースの場合、URLとパラメータは別々に用意したものを組み合わせることが多い
function generateXxxUrl(params: { url: string, paramA: number, paramB?: number }) { /* ... */ }
options
-
options
は選択肢を表すオブジェクトです (params
のより具体的なもの、option params
とも言えます) - 主に省略可能な引数、デフォルト値が設定できるものになります
- 対象とする処理の動作をカスタマイズするときによく使用されます
- 多くの場合、処理には何もオプションを渡さなかった場合のデフォルトのパターンが存在します
- そのような性質上、オプションは省略可能になると言えます
- 例えばコマンドライン引数の
mkdir -p dir1/dir2
は-p
がオプションでdir1/dir2
がパラメータです
こちらもメインの引数は残して、オプション引数をオブジェクト渡しにするとわかりやすくなります。
// ❌ 引数に型が同じもの、省略可能なものが複数あると、順序がわかりにくく間違いやすくなる
function fetchUser(id: number, withPosts?: boolean, withComments?: boolean) { /* ... */ }
// ✅ オプション引数をオブジェクト渡しにすると、わかりやすくなる
function fetchUser(id: number, options?: { withPosts?: boolean, withComments?: boolean }) { /* ... */ }
// id は fetchUser という動作の対象を表す値で、呼び出し時に必ず指定する必要がある
// ❌ options は省略可能であることが示唆されるので、必須の値を含めると意味が合わない
function fetchUser(options: { id: number, withPosts?: boolean, withComments?: boolean }) { /* ... */ }
// ✅ どうしても必須の値も含めてオブジェクトでまとめて渡したい場合は params にする
// params は options を包括する概念なので、params が options を内包することに問題はない
// `?` が params のオプション項目(省略可能)であることを示す形になる
function fetchUser(params: { id: number, withPosts?: boolean, withComments?: boolean }) { /* ... */ }
// このような表現と同等になる
function fetchUser({ id, ...options = {}}: { id: number, withPosts?: boolean, withComments?: boolean }) { /* ... */ }
// ❌ params と options を併用すると不格好なインタフェースになるので積極的な使用は避ける
// 上でも説明したようにメインの引数は展開するか、paramsに一部省略可能な項目を持たせるかのどちらかにまとめられる
function fetchUser(params: { id: number }, options?: { withPosts?: boolean, withComments?: boolean }) { /* ... */ }
JavaScript の addEventListener
も明確に識別可能なメインの引数を二つ持ったこのタイプに該当します。
// lib.dom.d.ts の定義
addEventListener<K extends keyof WindowEventMap>(type: K, listener: (this: Window, ev: WindowEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
// 使用例
window.addEventListener("scroll", () => {});
window.addEventListener("scroll", () => {}, { passive: true }); // options は動作のカスタマイズで任意
違う用途で使用される options
options
はHTMLの <select>
内の <option>
のリストに配列を渡すときに使用する場合もあります。
この記事では関数の引数に焦点を当てているため、違う用途で扱う場合は options
という変数名が配列として使用される場面もあり得る点にご注意ください。
args
-
args
は引数リスト自体をそのままの意味で扱いたいときに使用します -
args
は "arguments" という単語の省略形である関係上、JavaScript の arguments と同等の役割で使用します
// ✅ 引数リストの文脈には関知せず、引数リストとして扱う
function func<Args extends any[], R>(fn: (...args: Args) => R) {
return (...args: Args): R => {
// 何らかの前処理
const returnValue = fn(...args);
// 何らかの後処理
return returnValue;
}
}
parameters
-
parameters
はTypeScriptの引数リストの型を取得するためのユーティリティ型 Parameters と被ります - TypeScriptで定義済みであるため、独自に同じ名前の型を定義するべきではありません
- 結果的に
parameters
も予約語のようなものと捉えたほうが良い名前です
parameter
や parameters
を使用しない
口頭で「パラメータ」と言う言葉を使用していても、TypeScriptを使用する範囲においては変数や引数に parameter
や parameters
は使用しないことをおすすめします。
それらを使用すると、型として Parameters
を使いたくなってユーティリティ型と競合する可能性が生じるためです。
代わりにコード上では param
や params
を使用するようにしていれば、そのような問題は回避できます。
周辺の問題
ここまでで各単語の使い分けを説明しました。
次はその周辺で発生する問題に関して説明していきます。
名称の問題
param
と params
の読みは「パラム」と「パラムス」で区別できます。
しかし、コードドキュメントなどに "パラムス" とは書けないので、"パラメータ" と書きたくなりますが、そうすると単数と複数(param
と parmas
)の区別がつきにくくなります。
/**
* xxx
*
* @param params パラメータ
*/
function func(params: { /* 省略 */ }) { /* ... */ }
"パラメータリスト" とすると、params
が複数形であることがわかりますが、"リスト" と言いながら配列ではないですし paramList
でもないので若干の違和感が生じます。
この違和感を解消するには "パラメータオブジェクト" や "パラメータ群" などとすると良いでしょう。
名称 | コード上の読み | ドキュメント上の記述 |
---|---|---|
param | パラム | パラメータ |
params | パラムス | パラメータオブジェクト or パラメータ群 |
option | オプション | オプション |
options | オプションズ | オプションオブジェクト or オプション群 |
arg | アーグ | 引数 |
args | アーグス | 引数リスト |
単数と複数の使い分け
実際の開発中に、以下のような単数形と複数形が曖昧なコードを見かけることがあるのですが、
ちゃんと使い分けてあると意図が明確になり、特にレビュー時に不安にならずに済みます。
引数で扱っているものは単数なのか、複数なのか、ということを意識して使い分けることが重要です。
// ❌ param や option と言いながら、実際には複数項目を持ったオブジェクトである
function func(param: { /* 省略 */ }) { /* ... */ }
function func(option: { /* 省略 */ }) { /* ... */ }
// ❌ 複数項目を持ったオブジェクトなのに parameter という名前を使っている
// ❌ parameters にすると型を定義するときにTypeScriptのユーティリティ型と競合してしまう
function func(parameter: { /* 省略 */ }) { /* ... */ }
// ✅ 複数項目を持っているなら複数形を使う
// ✅ parameters は使用せずに params や options を使う
function func(params: { /* 省略 */ }) { /* ... */ }
function func(options: { /* 省略 */ }) { /* ... */ }
// ❌ args と言いながら、実際は単一の引数である
function func(args: { /* 省略 */ }) { /* ... */ }
// ✅ 単一の引数には arg を使う
function func(arg: { /* 省略 */ }) { /* ... */ }
// ✅ args を使うなら引数リストになるはずである
function func(...args: { /* 省略 */ }[]) { /* ... */ }
単一系の扱い
コード上では以下のように変数を記述することがありますが、ちょっとだけ注意点があります。
厳密には params
や options
の1つの name: value
の組が param
や option
という単位であり、 params
や options
から取り出したものは value
です。
const param = params[name]; // 暗黙的に paramValue 及び value を param としている
const option = options[name]; // 暗黙的に optionValue 及び value を option としている
const arg = args[index]; // 引数リストの配列の要素は引数なので arg で合致している
もし Param
クラスのインスタンスが存在するならこうなります。
const param = params.get(name);
const paramName = param.name; // パラメータ名
const paramValue = param.value; // パラメータ値
実際のプロジェクトでそこまで厳密にすることは少ないと思いますが、param
インスタンスが存在しなくても、実際に param
相当の単位が存在する場面はあります。
// [name, value] の組の単位が param や option に相当
Object.entries(params).forEach(([name, value]) => { /* 省略 */ });
Object.entries(options).forEach(([name, value]) => { /* 省略 */ });
通常は value
を param
や option
として扱ってもコード上問題になることはないものの、本来の意味を頭の片隅に置いておくと良いでしょう。
まとめ
今回はTypeScriptを使った開発においてブレやすい params
options
args
parameters
の使い分けについて解説しました。
結論は最初のほうに述べたとおりです。
何となく使っていた変数名も、名前の意味や役割を理解することで、自然とどの場面ではどれが適切かが定まってくるかと思います。
この記事を参考に、コード品質の向上やレビューの負担軽減に繋げてみてはいかがでしょうか。
株式会社ドワンゴでは、様々なサービス、コンテンツを一緒につくるメンバーを募集しています。 ドワンゴに興味がある。または応募しようか迷っている方がいれば、気軽に応募してみてください。
Discussion