50 votes

Lancer des exceptions en Scala, quelle est la "règle officielle" ?

Je suis le cours Scala sur Coursera. J'ai également commencé à lire le livre Scala d'Odersky.

Ce que j'entends souvent, c'est que ce n'est pas une bonne idée de lancer des exceptions dans les langages fonctionnels, parce que cela interrompt le flux de contrôle et que nous renvoyons généralement un Either avec le Failure ou le Success. Il semble également que Scala 2.10 fournira le Try qui va dans cette direction.

Mais dans le livre et le cours, Martin Odersky ne semble pas dire (du moins pour l'instant) que les exceptions sont mauvaises, et il les utilise beaucoup. J'ai également remarqué les méthodes assert / require...

Enfin, je suis un peu perdue car j'aimerais suivre les meilleures pratiques mais elles ne sont pas claires et le langage semble aller dans les deux sens...

Quelqu'un peut-il m'expliquer ce que je dois utiliser dans chaque cas ?

49voto

Rex Kerr Points 94401

La règle de base est d'utiliser les exceptions pour des cas vraiment exceptionnels**. Pour une défaillance "ordinaire", il est préférable d'utiliser Option o Either . Si vous êtes en interface avec Java où des exceptions sont levées lorsque quelqu'un éternue de la mauvaise façon, vous pouvez utiliser Try pour assurer votre sécurité.

Prenons quelques exemples.

Supposons que vous ayez une méthode qui récupère quelque chose dans une carte. Qu'est-ce qui pourrait mal se passer ? Eh bien, quelque chose de dramatique et de dangereux comme un défaut d'isolement * dépassement de pile, ou quelque chose d'attendu comme l'élément n'est pas trouvé. Vous laisseriez le défaut d'isolement un dépassement de pile lève une exception, mais si vous ne trouvez simplement pas d'élément, pourquoi ne pas renvoyer une valeur Option[V] au lieu de la valeur ou d'une exception (ou null ) ?

Supposons maintenant que vous écriviez un programme dans lequel l'utilisateur est censé saisir un nom de fichier. Si vous n'avez pas l'intention d'abandonner instantanément le programme en cas de problème, un fichier Either est la voie à suivre :

def main(args: Array[String]) {
  val f = {
    if (args.length < 1) Left("No filename given")
    else {
      val file = new File(args(0))
      if (!file.exists) Left("File does not exist: "+args(0))
      else Right(file)
    }
  }
  // ...
}

Supposons maintenant que vous souhaitiez analyser une chaîne contenant des nombres délimités par des espaces.

val numbers = "1 2 3 fish 5 6"      // Uh-oh
// numbers.split(" ").map(_.toInt)  <- will throw exception!
val tried = numbers.split(" ").map(s => Try(s.toInt))  // Caught it!
val good = tried.collect{ case Success(n) => n }

Vous avez donc trois façons (au moins) de faire face à différents types d'échec : Option pour qu'il ait fonctionné / n'ait pas fonctionné, dans les cas où le fait de ne pas fonctionner est un comportement attendu, et non un échec choquant et alarmant ; Either pour les cas où les choses peuvent fonctionner ou non (ou, en fait, dans tous les cas où vous avez deux options qui s'excluent mutuellement) et où vous voulez sauvegarder des informations sur ce qui n'a pas fonctionné ; et Try lorsque vous ne voulez pas vous occuper vous-même de la gestion des exceptions, mais que vous avez quand même besoin de vous interfacer avec du code qui ne connaît pas d'exceptions.

Par ailleurs, les exceptions constituent de bons exemples - c'est pourquoi vous les trouverez plus souvent dans un manuel ou un matériel d'apprentissage qu'ailleurs, je pense : les exemples des manuels sont très souvent incomplets, ce qui signifie que les problèmes graves qui devraient normalement être évités par une conception soignée devraient plutôt être signalés par la levée d'une exception.

* Edit : Les Segfaults plantent la JVM et ne devraient jamais se produire quel que soit le bytecode ; même une exception ne vous aidera pas dans ce cas. Je parlais de débordement de pile.

** Edit : Les exceptions (sans trace de pile) sont également utilisées pour le flux de contrôle dans Scala - c'est en fait un mécanisme assez efficace, et elles permettent des choses comme la définition d'une bibliothèque par l'utilisateur. break et d'une return qui revient de votre méthode alors que le contrôle est passé dans une ou plusieurs fermetures. La plupart du temps, vous ne devriez pas vous en préoccuper vous-même, sauf pour réaliser que la capture de tous Throwable n'est pas une très bonne idée, car vous risquez d'attraper par erreur l'une de ces exceptions relatives au flux de contrôle.

12voto

Dave Griffith Points 12923

C'est donc l'un des domaines dans lesquels Scala troque la pureté fonctionnelle contre la facilité de transition et l'interopérabilité avec les langages et environnements existants, en particulier Java. La pureté fonctionnelle est rompue par les exceptions, car elles brisent l'intégrité référentielle et rendent impossible le raisonnement équationnel. (Bien entendu, les récursions sans terminaison ont le même effet, mais peu de langages sont prêts à appliquer les restrictions qui les rendraient impossibles). Pour conserver la pureté fonctionnelle, vous utilisez Option/Maybe/Either/Try/Validation, qui codent tous le succès ou l'échec comme un type référentiellement transparent, et utilisez les diverses fonctions d'ordre supérieur qu'ils fournissent ou la syntaxe spéciale de la monade du langage sous-jacent pour rendre les choses plus claires. Ou, en Scala, vous pouvez simplement décider d'abandonner la pureté fonctionnelle, en sachant que cela peut rendre les choses plus faciles à court terme, mais plus difficiles à long terme. Cela est similaire à l'utilisation de "null" en Scala, ou de collections mutables, ou de "var" locaux. C'est un peu honteux, et il ne faut pas en faire trop, mais tout le monde a des délais à respecter.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X