4.メタプログラミングパターン。 19キュウ。 dr死の救助はdr死者自身の仕事です

時々例外をスローするライブラリメソッドがあるとします。

このメソッドは、定義されているファイルに触れたくないという意味でのライブラリです。たとえば、このファイルは定期的に更新されるライブラリを参照し、特に注意しないと各更新後の変更が失われるためです。それらの保全。

独自のコードでこのようなメソッドを変更するのが慣習です-動的言語では、選択したクラスの選択したメソッドをコード内で直接書き換えることができます。 例:



require ' net/http ' <br>

module Net <br>

class HTTP <br>

def get (*args)<br>

# <br>

end <br>

end <br>

end <br>

<br>







この手法は、 モンキーパッチングと呼ばれます。 問題は、サルはどこにいるのか? 彼らはそれとは全く関係ありません。 ゴリラの原因により近い。 しかし、彼らも非難すべきではありません。 そして、パルチザンはすべてのせいです! 当初、この用語は「ゲリラパッチ」と呼ばれていましたが、英語の用語は「ゴリラパッチ」と非常によく似ていましたが、そこでは酔って「モンキーパッチ」になりました(プログラマーはお互いに「サル」を攻撃し始めました) 「「ゴリラ」よりも攻撃的ではない、それは誰もが同意したことです)。



党派単位の相互作用について



パルチザンは何をしますか? 彼らは密かに、長い外交交渉、予備的脅威、人道回廊の開閉に関する合意なしに、戦争の規則やその他の手続きに関する合意なしに行動を開始します。



いくつかのパルチザングループが存在する場合があり、他のパルチザングループの存在を知っているか疑っていますが、単一の計画はありません。原則として、1つのグループは武器で列車を強奪し、2番目の列車を爆破することを決定できます。 ここでの一貫性は重要ですが、原則として、パルチザンが必要とする世界的な目標はとにかく達成されます。 強盗の最中に列車が爆発し、彼ら自身が死ぬのは不快です。

シングルスレッドアプリケーションのプログラミングでは、特にクラスの初期化に関して、同時性は現象として存在しません。 require



require



行われ、クラスが動的に作成/パッチされた後にスレッドを作成するのが一般的です。 require



で行うことは、別の難しい質問です( 1、2 )。 require



ロック(つまり、 require



実行require



まで他のスレッドに制御を与えない)は、 デッドロックが発生する可能性があるため不可能です)。



しかし、プログラマーはまだ党派ではありません。 彼らは敵の列車を強奪せず、爆発しませんが、作成して改善します。 だから、あなたは最善を願うことができます。 パルチザンパッチの文化に徐々に染み込んでいます-パルチザンですが、メソッドのセマンティクスとシグネチャが変わらないようにします。そうしないと、すべてが地獄に落ちます。



スタジオでの例!



したがって、任意のクラスの任意のメソッドを取得してオーバーライドできます。 彼らは、動的言語のクラスは開かれていると言います。

ああ、このオープンさを使用してどんな下劣なことができるか:

class Fixnum <br>

def * (x)<br>

42 <br>

end <br>

end <br>

puts 5 * 5 <br>

puts 5 * 14 <br>

<br>

class Fixnum <br>

alias orig_div / <br>

def / (x)<br>

puts " -: - #{ self } #{ x } " <br>

self .orig_div(x) <br>

end <br>

end <br>

puts 54 / 12 <br>

puts 13 / 0







最後の例では、 alias



言語構成体を使用します。これにより、関数本体が別の名前で保存されます。 alias



操作は、メソッドの本体をコピーして新しい名前を割り当てる操作と考える方が正しいです。 メソッドをオーバーライドする前にalias



使用して、選択した新しい名前の以前のパッチが適用されていないバージョンのメソッドにアクセスできるようにします。



このアプローチは実際に積極的に適用されています。 たとえば、次のようなコードを記述できます。

require ' net/http ' <br>

class HTTP <br>

alias get_orig get <br>

def restore_connection <br>

begin <br>

do_start<br>

true <br>

rescue <br>

false <br>

end <br>

end <br>

def get (*args)<br>

attempts = 0 <br>

begin <br>

get_orig(*args, &block)<br>

rescue Errno :: ECONNABORTED => e<br>

