18 votes

Vérification des conditions et traitement des exceptions

Quand le traitement des exceptions est-il préférable à la vérification des conditions ? Il existe de nombreuses situations où je peux choisir d'utiliser l'un ou l'autre.

Par exemple, voici une fonction de sommation qui utilise une exception personnalisée :

# module mylibrary 
class WrongSummand(Exception):
    pass

def sum_(a, b):
    """ returns the sum of two summands of the same type """
    if type(a) != type(b):
        raise WrongSummand("given arguments are not of the same type")
    return a + b

# module application using mylibrary
from mylibrary import sum_, WrongSummand

try:
    print sum_("A", 5)
except WrongSummand:
    print "wrong arguments"

Et voici la même fonction, qui évite d'utiliser les exceptions

# module mylibrary
def sum_(a, b):
    """ returns the sum of two summands if they are both of the same type """
    if type(a) == type(b):
        return a + b

# module application using mylibrary
from mylibrary import sum_

c = sum_("A", 5)
if c is not None:
    print c
else:
    print "wrong arguments"

Je pense que l'utilisation de conditions est toujours plus lisible et plus facile à gérer. Ou ai-je tort ? Quels sont les cas appropriés pour définir des API qui lèvent des exceptions et pourquoi ?

9voto

DevinB Points 5960

En règle générale, vous souhaitez utiliser le contrôle des conditions pour les situations qui sont compréhensibles, attendues et capables d'être traitées. Vous utiliserez les exceptions pour les cas incohérents ou impossibles à gérer.

Donc, si vous pensez à votre fonction "add". Elle ne devrait JAMAIS retourner null. Ce n'est pas un résultat cohérent pour l'addition de deux choses. Dans ce cas, il y a une erreur dans les arguments qui ont été passés et la fonction devrait no essayer de prétendre que tout va bien. C'est un cas parfait pour lancer une exception.

Il est préférable d'utiliser le contrôle des conditions et de renvoyer la valeur null si vous êtes dans un cas d'exécution normal. Par exemple, IsEqual pourrait être un bon cas pour utiliser des conditions, et renvoyer false si l'une de vos conditions échoue. PAR EXEMPLE.

function bool IsEqual(obj a, obj b)
{ 
   if(a is null) return false;
   if(b is null) return false;
   if(a.Type != b.Type) return false;

   bool result = false;
   //Do custom IsEqual comparison code
   return result;
}

Dans ce scénario, vous renvoyez false à la fois pour les cas d'exception ET pour le cas "les objets ne sont pas égaux". Cela signifie que le consommateur (l'appelant) ne peut pas savoir si la comparaison a échoué ou si les objets étaient simplement pas égal . Si ces cas doivent être distingués, vous devez alors utiliser des exceptions au lieu de conditions.

En fin de compte, vous devez vous demander si le consommateur sera en mesure de traiter spécifiquement le cas d'échec que vous avez rencontré. Si votre méthode/fonction ne peut pas faire ce qu'il doit faire alors vous voudrez probablement lever une exception.

9voto

Jochen Ritzel Points 42916

Les exceptions sont beaucoup plus faciles à gérer, car elles définissent des familles générales de choses qui peuvent mal tourner. Dans votre exemple, il n'y a qu'un seul problème possible, il n'y a donc aucun avantage à utiliser les exceptions. Mais si vous avez une autre classe qui fait la division, alors elle doit signaler que vous ne pouvez pas diviser par zéro. En retournant simplement None ne fonctionnerait plus.

D'autre part, les exceptions peuvent être sous-classées et vous pouvez attraper des exceptions spécifiques, selon l'importance que vous accordez au problème sous-jacent. Par exemple, vous pouvez avoir un DoesntCompute exception de base et des sous-classes comme InvalidType y InvalidArgument . Si vous souhaitez uniquement obtenir un résultat, vous pouvez regrouper tous les calculs dans un bloc qui récupère les données suivantes DoesntCompute mais il est tout aussi facile de gérer des erreurs très spécifiques.

2voto

Mike Graham Points 22480

Si vous posez la question, vous devriez probablement utiliser des exceptions. Les exceptions sont utilisées pour indiquer des circonstances exceptionnelles, un cas spécifique où les choses fonctionnent différemment des autres cas. C'est le cas pour pratiquement toutes les erreurs et pour beaucoup d'autres choses également.

Dans votre deuxième mise en œuvre de sum_ l'utilisateur doit vérifier à chaque fois, quelle était la valeur. Cela rappelle le boilerplate du C/Fortran/autres langages (et source fréquente d'erreurs) où les codes d'erreur ne sont pas vérifiés et que nous évitons. Vous devez écrire du code de ce type à tous les niveaux pour pouvoir propager les erreurs. Cela devient désordonné et est particulièrement évité en Python.

