exception - Réduire le code de gestion des erreurs en double en C #?

Translate

Je n'ai jamais été complètement satisfait du fonctionnement de la gestion des exceptions, il y a beaucoup d'exceptions et try / catch apporte à la table (déroulement de la pile, etc.), mais cela semble casser une grande partie du modèle OO dans le processus.

Quoi qu'il en soit, voici le problème:

Supposons que vous ayez une classe qui encapsule ou inclut des opérations d'E / S de fichiers en réseau (par exemple, lire et écrire dans un fichier à un chemin UNC particulier quelque part). Pour diverses raisons, vous ne voulez pas que ces opérations d'E / S échouent, donc si vous détectez qu'elles échouent, vous les réessayez et vous continuez à les réessayer jusqu'à ce qu'elles réussissent ou que vous atteigniez un délai d'expiration. J'ai déjà une classe RetryTimer pratique que je peux instancier et utiliser pour mettre en veille le thread actuel entre les tentatives et déterminer quand le délai d'expiration s'est écoulé, etc.

Le problème est que vous avez un tas d'opérations d'E / S dans plusieurs méthodes de cette classe et que vous devez encapsuler chacune d'elles dans la logique try-catch / retry.

Voici un exemple d'extrait de code:

RetryTimer fileIORetryTimer = new RetryTimer(TimeSpan.FromHours(10));
bool success = false;
while (!success)
{
    try
    {
        // do some file IO which may succeed or fail
        success = true;
    }
    catch (IOException e)
    {
        if (fileIORetryTimer.HasExceededRetryTimeout)
        {
            throw e;
        }
        fileIORetryTimer.SleepUntilNextRetry();
    }
}

Alors, comment éviter de dupliquer la plupart de ce code pour chaque opération d'E / S de fichier dans toute la classe? Ma solution était d'utiliser des blocs délégués anonymes et une seule méthode dans la classe qui exécutait le bloc délégué qui lui était passé. Cela m'a permis de faire des choses comme ça dans d'autres méthodes:

this.RetryFileIO( delegate()
    {
        // some code block
    } );

J'aime un peu cela, mais cela laisse beaucoup à désirer. J'aimerais savoir comment d'autres personnes résoudraient ce genre de problème.

This question and all comments follow the "Attribution Required."

Toutes les réponses

Translate

Cela semble être une excellente occasion de jeter un œil à la programmation orientée aspect. Voici un bon article surAOP dans .NET. L'idée générale est que vous extrayeriez le problème interfonctionnel (c'est-à-dire Réessayer pendant x heures) dans une classe distincte, puis annoteriez toutes les méthodes qui ont besoin de modifier leur comportement de cette manière. Voici à quoi cela pourrait ressembler (avec une belle méthode d'extension sur Int32)

[RetryFor( 10.Hours() )]
public void DeleteArchive()
{
  //.. code to just delete the archive
}
La source
Translate

Vous vous demandez simplement, que pensez-vous que votre méthode laisse à désirer? Vous pouvez remplacer le délégué anonyme par un .. nommé? déléguer, quelque chose comme

    public delegate void IoOperation(params string[] parameters);

    public void FileDeleteOperation(params string[] fileName)
    {
        File.Delete(fileName[0]);
    }

    public void FileCopyOperation(params string[] fileNames)
    {
        File.Copy(fileNames[0], fileNames[1]);
    }

    public void RetryFileIO(IoOperation operation, params string[] parameters)
    {
        RetryTimer fileIORetryTimer = new RetryTimer(TimeSpan.FromHours(10));
        bool success = false;
        while (!success)
        {
            try
            {
                operation(parameters);
                success = true;
            }
            catch (IOException e)
            {
                if (fileIORetryTimer.HasExceededRetryTimeout)
                {
                    throw;
                }
                fileIORetryTimer.SleepUntilNextRetry();
            }
        }
    }

    public void Foo()
    {
        this.RetryFileIO(FileDeleteOperation, "L:\file.to.delete" );
        this.RetryFileIO(FileCopyOperation, "L:\file.to.copy.source", "L:\file.to.copy.destination" );
    }
La source
Translate

Vous pouvez également utiliser une approche plus OO:

  • Créez une classe de base qui gère les erreurs et appelle une méthode abstraite pour effectuer le travail concret. (Modèle de méthode de modèle)
  • Créez des classes concrètes pour chaque opération.

Cela présente l'avantage de nommer chaque type d'opération que vous effectuez et vous donne un modèle de commande - les opérations ont été représentées sous forme d'objets.

La source
Translate

Voici ce que j'ai fait récemment. Cela a probablement été mieux fait ailleurs, mais cela semble assez propre et réutilisable.

J'ai une méthode utilitaire qui ressemble à ceci:

    public delegate void WorkMethod();

    static public void DoAndRetry(WorkMethod wm, int maxRetries)
    {
        int curRetries = 0;
        do
        {
            try
            {
                wm.Invoke();
                return;
            }
            catch (Exception e)
            {
                curRetries++;
                if (curRetries > maxRetries)
                {
                    throw new Exception("Maximum retries reached", e);
                }
            }
        } while (true);
    }

Ensuite, dans mon application, j'utilise la syntaxe d'expression Lamda de c # pour garder les choses en ordre:

Utility.DoAndRetry( () => ie.GoTo(url), 5);

Cela appelle ma méthode et réessaye jusqu'à 5 fois. À la cinquième tentative, l'exception d'origine est renvoyée à l'intérieur d'une exception de nouvelle tentative.

La source