Question

J'ai rencontré à plusieurs reprises le type de code suivant et je me demande si c'est une bonne pratique (du point de vue des performances) ou non :

try
{
    ... // some code
}
catch (Exception ex)
{
    ... // Do something
    throw new CustomException(ex);
}

Fondamentalement, ce que fait le codeur, c'est qu'il englobe l'exception dans une exception personnalisée et la relance.

En quoi cela diffère-t-il en termes de performances des deux suivants :

try
{
    ... // some code
}
catch (Exception ex)
{
    .. // Do something
    throw ex;
}

ou

try
{
    ... // some code
}
catch (Exception ex)
{
    .. // Do something
    throw;
}

En mettant de côté les arguments relatifs aux meilleures pratiques fonctionnelles ou de codage, existe-t-il une différence de performances entre les 3 approches ?

Était-ce utile?

La solution

@Brad Tutterow

L'exception n'est pas perdue dans le premier cas, elle est transmise au constructeur.Je serai cependant d'accord avec vous sur le reste, la deuxième approche est une très mauvaise idée à cause de la perte de trace de la pile.Lorsque je travaillais avec .NET, j'ai rencontré de nombreux cas où d'autres programmeurs faisaient exactement cela, et cela me frustrait énormément lorsque j'avais besoin de voir la véritable cause d'une exception, pour ensuite découvrir qu'elle était renvoyée à partir d'un énorme bloc try où Je n'ai maintenant aucune idée d'où vient le problème.

J'appuie également le commentaire de Brad selon lequel vous ne devriez pas vous soucier de la performance.Ce type de micro-optimisation est une idée HORRIBLE.À moins que vous ne parliez de lever une exception à chaque itération d'une boucle for qui s'exécute depuis longtemps, vous ne rencontrerez probablement pas de problèmes de performances du fait de votre utilisation des exceptions.

Optimisez toujours les performances lorsque vous disposez de mesures qui indiquent que vous DEVEZ optimiser les performances, puis touchez les points qui se sont révélés être les coupables.

Il est bien préférable d'avoir un code lisible avec des capacités de débogage faciles (IE ne cachant pas la trace de la pile) plutôt que de faire en sorte que quelque chose s'exécute une nanoseconde plus rapidement.

