Perché un metodo che restituisce un valore bool / int e ha l'oggetto reale come parametro di output?


12

Vedo il seguente schema di codice ovunque nella base di codice della mia azienda (applicazione .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
}

Sto cercando di capire perché dovresti fare questo invece di avere il Foometodo semplicemente prendere l'ID e restituire un Bazoggetto, invece di restituire un valore non utilizzato e avere l'oggetto reale come parametro ref o output.

C'è qualche vantaggio nascosto di questo stile di codifica che mi manca?



Nel tuo esempio, bazessere nulle l' boolessere restituito nonfalse sono equivalenti. non lo è mai , quindi a meno che non venga aggiornato prima che venga lanciato , quando verrà restituito non lo sarà mai . Sarebbe aiutare moltissimo se la spec per erano disponibili. In realtà, questo è forse il problema più grave esposto da questo codice. new BazObject()nullbazObjectExceptionFoofalsebaznullFoo
Steve Powell,

La mia memoria è confusa poiché era molto tempo fa, ma penso di aver incasinato l'esempio e stava verificando se "baz" era falso, non se fosse nullo. In ogni caso il modello mi è sembrato davvero arcaico, come se fosse da VB6 e lo sviluppatore non si è mai preso la briga di migliorare il suo codice (cosa che non ha fatto)
Wayne Molina,

Risposte:


11

Di solito usi quel modello in modo da poter scrivere codice in questo modo:

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

viene utilizzato nel CLR per TryGetValue nella classe del dizionario, ad esempio. Evita un po 'di ridondanza ma i parametri out e ref mi sono sempre sembrati un po' confusi


1
+1, questo è il motivo per cui, anche se tendo a restituire un oggetto sia con il booleano sia l'oggetto piuttosto che restituirli entrambi separatamente
pdr

Contrassegnando questa come risposta poiché spiega il motivo, anche se il consenso sembra essere piuttosto obsoleto, tranne in circostanze specifiche.
Wayne Molina,

Questa risposta giustifica l'uso di questo modello. Ma se è in TUTTO IL tuo codebase, è probabilmente una cattiva pratica come Sean side.
Codismo

11

Questo è il vecchio codice in stile C, prima che ci fossero eccezioni. Il valore restituito indica se il metodo ha avuto esito positivo o meno e, in caso affermativo, il parametro verrebbe riempito con il risultato.

In .net abbiamo eccezioni a tale scopo. Non ci dovrebbero essere motivi per seguire questo schema.

[modifica] Ovviamente ci sono implicazioni sulle prestazioni nella gestione delle eccezioni. Forse questo ha qualcosa a che fare con questo. Tuttavia, in quel frammento di codice è già stata generata un'eccezione. Sarebbe più pulito lasciarlo salire nello stack fino a quando non viene catturato in un posto più appropriato.


No. Non posso essere d'accordo. Non utilizzare mai un'eccezione per una condizione prevista. Se stai analizzando una stringa in un int, ad esempio, è sempre possibile che qualcuno passi una stringa non analizzabile. In questo caso non dovresti fare un'eccezione
pdr

Non è quello che ho detto. Se leggi il codice pubblicato da OP, si verifica esplicitamente un caso di eccezione, ma il metodo lo rileva e restituisce invece false. Questa domanda è correlata alla gestione degli errori, non alle condizioni previste.
Sean Edwards,

Sì, sembra essere utilizzato principalmente nei metodi che caricano i dati dal database, ad esempio, if (baz.Select())ma il più delle volte il valore restituito viene semplicemente eliminato e il valore viene verificato rispetto a null o ad alcune proprietà.
Wayne Molina,

@Sean, vedi cosa stai dicendo, ho solo obiettato alla frase "In .NET abbiamo delle eccezioni a tale scopo." Le eccezioni hanno uno scopo completamente diverso per me
pdr

1
Ok, vorrei essere chiaro. È vero che non dovresti semplicemente aggiungere un valore booleano a ogni metodo per tenere conto degli errori di codifica su argomenti non validi. Ma laddove l'input per un metodo provenga da un utente piuttosto che da uno sviluppatore , dovresti essere in grado di provare a gestire l'input senza generare un'eccezione se ha sbagliato.
pdr,

2

Dato lo snippet di codice sembra completamente inutile. Per me il modello di codice iniziale suggerirebbe che null per BazObject sarebbe un caso accettabile e il ritorno bool è una misura per determinare in modo sicuro un caso fallito. Se il seguente codice era:

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

Ciò avrebbe più senso per me farlo in questo modo. Forse questo è un metodo che qualcuno prima di te stava usando per garantire che il baz fosse passato per riferimento senza capire come i parametri dell'oggetto funzionino effettivamente in C #.


Penso che sia stato scritto da un membro attuale del team. Forse è qualcosa di cui dovrei preoccuparmi di O_O
Wayne Molina,

@Wayne M: Meno di una preoccupazione che una potenziale opportunità di allenamento :)
Joel Etherton,

1

A volte questo schema è utile quando tu, il chiamante, non dovresti preoccuparti se il tipo restituito è un tipo di riferimento o valore. Se si chiama un metodo di questo tipo per recuperare un tipo di valore, quel tipo di valore avrà bisogno di un valore non valido stabilito (ad es. Double.NaN) o è necessario un altro modo per determinare il successo.


Ha senso. Sembra un po 'strano, ma sembra una ragione valida.
Wayne Molina,

0

L'idea è di restituire un valore che indica se il processo ha avuto esito positivo, consentendo un codice come questo:

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

Se l'oggetto non può essere nullo (che appare corretto nel tuo esempio), è meglio farlo come segue:

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

Se l'oggetto può essere nullo, le eccezioni sono la strada da percorrere:

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

È un modello comune usato in C, dove non ci sono eccezioni. Consente alla funzione di restituire una condizione di errore. Le eccezioni sono generalmente una soluzione molto più pulita.


Soprattutto perché il codice sta già generando un'eccezione.
David Thornley,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.