Lanciare un'eccezione quando un metodo non completa o implementa un lavoro in giro? [duplicare]

2

Ho studiato questo argomento un bel po 'e non sono sicuro di quale sia il modo migliore. Ho ancora molto da imparare (libri, blog, scambi di stack, ecc.), Ma semplicemente non riesco a trovare un consenso.

Fondamentalmente ecco la situazione: hai una funzione che dovrebbe restituire un risultato, ma la funzione fallisce (il parametro è cattivo, si verifica un'eccezione, la risorsa è bloccata / non raggiungibile, ecc.) se dovessi lanciare un'eccezione o restituire qualcosa ?

Semplicemente non vedo il modo migliore per farlo, ecco come appaiono le cose:

Le persone sembrano concordare sul fatto che restituire null e codificare per null (aspettarsi null e controllarlo) è una cattiva pratica e sono pienamente d'accordo. Ma dall'altra parte delle cose le persone non sembrano gradire "eccezioni vexing", ad esempio Int32.parseInt lancia un'eccezione invece di restituire null, questa funzione ha una controparte che non lo fa (tryParse) che restituisce un valore booleano ma ha un parametro di output:

bool result = Int32.TryParse(value, out number);
if (result)
{
    Console.WriteLine("Converted '{0}' to {1}.", value, number);         
}

Personalmente trovo questo metodo un po 'irritante, ho studiato il threading con Linux e la maggior parte dei metodi erano methodA(in, in, out, in) che è orribile. Con un IDE corretto questo problema è molto più piccolo, ma mi ha lasciato ancora un cattivo gusto in bocca.

E in alcuni linguaggi (come Java) questo non è esattamente facile da fare, dato che non ha% descrittore di% c_de (o come si chiama) e passa per valore.

Quindi si tratta di questo: come sarebbe meglio gestirlo? Aggiungi eccezioni a quando il tuo codice non può essere completato (se non è un numero, non posso analizzarlo, non posso restituire un numero) o dovresti risolvere il problema. In C, C ++ o C # probabilmente andrei sempre con l'approccio out , ma in Java questo sembra un modo davvero doloroso per risolvere il problema, e non sono sicuro che sia disponibile in altre lingue o meno.

    
posta Kalec 21.09.2015 - 18:21
fonte

7 risposte

5

La linea di pensiero moderna, che è considerata la migliore pratica dalla maggior parte delle pubblicazioni e nella maggior parte dei luoghi di lavoro, è quella di mai restituire i codici dei risultati, e invece di < strong> sempre genera un'eccezione se non puoi restituire un valore valido. (Ma leggi di più, c'è una svolta.)

Il motivo principale per cui C # offre tryParse() è perché in Microsoft Dot Net il lancio di un'eccezione è oltraggiosamente lento, (perché il runtime DotNet funziona in promiscuo collusione con il sistema operativo Windows sottostante,) quindi non puoi permettertti di avere eccezioni lanciate e catturate durante il normale flusso di operazioni. Al contrario, in Java lanciare un'eccezione e catturarla è relativamente poco dispendiosa (poiché viene gestita interamente all'interno della VM senza colpire il sistema operativo sottostante, quale che sia), così le persone lo fanno sempre e non ne hanno paura .

Se sei davvero preoccupato per il sovraccarico delle prestazioni delle eccezioni, allora puoi "piegare" le best practice per adattarle alle tue esigenze.

Le buone pratiche impongono che qualsiasi eccezionale situazione deve essere segnalata per mezzo di un'eccezione, mentre qualsiasi situazione non deve non essere segnalato per eccezione. Pertanto, è possibile ridefinire, se lo si desidera, quale situazione non eccezionale è, per includere, ad esempio, il caso in cui non si è in grado di analizzare una determinata stringa. Se ti aspetti che i guasti siano comuni, è ragionevole. Quindi, in questo scenario, è possibile indicare un errore di analisi con qualche mezzo diverso da un'eccezione, perché hai appena ridefinito il fallimento in analisi come situazione non eccezionale.

Quindi, in C #, oltre allo stile (certamente imbarazzante) try...() dei metodi puoi sempre restituire una primitiva nullable ( int? ) o quella "facoltativa generica" suggerita da Winston Ewert. In java, puoi sempre restituire una primitiva in scatola, ( Integer invece di int ,) che può essere nullo.

    
risposta data 21.09.2015 - 19:36
fonte
5

Una possibilità è usare un generico facoltativo. Ad esempio, vedi uno da Guava:

link

