È sbagliato preferire le eccezioni per centralizzare la logica di gestione degli errori?

1

Usando C ++, spesso sento che dovresti evitare di lanciare eccezioni per il controllo del flusso e dovresti evitare di chiamare le funzioni nelle condizioni in cui sai che verranno lanciate. Ad esempio, se una funzione viene generata quando la si passa una stringa vuota, perché non verificare la presenza di vuoto prima di chiamare tale funzione?

Personalmente ho anche lasciato andare le funzioni anche nei casi in cui so che potevano basarsi sulla violazione delle precondizioni. Si noti che questi sono casi di runtime e non sono errori logici. Usando l'esempio di stringa, questa potrebbe essere una risposta del server che contiene una stringa vuota (e in questo caso non potremmo fare nulla di significativo con una stringa vuota).

Personalmente, mi piace l'idea di tutti i fallimenti che spostano lo stack in un punto centrale in cui gli errori possono essere gestiti in modo uniforme, invece di avere il branching del codice e la logica dei casi speciali per gestire i singoli punti di errore. Questo può diventare davvero sgradevole attraverso più profondità di chiamate di funzione. C'è molta semplicità e meno predizione dell'errore nella scrittura del codice per assumere il successo, e lasciare che il linguaggio sottostante / strumenti interrompa il controllo del flusso in casi eccezionali / di errore. Negli scenari più controversi, lancio persino le mie condizioni if invece di tornare semplicemente per ottenere il vantaggio di un punto di uscita / gestione centrale per tutte le eccezioni, anche quelle generate dalle funzioni.

Si tratta tecnicamente di "eccezioni per il controllo del flusso" che è spesso scoraggiato? Dove disegna la linea? Cosa c'è di buono, cos'è il male? È meglio avere una combinazione di logica "ritorno graduale" guidata da condizioni if e gestione delle eccezioni?

Aggiornamento:

Ecco un esempio di codice se aiuta. Tieni a mente che questo è molto semplificato, di solito ci sono molti più punti di errore e la logica di recupero è più sofisticata:

void DoThingWithResponse(std::string const& data)
{
    if (data.empty())
    {
        throw std::runtime_error{"Some exception because data was empty"};
    }

    // Do real thing with data, because we know it's valid here
}

void ResponseCallback(std::string const& data, int http_status_code)
{
    try
    {
         if (http_status_code != 200)
         {
             throw std::runtime_error{"Server indicated failure"};
         }

         DoThingWithResponse(data);
    }
    catch (std::exception const& e)
    {
        std::cerr << "Something bad happened: " << e.what();
        RetryRequest();
    }
}
    
posta void.pointer 04.06.2018 - 16:01
fonte

1 risposta

8

Penso che tutti possiamo essere d'accordo sul fatto che ciò sia negativo:

void search(TreeNode node, Object data) throws ResultException 
{
    if (node.data.equals(data))
        throw new ResultException(node); // Found desired node
    else 
    {
        search(node.leftChild, data);
        search(node.rightChild, data);
    }
}

E questo è male:

try 
{
    for (int i = 0; ; i++)
        array[i]++;
} 
catch (ArrayIndexOutOfBoundsException e) {} // Loop completed, carry on.

E questo è buono:

class SomeClass
{
    SomeClass(string requiredParameter)
    {
        if (requiredParameter.NotSupplied)
           throw new ArgumentException("requiredParameter not supplied.");
    }
}

Perché i primi due esempi sono così chiaramente negativi? Perché stanno violando gli idiomi naturali della lingua. (o il Principio della Minima Sorpresa, se preferisci)

Perché il terzo esempio va bene? Perché, se non si fornisce il parametro richiesto, il costruttore non può avere successo. Non c'è modo significativo per recuperare da questo, oltre a lanciare un'eccezione e lasciare che il chiamante gestirlo.

Quindi come gestisci le situazioni borderline? Allo stesso modo in cui prendi qualsiasi altra decisione nello sviluppo del software. Pesate i pro ei contro e seguite l'approccio che soddisfa in modo efficace le vostre esigenze specifiche.

Uno dei contro è la prestazione. Un'eccezione generata ti costerà da qualche parte tra le 100 e le 10.000 volte il costo di una semplice chiamata di funzione non banale o di una dichiarazione di ritorno (a seconda del linguaggio di programmazione), quindi non puoi lanciarne una in un ciclo stretto dove la performance è critico, a meno che non sia una condizione di errore irrecuperabile e non è possibile continuare il ciclo.

    
risposta data 04.06.2018 - 17:37
fonte

Leggi altre domande sui tag