if (attempts += 1 ) < 3 <br>

restore_connection<br>

retry <br>

end <br>

raise e<br>

end <br>

end <br>

end







このコードは一部の人にとっては突破口のように思えるかもしれませんが、私を満足させるものではありません。 私はこのように書きたい:

<br>

require ' net/http ' <br>

class HTTP <br>

make_rescued :get ,<br>

:rescue => [ Errno :: ECONNABORTED , Errno :: ECONNRESET , EOFError , Timeout :: Error ],<br>

:retry_attempts => 3 ,<br>

:on_success => lambda {| obj , args , res | puts " We did it!: #{ args.inspect } " },<br>

:sleep_before_retry => 1 ,<br>

:ensure => lambda {| obj , args | puts " Finishing : #{ args.inspect } " },<br>

:timeout => 3 ,<br>

:retry_if => lambda do | obj , args , e , attempt |<br>

obj.instance_eval do <br>

case e<br>

when Errno :: ECONNABORTED , Errno :: ECONNRESET <br>

# ! ! <br>

restore_connection<br>

when EOFError , Timeout :: Error <br>

# ? <br>

true <br>

end <br>

end <br>

end <br>

end <br>

<br>







例外的な状況に対してメソッドをより寛容にすることは、最も重要で、しばしば発生するタスクです。 例外処理コードは徐々にシェアを増やしており、ヒューリスティックプログラミング、Webプログラミング、一般に現代のプログラミングでは、すでに30%以上を占めており、ビジネスロジックの不可欠なコンポーネントです。 また、これは重要なので、さまざまなオプションを考慮してレスキュータスクを完全に解決する汎用make_rescued



を作成してみませんか? 新しいパターンを作成する時が来ました!



はい、Rubyのメタプログラミングパターンは、多くの場合、変更メソッドとして提示されます。 他の典型的なパターンは、不純物、extend&include&included不純物技術自体、 method_missing



メソッドです。 これについては、次のトピックで説明します。



近似1.次のオプションを検討します:rescue ,: retry_attempts

module MakeRescued <br>

def extract_options (args)<br>

args.pop if args.last.is_a?( Hash )<br>

end <br>

def alias_method (a, b)<br>

class_eval " alias #{ a }   #{ b } " <br>

end <br>

def make_rescued (*methods)<br>

options = extract_options(methods)<br>

exceptions = options[ :rescue ] || [ Exception ]<br>

methods.each do | method |<br>

method_without_rescue = " #{ method } _without_rescue " <br>

alias_method method_without_rescue, method<br>

define_method(method) do |* args |<br>

retry_attempts = 0 <br>

begin <br>

send(method_without_rescue, *args)<br>

rescue Exception => e<br>

retry_attempts += 1 <br>

unless options[ :retry_attempts ] && retry_attempts > options[ :retry_attempts ]<br>

if exceptions.any?{| klass | klass===e}<br>

retry <br>

end <br>

end <br>

raise e<br>

end <br>

end <br>

end <br>

end <br>

end <br>

<br>









近似2.提案されたすべてのオプションを検討します



require ' timeout ' <br>

<br>

module MakeRescued <br>

def extract_options (args)<br>

args.last.is_a?( Hash ) ? args.pop : {}<br>

end <br>

def alias_method (a, b)<br>

class_eval " alias #{ a }   #{ b } " <br>

end <br>

def make_rescued (*methods)<br>

options = extract_options(methods)<br>

exceptions = options[ :rescue ] || [ Exception ]<br>

methods.each do | method |<br>

method_without_rescue = " #{ method } _without_rescue " <br>

alias_method method_without_rescue, method<br>

define_method(method) do |* args |<br>

retry_attempts = 0 <br>

begin <br>

res = nil <br>

res = if options[ :timeout ]<br>

Timeout ::timeout( options[ :timeout ] ) do <br>

send(method_without_rescue, *args)<br>

end <br>

else <br>

send(method_without_rescue, *args)<br>

end <br>

options[ :on_success ][ self ,args,res] if options[ :on_success ]<br>

res<br>

rescue Exception => e<br>

retry_attempts += 1 <br>

unless options[ :retry_attempts ] && retry_attempts > options[ :retry_attempts ]<br>