In questo caso, la tua funzione diventa

Optional<Int32> parseInt(String);

Ora il codice chiamante deve decidere in modo esplicito come vuole gestire il caso di errore. Il generico facoltativo Guava ha una varietà di metodi per semplificare i casi di uso comune.

In qualche modo, è come un null, in quanto includi lo stato di errore come valore di ritorno. Tuttavia, questo è esplicito. Se si tenta di passare un Opzionale dove è previsto un Int32, si otterrà un errore del compilatore. Nel punto in cui Opzionale diventa un Int32, sai che devi pensare a gestire il caso di errore.

    
risposta data 21.09.2015 - 19:10
fonte
2

Da un lato, tutto dipende dal contratto della procedura. Qualunque cosa tu scelga, dovresti assicurarti che ciò che ti aspetti dal chiamante (ad es., Condizioni preliminari) e ciò che il chiamante può aspettarti da te (ad es. Postcondizioni) sia chiaro, compreso ciò che il chiamante dovrebbe aspettarsi in caso di fallimento. Se si mette l'onere di controllare i valori di ritorno sul chiamante, assicurarsi che il chiamante sappia.

Preferisco le eccezioni. Le eccezioni sono vantaggiose perché rendono difficile per il codice client ignorare silenziosamente l'errore. In Java, ad esempio, le eccezioni non Runtime devono essere rilevate o dichiarate esplicitamente come generate; Generalmente le eccezioni del runtime non dovrebbero essere scoperte e sono pensate per terminare il programma a causa di un bug. Le eccezioni rendono inoltre più semplice separare la logica "reale" di risoluzione del problema dalla logica di "gestione degli errori", che facilita la lettura del codice.

Ciò che non suggerirei è mescolare usando le eccezioni con l'uso dei codici di ritorno - questo confonderà chiunque utilizzi la tua API.

    
risposta data 21.09.2015 - 18:47
fonte
1

Devi lanciare un'eccezione.

Persone come TryParse perché altrimenti hai bisogno di una funzione IsParsable che probabilmente fa la stessa cosa sotto il cofano.

Personalmente cerco di definire le mie funzioni in modo tale che le eccezioni comuni possano essere gestite all'interno della funzione stessa. ad esempio

//returns empty list when no objects are found
IEnumerable<Objects> Repository.GetObjects(string search)

La cosa da evitare è una situazione in cui lanciare un'eccezione costringerà il codice chiamante a utilizzare l'eccezione come parte della logica, ad esempio.

Try {
    This();
}
catch() {
    That();
}

Poiché questo è costoso e soggetto a errori quando vengono lanciate eccezioni impreviste.

    
risposta data 21.09.2015 - 18:41
fonte
1

In generale, genera un'eccezione, non implementare un lavoro in giro.

Il principio di base è scrivere il metodo in modo tale che sia chiaro quale sarà il risultato dal chiamare la funzione e che il chiamante si aspetti che la funzione abbia successo nel suo scopo.

L'Int.TryParse () lo estende un po '(ma non oltre il punto di rottura) in quanto è chiaramente implicito che il ritorno da questa funzione restituisce vero / falso se la stringa può essere analizzata in un numero intero. Il valore di ritorno di false implica che la funzione abbia determinato con successo che il parametro stringa non era un intero formattato correttamente.

La chiave, è che non è al posto delle eccezioni. Questa funzione genererà comunque eccezioni (ad es. ThreadAbortException, InvalidArgException etc ...). Le eccezioni non vengono nascoste da un valore restituito di "false".

Quando utilizzare l'implementazione Try?

In genere utilizzo l'Int.Parse () in uso normale.

L'eccezione a questo è se stavo scrivendo la funzione di analisi effettiva per un set di dati più grande. Forse un file di configurazione o la convalida dei dati utente inseriti in un'interfaccia utente. In queste situazioni il vero scopo del codice è fare una serie di controlli di convalida sui dati, e io sono al punto giusto nel codice per implementare diverse azioni basate sulla formattazione dei dati.

Se la mia unica altra opzione fosse:

try
{
   ...
}
catch(ParseException ex)
{
   //  do this instead....
}

e vorrei ingoiare l'eccezione, quindi usare TryParse () ha senso.

    
risposta data 21.09.2015 - 19:42
fonte
1

Dipende, ma in caso di dubbi, genera un'eccezione .

