2009-06-26 6 views
8

Je suis en train de coder différentes routines et je fais de mon mieux pour le garder net et refactorisé.Corriger la structure pour vérifier les erreurs en utilisant NSError

Méthodes Je crée commencent à ressembler à ce code:

-(IBAction)buttonPress:(id)sender { 
    // Create Document Shopping List with this document 
    [self doSomething:&error]; 

    if(error) { 
     [NSApp presentError:&error]; 
     return nil; 
    } 

    [self doSomethingElse:&error]; 

    if(error) { 
     [NSApp presentError:&error]; 
     return nil; 
    } 

    [self doYetSomethingElse:&error]; 

    if(error) { 
     [NSApp presentError:&error]; 
     return nil; 
    } 
} 

J'adore NSError, mais cela semble être un moyen terriblement maladroit de traiter toutes mes erreurs.

Quelques réflexions que j'ai eu sur les façons alternatives:

a) la vérification d'erreur pourrait être intégrée dans les méthodes DoSomething, doSomethingElse etc, mais je ne serais pas en mesure de quitter la méthode de presse de bouton sans faire une sorte de vérification de la valeur de retour, ce qui me ramènerait à une structure similaire.

b) Je pourrais configurer l'erreur NSError comme étant la valeur clé observée, mais quelque chose à ce sujet me semble profondément erroné. Je suis très conscient des possibilités d'abus avec le KVO, alors j'essaie de tout faire sans lui autant que possible.

Sûrement il me manque quelque chose de vraiment basique ici? Y a-t-il un modèle qui peut m'aider? Ou est-ce que cette structure est OK?

+0

Je suis absolument renversé par la réponse incroyable que j'ai reçue au cours des 10 dernières minutes! Merci beaucoup pour vos réponses. Très, très utile. –

Répondre

11

Je pense qu'aucune des réponses présentées ici ne prend en compte les pratiques recommandées par Apple.

Fondamentalement, vous ne devriez jamais vérifier l'objet NSError pour déterminer s'il y avait un problème. Vous vérifiez s'il y a eu un problème en vérifiant la valeur retournée par la méthode qui prend un pointeur vers un pointeur vers un NSError.

De the Apple documentation:

Important succès ou l'échec est indiquée par la valeur de retour de la méthode . Bien que les méthodes de cacao qui renvoient indirectement des objets d'erreur dans le domaine d'erreur Cocoa sont garantis pour renvoyer ces objets si la méthode indique l'échec par directement le retour nil ou NO, vous devriez toujours vérifier que la valeur de retour est nil ou NO avant de tenter de faire quelque chose avec l'objet NSError.

+0

Certaines méthodes comme sendAsynchronousRequest renvoient void et la seule chose que vous pouvez faire est de vérifier NSError. Ou est-ce que je manque quelque chose? –

+2

Dans ce cas, vérifiez les autres paramètres. C'est, NSURLResponse et NSData. –

0

Remarque: Lorsque j'ai publié ce message, je ne connaissais aucun débat sur les exceptions MacOS/Cocoa. Gardez cela à l'esprit lorsque vous considérez cette réponse. Pourquoi ne pas utiliser exceptions à la place? Cela vous permettra de vous passer du paramètre error sur vos méthodes et de gérer vos erreurs en un seul endroit.

-(IBAction)buttonPress:(id)sender { 
    // Create Document Shopping List with this document 
    @try { 
     [self doSomething]; 
     [self doSomethingElse]; 
     [self doYetSomethingElse]; 
    } @catch (NSException* e) { 
     [NSApp presentException:e]; 
    } 
    return nil; 
} 

Vous aurez bien sûr besoin de lever des exceptions selon les besoins à partir de vos méthodes de doXXXX:

NSException* e = [NSException exceptionWithName:@"MyException" 
             reason:@"Something bad happened" 
             userInfo:nil]; 
@throw e; 
+3

Un débat est en cours pour savoir si l'utilisation d'exceptions pour le contrôle de flux est acceptable sous Mac OS X/Cocoa. Personnellement, je prends la position que (la plupart des) Apple prend, que les exceptions dans Cocoa sont pour des erreurs de programmation exceptionnelles, et ne devraient donc pas être utilisées pour le contrôle de flux. – danielpunkass

+0

Je serais intéressé de savoir pourquoi cela a été marqué. Qu'ai-je manqué? – teabot

+2

Ah, merci de l'avoir clarifié @danielpunkass - Je viens d'un environnement Java et j'ai donc été heureux d'utiliser des exceptions en Objective-C, ignorant un tel débat. – teabot

1

