はじめに
引数が複数あって、同じ型ならNamed Parameter(名前付き引数)を選ぶ方がいいよねって感じの話です。
オブジェクト渡しの関数のがより良い
[1]順番に依存した引数
const calculateHoge = (width: number, height: number, depth: number) => width * height * (depth + 500)
calculateHoge(1, 2, 3)
[2]オブジェクト渡しの引数
const calculateHoge = ({ width, height, depth }: { width: number, height: number, depth: number }) => width * height * (depth + 500)
calculateHoge({ width: 1, height: 2, depth: 3 })
[1]よりも[2]のほうがよりよいと考えています。
理由
なぜかというと、オブジェクト渡しのがミスをしにくいからです。
[1]の場合は、順番を誤っていてもコンパイルエラーになりません。関数の利用者側は順番を間違えるだけでバグを生み出します。また、同じような機能で引数の順番が微妙に異なるような関数が他にある場合には余計にバグを生み出しやすいです。
[2]の場合は、明示的にキー名を書く必要があり、[1]に比べて記述量が増える代わりに、間違える可能性は低いです。
下記のような関数が別に存在したらどう思うでしょうか?
const calculatteFuga = (height: number, width: number, depth: number) => calculateHoge(width + 500, height, depth)
「引数の順番を合わせろ」って思いませんか?
自分は思います。PHPの標準関数で昔よく「引数の順番がー」ってなってたような記憶があります。
ただ、機能的に似ているけども引数の順番が異なる関数が発生することは、チームで開発しているものでは簡単に起こりうると思います。
このようにせっかく型をつけても引数の順番を間違えるだけでバグにつながるのはよろしくないので、引数にオブジェクトを渡すの形のがよりよいと考えています。
なぜ独自の型を定義して使わないのか
「Width型、Height型、Depth型のような独自の型定義を使えばよいのではないか?」という話もあると思います。
ただ引数の順番があってない似たような関数に関する問題の一部(見た目上の整合性やわかりやすさ)は解決しないので、下記のような感じになるかと思います。
Structural Subtypingなので、ひと手間加えないといけないっていうのとそもそも記述量が大幅に増えるのがネックになると思っています。
type Brand<K, T> = K & { __brand: T }
type Depth = Brand<number, "Depth">
type Height = Brand<number, "Heiht">
type Width = Brand<number, "Width">
const width = 10 as Width
const depth = 10 as Depth
const height = 10 as Height
const calculateArea = ({ width, height, depth }: { width: Width, height: Height, depth: Depth }) => width * height * (depth + 500)