Quelques autres remarques :

  • Souvent, vous n'avez pas besoin de faire vos propres exceptions. Dans de nombreux cas, les exceptions intégrées comme ValueError y TypeError sont appropriées.
  • Lorsque je crée une nouvelle exception, ce qui est assez utile, j'essaie souvent de sous-classer quelque chose de plus spécifique que Exception . La hiérarchie des exceptions intégrées est la suivante aquí .
  • Je n'implémenterais jamais une fonction comme sum_ La vérification des types rend votre code moins flexible, moins facile à maintenir et moins idiomatique.

    J'écrirais simplement la fonction

    def sum_(a, b):
        return a + b

    qui fonctionnerait si les objets étaient compatibles et sinon, elle déjà lance une exception, le TypeError que tout le monde a l'habitude de voir. Voyez comment fonctionne ma mise en œuvre

    >>> sum_(1, 4)
    5
    >>> sum_(4.5, 5.0)
    9.5
    >>> sum_([1, 2], [3, 4])
    [1, 2, 3, 4]
    >>> sum_(3.5, 5) # This operation makes perfect sense, but would fail for you
    8.5
    >>> sum_("cat", 7) # This makes no sense and already is an error.
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 1, in sum_
    TypeError: cannot concatenate 'str' and 'int' objects

    Mon code était plus court et plus simple, mais il est plus robuste et plus souple que le vôtre. C'est pourquoi nous évitons le contrôle de type en Python.

2voto

Ned Batchelder Points 128913

La raison principale pour laquelle je préfère les exceptions aux retours d'état est de considérer ce qui se passe si le programmeur oublie de faire son travail. Avec les exceptions, vous pouvez oublier d'attraper une exception. Dans ce cas, votre système échouera visiblement, et vous aurez l'occasion de réfléchir à l'endroit où ajouter une exception. Avec les retours d'état, si vous oubliez de vérifier le retour, il sera silencieusement ignoré, et votre code continuera, pour éventuellement échouer plus tard d'une manière mystérieuse. Je préfère l'échec visible à l'échec invisible.

Il y a d'autres raisons, que j'ai expliquées ici : Exceptions et retours d'état .

2voto

SPIRiT_1984 Points 766

En fait, le problème de l'utilisation des exceptions réside dans la logique de l'entreprise. Si la situation est exceptionnelle (c'est-à-dire qu'elle ne devrait pas se produire du tout), une exception peut être utilisée. Cependant, si la situation est possible du point de vue de la logique métier, elle doit être traitée par une vérification conditionnelle, même si cette condition semble beaucoup plus compliquée.

Par exemple, voici le code que j'ai rencontré dans l'instruction préparée, lorsque le développeur fixe les valeurs des paramètres (Java, pas Python) :

// Variant A
try {
  ps.setInt(1, enterprise.getSubRegion().getRegion().getCountry().getId());
} catch (Exception e) {
  ps.setNull(1, Types.INTEGER);
}

Avec la vérification conditionnelle, cela s'écrirait comme suit :

// Variant B
if (enterprise != null && enterprise.getSubRegion() != null
  && enterprise.getSubRegion().getRegion() != null
  && enterprise.getSubRegion().getRegion().getCountry() != null) {
  ps.setInt(1, enterprise.getSubRegion().getRegion().getCountry().getId()); 
} else {
  ps.setNull(1, Types.INTEGER);
}

La variante B semble beaucoup plus compliquée à première vue, cependant, c'est la correct un, puisque cette situation est possible du point de vue commercial (le pays peut ne pas être spécifié). L'utilisation d'exceptions causera des problèmes de performance et entraînera une mauvaise compréhension du code, car il ne sera pas clair s'il est acceptable que le pays soit vide.

La variante B peut être améliorée par l'utilisation de fonctions auxiliaires en EnterpriseBean qui renverra immédiatement la région et le pays :

public RegionBean getRegion() {
  if (getSubRegion() != null) {  
    return getSubRegion().getRegion();
  } else {
    return null;
  }
}

public CountryBean getCountry() {
  if (getRegion() != null) {
    return getRegion().getCountry();
  } else {
    return null;
  }
}

Ce code utilise quelque chose comme le chaînage, et chaque obtenir semble assez simple et n'utilise qu'un seul prédécesseur. Par conséquent, la variante B peut être réécrite comme suit :

// Variant C
if (enterprise != null && enterprise.getCountry() != null) {
  ps.setInt(1, enterprise.getCountry().getId());
} else {
  ps.setNull(1, Types.INTEGER);
}

Lisez aussi ceci Article de Joel sur la raison pour laquelle les exceptions ne devraient pas être surutilisées. Et une essai par Raymon Chen.

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