if exceptions.any?{| klass | klass===e}<br>

if options[ :retry_if ] && options[ :retry_if ][ self ,args,e,retry_attempts]<br>

sleep options[ :sleep_before_retry ] if options[ :sleep_before_retry ]<br>

retry <br>

end <br>

end <br>

end <br>

options[ :on_fail ][ self ,args,e] if options[ :on_fail ]<br>

raise e<br>

ensure <br>

options[ :ensure ][ self ,args,res] if options[ :ensure ]<br>

res<br>

end <br>

end <br>

end <br>

end <br>

end <br>

<br>

Module .module_eval { include MakeRescued }









このコードはさらに開発できます。 たとえば、 :default



オプションを追加します。これは、 Exception



Exception



場合のデフォルトのメソッド値を示します。 このオプションがブロックと等しい場合( Proc



クラスのオブジェクトがあります)、パラメーター(self, args)



このブロックを呼び出し、メソッドの結果として計算結果を返す必要があります。



make_rescued



メソッドを改善するための他の提案を歓迎します。



クラシック: alias_method_chain





これは、古典的な猿のパッチングです。 彼女が知る必要がある:



彼女に批判的にアプローチします:





alias_method_chain



メソッドについて詳しく説明します。 次のように書くことができることに注意してください。

 ...
   def get_with_rescue(* args)
     ...
       get_without_rescue(* args)
     ...
  終わり

   alais_method_chain:get ,: rescue


メソッドはモジュール用に事前定義されています

def alias_method_chain (target, feature)<br>

alias_method " #{ target } _without_ #{ feature } " , target<br>

alias_method target, " #{ target } _with_ #{ feature } " <br>

end <br>







method_with_feature



およびmethod_without_feature



命名表記を使用すると、プログラマーは呼び出しスタックから、パルチザンによってパッチが適用されたメソッドが深くなっていることを理解できます。 Exception



をスローするとException



意味のあるメソッド名が表示されます。 さらに、各機能に2つのメソッドがあります-この機能の有無にかかわらず、それらを直接呼び出すことが必要になる場合があります。

class Module <br>

def alias_method (a, b)<br>

class_eval " alias #{ a }   #{ b } " <br>

end <br>

def alias_method_chain (target, feature)<br>

alias_method " #{ target } _without_ #{ feature } " , target<br>

alias_method target, " #{ target } _with_ #{ feature } " <br>

end <br>

end <br>

<br>

# : method_without_feature method_with_feature <br>

class Abc <br>

def hello <br>

puts " hello " <br>

raise ' Bang! ' <br>

end <br>

<br>

def hello_with_attention <br>

puts " attention, " <br>

hello_without_attention<br>

end <br>

alias_method_chain :hello , :attention <br>

<br>

def hello_with_name (name)<br>

puts " my darling #{ name } , " <br>

hello_without_name<br>

end <br>

alias_method_chain :hello , :name <br>

end <br>

<br>

Abc .new.hello( ' Liza ' )<br>







 greck $ ruby​​ method_chain_sample_backtrace.rb
 method_chain.rb:14:「hello_without_attention」:バン!  (RuntimeError)
	 method_chain.rbから:19: `hello_without_name '
	 method_chain.rbから:25:「hello」
	 method_chain.rbから:30
私の最愛のライザ、
注意
こんにちは
グレック$ 


このような手法によって提供される別の重要なボーナスがあります:ドロップされたアクションのバックトレースから行をクリックすると、さまざまなIDEが目的のメソッドの目的の行にすばやくジャンプできます。これは、 method_without_feature



メソッドのみがmethod_without_feature



れる単純化された代替アプローチとは異なり、メソッドが実際に異なる名前を持っているためです、すべての定義で同じメソッドが定義されています:

<br>

# , method_without_feature <br>

class Abc <br>

def hello <br>

puts " hello " <br>

raise ' Bang! ' <br>

end <br>

<br>

alias hello_without_attention hello <br>

def hello <br>

puts " attention, " <br>

hello_without_attention<br>

end <br>

<br>

alias hello_without_name hello <br>

def hello (name)<br>

puts " my darling #{ name } , " <br>

hello_without_name<br>

end <br>

end <br>

<br>











参照資料






All Articles