Generare / non lanciare un'eccezione basata su un parametro: perché questa non è una buona idea?

StackOverflow https://stackoverflow.com/questions/624647

Domanda

Stavo cercando MSDN e ho trovato questo articolo che ha fornito un suggerimento interessante: Non avere membri pubblici che possono generare o meno eccezioni in base a qualche opzione.

Ad esempio:

Uri ParseUri(string uriValue, bool throwOnError)

Ora ovviamente posso vedere che nel 99% dei casi sarebbe orribile, ma il suo uso occasionale è giustificato?

Un caso che ho visto usato è con un " AllowEmpty " parametro quando si accede ai dati nel database o in un file di configurazione. Ad esempio:

object LoadConfigSetting(string key, bool allowEmpty);

In questo caso, l'alternativa sarebbe restituire null. Ma poi il codice chiamante verrebbe disseminato di controllo dei riferimenti null. (E il metodo impedirebbe anche la possibilità di consentire effettivamente null come valore specificamente configurabile, se tu fossi così incline).

Quali sono i tuoi pensieri? Perché questo sarebbe un grosso problema?

È stato utile?

Soluzione

Penso che sia sicuramente una cattiva idea avere una decisione di lancio / non lancio basata su un valore booleano. Vale a dire perché richiede agli sviluppatori che guardano un pezzo di codice di avere una conoscenza funzionale dell'API per determinare cosa significa il valore booleano. Questo è un problema da solo, ma quando cambia la gestione degli errori sottostanti può rendere molto facile per gli sviluppatori fare errori durante la lettura del codice.

In questo caso sarebbe molto meglio e più leggibile avere 2 API.

Uri ParseUriOrThrow(string value);

bool TryParseUri(string value, out Uri uri);

In questo caso è chiaro al 100% cosa fanno queste API.

Articolo sul perché i booleani sono cattivi come parametri: http : //blogs.msdn.com/jaredpar/archive/2007/01/23/boolean-parameters.aspx

Altri suggerimenti

Di solito è meglio scegliere un meccanismo di gestione degli errori e attenersi ad esso in modo coerente. Consentire questo tipo di codice flip-flop non può davvero migliorare la vita degli sviluppatori.

Nell'esempio sopra, cosa succede se l'analisi fallisce e throwOnError è falso? Ora l'utente deve indovinare se NULL se sta per essere restituito, o dio lo sa ...

È vero che c'è un dibattito in corso tra eccezioni e valori di ritorno come il miglior metodo di gestione degli errori, ma sono abbastanza certo che ci sia consenso sull'essere coerenti e attenersi a qualsiasi scelta tu faccia. L'API non può sorprendere i suoi utenti e la gestione degli errori dovrebbe far parte dell'interfaccia ed essere chiaramente definita come l'interfaccia.

È un po 'brutto dal punto di vista della leggibilità. Gli sviluppatori tendono ad aspettarsi che ogni metodo generi un'eccezione e, se vogliono ignorare l'eccezione, la prenderanno da soli. Con l'approccio della "bandiera booleana", ogni singolo metodo deve implementare questa semantica che inibisce le eccezioni.

Tuttavia, penso che l'articolo MSDN si riferisca rigorosamente ai flag 'throwOnError'. In questi casi o l'errore viene ignorato all'interno del metodo stesso (errato, in quanto nascosto) o viene restituito un tipo di oggetto null / errore (errato, poiché non si utilizzano le eccezioni per gestire l'errore, che è incoerente e l'errore stesso -prone).

Mentre il tuo esempio mi sembra perfetto. Un'eccezione indica un fallimento del metodo nell'esecuzione del proprio dovere - non esiste un valore di ritorno. Tuttavia, il flag 'allowEmpty' modifica la semantica del metodo, quindi ciò che sarebbe stata un'eccezione ("valore vuoto") è ora previsto e legale. Inoltre, se avevi generato un'eccezione, non saresti in grado di restituire facilmente i dati di configurazione. Quindi sembra OK in questo caso.

In qualsiasi API pubblica è davvero una cattiva idea avere due modi per verificare la presenza di una condizione difettosa perché diventa ovvio cosa accadrà se si verifica l'errore. Basta guardare il codice non aiuterà. Devi capire la semantica del parametro flag (e nulla impedisce che sia un'espressione).

Se il controllo di null non è un'opzione e se devo recuperare da questo errore specifico, preferisco creare un'eccezione specifica in modo da poterla catturare in seguito e gestirla in modo appropriato. In ogni altro caso, lancio un'eccezione generale.

Un altro esempio in linea con questo potrebbe essere l'insieme dei metodi TryParse su alcuni dei tipi di valore

bool DateTime. TryParse (testo della stringa, uscita DateTime)

Avere un parametro donTThrowException sconfigge l'intero punto delle eccezioni (in qualsiasi lingua). Se il codice chiamante vuole avere:

public static void Main()
{
        FileStream myFile = File.Open("NonExistent.txt", FileMode.Open, FileAccess.Read);
}

sono benvenuti (C # non ha nemmeno verificato le eccezioni). In Java la stessa cosa si realizzerebbe con:

public static void main(String[] args) throws FileNotFoundException
{
        FileInputStream fs = new FileInputStream("NonExistent.txt");
}

Ad ogni modo, è compito del chiamante decidere come gestire (o meno) l'eccezione, non la chiamata.

Nel link all'articolo c'è una nota che le eccezioni non dovrebbero essere usate per il flusso di controllo - il che sembra implicito nelle missioni di esempio. Le eccezioni dovrebbero riflettere l'errore a livello di metodo. Per avere una firma che è OK lanciare un errore sembra che il design non sia stato pensato.

sottolinea il libro CLR di Jeffrey Richters tramite C # - " dovresti generare un'eccezione quando il metodo non può completare il suo compito come indicato dal suo nome " ;.

Il suo libro ha anche segnalato un errore molto comune. Le persone tendono a scrivere codice per catturare tutto (le sue parole " Un onnipresente errore degli sviluppatori che non sono stati adeguatamente addestrati sull'uso corretto delle eccezioni tendono ad usare i blocchi di cattura troppo spesso e in modo improprio. Quando catturi un'eccezione, stai affermando che ti aspettavi questa eccezione, capisci perché si è verificata e sai come gestirla. ")

Ciò mi ha fatto provare a codificare eccezioni che posso aspettarmi e in grado di gestire nella mia logica, altrimenti dovrebbe essere un errore.

Convalida i tuoi argomenti e previene le eccezioni, e prendi solo ciò che puoi gestire.

Direi che spesso è utile avere un parametro che indica se un errore dovrebbe causare un'eccezione o semplicemente restituire un'indicazione di errore, poiché tali parametri possono essere facilmente passati da una routine esterna a una interna. Considera qualcosa come:

  Byte [] ReadPacket (bool DontThrowIfNone) // Documentato come restituito null se non presente   {     int len ??= ReadByte (DontThrowIfNone); // Documentato come restituito -1 se nulla     if (len

Se qualcosa come TimeoutException durante la lettura dei dati dovrebbe causare un'eccezione, tale eccezione dovrebbe essere generata in ReadByte () o ReadMultiBytesbytes (). Se, tuttavia, tale mancanza di dati dovesse essere considerata normale, la routine ReadByte () o ReadMultiBytesbytes () non dovrebbe generare un'eccezione. Se uno utilizzava semplicemente il modello do / try, le routine ReadPacket e TryReadPacket dovrebbero avere un codice quasi identico, ma con uno che utilizza i metodi Read * e l'altro con i metodi TryRead *. Icky.

Potrebbe essere meglio usare un'enumerazione piuttosto che un valore booleano.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top