Gestion des exceptions

Crystal gère les exceptions en levant et récupérant les exceptions.

Lever une exception

Vous levez des exceptions en invoquant la méthode de haut niveau raise. Contrairement à d'autres mots clés, raise est une méthode normale avec deux surcharges: une acceptant une String et un autre acceptant une instance exception:

raise "OH NO!"
raise Exception.new("Some error")

La version String crée simplement une nouvelle instance d'Exception avec ce message.

Seules les instances ou sous-classes d'Exception peuvent être levées.

Définir des exceptions personnalisées

Pour définir un type personnalisé d'exception, créez simplement une sous-classe à partir d'Exception:

class MyException < Exception
end

class MyOtherException < Exception
end

Vous pouvez, comme d'habitude, définir un constructeur pour votre exception ou simplement utiliser celui par défaut.

Récupérer des exceptions

Pour récupérer toute exception utilisez une expression begin ... rescue ... end:

begin
  raise "OH NO!"
rescue
  puts "Rescued!"
end

# Output: Rescued!

Pour accéder à une exception récupérée vous pouvez spécifier une variable dans la clause rescue:

begin
  raise "OH NO!"
rescue ex
  puts ex.message
end

# Output: OH NO!

Pour récupérer seulement un type d'exception (ou n'importe laquelle de ses sous-classes):

begin
  raise MyException.new("OH NO!")
rescue MyException
  puts "Rescued MyException"
end

# Output: Rescued MyException

Et pour y accéder, utilisez une syntaxe similaire aux restrictions de type:

begin
  raise MyException.new("OH NO!")
rescue ex : MyException
  puts "Rescued MyException: #{ex.message}"
end

# Output: Rescued MyException: OH NO!

Des clauses multiples rescue peuvent être spécifiées:

begin
  # ...
rescue ex1 : MyException
  # only MyException...
rescue ex2 : MyOtherException
  # only MyOtherException...
rescue
  # any other kind of exception
end

Vous pouvez aussi récupérer des types multiples d'exception d'un coup en spécifiant un type union:

begin
  # ...
rescue ex : MyException | MyOtherException
  # only MyException or MyOtherException
rescue
  # any other kind of exception
end

ensure

Une clause ensure est exécutée à la fin d'une expression begin ... end ou begin ... rescue ... end qu'une exception ait été levée ou non:

begin
  something_dangerous
ensure
  puts "Cleanup..."
end

# Will print "Cleanup..." after invoking something_dangerous,
# regardless of whether it raised or not

Ou:

begin
  something_dangerous
rescue
  # ...
ensure
  # this will always be executed
end

Les clauses ensure sont généralement utilisées pour faire du ménage, libérer des ressources, etc.

else

Une clause else est exécutée seulement si aucune exception n'a été levée:

begin
  something_dangerous
rescue
  # execute this if an exception is raised
else
  # execute this if an exception isn't raised
end

Une clause else peut seulement être spécifiée si au moins une clause rescue est spécifiée.

Forme de syntaxe courte

La gestion d'exception possède une forme de syntaxe courte: elle assume qu'une définition de méthode est une expression implicite begin ... end, puis spécifie les clauses rescue, ensure et else:

def some_method
  something_dangerous
rescue
  # execute if an exception is raised
end

# L'exemple précédent est équivalent à:
def some_method
  begin
    something_dangerous
  rescue
    # execute if an exception is raised
  end
end

Un exemple avec ensure:

def some_method
  something_dangerous
ensure
  # always execute this
end

# L'exemple précédent est équivalent à:
def some_method
  begin
    something_dangerous
  ensure
    # always execute this
  end
end

Inférence de type

Les variables déclarées dans une partie begin d'une gestion d'exception ont aussi le type Nil quand considérées dans un bloc rescue ou ensure. Par exemple:

begin
  a = something_dangerous_that_returns_Int32
ensure
  puts a + 1 # error, undefined method '+' for Nil
end

Ce qui précéde arrive si something_dangerous_that_returns_Int32 ne léve jamais d'exception, ou si une valeur a été assignée à a puis une méthode qui peut lever une exception est levée:

begin
  a = 1
  something_dangerous
ensure
  puts a + 1 # error, undefined method '+' for Nil
end

Bien qu'il est évident que a se verra toujours affecter une valeur, le compilateur pensera toujours que a n'aura jamais une chance d'être initialisée. Bien que cette logique puisse évoluer à l'avenir, pour l'instant cela vous force à laisser vos gestions d'exception à leur strict minimum, rendant le but de votre code plus clair:

# Clearer than the above: `a` doesn't need
# to be in the exception handling code.
a = 1
begin
  something_dangerous
ensure
  puts a + 1 # works
end

Alternatives à la gestion d'erreur

Bien que les exceptions soient disponibles comme l'un des mécanismes de gestion d'erreur, ce n'est pas votre unique choix. Lever une exception implique l'allocation de mémoire, et exécuter une gestion d'exception est généralement lent.

La librairie standard fournit généralement plusieurs méthodes pour accomplir quelque chose: une léve, l'autre retourne nil. Par exemple:

array = [1, 2, 3]
array[4]  # raises because of IndexError
array[4]? # returns nil because of index out of bounds

La convention habituelle est de fournir une méthode alternative "question" pour indiquer que cette variante de la méthode retourne nil au lieu de lever une exception. Cela permet à l'utilisatrice de choisir entre travailler avec des exceptions ou avec nil. Remarquez, cependant, que ce n'est pas disponible pour toutes les méthodes existantes, étant donné que les exceptions sont à privilégier car elles ne polluent pas votre code avec la logique de gestion d'erreurs.

results matching ""

    No results matching ""