Écrit par Martin C.
Les exceptions servent, comme leur nom l’indique, à gérer des erreurs au caractère rare et imprévu.
Elles n’ont pas vocation à gérer des erreurs prévisibles comme une erreur de saisie d’un utilisateur par exemple.
Venons-en rapidement aux faits :
begin
3 + '4'
rescue => e
puts e.class
puts e.message
puts e.trace
end
Nous renverra:
TypeError
String can't be coerced into Fixnum
./exceptions.rb:4:in `+'
./exceptions.rb:4:in `<main>'
On a donc une erreur de la classe TypeError
. Si on regarde la classe parente de TypeError
il s’agit de StandardError
. Cette classe est en fait la classe par défaut utilisée dans notre rescue
.
Notre code revient donc à :
begin
3 + '4'
rescue StandardError => e
puts e.class
puts e.message
puts e.trace
end
Il est fortement déconseillé d’attraper des erreurs en dehors des sous-classes de StandardError
car elles sont utilisées par le système. Par exemple, on y retrouve NoMemoryError
, LoadError
ou encore SystemExit
pour n’en citer que quelques-unes.
On comprend mieux pourquoi les attraper pourrait avoir des effets de bords. Quelques exemples :
begin
require 'foo'
rescue Exception
puts $!
end
Ici, on attrape l’exception de type LoadError
, ce qui risque de masquer le problème de chargement de notre fichier.
À noter l’utilisation de la variable globale $!
qui récupère automatiquement la dernière exception.
Autre exemple :
while true
puts "Say quit to esc"
begin
str = gets
exit if "quit" == str.chomp
rescue Exception => e
puts "Exception #{e.class}"
end
end
Bon courage pour quitter, le rescue attrapera l’exception SystemExit
si vous tapez quit
mais également Interrupt
si vous faites un ^C
.
À part pour faire une lib de debug ou autre type de wrapper ne faites pas de rescue Exception
. Si vous avez besoin d’un wrapper, pensez à relancer l’exception :
begin
require 'foo'
rescue Exception => e
# j'attrape tout, mais je sais ce que je fais
puts $!
raise e
end
Lorsque vous avez besoin de vous assurer qu’un traitement sera réalisé même si une exception survient vous pouvez utiliser ensure
:
require 'tempfile'
begin
file = Tempfile.new('a')
raise StandardError.new("ugh")
rescue => e
puts $!
raise e
ensure
file.close
file.unlink
end
Notre section ensure
va s’assurer que le fichier est bien fermé et supprimé. J’ai volontairement re-lancé l’exception pour montrer que même si le rescue
relance l’exception, le passage dans le ensure
est garanti.
Bien entendu, vous pouvez créer votre propre exception comme une classe classique :
class MyOwnException < Exception; end
raise MyOwnException.new('foo')
Si vous souhaitez attraper plusieurs types d’exception sans distinction, vous pouvez parfaitement écrire :
require 'tempfile'
begin
file = Tempfile.new('a')
raise ArgumentError.new("ugh")
rescue ArgumentError, ZeroDivisionError => e
puts $!
raise e
ensure
file.close
file.unlink
end
Vous pouvez également gérer des comportements différents avec plusieurs instructions rescue
:
require 'tempfile'
begin
file = Tempfile.new('a')
raise ArgumentError.new("ugh")
rescue ArgumentError => e
puts $!
# je renvoie l'erreur uniquement si c'est une exception de type ArgumentError
raise e
rescue ZeroDivisionError => e
puts $!
ensure
file.close
file.unlink
end
Pour connaître la liste des exceptions disponibles, je vous ai préparé quelques lignes pour parcourir les différentes classes existantes et les afficher sous forme d’arbre :
def subclasses_tree(klass, level = 0)
puts level.zero? ? klass.to_s : "#{' ' * (4 * (level - 1))}├── #{klass}"
descendants_of(klass).select { |k| k.superclass == klass }.each do |k|
subclasses_tree(k, level + 1)
end
end
def descendants_of(klass)
ObjectSpace.each_object(Class).select { |k| k < klass }
end
subclasses_tree(Exception)
Résultat :
Exception
├── MonitorMixin::ConditionVariable::Timeout
├── SystemStackError
├── NoMemoryError
├── SecurityError
├── ScriptError
├── NotImplementedError
├── LoadError
├── Gem::LoadError
├── Gem::ConflictError
├── SyntaxError
├── StandardError
├── FiberError
├── ThreadError
├── Math::DomainError
├── LocalJumpError
├── IOError
├── EOFError
├── RegexpError
├── ZeroDivisionError
├── SystemCallError
├── Errno::ERPCMISMATCH
├── Errno::EPROGUNAVAIL
├── Errno::EPROGMISMATCH
├── Errno::EPROCUNAVAIL
├── Errno::EPROCLIM
├── Errno::ENOTSUP
├── Errno::ENOATTR
├── Errno::ENEEDAUTH
├── Errno::EFTYPE
├── Errno::EBADRPC
├── Errno::EAUTH
├── Errno::EOWNERDEAD
├── Errno::ENOTRECOVERABLE
├── Errno::ECANCELED
├── Errno::EDQUOT
├── Errno::ESTALE
├── Errno::EINPROGRESS
├── IO::EINPROGRESSWaitWritable
├── IO::EINPROGRESSWaitReadable
├── Errno::EALREADY
├── Errno::EHOSTUNREACH
├── Errno::EHOSTDOWN
├── Errno::ECONNREFUSED
├── Errno::ETIMEDOUT
├── Errno::ETOOMANYREFS
├── Errno::ESHUTDOWN
├── Errno::ENOTCONN
├── Errno::EISCONN
├── Errno::ENOBUFS
├── Errno::ECONNRESET
├── Errno::ECONNABORTED
├── Errno::ENETRESET
├── Errno::ENETUNREACH
├── Errno::ENETDOWN
├── Errno::EADDRNOTAVAIL
├── Errno::EADDRINUSE
├── Errno::EAFNOSUPPORT
├── Errno::EPFNOSUPPORT
├── Errno::EOPNOTSUPP
├── Errno::ESOCKTNOSUPPORT
├── Errno::EPROTONOSUPPORT
├── Errno::ENOPROTOOPT
├── Errno::EPROTOTYPE
├── Errno::EMSGSIZE
├── Errno::EDESTADDRREQ
├── Errno::ENOTSOCK
├── Errno::EUSERS
├── Errno::EILSEQ
├── Errno::EOVERFLOW
├── Errno::EBADMSG
├── Errno::EMULTIHOP
├── Errno::EPROTO
├── Errno::ENOLINK
├── Errno::EREMOTE
├── Errno::ENOSR
├── Errno::ETIME
├── Errno::ENODATA
├── Errno::ENOSTR
├── Errno::EIDRM
├── Errno::ENOMSG
├── Errno::ELOOP
├── Errno::ENOTEMPTY
├── Errno::ENOSYS
├── Errno::ENOLCK
├── Errno::ENAMETOOLONG
├── Errno::EDEADLK
├── Errno::ERANGE
├── Errno::EDOM
├── Errno::EPIPE
├── Errno::EMLINK
├── Errno::EROFS
├── Errno::ESPIPE
├── Errno::ENOSPC
├── Errno::EFBIG
├── Errno::ETXTBSY
├── Errno::ENOTTY
├── Errno::EMFILE
├── Errno::ENFILE
├── Errno::EINVAL
├── Errno::EISDIR
├── Errno::ENOTDIR
├── Errno::ENODEV
├── Errno::EXDEV
├── Errno::EEXIST
├── Errno::EBUSY
├── Errno::ENOTBLK
├── Errno::EFAULT
├── Errno::EACCES
├── Errno::ENOMEM
├── Errno::EAGAIN
├── IO::EAGAINWaitWritable
├── IO::EAGAINWaitReadable
├── Errno::ECHILD
├── Errno::EBADF
├── Errno::ENOEXEC
├── Errno::E2BIG
├── Errno::ENXIO
├── Errno::EIO
├── Errno::EINTR
├── Errno::ESRCH
├── Errno::ENOENT
├── Errno::EPERM
├── Errno::NOERROR
├── EncodingError
├── Encoding::ConverterNotFoundError
├── Encoding::InvalidByteSequenceError
├── Encoding::UndefinedConversionError
├── Encoding::CompatibilityError
├── RuntimeError
├── Gem::Exception
├── Gem::VerificationError
├── Gem::RubyVersionMismatch
├── Gem::RemoteSourceException
├── Gem::RemoteInstallationSkipped
├── Gem::RemoteInstallationCancelled
├── Gem::RemoteError
├── Gem::OperationNotSupportedError
├── Gem::InvalidSpecificationException
├── Gem::InstallError
├── Gem::ImpossibleDependenciesError
├── Gem::GemNotFoundException
├── Gem::SpecificGemNotFoundException
├── Gem::FormatException
├── Gem::FilePermissionError
├── Gem::EndOfYAMLException
├── Gem::DocumentError
├── Gem::GemNotInHomeException
├── Gem::DependencyRemovalException
├── Gem::DependencyError
├── Gem::UnsatisfiableDependencyError
├── Gem::DependencyResolutionError
├── Gem::CommandLineError
├── NameError
├── NoMethodError
├── RangeError
├── FloatDomainError
├── IndexError
├── StopIteration
├── KeyError
├── ArgumentError
├── UncaughtThrowError
├── Gem::Requirement::BadRequirementError
├── TypeError
├── SignalException
├── Interrupt
├── fatal
├── SystemExit
├── Gem::SystemExitException
Vous savez tout, bonne gestion d’exceptions avec Ruby !