Avec notre SDK public, nous avons tendance à vouloir donner des messages très informatifs sur les raisons pour lesquelles une exception se produit. Par exemple:
if (interfaceInstance == null)
{
string errMsg = string.Format(
"Construction of Action Argument: {0}, via the empty constructor worked, but type: {1} could not be cast to type {2}.",
ParameterInfo.Name,
ParameterInfo.ParameterType,
typeof(IParameter)
);
throw new InvalidOperationException(errMsg);
}
Cependant, cela a tendance à encombrer le flux du code, car il a tendance à mettre l'accent sur les messages d'erreur plutôt que sur ce que fait le code.
Un collègue a commencé à refactoriser une partie de l'exception en lançant quelque chose comme ceci:
if (interfaceInstance == null)
throw EmptyConstructor();
...
private Exception EmptyConstructor()
{
string errMsg = string.Format(
"Construction of Action Argument: {0}, via the empty constructor worked, but type: {1} could not be cast to type {2}.",
ParameterInfo.Name,
ParameterInfo.ParameterType,
typeof(IParameter)
);
return new InvalidOperationException(errMsg);
}
Ce qui rend la logique du code plus facile à comprendre, mais ajoute de nombreuses méthodes supplémentaires pour gérer les erreurs.
Quelles sont les autres façons d'éviter le problème de la "logique d'encombrement des messages à longue exception"? Je pose principalement des questions sur C # /. NET idiomatique, mais comment les autres langages le gèrent également.
[Éditer]
Ce serait bien d'avoir les avantages et les inconvénients de chaque approche également.
la source
Exception.Data
propriété, de la capture d'exceptions "pointilleuse", de la capture de code et de l'ajout de son propre contexte, ainsi que de la pile d'appels capturés, fournissent toutes des informations qui devraient permettre des messages beaucoup moins verbeux.System.Reflection.MethodBase
Semble enfin prometteur pour fournir des détails à passer à votre méthode de "construction d'exception".Exception.Data
. L'accent devrait être mis sur la capture de la télémétrie. Refactoring ici est bien, mais il manque le problème.Réponses:
Pourquoi ne pas avoir des classes d'exception spécialisées?
Cela pousse la mise en forme et les détails à l'exception elle-même et laisse la classe principale épurée.
la source
Microsoft semble (en regardant la source .NET) utiliser parfois des chaînes de ressources / environnement. Par exemple
ParseDecimal
:Avantages:
Les inconvénients:
la source
Pour le scénario du SDK public, j'envisagerais fortement d'utiliser les contrats de code Microsoft car ils fournissent des erreurs informatives, des vérifications statiques et vous pouvez également générer de la documentation à ajouter aux documents XML et aux fichiers d'aide générés par Sandcastle . Il est pris en charge dans toutes les versions payantes de Visual Studio.
Un avantage supplémentaire est que si vos clients utilisent C #, ils peuvent tirer parti de vos assemblys de référence de contrat de code pour détecter les problèmes potentiels avant même d'exécuter leur code.
La documentation complète pour les contrats de code est ici .
la source
La technique que j'utilise est de combiner et d'externaliser la validation et le lancement à une fonction d'utilité.
L'avantage le plus important est qu'il est réduit à une seule ligne dans la logique métier .
Je parie que vous ne pouvez pas faire mieux si vous ne pouvez pas le réduire davantage - pour éliminer toutes les validations d'arguments et les gardes d'état d'objet de la logique métier, en ne gardant que les conditions exceptionnelles opérationnelles.
Il y a, bien sûr, des façons de le faire - Langage fortement tapé, conception "aucun objet invalide autorisé à tout moment", Design by Contract , etc.
Exemple:
la source
[note] J'ai copié ceci de la question dans une réponse au cas où il y aurait des commentaires à ce sujet.
Déplacez chaque exception dans une méthode de la classe, en prenant tous les arguments qui ont besoin de la mise en forme.
Veuillez joindre toutes les méthodes d'exception dans la région, et placez - les à la fin de la classe.
Avantages:
Les inconvénients:
la source
#region #endregion
(ce qui les rend cachées à l'EDI par défaut), et si elles sont applicables à différentes classes, mettez-les dans uneinternal static ValidationUtility
classe. Au fait, ne vous plaignez jamais de longs noms d'identifiants devant un programmeur C #.Si vous pouvez vous en sortir avec des erreurs un peu plus générales, vous pouvez écrire une fonction de conversion générique statique publique pour vous, qui déduit le type de source:
Il existe des variantes possibles (pensez
SdkHelper.RequireNotNull()
), qui vérifient uniquement les exigences sur les entrées, et lancent si elles échouent, mais dans cet exemple, la combinaison de la distribution avec la production du résultat est auto-documentée et compacte.Si vous êtes sur .net 4.5, il existe des moyens pour que le compilateur insère le nom de la méthode / du fichier actuel comme paramètre de méthode (voir CallerMemberAttibute ). Mais pour un SDK, vous ne pouvez probablement pas obliger vos clients à passer à 4.5.
la source
Ce que nous aimons faire pour les erreurs de logique métier (pas nécessairement les erreurs d'argument, etc.) est d'avoir une seule énumération qui définit tous les types d'erreurs potentiels:
Les
[Display(Name = "...")]
attributs définissent la clé dans les fichiers de ressources à utiliser pour traduire les messages d'erreur.En outre, ce fichier peut être utilisé comme point de départ pour rechercher toutes les occurrences où un certain type d'erreur est généré dans votre code.
La vérification des règles métier peut être déléguée à des classes Validator spécialisées qui produisent des listes de règles métier violées.
Nous utilisons ensuite un type d'exception personnalisé pour transporter les règles violées:
Les appels de service backend sont enveloppés dans un code générique de gestion des erreurs, qui traduit la règle Busines violée en un message d'erreur lisible par l'utilisateur:
Voici
ToTranslatedString()
une méthode d'extension permettant deenum
lire les clés de ressource à partir des[Display]
attributs et d'utiliser leResourceManager
pour traduire ces clés. La valeur de la clé de ressource respective peut contenir des espaces réservés pourstring.Format
, qui correspondent à ceux fournisMessageParameters
. Exemple d'une entrée dans le fichier resx:Exemple d'utilisation:
Avec cette approche, vous pouvez dissocier la génération du message d'erreur de la génération de l'erreur, sans avoir à introduire une nouvelle classe d'exception pour chaque nouveau type d'erreur. Utile si différentes interfaces doivent afficher des messages différents, si le message affiché doit dépendre de la langue et / ou du rôle de l'utilisateur, etc.
la source
J'utiliserais des clauses de garde comme dans cet exemple pour que les vérifications de paramètres puissent être réutilisées.
De plus, vous pouvez utiliser le modèle de générateur pour que plus d'une validation puisse être chaînée. Cela ressemblera un peu à des assertions fluides .
la source