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();
}
}