📚

名前で適切なArrayメソッドを検討してみる

2023/01/29に公開

まゆもとゆきひろさんの格言で「名前重要」という言葉がありますね。「名前重要」は名前をつける視点で重要性が語られています。

私はプログラミング言語が持つさまざまな機能を選ぶ/呼び出す視点においても名前は重要だと考えています。同じ結果となる複数の異なるメソッドを使用したコードがあるとき、どのメソッドを使用することが可読性に貢献するのかを考えます。それぞれのメソッド名に込められた言語設計の意図を考えて適切なメソッドを選ぶことで、可読性の高いコードが書けると思います。

変換する: forEach()vsmap()

forEach()map()、どちらを使っても配列データを変換するロジックを記述可能です。しかし、今回は名前という切り口で、可読性が高いのはどちらかを考えたいと思います。

forEach: 各要素に対して

"for each(〜に対してそれぞれ)"なので、Arrayメソッドとして解釈すると「各要素に対して」という意味ですね。MDNではforEachの振る舞いについて次のように説明されています。

forEach()メソッドは、与えられた関数を、配列の各要素に対して一度ずつ実行します。

引数を確認するとcallbackFnには、3つの引数(element, index, array)が渡されます。そして同じインターフェース(戻り値の型を考慮しない)のメソッドはたくさんあります。(find系とsomeは同じインターフェースを持ちますが、callbackFnTruthyを返すとそれ以降の要素については実行されません。)

  • Array.prototype.every()
  • Array.prototype.filter()
  • Array.prototype.flatMap()
  • Array.prototype.map()

上記メソッドはforEach()のように各要素に対して関数を実行することが可能です。言い換えるとこれらのメソッドも「各要素に対して」と解釈できる振る舞いを持つわけです。しかし、他のメソッドは「各要素に対して」だけではなく+αの機能を持ちます。具体的には各要素に対して関数を実行し、その戻り値からメソッド自体の戻り値を計算する機能を持っています。一方でforEach()だけ各要素に対して関数を実行する以外の機能を持ちません。callbackFnの戻り値は破棄され、メソッド自体の戻り値は必ずundefinedです。

まとめると、同じインターフェースを持つメソッドの中で最もバニラな存在がforEach()です。だから最も曖昧な単語がメソッド名に使用されていると考えられます。

map: 写像、マッピング(変換する)

mapは数学用語の写像の意味がピッタリだと思いますが、メソッドなので動詞で「変換する」と表現する方が個人的に分かりやすいです。

考察

変換処理にはmap()を使うのが適切だと思います。理由はmap()を読んだ時点で変換処理が記述されることが伝わるためです。forEach()だとcallbackFnの処理内容を読むまで何をするのか分かりません。forEach()が目に入った時点では「各要素に対して、何をするの?」と考えるでしょう。

コードブロックをパラグラフとして解釈すると、メソッド名によってそのパラグラフのトピックが伝わります。forEach()はトピックとして読み手に伝えられる情報がmap()より少ないため、可読性に劣ると言えます。

繰り返しになりますがforEach()は同じインターフェースを持つメソッド群において最もバニラなメソッドです。他のメソッドで目的を達成するための適切なメソッドがないかを探して、どれにも該当しない場合のみforEach()の出番だと考えています。

条件を満たす要素があるか: filter()vssome()

過去に次のコードをリファクタリングしたことがあります。

// before
if (array.filter(...).length > 0) {
  ...
}
// after
if (array.some(...)) {
  ...
}

それぞれのメソッド名の意味をおさらいして、someにした理由を解説します。

filter: 濾過する

filterは濾過するという意味です。この単語が読み手に与える印象を考えます。

例えば「川の水を濾過する」という文がある場合、濾過によって不純物が取り除かれた水はきっと何かに使用されることを読み手としては期待しますね。プログラムにおいてもfilter()を読んだら、その戻り値である配列がどう使われるのか注意を向けるでしょう。

some: いくつか(ある)

someは名詞を修飾する言葉で可算/不可算に関係なく使用出来ますが、Arrayメソッドとしては「いくつか」と訳します。someを詳しく調べると"not stated"、"approximately"などと説明されており、正確さについては関心を持たないことを意味する単語であることが分かります。

プログラムではsomeを使うことで「あるのか/ないのか」という点に関心があり、どれぐらいあるのか正確な数は気にしなくても問題ないことが読み手に伝わります。

考察

改めてリファクタリングしたコードを確認しておきます。

// before
if (array.filter(...).length > 0) {
  ...
}
// after
if (array.some(...)) {
  ...
}

filter()の場合、その戻り値だけでは条件を満たす要素があるかの判定になりません。そのためlength > 0で配列の数をチェックしています。

filter()some()、それぞれの読み手の解釈を比べてみましょう。

  • フィルターして、その条件を満たす要素の数が0よりも多いか
  • 条件を満たす要素があるか

「条件を満たす要素があるか」は、目的を伝える表現として一切削れるものはありません。「フィルターして、その条件を満たす要素の数が0よりも多いか」という表現は、「フィルターして」という不要な情報が混ざりますし、「条件を満たす要素の数が0よりも多いか」という表現は「条件を満たす要素があるか」という表現に比べれば複雑です。

今回のケースではfilter()を使うよりも、ノイズの少ないsome()を使う方が可読性に優れています。

まとめ

Arrayメソッドを名前で選ぶ視点について書きました。

名前で考えるためには、ひとつ一つの英単語の意味を深く知る必要があります。それはプログラムの機能として判断するよりも余計なコストに感じるかもしれません。しかし、可読性を考えるなら正しい名前をつけることや選ぶことが大切だと思います。

今回紹介した名前以外の視点で、機能の内容で判断するおすすめの方法があります。それは「専用機能があれば、それを使え」です。

「専用機能があれば、それを使え」という判断と、今回記事にした名前による判断は、一部例外はあるかもしれませんがほぼ一致します。理由は、どのプログラミング言語においても機能につけられる名前はしっかりと考え抜かれているからです。

filter()vssome()では私は次のように考えます。

some()で書けるのになぜfilter()を使ったのだろう。もしかしたらfilter()で副作用のある処理を見落としているのかも。。

そして注意深くコードを読み、何も見落としていないことを確認して疲れているでしょう。

繰り返しますが、専用機能があればそれを使いましょう。汎用的な機能や、別用途の機能を使う場合より確実に可読性の高くなります。この主張はシンプルで強力ですが、きっと短い記事になります。今回は専用機能を使わない場合の不自然さ、気持ち悪さについても言語化しておきたかったので名前視点で記事を書いてみました。

以上、最後まで読んでいただいてありがとうございました。

Discussion