💎

Rubyの関数の引数の渡し方を比較してみた

2021/07/04に公開

Rubyの関数の引数の、1.普通に並べて渡す(?)、2.キーワード引数で渡す、3.ハッシュで渡すの3通りの渡し方を比較してみました。

動作環境

❯ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.15.7
BuildVersion:   19H2
❯ ruby -v
ruby 2.7.3p183 (2021-04-05 revision 6847ee089d) [x86_64-darwin19]

概要

まず、3通りの渡し方の特徴です。

方法 メリット デメリット
普通に並べて渡す 渡す側が簡単
過不足があるときにエラーが発生する
順番に依存する
キーワード引数で渡す 順番に依存しない
過不足があるときにエラーが発生する
引数が多い場合、面倒
ハッシュで渡す 受け取る側が楽
順番に依存しない
過不足があるときにエラーが発生しない

1. 普通に並べて渡す場合

minitestでエラーを検証する方法は、Minitestで例外クラスとエラーメッセージを検証する方法を参考にしました。

普通に並べて渡す場合は、引数の個数に過不足があると例外が発生します。また、引数にデフォルト値を設定することも出来ます。

require 'minitest/autorun'

def sum_normal(a, b)
  a + b
end

def sum_normal_with_default(a, b = 100)
  a + b
end

class NormalArgumentsTest < Minitest::Test
  def test_デフォルト値がない場合
    # 正常系
    assert_equal 30, sum_normal(10, 20)

    # 引数が足りない場合エラーになる
    e = assert_raises ArgumentError do
      sum_normal(10)
    end
    assert_equal 'wrong number of arguments (given 1, expected 2)', e.message

    # 引数が多い場合エラーになる
    e = assert_raises ArgumentError do
      sum_normal(10, 20, 30)
    end
    assert_equal 'wrong number of arguments (given 3, expected 2)', e.message
  end

  def test_デフォルト値がある場合
    # 渡さなかった場合はデフォルト値が使われる
    assert_equal 110, sum_normal_with_default(10)
  end
end

この書き方のメリットは、簡単に書けることと、過不足があるときに例外が発生することです。デメリットは、引数が複数ある場合に、渡す順番に気をつけないといけないことです。

2. キーワード引数で渡す場合

キーワード引数の場合も、普通の渡し方と同様に過不足があると例外が発生したり、デフォルト値を設定することが出来ます。

require 'minitest/autorun'

def sum_keyword(a:, b:)
  a + b
end

def sum_keyword_with_default(a:, b: 100)
  a + b
end

class KeywordArgumentsTest < Minitest::Test
  def test_デフォルト値がない場合
    # 正常系
    assert_equal 30, sum_keyword(a: 10, b: 20)

    # 順番が逆でもOK
    assert_equal 30, sum_keyword(b: 20, a: 10)

    # 引数が足りない場合エラーになる
    e = assert_raises ArgumentError do
      sum_keyword(a: 10)
    end
    assert_equal 'missing keyword: :b', e.message

    # 余分な引数が入った場合エラーになる
    e = assert_raises ArgumentError do
      sum_keyword(a: 10, b: 20, c: 30)
    end
    assert_equal 'unknown keyword: :c', e.message
  end

  def test_デフォルト値がある場合
    # 渡さなかった場合はデフォルト値が使われる
    assert_equal 110, sum_keyword_with_default(a: 10)
  end
end

キーワード引数を使うことのメリットは、渡す側が順番に気をつけなくても良いことです。デメリットは、引数の個数が多かったり、名前が長かったりするときに、渡す側がちょっと面倒になることです。

3. ハッシュで渡す場合

ハッシュで渡す場合は、引数に過不足があっても例外は発生しません。デフォルト値を設定したい場合は、デフォルトのハッシュを作って、それとマージしてあげれば良いです。

require 'minitest/autorun'

def sum_hash(hash)
  hash[:a] + hash[:b]
end

def sum_hash_with_default(hash)
  default_hash = { b: 100 }
  hash = default_hash.merge(hash)

  hash[:a] + hash[:b]
end

class HashArgumentsTest < Minitest::Test
  def test_デフォルト値がない場合
    # 正常系
    assert_equal 30, sum_hash({ a: 10, b: 20 })

    # 順番は気にしなくてOK
    assert_equal 30, sum_hash({ b: 20, a: 10 })

    # 余分な値が入ってもエラーにならない
    assert_equal 30, sum_hash({ a: 10, b: 20, c: 30 })

    # 不足していても(渡したときは)エラーにならない
    e = assert_raises TypeError do
      assert_equal 10, sum_hash({ a: 10 })
    end
    assert_equal 'nil can\'t be coerced into Integer', e.message
  end

  def test_デフォルト値がある場合
    # 渡さなかった場合はデフォルト値が使われる
    assert_equal 110, sum_hash_with_default({ a: 10 })
  end
end

ハッシュで渡すことのメリットは、キーワード引数と同様に順番を気にしなくて良いことです。また、受け取る側は引数にハッシュを1個書くだけなので楽です。デメリットは、渡す側が何を渡せばいいのか分かりにくかったり、受け取る側がちゃんと値が渡されてくるかどうか分からないことです。

この弱点は、以下のようにしてハッシュを検証するとカバー出来そうです。

def func(hash)
  validate(hash)
  
  # 処理
end

def validate(hash)
  raise ArgumentError if hash[:key1].nil?
  raise ArgumentError if hash[:key2].nil?
end

まとめ

基本的にはキーワード引数を使えばいいのではないかと思います。長くなってしまう場合は、ハッシュで渡して検証する方法を使うことも考えようと思います。

状況 どれを使うか
大体の状況 キーワード引数
引数の個数が1個か2個で、増えなさそうな場合 普通の渡し方
引数がたくさんある場合 ハッシュで渡して、検証メソッドを実装する

可変長引数(*args)や最後のハッシュ(**hash)というのもあるようなので、頭の片隅に置いておきたいと思います。

参考

GitHubで編集を提案

Discussion