Pourquoi avoir une méthode qui retourne un bool / int et a l'objet réel comme paramètre de sortie?

12

Je vois le modèle de code suivant partout dans la base de code de mon entreprise (application .NET 3.5):

bool Foo(int barID, out Baz bazObject) { 
    try { 
            // do stuff
            bazObject = someResponseObject;

            return true;
    }
    catch (Exception ex) { 
        // log error
        return false;
    }
}

// calling code
BazObject baz = new BazObject();
fooObject.Foo(barID, out baz);

if (baz != null) { 
    // do stuff with baz
}

J'essaie de comprendre pourquoi vous feriez cela au lieu d'avoir la Foométhode simplement prendre l'ID et renvoyer un Bazobjet, au lieu de renvoyer une valeur qui n'est pas utilisée et que l'objet réel soit un paramètre de référence ou de sortie.

Y a-t-il un avantage caché de ce style de codage qui me manque?

Wayne Molina
la source
Dans votre exemple, l' bazêtre nullet l' boolêtre retourné ne falsesont pas équivalents. new BazObject()n'est jamais null, donc à moins qu'il ne bazObjectsoit mis à jour avant que le Exceptionsoit jeté Foo, quand falseest retourné bazne le sera jamais null. Cela aiderait énormément si les spécifications pour Fooétaient disponibles. En fait, c'est peut-être le problème le plus grave présenté par ce code.
Steve Powell
Ma mémoire est floue car c'était il y a longtemps mais je pense que j'avais foiré l'exemple et qu'il vérifiait si "baz" était faux, pas s'il était nul. Quoi qu'il en soit, le modèle me semblait vraiment archaïque, comme s'il provenait de VB6 et que le développeur n'avait jamais pris la peine d'améliorer son code (ce qu'il n'a pas fait)
Wayne Molina

Réponses:

11

Vous utilisez généralement ce modèle pour pouvoir écrire du code comme ceci:

if (Foo(barId, out bazObject))
{
  //DoStuff with bazobject
}

il est utilisé dans le CLR pour TryGetValue dans la classe de dictionnaire par exemple. Cela évite une certaine redondance mais les paramètres out et ref me semblaient toujours un peu compliqués

Homde
la source
1
+1, c'est la raison pour laquelle, bien que j'ai tendance à retourner un objet avec à la fois le booléen et l'objet plutôt que de les renvoyer séparément
pdr
Je vais marquer cela comme la réponse, car cela explique la raison, bien que le consensus semble être assez dépassé, sauf dans des circonstances spécifiques.
Wayne Molina
Cette réponse justifie l'utilisation de ce modèle. Mais si c'est PARTOUT dans votre base de code, c'est probablement une mauvaise pratique comme côté Sean.
Codisme
11

Il s'agit d'un ancien code de style C, avant qu'il n'y ait des exceptions. La valeur renvoyée indiquait si la méthode avait réussi ou non, et si c'était le cas, le paramètre serait rempli avec le résultat.

Dans .net, nous avons des exceptions à cet effet. Il ne devrait y avoir aucune raison de suivre ce modèle.

[modifier] Il y a évidemment des implications de performances dans la gestion des exceptions. Peut-être que ceci a quelque chose à y voir. Cependant, dans cet extrait de code, une exception est déjà levée. Il serait plus propre de simplement le laisser remonter la pile jusqu'à ce qu'il soit pris dans un endroit plus approprié.

Sean Edwards
la source
Non, je ne suis pas d'accord avec ça. N'utilisez jamais d'exception pour une condition attendue. Si vous analysez une chaîne en un int, par exemple, il est toujours possible que quelqu'un passe une chaîne non analysable. Vous ne devriez pas lever d'exception dans ce cas
pdr
Ce n'est pas ce que j'ai dit. Si vous lisez le code publié par OP, un cas d'exception se produit explicitement, mais la méthode le capture et renvoie false à la place. Cette question est liée à la gestion des erreurs et non aux conditions attendues.
Sean Edwards,
Oui, il semble être principalement utilisé dans les méthodes qui chargent les données de la base de données, par exemple, if (baz.Select())mais le plus souvent, la valeur de retour est simplement jetée et la valeur est vérifiée par rapport à null ou à une propriété.
Wayne Molina
@Sean, voyez ce que vous dites, je m'oppose simplement à la phrase "Dans .NET, nous avons des exceptions à cet effet." Les exceptions ont un but entièrement différent pour moi
pdr
1
Ok, permettez-moi d'être clair. Il est vrai que vous ne devez pas simplement ajouter un booléen à chaque méthode pour tenir compte des erreurs de codage sur des arguments non valides. Mais lorsque l'entrée d'une méthode provient d'un utilisateur plutôt que d'un développeur, vous devriez pouvoir essayer de gérer l'entrée sans lever d'exception en cas d'erreur.
pdr
2

Étant donné l'extrait de code, il semble complètement inutile. Pour moi, le modèle de code initial suggérerait que null pour BazObject serait un cas acceptable et le retour booléen est une mesure pour déterminer un cas d'échec en toute sécurité. Si le code suivant était:

// calling code
BazObject baz = new BazObject();
bool result = fooObject.Foo(barID, out baz);

if (result) { 
    // do stuff with baz
    // where baz may be 
    // null without a 
    // thrown exception
}

Cela aurait plus de sens pour moi de procéder de cette façon. C'est peut-être une méthode que quelqu'un avant vous utilisait pour garantir que baz était passé par référence sans comprendre comment les paramètres d'objet fonctionnent réellement en C #.

Joel Etherton
la source
Je pense qu'il a été écrit par un membre actuel de l'équipe. C'est peut-être quelque chose dont je devrais m'inquiéter O_O
Wayne Molina
@Wayne M: Moins d'inquiétude qu'une opportunité de formation potentielle :)
Joel Etherton
1

Parfois, ce modèle est utile lorsque vous, l'appelant, n'êtes pas censé vous soucier de savoir si le type retourné est un type de référence ou de valeur. Si vous appelez une telle méthode pour récupérer un type de valeur, ce type de valeur aura besoin d'une valeur non valide établie (par exemple, double.NaN), ou vous avez besoin d'une autre manière de déterminer le succès.

Kevin Hsu
la source
Ça a du sens. Cela semble un peu bizarre, mais cela semble être une raison valable.
Wayne Molina
0

L'idée est de renvoyer une valeur qui indique si le processus a réussi, en autorisant un code comme celui-ci:

Baz b;
if (fooObject.foo(id, out b)) {
   // do something with b
}
else {
   Error.screamAndRun("object lookup/whatever failed! AAaAAAH!");
}

Si l'objet ne peut pas être nul (ce qui semble correct dans votre exemple), il vaut mieux procéder comme suit:

Baz b = fooObject.foo(id);
if (b != null) {
   // do something with b
}
else {
   Error.screamAndRun("object lookup/whatever failed! AAaAAAH!");
}

Si l'objet peut être nul, les exceptions sont la voie à suivre:

try {
   Baz b = fooObject.foo(id);
}
catch (BazException e) {
   Error.screamAndRun("object lookup/whatever failed! AAaAAAH!");
}

C'est un modèle commun utilisé en C, où il n'y a pas d'exceptions. Il permet à la fonction de renvoyer une condition d'erreur. Les exceptions sont généralement une solution beaucoup plus propre.

Michael K
la source
D'autant plus que le code lève déjà une exception.
David Thornley,