🐹

params options args parameters の使い分け

2024/05/22に公開

はじめに

株式会社ドワンゴのニコニコ生放送でフロントエンドを担当している misuken です。

今回はTypeScriptを使った開発においてブレやすい params options args parameters の使い分けについて解説します。

これらの名前は統一されていないとコードの可読性を下げる原因にもなり、自身が携わるプロジェクトでもレビュー中にコードのブレが気になることから、うまく使い分けるコツを記事として公開することにしました。

それぞれの名前の意味や役割を理解し、適切に使い分けることでコード品質の向上やレビューの負担軽減に繋げていただけると幸いです。

前提

この記事は予約語などの関係で TypeScript を前提に書いていますが、考え方自体は他の言語でも利用できると思うので、もしよければ参考にしてみてください。

結論

時間の無い方のために結論から先に書くと次のようになります。

  • params は名前付き引数に使用するオブジェクトです
  • options は省略可能な値、主に処理の動作をカスタマイズするときに使用するオブジェクトです
  • args は引数自体を引数の意味で使用する配列です
  • parameters の利用は避け、代わりに paramsoptions を使用しましょう
  • 名称の問題を避けるために、単数と複数の使い分けを意識しましょう
  • 単一の単位では組と名前と値の関係を意識しましょう
  • 単数、複数、コード、口頭、ドキュメントでブレのない名称を使用しましょう
名称 利用 コード上の読み ドキュメント上の記述
param 値に準ずる パラム パラメータ
params オブジェクト パラムス パラメータオブジェクト or パラメータ群
option 値に準ずる オプション オプション
options オブジェクト オプションズ オプションオブジェクト or オプション群
arg 値に準ずる アーグ 引数
args 配列 アーグス 引数リスト
parameter - - -
parameters - - -

paramoption は組の単位でのみ使うようにすることで、コード上ではほとんど現れないようにできるはずです

各単語の意味

まずは params options args parameters の意味や、どのように扱うとうまく使い分けられるのかを例を交えて説明します。

params

  • params は名前付き引数に使用するオブジェクトです
  • クエリパラメータなど、元々 "パラメータ" と呼ばれるものにも使います
    • URLSearchParams など、params という単語が使われることが多いです
  • 引数の順序がわかりにくい場合のオブジェクト渡しに有効です
  • なんでもかんでも params で済ませてしまわないよう、より具体的な文脈(configcontext など)を持つ場合はそちらを優先します
  • オブジェクトに渡す値の用途として、データ的な意味合いの値にも対応しています

引数の順序問題を改善する例

// ❌ 引数の順序がわかりにくく、間違いやすい
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 も予約語のようなものと捉えたほうが良い名前です

parameterparameters を使用しない

口頭で「パラメータ」と言う言葉を使用していても、TypeScriptを使用する範囲においては変数や引数に parameterparameters は使用しないことをおすすめします。
それらを使用すると、型として Parameters を使いたくなってユーティリティ型と競合する可能性が生じるためです。

代わりにコード上では paramparams を使用するようにしていれば、そのような問題は回避できます。

周辺の問題

ここまでで各単語の使い分けを説明しました。

次はその周辺で発生する問題に関して説明していきます。

名称の問題

paramparams の読みは「パラム」と「パラムス」で区別できます。
しかし、コードドキュメントなどに "パラムス" とは書けないので、"パラメータ" と書きたくなりますが、そうすると単数と複数(paramparmas)の区別がつきにくくなります。

/**
 * 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: { /* 省略 */ }[]) { /* ... */ }

単一系の扱い

コード上では以下のように変数を記述することがありますが、ちょっとだけ注意点があります。
厳密には paramsoptions の1つの name: value の組が paramoption という単位であり、 paramsoptions から取り出したものは 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]) => { /* 省略 */ });

通常は valueparamoption として扱ってもコード上問題になることはないものの、本来の意味を頭の片隅に置いておくと良いでしょう。

まとめ

今回はTypeScriptを使った開発においてブレやすい params options args parameters の使い分けについて解説しました。

結論は最初のほうに述べたとおりです。

何となく使っていた変数名も、名前の意味や役割を理解することで、自然とどの場面ではどれが適切かが定まってくるかと思います。

この記事を参考に、コード品質の向上やレビューの負担軽減に繋げてみてはいかがでしょうか。

株式会社ドワンゴでは、様々なサービス、コンテンツを一緒につくるメンバーを募集しています。 ドワンゴに興味がある。または応募しようか迷っている方がいれば、気軽に応募してみてください。

Discussion