L'unica cosa che dovresti mai è quella di restituire un valore di errore che è dello stesso tipo di un risultato legittimo. Come avere IndexOf() che restituisce -1 se la sottostringa non viene trovata. Questo è sbagliato perché ti dimentichi facilmente di controllare questo valore speciale, in modo da nascondere gli errori senza gestirli, il che porta a bug difficili da trovare. Anche il ritorno null ha questo problema perché null è un valore valido per qualsiasi tipo di oggetto, quindi porta allo stesso problema.

Il modello TryParse() è in qualche modo migliore, poiché è esplicito in quanto restituisce un flag che indica se l'operazione è riuscita. Ovviamente puoi decidere di ignorare il valore di ritorno, ma non è probabile che tu lo faccia per errore. Inoltre, il metodo TryParse() -method ha la controparte Parse() , che genera un'eccezione se l'analisi fallisce. Quindi se il tuo codice non è in grado di recuperare da un errore di analisi si usa comunque la versione Parse() . Se TryParse() non ha avuto la contropartita Parse() , correrai il rischio che le persone ignorino il codice di errore e nascondano gli errori, ma questo non è probabile quando è semplicemente più semplice usare Parse() .

Quindi, anche se implementi un metodo Try... -pattern, dovresti avere una controparte che genera un errore. Il che porta al punto che tu come default dovresti implementare la versione che genera errori.

    
risposta data 21.09.2015 - 20:30
fonte
0

Ho appena completato un progetto in cui stavo praticamente TUTTI i miei metodi che fanno questo tipo di lavoro in una classe di tipo "message", che (in C #) ha un booleano concreto per successo / fallimento, insieme a messaggi di errore / successo raccolte di stringhe. Esiste una versione generica <T> che eredita dalla versione non generica, quindi un metodo potrebbe restituire un TransactionResult normale (che segnala solo se una chiamata dati ha avuto esito negativo o riuscito, con ulteriore messaggistica in base alle esigenze) o un TransactionResult<string> che è il primo, più un campo stringa che contiene il valore restituito (in questo caso, string ).

Ora posso solo ispezionare il campo "Success" dell'oggetto restituito, senza dover sapere che "-1" significa "stringa non trovata". Potrei anche avvolgere la mia proprietà "ResultObject" con un campo privato di backup e controllarlo per null quando si imposta (se fossi così inclinato). Niente più gestione degli errori per le cose comuni, e nel complesso è stato abbastanza facile da implementare.

public class TransactionResult
    {
        public bool Success { get; set; }
        public ValidationResult[] TransactionErrors { get; private set; }
        public string[] SuccessMessages { get; private set; } // in case you need to signal a partial success, or a custom result message

        public TransactionResult()
        {
            TransactionErrors = new ValidationResult[0];
            SuccessMessages = new string[0];
            Success = true; // Easier to let it be 'true' by default, and 'false' only when you add an error or set it manually!
        }

        public void AddTransactionError(string errorMessage, string[] memberNames = null)
        {
            var newError = new ValidationResult(errorMessage, memberNames ?? new[] { "" });
            TransactionErrors = TransactionErrors.Concat(new[] { newError }).ToArray();
            Success = false;
        }

        public void MergeWithErrorsFrom(TransactionResult transaction)
        {
            if (transaction == null || !transaction.TransactionErrors.Any())
                return;

            SuccessMessages = SuccessMessages.Concat(transaction.SuccessMessages).ToArray();
            TransactionErrors = TransactionErrors.Concat(transaction.TransactionErrors).ToArray();
            if (TransactionErrors.Any())
                Success = false;
        }

        public void AddSuccessMessage(string message)
        {
            SuccessMessages = SuccessMessages.Concat(new[] { message }).ToArray();
        }
    }

    public class TransactionResult<T> : TransactionResult
    {
        public T ResultObject { get; set; }

        public void MergeWithResultsFrom(TransactionResult<T> transaction)
        {
            MergeWithErrorsFrom(transaction);
            ResultObject = transaction.ResultObject;
        }
    }

La classe precedente utilizza la classe System.ComponentModel.DataAnnotations.ValidationResult come classe "error" di tipo backing, dal momento che sto lavorando in MVC4, il che significa che i miei risultati possono legarsi all'interfaccia utente in modo automatico, ma è possibile eseguire il rollover anche la tua versione di quell'errore. Non mi dispiacerebbe lavorare in un linguaggio in cui questo tipo di struttura faceva automaticamente parte del linguaggio / framework invece di dover girare il mio, ma questo è piuttosto supponente.

    
risposta data 21.09.2015 - 22:42
fonte

Leggi altre domande sui tag