Je pense que la vraie question est de savoir combien vous avez besoin granularité dans votre gestion des erreurs. Votre code est fondamentalement correct - mais vous pouvez nettoyer les choses en vérifiant les erreurs moins fréquemment, ou en utilisant une approche fourre-tout comme teabot mentionné dans sa réponse avec NSException. Beaucoup de fonctions intégrées qui renvoient des erreurs (comme moveItemAtPath :) de NSFileManager renvoient un BOOL en plus de fournir un objet NSError - vous pouvez donc également utiliser des instructions if imbriquées si vous n'avez pas besoin des informations NSError.

Tout et tout cependant, il n'y a vraiment pas un excellent moyen de le faire. Si votre code est long, KVO pourrait être une bonne solution. Je n'ai jamais essayé pour une situation comme celle-ci, cependant.

+0

Merci pour le commentaire, Ben. Idéalement, j'aimerais pouvoir traiter toutes les erreurs de manière cohérente en utilisant NSError. Je suis un peu confus par les fonctions qui retournent parfois un BOOL - s'il y a une manière standard de traiter les erreurs en utilisant NSError, pourquoi Apple brouillerait-il les eaux en fournissant des valeurs de retour? Ça m'a toujours un peu dérouté. –

+1

S'appuyer sur un objet NSError est, franchement, idiot. Il est très facile de passer à côté d'un cas où, au moment de l'écriture du code pour créer un objet d'erreur, il y avait trop de tracas, et par conséquent, la méthode signale à tort le succès. C'est la raison pour laquelle Apple traite les informations d'erreur comme elles le font. Utilisez un test simple d'une méthode renvoyant NO ou zéro pour déterminer qu'elle a échoué. Puis, descendez l'objet NSError (s'il est disponible) pour plus de détails. –

+0

Très bon point, Mike. Ça me donne autre chose à considérer. Il suffit de lire ceci, je ne dois pas avoir vérifié assez rapidement. –

2

L'essentiel de votre question est de savoir s'il existe des améliorations structurelles que vous pouvez apporter à la gestion des erreurs. Je pense donc, en introduisant essentiellement plus de couches d'imbrication, soit en extrayant plus de code dans des méthodes/fonctions séparées, soit en introduisant l'imbrication dans votre méthode d'échantillonnage de haut niveau. L'idée est la suivante: lorsque vous manipulez la plupart des erreurs, vous êtes probablement intéressé à effectuer une autre tâche ou à échouer et propager l'erreur dans la chaîne afin qu'un contrôleur responsable puisse transmettre l'erreur à l'utilisateur via UI.

En utilisant cette idée de « Propager ou la poignée », je réécris votre méthode d'échantillonnage comme ceci:

-(IBAction)buttonPress:(id)sender { 

    // Create Document Shopping List with this document 
    [self doSomething:&error];  
    if(error == nil) { 
     [self doSomethingElse:&error]; 
     if (error == nil) { 
      [self doYetSomethingElse:&error]; 
     } 
    } 

    if(error) { 
     [NSApp presentError:&error]; 
    }  
} 

Notez qu'il existe de bons arguments contre l'introduction trop d'imbrication dans une méthode particulière. L'imbrication de ce genre est essentiellement une courte alternative à l'extraction de méthodes. Il peut être plus logique, par exemple, que "doSomething:" appelle lui-même doSomethingElse :, qui appelle doYetSomethingElse: par exemple. Cela imposerait la même structure sur le code que le if-nest, mais serait sans doute plus maintenable. En outre, je ne suis pas un fan des déclarations de retour en ligne. Dans ce cas particulier, la méthode exemple n'appelle pas réellement une valeur de retour, mais si c'est le cas, je préfère définir une variable locale à la valeur renvoyée et retourner uniquement à la fin du contrôle de flux. Saut d'une fonction ou d'une méthode prématurément est un moyen sûr de rencontrer des bugs étranges, à mon humble avis.

+0

Merci pour une excellente suggestion, Daniel. J'ai hésité à le faire parce que Code Complete recommande de minimiser l'imbrication. J'ai fait quelques cauchemars imbriqués si ... d'autres déclarations dans mon temps. Et si j'avais plus de 5 étapes, je suppose que cela deviendrait encombrant. En revanche, si j'avais plus de 5 étapes, je suppose que ce serait un signe avant-coureur que mon code devait être refactorisé de toute façon. Et cette imbrication spécifique est encore assez lisible et est beaucoup plus propre que mon original, donc c'est ce que je vais faire. Merci encore! –

+1

Glad vous avez apprécié la réponse. Je pense aussi à Code Complete et à minimiser l'imbrication, mais je rationalise comme je l'ai fait ci-dessus, pour considérer que l'imbrication minimale n'est qu'un raccourci pour le code qui peut être factorisé dans une autre méthode ou fonction lorsque les circonstances l'exigent. Généralement, je pense que l'imbrication est meilleure que l'alternative, une fonction "run-on" ... – danielpunkass

Questions connexes