確かに、かなり大胆なメソッドがある状況に直面しているので、そのコードの一部を別のメソッドに配置する必要があり、クラス/モジュールは単一のメソッドに属し、他のどこでも使用されていないメソッドであふれています。 恐ろしいしゃれですよね?
クラスの実装を知りたいだけの場合、これらの非常に補助的なメソッドは目にとって非常に冷淡であり、コードを前後にジャンプする必要があります。 はい、もちろん、それらを別々のモジュールに分けることができますが、これは冗長すぎる場合が多いと思います(たとえば、実際には、n個の部分に分解される1つのメソッドのみを定義するモジュールを作成したくない)。 これらのヘルパー関数が1行で構成されている場合(たとえば、解析されたJSONから特定の要素を取得するメソッドなど)、それは特に不快です。
そして、解析について説明したので、やや総合的な例を挙げましょう。
やや合成的な例
挑戦する
ルーブルに対する異なる通貨の為替レートでハッシュを生成します。 このようなもの:
{ 'USD' => 30.0, 'EUR' => 50.0, ... }
解決策
中央銀行のWebサイトには次のようなページがあります: http : //www.cbr.ru/scripts/XML_daily.asp
実際、すべては次のように実行できます。
require 'open-uri' require 'active_support/core_ext/hash' # for Hash#from_xml def rate_hash uri = URI.parse('http://www.cbr.ru/scripts/XML_daily.asp') xml_with_currencies = uri.read rates = Hash.from_xml(xml_with_currencies)['ValCurs']['Valute'] rates.map(&method(:rate_hash_element)).to_h end def rate_hash_element(rate) [rate['CharCode'], rubles_per_unit(rate)] end def rubles_per_unit(rate) rate['Value'].to_f / rate['Nominal'].to_f end
またはクラス:
require 'open-uri' require 'active_support/core_ext/hash' # for Hash#from_xml class CentralBankExchangeRate def rubles_per(char_code) rate_hash_from_cbr[char_code] || fail('I dunno :C') end # # other public methods for other currencies # private # Gets daily rates from Central Bank of Russia def rate_hash_from_cbr uri = URI.parse('http://www.cbr.ru/scripts/XML_daily.asp') xml_with_currencies = uri.read rates = Hash.from_xml(xml_with_currencies)['ValCurs']['Valute'] rates.map(&method(:rate_hash_element)).to_h end # helper method for #rate_hash_from_cbr def rate_hash_element(rate) [rate['CharCode'], rubles_per_unit[rate]] end # helper method for #rate_hash_element def rubles_per_unit(rate) rate['Value'].to_f / rate['Nominal'].to_f end # # other private methods # end
使用する価値のあるライブラリについては説明しません。レールがあると想定し、そこからHash#from_xml
を使用します。
実際、 #rate_hash
メソッドは問題を解決しますが、残りの2つのメソッドはそれを補助します。 彼らの存在が非常に気を散らすことに同意する。
xml_with_currencies
変数に注意してxml_with_currencies
。その値は1回しか使用されません。つまり、その存在は完全にオプションであり、 Hash.from_xml(uri.read)['ValCurs']['Valute']
と書くことができます。その使用は、コードの可読性をわずかに改善すると思います。 実際には、ヘルパーメソッドの外観は同じトリックですが、コードの断片です。
おそらくタイトルで既に推測しているように、このような補助的な方法にはlambasを使用することをお勧めします。
ラムダを使用したソリューション
require 'open-uri' require 'active_support/core_ext/hash' # for Hash#from_xml def rate_hash uri = URI.parse('http://www.cbr.ru/scripts/XML_daily.asp') xml_with_currencies = uri.read rates = Hash.from_xml(xml_with_currencies)['ValCurs']['Valute'] rubles_per_unit = -> (r) { r['Value'].to_f / r['Nominal'].to_f } rate_hash_element = -> (r) { [r['CharCode'], rubles_per_unit[r]] } rates.map(&rate_hash_element).to_h end
またはクラス:
require 'open-uri' require 'active_support/core_ext/hash' # for Hash#from_xml class CentralBankExchangeRate def rubles_per(char_code) rate_hash_from_cbr[char_code] || fail('I dunno :C') end # # other public methods for other currencies # private # Gets daily rates from Central Bank of Russia def rate_hash_from_cbr uri = URI.parse('http://www.cbr.ru/scripts/XML_daily.asp') xml_with_currencies = uri.read rates = Hash.from_xml(xml_with_currencies)['ValCurs']['Valute'] rubles_per_unit = ->(r) { r['Value'].to_f / r['Nominal'].to_f } rate_hash_element = ->(r) { [r['CharCode'], rubles_per_unit[r]] } rates.map(&rate_hash_element).to_h end # # other private methods # end
これで、使用に適したメソッドが1つあることがすぐにわかります。 そして、その実装に没頭したい場合、ラムダは非常にキャッチで理解しやすいので、読み取りに関する問題も発生しないはずです(構文糖のおかげ)。
しかし、そうではありません!
私の知る限り、JavaScriptでは、関数を相互にネストすることは公正な習慣です。
function foo() { return bar(); function bar() { return 'bar'; } }
これは、 foo()
を呼び出すたびに関数barを作成してから破棄するためです。 さらに、いくつかのfoo()
を並列実行すると、3つの同一の関数が作成され、これもメモリを消費します。 更新しました。 ここでは「気軽に使用してください」 と書いているので、私は間違っています。
しかし、私たちの方法のために余分な秒を消費するという問題はどれほど重要ですか? 個人的には、0.5秒の利得のためにさまざまな便利なデザインを放棄する理由はないと思います。 例:
some_list.each(&:method)
より遅い
some_list.each { |e| e.method }
前者の場合、 Proc
への暗黙的なキャストProc
です。
さらに、Rubyはまだクライアントではなくサーバーで動作するため、速度ははるかに高速です(ただし、ここでも賭けることができます。サーバーが多くの人にサービスを提供し、1秒間の損失でさえ数分/時間/日に増加するためです) )
それでも、速度はどのくらいですか?
現実から離れた実験を行ってみましょう。
using_lambda.rb:
N = 10_000_000 def method(x) sqr = ->(x) { x * x } sqr[x] end t = Time.now N.times { |i| method(i) } puts "Lambda: #{Time.now - t}"
using_method.rb:
N = 10_000_000 def method(x) sqr(x) end def sqr(x) x * x end t = Time.now N.times { |i| method(i) } puts "Method: #{Time.now - t}"
打ち上げ:
~/ruby-test $ alias test-speed='ruby using_lambda.rb; ruby using_method.rb' ~/ruby-test $ rvm use 2.1.2; test-speed; rvm use 2.2.1; test-speed; rvm use 2.3.0; test-speed Using /Users/nondv/.rvm/gems/ruby-2.1.2 Lambda: 11.564349 Method: 1.523036 Using /Users/nondv/.rvm/gems/ruby-2.2.1 Lambda: 9.270079 Method: 1.523763 Using /Users/nondv/.rvm/gems/ruby-2.3.0 Lambda: 9.254366 Method: 1.333142
つまり lambasを使用すると、メソッドを使用した同様のコードよりも約7倍遅くなります。
おわりに
この投稿は、habrosocietyがこの「トリック」の使用について何を考えているかを知ることを期待して書かれました。
たとえば、速度がミリ秒ごとに戦う必要があるほど重要ではなく、メソッド自体が毎秒100万回呼び出されない場合、この場合、速度を犠牲にすることは可能ですか? または、一般的にこれは読みやすさを改善しないと思いますか?
残念ながら、上記の例は、ラムダのこのような奇妙な使用の意味を示していません。 意味は、十分に多くのプライベートメソッドを持つクラスがあり、そのほとんどが他のプライベートメソッドで1回だけ使用される場合に表示されます。 これにより、 def
およびend
ヒープはなく、かなり単純な単一行関数( -> (x) { ... }
)があるため、これは、設計上、個々のクラスメソッドの作業の実装の理解を促進するはず-> (x) { ... }
お時間をいただきありがとうございます!
UPD。
私がこれについて話した人の中には、この考えをよく理解していなかった人もいました。
- すべてのプライベートメソッドをラムダに置き換えることはお勧めしません 。 目的のメソッド(およびメソッド自体はプライベートである可能性が高い)以外で使用されない、非常に単純な単一行ファイルのみを置き換えることをお勧めします。
- さらに、単純な1行であっても、コードの可読性が本当に向上し、同時に速度の低下がそれほど大きくない場合にのみ、状況から進んでこの「トリック」を使用する必要があります。
- ラムダを使用する主な利点は、コードの行数を減らし、コードの最も重要な部分を視覚的に強調表示することです(テキストエディターはメインメソッドと補助メソッドを等しく強調表示し、ここではラムダを使用します)。
- ラムダへの純粋なクリーン関数
UPD2。
ところで、最初の例では、2つの補助メソッドを1つに組み合わせることができます。
def rate_hash_element(rate) rubles_per_unit = rate['Value'].to_f / rate['Nominal'].to_f [rate['CharCode'], rubles_per_unit] end
UPD3。 2016年8月10日から
ruby-style-guideがこのトリックに言及していることがわかりました。 (リンク)[ https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#no-nested-methods ]