Warum eine Methode, die ein bool / int zurückgibt und das tatsächliche Objekt als Ausgabeparameter hat?

12

Ich sehe das folgende Codemuster überall in der Codebasis meines Unternehmens (.NET 3.5-Anwendung):

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
}

Ich versuche, mich darum zu Fookümmern, warum Sie dies tun würden, anstatt die Methode einfach die ID nehmen und ein BazObjekt zurückgeben zu lassen, anstatt einen Wert zurückzugeben, der nicht verwendet wird, und das tatsächliche Objekt als Referenz- oder Ausgabeparameter zu haben.

Gibt es einen versteckten Vorteil dieses Codierungsstils, den ich vermisse?

Wayne Molina
quelle
In Verbindung stehender Thread: Wann und warum Sie void (anstelle von bool / int) verwenden sollten
Péter Török
In Ihrem Beispiel zu bazsein nullund das zurückgegebene boolWesen falseist nicht gleichwertig. new BazObject()ist nie null, es sei denn , bazObjectaktualisiert wird , bevor die Exceptionin geworfen wird Foo, wenn falsezurückgegeben wird , bazwird es nie sein null. Es würde enorm helfen, wenn die Spezifikation für Fooverfügbar wäre. In der Tat ist dies vielleicht das schwerwiegendste Problem, das dieser Code aufweist.
Steve Powell
Mein Gedächtnis ist verschwommen, wie es vor langer Zeit war, aber ich glaube, ich hatte das Beispiel durcheinander gebracht und es wurde überprüft, ob "baz" falsch war, nicht ob es null war. Auf jeden Fall schien mir das Muster wirklich archaisch, wie es von VB6 war, und der Entwickler machte sich nie die Mühe, seinen Code zu verbessern (was er nicht tat)
Wayne Molina

Antworten:

11

Normalerweise verwenden Sie dieses Muster, damit Sie Code wie folgt schreiben können:

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

Es wird beispielsweise in der CLR für TryGetValue in der Dictionary-Klasse verwendet. Es vermeidet Redundanz, aber Out- und Ref-Parameter schienen mir immer etwas chaotisch

Homde
quelle
1
+1, dies ist der Grund, warum, obwohl ich dazu neige, ein Objekt sowohl mit dem Booleschen als auch mit dem Objekt zurückzugeben, anstatt beide getrennt zurückzugeben
pdr
Ich werde dies als Antwort markieren, da es den Grund erklärt, obwohl der Konsens zu sein scheint, dass es ziemlich veraltet ist, außer unter bestimmten Umständen.
Wayne Molina
Diese Antwort rechtfertigt die Verwendung dieses Musters. Aber wenn es ALLES ÜBER Ihrer Codebasis ist, ist es wahrscheinlich eine schlechte Praxis wie Sean Seite.
Codismus
11

Dies ist alter C-Code, bevor es Ausnahmen gab. Der Rückgabewert gab an, ob die Methode erfolgreich war oder nicht, und wenn dies der Fall war, wurde der Parameter mit dem Ergebnis gefüllt.

In .net haben wir Ausnahmen für diesen Zweck. Es sollte keinen Grund geben, diesem Muster zu folgen.

[Bearbeiten] Es gibt offensichtlich Auswirkungen auf die Leistung bei der Ausnahmebehandlung. Vielleicht hat das etwas damit zu tun. In diesem Code-Snippet wird jedoch bereits eine Ausnahme ausgelöst. Es wäre sauberer, es einfach den Stapel nach oben bewegen zu lassen, bis es an einer geeigneteren Stelle gefangen ist.

Sean Edwards
quelle
Kann dem nicht zustimmen. Verwenden Sie niemals eine Ausnahme für einen erwarteten Zustand. Wenn Sie beispielsweise eine Zeichenfolge in eine int analysieren, ist es immer möglich, dass jemand eine nicht analysierbare Zeichenfolge übergibt. Sie sollten in diesem Fall keine Ausnahme
auslösen
Das ist nicht was ich sagte. Wenn Sie den von OP veröffentlichten Code lesen, tritt explizit ein Ausnahmefall auf, der jedoch von der Methode abgefangen und stattdessen false zurückgegeben wird. Diese Frage bezieht sich auf die Fehlerbehandlung, nicht auf die erwarteten Bedingungen.
Sean Edwards
Ja, es scheint hauptsächlich in Methoden verwendet zu werden, die Daten aus der Datenbank laden, z. B. if (baz.Select())aber meistens wird der Rückgabewert einfach weggeworfen und der Wert gegen null oder eine Eigenschaft geprüft.
Wayne Molina
@ Sean, sehen Sie, was Sie sagen, ich lehne nur den Satz "In .NET haben wir Ausnahmen für diesen Zweck." Ausnahmen dienen für mich einem ganz anderen Zweck
pdr
1
Ok, lass mich klar sein. Es ist richtig, dass Sie nicht jeder Methode einen Booleschen Wert hinzufügen sollten, um Codierungsfehler bei ungültigen Argumenten zu berücksichtigen. Aber wo der Eingang zu einem Verfahren von einem Benutzer kommt eher als ein Entwickler sollten Sie in der Lage sein , zu versuchen , den Eingang zu handhaben, ohne Auslösen eine Ausnahme , wenn sie es falsch.
pdr
2

Angesichts des Code-Snippets sieht es völlig sinnlos aus. Für mich würde das anfängliche Codemuster darauf hinweisen, dass null für BazObject ein akzeptabler Fall wäre und die Bool-Rückgabe ein Maß für die sichere Bestimmung eines Fehlerfalls ist. Wenn der folgende Code war:

// 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
}

Das wäre für mich sinnvoller, es so zu machen. Vielleicht ist dies eine Methode, die jemand vor Ihnen verwendet hat, um sicherzustellen, dass baz als Referenz übergeben wurde, ohne zu verstehen, wie Objektparameter in C # tatsächlich funktionieren.

Joel Etherton
quelle
Ich denke, es wurde von einem aktuellen Mitglied des Teams geschrieben. Vielleicht sollte ich mir wegen O_O
Wayne Molina
@ Wayne M: Weniger Sorgen als eine mögliche Trainingsmöglichkeit :)
Joel Etherton
1

Manchmal ist dieses Muster nützlich, wenn Sie als Anrufer sich nicht darum kümmern sollen, ob der zurückgegebene Typ ein Referenz- oder ein Werttyp ist. Wenn Sie eine solche Methode aufrufen, um einen Werttyp abzurufen, benötigt dieser Werttyp entweder einen festgelegten ungültigen Wert (z. B. double.NaN) oder Sie benötigen eine andere Methode zur Bestimmung des Erfolgs.

Kevin Hsu
quelle
Das macht Sinn. Scheint ein bisschen komisch, aber das klingt nach einem triftigen Grund.
Wayne Molina
0

Die Idee ist, einen Wert zurückzugeben, der angibt, ob der Prozess erfolgreich war, und Code wie diesen zulässt:

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

Wenn das Objekt nicht null sein kann (was in Ihrem Beispiel korrekt erscheint), gehen Sie besser wie folgt vor:

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

Wenn das Objekt kann null sein, sind Ausnahmen der Weg zu gehen:

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

Es ist ein allgemeines Muster, das in C verwendet wird, wo es keine Ausnahmen gibt. Dadurch kann die Funktion eine Fehlerbedingung zurückgeben. Ausnahmen sind im Allgemeinen eine viel sauberere Lösung.

Michael K.
quelle
Zumal der Code bereits eine Ausnahme auslöst.
David Thornley