2

独自のメソッドを追加するために Array にモンキー パッチを適用していArray#left_outer_joinます。私は Ruby を初めて使用するので、かなりのことを行い、新しい配列を返すメソッドと、現在の配列を置き換える bang メソッドが必要です。しかし、私は同じコードを 2 回書いてしまい、非 bang メソッドと bang メソッドの唯一の違いは map または map への呼び出しです! それぞれ。

ブロックとメタプログラミングについて少し読んだところ、次のようになりました。

class Array
  def left_outer_join_proc(method, ary, &block)
    self.send(method) do |obj1|
      ary.each do |obj2|
        if yield obj1, obj2
          obj2.keys.each do |key|
            obj1[key] = obj2[key]
          end
          break
        end
      end
      obj1
    end
  end

  def left_outer_join(ary, &block)
    left_outer_join_proc(:map, ary, &block)
  end

  def left_outer_join!(ary, &block)
    left_outer_join_proc(:map!, ary, &block)
  end
end

events.left_outer_join!(users) {|event, user| event['user_id'] == user['user_id'] }

これまでのところ、これは問題なく動作し、Object.send(SO によると) メソッドを動的に呼び出すための最良の使用法であり、私はこのアプローチが好きです (私の純粋主義者はArray、3 番目のメソッドでクラスを汚染することを嫌います)。

ここでの質問: 非 bang メソッドと bang メソッドの両方を定義して DRY に保つためのベスト プラクティスは何ですか?

編集: この質問は、「bang メソッドは破壊的なメソッドを意味しますか?」に関するものではありません。しかし、実際には「 and を書くArray#add_twoとしたら、 を使用してメソッドを定義し、 を使用して別Array#add_two!のメソッドを定義する必要がないようにするにはどうすればよいでしょうか。map{|x| x +2 }map!{|x| x + 2 }

私は私が使用できることを知っています

def add_two!(x)
  self = add_two(x)
end 

しかし、私は「最高のパフォーマンス、最高の可読性」タイプの回答を求めています (ソースを見て、「微妙な」パフォーマンスの違いを確認してください) Array#mapArray#map!

4

4 に答える 4

1

非 bang メソッドと bang メソッドの両方を定義するためのベスト プラクティスは何ですか

すべての状況に適用できる「ベスト プラクティス」はありません。常識を働かせてください。

たとえば、map!of array は配列をその場で変更します。強打バージョンは「危うし!破壊法!」という意味。可能な実装:

def map
  # do the mapping
end

def map!
  @elements = map
end

save!の ActiveRecord は、操作が失敗した場合にエラーを発生させます。Bang は「エラーを発生させる」という意味です。

一般に、メソッド名の感嘆符は、プログラマーに「気をつけてください、ここで危険なことが起こる可能性があります」と警告する必要があります。可能な実装:

def save
  save!
  true
rescue
  false
end

def save!
  # do the saving. Raise error if something goes wrong
end

繰り返しますが、メソッドの bang バージョンには無数のユースケースが存在する可能性があるため、単一のパターン/アプローチはあり得ません。

于 2013-10-15T09:19:14.583 に答える
1

一部のオブジェクト (Arrayや などString) には、オブジェクトの内部状態全体を同じタイプの別のオブジェクトの状態に置き換えることができるメソッドがあります。通常、このメソッドには適切な名前が付けられてreplaceいます。そのようなメソッドを持つオブジェクトの場合、破壊的なメソッドと非破壊的なメソッドを単純に実装できますreplace

def foo
  # return new instance
end

def foo!
  replace(foo)
  nil # or self
end

逆に、すべてのオブジェクトには、同一の内部状態を持つ複製コピーを作成する、 と呼ばれるメソッドがありますdup。非破壊バージョンと破壊バージョンを実装できますdup

def foo!
  # modify internal state
  nil # or self
end

def foo
  dup.foo!
end

ただし、破壊的バージョンと非破壊的バージョンを別々に実装することで、より効率的にできる場合があります。その場合、ある程度の重複は避けられないでしょう。

ご了承ください

self = add_two(x)

動作しません。構文的にも有効ではありません。また、構文的に有効な場合でも、という名前のローカル変数に代入するだけでself、特別なキーワードは変更されませんself

于 2013-10-15T12:06:34.443 に答える
1

ここで「繰り返し」を見る:

  def left_outer_join(ary, &block)
    left_outer_join_proc(:map, ary, &block)
  end

  def left_outer_join!(ary, &block)
    left_outer_join_proc(:map!, ary, &block)
  end

この場合、あなたは合理的である限りのことをしたと思います。およびdefメソッド名が必要であり、ほとんどの共有ロジックがある proc の呼び出しも必要です。文字列編集の違いはわずかですが、違いの配置は重要です。また、コードを読んで理解するのもかなり簡単です。

さらに進むには、次のようなことができます

 ['', '!'].each do |bang_type|
   define_method( "left_outer_join#{bang_type}" ) do |ary, &block|
     left_outer_join_proc( "map#{bang_type}", ary, &block )
   end
 end

しかし、上記は DRY の概念を極限まで進めており、結果として得られるコードの読み取りとデバッグがはるかに困難になります。

于 2013-10-15T09:26:12.790 に答える