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

Je n'ai jamais été complètement satisfait de la façon dont fonctionne la gestion des exceptions, il y a beaucoup d'exceptions et essayez / catch apporte à la table (déroulement de la pile, etc.), mais il semble briser beaucoup du modèle OO dans le processus.

De toute façon, voici le problème:

Supposons que vous ayez une classe qui englobe ou inclut des opérations d'E / S de fichiers en réseau (par exemple, lire et écrire dans un fichier sur 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 dormir le thread actuel entre les tentatives et déterminer quand le délai d'attente 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 vous devez envelopper 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 évitez-vous de dupliquer la majeure partie de ce code pour chaque opération d'E / S de fichiers dans la classe? Ma solution consistait à utiliser des blocs de 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 ça un peu, mais ça laisse beaucoup à désirer. J'aimerais entendre comment les autres pourraient résoudre ce genre de problème.

0
Juste un FYI général: Il est presque toujours mieux simplement throw; au lieu de throw e;
ajouté l'auteur Dan Tao, source

4 Réponses

Je me demandais simplement, que pensez-vous que votre méthode laisse à désirer? Vous pouvez remplacer le délégué anonyme par un .. nommé? délégué, 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" );
    }
0
ajouté

Cela ressemble à une excellente occasion de jeter un coup d'œil à la programmation orientée aspect. Voici un bon article sur AOP dans .NET . L'idée générale est que vous extrayez la préoccupation interfonctionnelle (c'est-à-dire réessayez pendant x heures) dans une classe distincte, puis vous annotez toutes les méthodes qui doivent modifier leur comportement de cette manière. Voici comment cela pourrait ressembler (avec une belle méthode d'extension sur Int32)

[RetryFor( 10.Hours() )]
public void DeleteArchive()
{
  //.. code to just delete the archive
}
0
ajouté

Vous pouvez également utiliser une approche plus orientée objet:

  • 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 modèle de modèle)
  • Créez des classes concrètes pour chaque opération.

Cela a 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 comme des objets.

0
ajouté

Voici ce que j'ai fait récemment. Il a probablement été fait ailleurs mieux, mais il 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 bien rangées:

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

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

0
ajouté
Mais pourquoi le code personnalisé WorkMethod délègue-t-il à la place de Action ?
ajouté l'auteur Dan Tao, source