web-dev-qa-db-ja.com

メソッドパラメータの代わりにハッシュを渡す

Ruby(および一般的に動的に型付けされた言語)では、非常に一般的な方法は、具体的なメソッドパラメータを宣言する代わりにハッシュを渡すことです。たとえば、パラメータを持つメソッドを宣言する代わりに次のように呼び出します:

def my_method(width, height, show_border)
my_method(400, 50, false)

次のようにできます:

def my_method(options)
my_method({"width" => 400, "height" => 50, "show_border" => false})

それについてのあなたの意見を知りたい。それは良い習慣ですか、悪い習慣ですか?このプラクティスを使用するのはどのような状況で有効であり、どのような状況で危険になる可能性がありますか?

77
rmaruszewski

どちらのアプローチにも長所と短所があります。標準の引数を置き換えるオプションハッシュを使用すると、メソッドを定義するコードの明確さが失われますが、オプションハッシュを使用して作成された疑似名前付きパラメーターのため、メソッドを使用するたびに明確になります。

私の一般的なルールは、メソッドの引数がたくさんある(3つまたは4つ以上)か、オプションの引数がたくさんある場合、オプションハッシュを使用するか、標準の引数を使用することです。ただし、オプションハッシュを使用する場合は、可能な引数を説明するメソッド定義にコメントを常に含めることが重要です。

27
benofsky

Rubyには暗黙的なハッシュパラメーターがあるため、次のようにも記述できます。

def my_method(options = {}) 

my_method(:width => 400, :height => 50, :show_border => false)

そしてRuby 1.9と新しいハッシュ構文で

my_method( width: 400, height: 50, show_border: false )

関数が3〜4個以上のパラメーターを取る場合、それぞれの位置をカウントせずに、どちらが何であるかを簡単に確認できます。

40
MBO

私はあなたが次のいずれかであると言うでしょう:

  1. 6つ以上のメソッドパラメーターがある
  2. optionsを渡す必要があります。一部はオプション、一部はデフォルト値があります

ほとんどの場合、ハッシュを使用する必要があります。ドキュメントを参照しなくても、引数の意味を簡単に確認できます。

メソッドがどのオプションを使用するかを理解するのは難しいと言っている人にとって、それはコードが不十分に文書化されていることを意味します。 [〜#〜] yard [〜#〜] を使用すると、@optionタグでオプションを指定します:

##
# Create a box.
#
# @param [Hash] options The options hash.
# @option options [Numeric] :width The width of the box.
# @option options [Numeric] :height The height of the box.
# @option options [Boolean] :show_border (false) Whether to show a
#   border or not.
def create_box(options={})
  options[:show_border] ||= false
end

しかし、その特定の例では、そのような少数の単純なパラメーターがあるので、私はこれで行くと思います:

##
# Create a box.
#
# @param [Numeric] width The width of the box.
# @param [Numeric] height The height of the box.
# @param [Boolean] show_border Whether to show a border or not.
def create_box(width, height, show_border=false)
end
7
sarahhodne

パラメーターとしてHashを使用する利点は、引数の数と順序への依存関係を削除することです。

実際には、これは後で、クライアントコードとの互換性を損なうことなくメソッドをリファクタリング/変更できる柔軟性があることを意味します(これは、実際にクライアントコードを変更できないため、ライブラリを構築するときに非常に便利です)。

(Sandy Metzの "Rubyでの実用的なオブジェクト指向設計" は、Rubyでのソフトウェア設計に興味がある人にとっては素晴らしい本です)

このパラメーターの受け渡し方法は、2つ以上のパラメーターがある場合、または多数のオプションパラメーターがある場合に、より明確になると思います。基本的に、メソッド呼び出しを明示的に自己文書化します。

3
Rich

Rubyでは正式なパラメータではなくハッシュを使用することは一般的ではありません。

私はこれが混同されていると思うパラメータとしてハッシュを渡す一般的なパターンとGUIツールキットでウィンドウの属性を設定します。

メソッドまたは関数に多数の引数がある場合は、それらを明示的に宣言して渡します。インタプリタがすべての引数を渡したことを確認するという利点があります。

言語機能を乱用しないでください。使用する場合と使用しない場合を知ってください。

3
Chris McCauley

それは良い習慣です。メソッドのシグネチャと引数の順序について考える必要はありません。別の利点は、入力したくない引数を簡単に省略できることです。 ExtJSフレームワークは、このタイプの引数の受け渡しを広範囲にわたって使用しているので、ご覧ください。

2
Li0liQ

動的言語を使用している人は誰も気にしないはずですが、ハッシュを関数に渡すとプログラムが受けるパフォーマンスの低下について考えてください。

コードがソースコードリテラルであるすべてのメンバーでハッシュを使用している場合、インタープリターはおそらく静的constハッシュオブジェクトを作成し、ポインターでのみ参照するのに十分スマートです。

ただし、これらのメンバーのいずれかが変数である場合は、呼び出されるたびにハッシュを再構築する必要があります。

Perlの最適化をいくつか行いましたが、この種のことは内部コードループで顕著になります。

関数パラメーターのパフォーマンスが大幅に向上しました。

1
Zan Lynx

それはトレードオフです。明確さを失い(どのパラメーターを渡すかを知る方法)とチェック(適切な数の引数を渡しましたか?)と柔軟性を獲得します(メソッドは受信しないパラメーターをデフォルトにすることができます。パラメータを増やし、既存のコードを壊さない)

この質問は、より大きな強/弱タイプの議論の一部として見ることができます。 Steve yegge's ブログを参照してください。非常に柔軟な引数の受け渡しをサポートしたい場合に、CとC++でこのスタイルを使用しました。おそらく、いくつかのクエリパラメータを使用した標準のHTTP GETは、まさにこのスタイルです。

ハッシュ評価に行く場合、テストが本当に良いことを確認する必要があると思いますが、間違ったスペルのパラメーター名の問題は実行時にのみ現れます。

1
djna

ハッシュは、複数のオプションの引数を渡すのに特に便利です。ハッシュを使用します。たとえば、引数がオプションであるクラスを初期化します。

class Example

def initialize(args = {})

  @code

  code = args[:code] # No error but you have no control of the variable initialization. the default value is supplied by Hash

  @code = args.fetch(:code) # returns IndexError exception if the argument wasn't passed. And the program stops

  # Handling the execption

  begin

     @code = args.fetch(:code)

  rescue 

 @code = 0

  end

end
0
Leonardo Ostan

一般的に、不可能でない限り、常に標準引数を使用する必要があります。オプションを使用する必要がないときにオプションを使用するのは、悪い習慣です。標準の引数は明確で、自己文書化されています(適切な名前が付けられている場合)。

オプションを使用する1つの(そしておそらく唯一の)理由は、関数が処理せずに別の関数に渡す引数を受け取る場合です。

以下に例を示します。

def myfactory(otype, *args)
  if otype == "obj1"
    myobj1(*args)
  elsif otype == "obj2"
    myobj2(*args)
  else
    puts("unknown object")
  end
end

def myobj1(arg11)
  puts("this is myobj1 #{arg11}")
end

def myobj2(arg21, arg22)
  puts("this is myobj2 #{arg21} #{arg22}")
end

この場合、「myfactory」は、「myobj1」または「myobj2」に必要な引数を認識していません。 「myfactory」は単に引数を「myobj1」と「myobj2」に渡し、それらをチェックして処理するのは彼らの責任です。

0
Dragan Nikolic