Passaggio di più classi di errore alla clausola di salvataggio di ruby ​​in modo DRY


100

Ho del codice che deve salvare più tipi di eccezioni in ruby:

begin
  a = rand
  if a > 0.5
    raise FooException
  else
    raise BarException
  end
rescue FooException, BarException
  puts "rescued!"
end

Quello che vorrei fare è in qualche modo memorizzare l'elenco dei tipi di eccezione che voglio salvare da qualche parte e passare quei tipi alla clausola di salvataggio:

EXCEPTIONS = [FooException, BarException]

e poi:

rescue EXCEPTIONS

È anche possibile, ed è possibile senza alcune chiamate davvero hacker eval? Non sono fiducioso dato che vedo TypeError: class or module required for rescue clausequando provo quanto sopra.


2
E per quanto riguarda il salvataggio * ECCEZIONI?
Roman

Risposte:


197

È possibile utilizzare un array con l'operatore splat *.

EXCEPTIONS = [FooException, BarException]

begin
  a = rand
  if a > 0.5
    raise FooException
  else
    raise BarException
  end
rescue *EXCEPTIONS
  puts "rescued!"
end

Se intendi utilizzare una costante per l'array come sopra (con EXCEPTIONS), nota che non puoi definirla all'interno di una definizione, e anche se la definisci in qualche altra classe, devi fare riferimento ad essa con il suo spazio dei nomi. In realtà, non deve essere una costante.


Operatore Splat

L'operatore splat *"decomprime" un array nella sua posizione in modo che

rescue *EXCEPTIONS

significa lo stesso di

rescue FooException, BarException

Puoi anche usarlo all'interno di un array letterale come

[BazException, *EXCEPTIONS, BangExcepion]

che è lo stesso di

[BazException, FooException, BarException, BangExcepion]

o in una posizione di argomento

method(BazException, *EXCEPTIONS, BangExcepion)

che significa

method(BazException, FooException, BarException, BangExcepion)

[] si espande in vacuità:

[a, *[], b] # => [a, b]

Una differenza tra ruby ​​1.8 e ruby ​​1.9 è con nil.

[a, *nil, b] # => [a, b]       (ruby 1.9)
[a, *nil, b] # => [a, nil, b]  (ruby 1.8)

Attenzione agli oggetti sui quali to_aè definito, come to_averrà applicato in questi casi:

[a, *{k: :v}, b] # => [a, [:k, :v], b]

Con altri tipi di oggetti, ritorna se stesso.

[1, *2, 3] # => [1, 2, 3]

2
Questo sembra funzionare anche in Ruby 1.8.7. Qual è il termine per utilizzare il carattere "*" davanti EXCEPTIONSin questo caso? Vorrei saperne di più.
apb

2
@ Andy Si chiama splat. Di solito ha l'effetto di scomporre un array in oggetti separati da virgole. Quando viene utilizzato nell'argomento che riceve la posizione di una definizione di metodo, funziona in un altro modo: mette insieme gli argomenti in un array. È abbastanza utile in varie occasioni. Buono a sapersi che funziona con 1.8.7. Ho modificato la mia risposta di conseguenza.
sawa

20
Nota che se desideri accedere all'istanza di eccezione, utilizza questa sintassi: rescue InvalidRequestError, CardError => e(vedi mikeferrier.com/2012/05/19/… )
Peter Ehrlich

Questa sintassi funziona perfettamente:, rescue *EXCEPTIONS => edove EXCEPTIONSè un array di nomi di classi di eccezioni.
aks

3

Sebbene la risposta data da @sawa sia tecnicamente corretta, penso che utilizzi in modo improprio il meccanismo di gestione delle eccezioni di Ruby.

Come il commento di suggerisce Peter Ehrlich (indicando un vecchio post sul blog di Mike Ferrier ), Ruby è già dotato di un meccanismo di gestione delle eccezioni DRY:

puts 'starting up'
begin
  case rand(3)
  when 0
    ([] + '')
  when 1
    (foo)
  when 2
    (3 / 0)
  end
rescue TypeError, NameError => e
  puts "oops: #{e.message}"
rescue Exception => e
  puts "ouch, #{e}"
end
puts 'done'

Utilizzando questa tecnica, possiamo accedere all'oggetto eccezione, che di solito contiene alcune informazioni preziose.


1

Mi sono appena imbattuto in questo problema e ho trovato una soluzione alternativa. Nel caso in cui il tuo FooExceptioneBarException saranno tutte classi di eccezione personalizzate e in particolare se sono tutte tematicamente correlate, potete strutturare la gerarchia di ereditarietà in modo che erediteranno tutte dalla stessa classe genitore e quindi salveranno solo la classe genitore.

Per esempio ho avuto tre eccezioni: FileNamesMissingError, InputFileMissingError, e OutputDirectoryErrorche volevo soccorso con una dichiarazione. Ho chiamato un'altra classe di eccezioni FileLoadErrore quindi ho impostato le tre eccezioni precedenti per ereditarle. Poi ho salvato solo FileLoadError.

Come questo:

class FileLoadError < StandardError
end

class FileNamesMissingError < FileLoadError
end

class InputFileMissingError < FileLoadError
end

class OutputDirectoryError < FileLoadError
end

[FileNamesMissingError,
 InputFileMissingError,
 OutputDirectoryError].each do |error| 
   begin  
     raise error
   rescue FileLoadError => e
     puts "Rescuing #{e.class}."
   end 
end
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.