Une dernière remarque sur l'encapsulation des exceptions dans une exception personnalisée...cela peut être une construction très utile, en particulier lorsqu'il s'agit d'interfaces utilisateur.Vous pouvez envelopper chaque cas exceptionnel connu et raisonnable dans une exception personnalisée de base (ou une exception qui s'étend de ladite exception de base), puis l'interface utilisateur peut simplement intercepter cette exception de base.Une fois interceptée, l'exception devra fournir un moyen d'afficher des informations à l'utilisateur, par exemple une propriété ReadableMessage, ou quelque chose du genre.Ainsi, chaque fois que l'interface utilisateur manque une exception, c'est à cause d'un bogue que vous devez corriger, et chaque fois qu'elle détecte une exception, il s'agit d'une condition d'erreur connue qui peut et doit être gérée correctement par l'interface utilisateur.

Autres conseils

Évidemment, vous encourez la pénalité liée à la création de nouveaux objets (la nouvelle exception). Ainsi, exactement comme vous le faites avec chaque ligne de code que vous ajoutez à votre programme, vous devez décider si la meilleure catégorisation des exceptions paie pour le travail supplémentaire.

À titre de conseil pour prendre cette décision, si vos nouveaux objets ne contiennent pas d'informations supplémentaires sur l'exception, vous pouvez oublier de construire de nouvelles exceptions.

Cependant, dans d’autres circonstances, disposer d’une hiérarchie d’exceptions est très pratique pour l’utilisateur de vos classes.Supposons que vous implémentiez le modèle Facade, aucun des scénarios envisagés jusqu'à présent n'est bon :

  1. n'est pas bon que vous souleviez chaque exception en tant qu'objet Exception parce que vous perdez (probablement) des informations précieuses
  2. ce n'est pas bien non plus de soulever tous les types d'objets que vous attrapez car en faisant cela, vous échouez dans la création de la façade

Dans ce cas hypothétique, la meilleure chose à faire est de créer une hiérarchie de classes d'exceptions qui, en faisant abstraction de vos utilisateurs des complexités internes du système, leur permet de savoir quelque chose sur le type d'exception produite.

En remarque :

Personnellement, je n'aime pas l'utilisation d'exceptions (hiérarchies de classes dérivées de la classe Exception) pour implémenter la logique.Comme dans le cas :

try {
        // something that will raise an exception almost half the time
} catch( InsufficientFunds e) {
        // Inform the customer is broke
} catch( UnknownAccount e ) {
        // Ask for a new account number
}

Comme David, je suppose que les deuxième et troisième fonctionnent mieux.Mais l’un des trois fonctionnerait-il suffisamment mal pour s’en inquiéter ?Je pense qu'il y a des problèmes plus importants que les performances dont il faut s'inquiéter.

FxCop recommande toujours la troisième approche plutôt que la seconde afin que la trace de la pile d'origine ne soit pas perdue.

Modifier:Suppression des éléments qui étaient tout simplement faux et Mike a eu la gentillesse de le souligner.

Ne faites pas :

try
{
    // some code
}
catch (Exception ex) { throw ex; }

Comme cela perdra la trace de la pile.

Faites plutôt :

try
{
    // some code
}
catch (Exception ex) { throw; }

Juste le lancer fera l'affaire, il vous suffit de transmettre la variable d'exception si vous voulez qu'elle soit l'exception interne sur une nouvelle exception personnalisée.

Comme d'autres l'ont dit, les meilleures performances viennent de celle du bas puisque vous relancez simplement un objet existant.Celui du milieu est le moins correct car il perd la pile.

Personnellement, j'utilise des exceptions personnalisées si je souhaite découpler certaines dépendances dans le code.Par exemple, j'ai une méthode qui charge les données à partir d'un fichier XML.Cela peut mal se passer de différentes manières.

La lecture à partir du disque pourrait échouer (FileIOException), l'utilisateur pourrait essayer d'y accéder depuis un endroit où il n'est pas autorisé (SecurityException), le fichier pourrait être corrompu (XmlParseException), les données pourraient être dans un mauvais format (DeserialisationException).

Dans ce cas, afin qu'il soit plus facile pour la classe appelante de comprendre tout cela, toutes ces exceptions renvoient une seule exception personnalisée (FileOperationException), ce qui signifie que l'appelant n'a pas besoin de références à System.IO ou System.Xml, mais peut toujours accédez à l'erreur survenue via une énumération et à toute information importante.

Comme indiqué, n'essayez pas de micro-optimiser quelque chose comme ça, le fait de lever une exception est la chose la plus lente qui se produit ici.La meilleure amélioration à apporter est d’essayer d’éviter une exception.

public bool Load(string filepath)
{
  if (File.Exists(filepath)) //Avoid throwing by checking state
  {
    //Wrap anyways in case something changes between check and operation
    try { .... }
    catch (IOException ioFault) { .... }
    catch (OtherException otherFault) { .... }
    return true; //Inform caller of success
  }
  else { return false; } //Inform caller of failure due to state
}

Le lancement dans votre premier exemple entraîne la surcharge de la création d'un nouvel objet CustomException.

Le nouveau lancement dans votre deuxième exemple lèvera une exception de type Exception.

La relance dans votre troisième exemple lèvera une exception du même type que celle levée par votre "du code".

Ainsi, les deuxième et troisième exemples utilisent moins de ressources.

Attendez....pourquoi nous soucions-nous des performances si une exception est levée ?Sauf si nous utilisons des exceptions dans le cadre du flux d'application normal (ce qui est WAYYYY contre les meilleures pratiques).

Je n'ai vu que des exigences de performance en matière de réussite mais jamais en matière d'échec.

D'un point de vue purement performant, je suppose que le troisième cas est le plus performant.Les deux autres doivent extraire une trace de pile et construire de nouveaux objets, ce qui peut prendre beaucoup de temps.

Cela dit, ces trois blocs de code ont très différents comportements (externes), donc les comparer revient à se demander si QuickSort est plus efficace que l'ajout d'un élément à une arborescence rouge-noir.Ce n'est pas aussi important que de choisir la bonne chose à faire.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top