** EDIT: Il existe plusieurs options ci-dessous qui fonctionneraient. Veuillez voter / commenter en fonction de votre point de vue sur la question.
Je travaille sur le nettoyage et l'ajout de fonctionnalité à la méthode AC # avec la structure de base suivante: p> Il existe de nombreuses étapes et dans la plupart des cas, l'étape x doit réussir afin de passer à l'étape x + 1. Maintenant, je dois ajouter certaines fonctionnalités qui seront toujours exécutées à la fin de la méthode, mais qui dépend de la valeur de retour. Je cherche des recommandations sur la manière de le refaire de manière propremente pour l'effet souhaité. Le choix évident serait de mettre la fonctionnalité qui dépend de la valeur de retour dans le code d'appel, mais je ne suis pas en mesure de modifier les appelants. P> une option: p> Cela me semble un peu moche. Au lieu de cela, je pourrais jeter les méthodes Performsteppx (). Cependant, cela laisse le problème de la réglage de la variable retourstatus de manière appropriée dans le bloc de captures de processeur (). Comme vous l'avez peut-être remarqué, la valeur renvoyée sur une défaillance d'une étape de traitement dépend de quelle étape a échoué. P> public void processStuff()
{
Status returnStatus = Status.Success;
try
{
bool step1succeeded = performStep1(); //throws on failure
bool step2succeeded = performStep2(); //throws on failure
//.. the rest of the steps ..//
}
catch (Exception ex)
{
log(ex);
returnStatus = Status.Error; //This is wrong if step 2 fails!
}
finally
{
//some necessary cleanup
}
FinalProcessing(returnStatus);
return returnStatus;
}
10 Réponses :
pouvez-vous faire performstep1 code> et
performstep2 code> lancer des exceptions différentes? p>
Cela m'a eu lieu. Que pensent les autres que les autres pensent de cette approche?
Je lancerais des exceptions qui ont un sens.
Voulez-vous dire lancer des exceptions avec des types personnalisés, nommés pour refléter clairement leurs objectifs?
Je pense que c'est une bonne idée de jeter et de prendre 2 types d'exceptions distincts. En outre, c'est une mauvaise idée d'attraper juste un type d'exception comme vous l'avez fait dans votre exemple de code. S'il y a une exception inattendue dans votre code PermetStep1, vous souhaitez dire à l'utilisateur / programmeur que quelque chose a mal tourné, et ce n'est pas seulement une étape échouée.
Lancer des exceptions ne permettra pas l'exécution de continuer à des avertissements. Au moins pas avec plusieurs grands essais ... Blocs de capture.
Vous pouvez refactoriser les étapes dans une interface, de sorte que chaque étape, au lieu d'être une méthode, expose une méthode de course () et une propriété d'état - et vous pouvez les exécuter dans une boucle, jusqu'à ce que vous appuyiez une exception. De cette façon, vous pouvez conserver les informations complètes sur l'étape couru et quel statut chaque étape avait. P>
En tant que légère modification, la méthode d'exécution de l'interface () pourrait revenir true ou false, permettant au champ d'état d'être interrogé uniquement pour les étapes échouées. L'exception pourrait être lancée dans la boucle si le statut est une erreur. Cela permet à la boucle de continuer à des avertissements.
Cela dépend vraiment de la question de savoir si les étapes appartiennent ou non à leurs propres cours ou non. Création de cours pour chaque étape, créant une interface, puis en garder une trace de tous les objets pourraient devenir trop lourds si ce n'est pas le cas.
@jasonh True, mais la gestion des étapes et de l'ordre d'exécution pourrait être traitée avec des usines. Si cette solution est mise en œuvre correctement, même ajouter des étapes ou modifier l'ordre d'exécution pourrait être accompli sans changer de code et de recompiler.
@Jasonh: Tout d'abord, toutes les étapes partagent une fonctionnalité courante (exécution et statut), et le processus est déjà indiqué comme une succession de «étapes» afin de moi l'argument selon lequel il existe une interface ici semble forte. Ensuite, s'il n'y a vraiment que 3 étapes et que la conception ne changera pas, cela pourrait être surchargé - mais sinon, il s'agit d'une approche assez flexible et maintenable.
Obtenez une classe de tuple. Ensuite, faites:
var steps = new [] { { step = (Action)performStep1, status = Status.Error }, { step = (Action)performStep2, status = Status.Error }, }; var status = Status.Success foreach (var item in steps) { try { item.step(); } catch (Exception ex) { log(ex); status = item.status; break; } } // "Finally" code here
Cela n'autoriserait pas l'exécution du code de continuer en présence d'un avertissement à la pratique de la pratique.
En outre, la méthode ne suit pas le motif que je montre% 100. Certaines étapes récupèrent des données qui doivent être transmises à des étapes ultérieures.
@Jhedings Comme je l'ai vu, toute exception déclenche la fin du traitement; juste le statut change. @oDrade, bien l'original vient d'avoir Pertinstep1 / 2/3 () ... :)
Bon travail avec les abstractions ... Dommage que les actions ne reviennent pas Bool ou cela pourrait fonctionner parfaitement.
Parfait ... Je n'avais pas vu cela générique avant. +1
Vous pouvez effectuer le traitement dans votre section enfin code>.
Enfin CODE> est amusant en ce que même si vous êtes retourné dans votre
ESSAYER CODE> bloquer le bloc
enfin code> sera toujours exécuté avant que la fonction ne revienne réellement. Il se souviendra de quelle valeur a été renvoyée, cependant, vous pouvez alors également abandonner le
renvoyer code> à la fin de votre fonction.
public void processStuff()
{
Status returnStatus = Status.Success;
try
{
if (!performStep1())
return returnStatus = Status.Error;
if (!performStep2())
return returnStatus = Status.Warning;
//.. the rest of the steps ..//
}
catch (Exception ex)
{
log(ex);
return returnStatus = Status.Exception;
}
finally
{
//some necessary cleanup
FinalProcessing(returnStatus);
}
}
N'utilisez pas d'exceptions pour contrôler le flux de votre programme. Personnellement, je quitterais le code tel qu'il est. Pour ajouter la nouvelle fonctionnalité à la fin, vous pouvez faire:
public void processStuff() { Status returnStatus = Status.Success; try { if (!performStep1()) returnStatus = Status.Error; else if (!performStep2()) returnStatus = Status.Warning; //.. More steps, some of which could change returnStatus..// else if (!performStep3()) returnStatus = Status.Error; } catch (Exception ex) { log(ex); returnStatus = Status.Error; } finally { //some necessary cleanup } // Do your FinalProcessing(returnStatus); return returnStatus; }
Je suis vraiment d'accord avec la première moitié de votre réponse. Il y a de bonnes réponses ici qui amélioreraient la lisibilité sans violer ce principe.
Comme c'est le cas, cela ne se comporte pas au besoin. Je dois faire quelque chose à la fin de la méthode qui dépend de la valeur de retour. Et, comme je le dis, je ne peux pas changer le code d'appel.
Désolé @oDrade j'ai oublié que vous aviez besoin de changer de fonctionnalité. Voyez si ma réponse mise à jour vous aide.
+1 pour souligner que la manipulation des exceptions ne devrait pas être le flux d'exécution de contrôle
Cela semble être une approche assez propre. Merci.
Le seul problème ici est que vous ne pourriez pas être en mesure d'exécuter des tests ultérieurs. I.e. Une fois qu'un "PerformStepn" renvoie false, les étapes restantes ne seront pas exécutées. (Se référant à "Dans la plupart des cas, l'étape x doit réussir afin de passer à l'étape x + 1")
-1 Sauf si vous vous souciez de la performance, si votre code est plus clair en utilisant des exceptions, alors faites-le i>.
Pour clarifier, c'est juste une situation malheureuse que les exceptions sont tellement ralentissées sur .net que vous devez sortir de votre chemin pour les éviter, même quand ils seraient parfaitement clairs et un bon choix pour le débit.
Il existe définitivement une lisibilité dégradant lors de l'utilisation d'exceptions pour contrôler le flux d'un programme. Les exceptions concernent des situations exceptionnelles, celles que vous n'attendez pas.
Ils ne doivent pas être. OCAML, par exemple, utilise des exceptions assez régulièrement, mais leur vitesse d'exception ne suce pas comme .NET est donc une solution acceptable dans plus de cas.
Et je ne dis pas que vous devriez toujours utiliser des exceptions pour le flux de contrôle, juste quand il est clair. Et dans ce cas, sachant que PerformStep12345 va lancer, je pense que c'est extrêmement clair ...
Vous pouvez inverser votre paramètre de statut. Définissez le statut d'erreur avant d'appeler la méthode de lancement d'exception. À la fin, définissez le succès de son succès si aucune exception.
Status status = Status.Error; try { var res1 = performStep1(); status = Status.Warning; performStep2(res1); status = Status.Whatever performStep3(); status = Status.Success; // no exceptions thrown } catch (Exception ex) { log(ex); } finally { // ... }
"Échec par défaut"? Cela lui permet de lancer des exceptions de chaque étape car il le souhaitait, tout en veillant à ce que l'échec de chaque étape soit définie. Pas de conditionnels supplémentaires ou plus de code. C # Sorta Limited lorsqu'il s'agit de refactoriser à un niveau aussi petit.
Qu'en est-il de niché si?
pourrait fonctionner et ne pouvait pas fonctionner p>
Il supprimerait chaque retour pour laisser un seul p>
Cela pourrait fonctionner, mais cela mènerait une monstruosité profondément imbriquée.
Ne sachant pas que vos besoins logiques sont des exigences logiques, je commencerais à créer une classe abstraite pour servir d'objet de base pour exécuter une étape spécifique et renvoyer l'état de l'exécution. Il devrait avoir des méthodes surestibles pour mettre en œuvre l'exécution des tâches, les actions sur le succès et les actions sur l'échec. gérez également la logique mettant la logique dans cette classe pour gérer le succès ou l'échec de la tâche: Après avoir créé des classes de tâches pour exécuter vos étapes, ajoutez-les à une tige de tri. Exécution, puis itérer à travers eux vérifiant le STAUS lorsque la tâche a terminé: p> i laissé dans la gestion des erreurs car je ne sais pas si vos erreurs de piégeage sur l'exécution de la tâche ou Je cherche juste l'exception que vous lanciez une étape particulière défaillante. p> p>
Élargir sur l'approche d'interface ci-dessus:
public enum Status { OK, Error, Warning, Fatal } public interface IProcessStage { String Error { get; } Status Status { get; } bool Run(); } public class SuccessfulStage : IProcessStage { public String Error { get { return null; } } public Status Status { get { return Status.OK; } } public bool Run() { return performStep1(); } } public class WarningStage : IProcessStage { public String Error { get { return "Warning"; } } public Status Status { get { return Status.Warning; } } public bool Run() { return performStep2(); } } public class ErrorStage : IProcessStage { public String Error { get { return "Error"; } } public Status Status { get { return Status.Error; } } public bool Run() { return performStep3(); } } class Program { static Status RunAll(List<IProcessStage> stages) { Status stat = Status.OK; foreach (IProcessStage stage in stages) { if (stage.Run() == false) { stat = stage.Status; if (stat.Equals(Status.Error)) { break; } } } // perform final processing return stat; } static void Main(string[] args) { List<IProcessStage> stages = new List<IProcessStage> { new SuccessfulStage(), new WarningStage(), new ErrorStage() }; Status stat = Status.OK; try { stat = RunAll(stages); } catch (Exception e) { // log exception stat = Status.Fatal; } finally { // cleanup } } }
Voyant vos modifications pour réussir les données entre les étapes de traitement, cela pourrait ne pas fonctionner tel quel. Espérons que cela vous fera commencer.
Je vous conseillerais de refactoriser les étapes dans des cours séparées, après tout, votre classe ne devrait avoir une seule responsabilité que de toute façon. Je pense que cela ressemble à sa responsabilité devrait contrôler la course des étapes. P>