2015-11-20 20 views
5

Mi chiedo, c'è un modo migliore per scrivere codice quando ci sono funzioni con ritorni di stato.Codifica delle migliori pratiche C++

Di seguito è riportato un esempio. (Si prega di ignorare gli errori di codice semplici se ce ne sono. Sono in particolare parlando della struttura. Inoltre, io sono al lavoro e non hanno un compilatore su questo computer)

#include "Session.h" 

Session::Session(const char * IPaddress, unsigned int openPort) 
{ 
    ssh_session mySession; 
    hostIP = IPaddress; 
    port = openPort; 
} 

int Session::cBeginSession() 
{ 
    try 
    { 
     int status = ssh_options_set(mySession, SSH_OPTIONS_HOST, &hostIP); 
     if (status == 0) 
     { 
      status = ssh_options_set(mySession, SSH_OPTIONS_LOG_VERBOSITY,      
            SSH_LOG_PROTOCOL); 
      if(status == 0) 
      { 
       status = ssh_options_set(mySession, SSH_OPTIONS_PORT, &port); 
       if (status == 0) 
       { 
        std::cout << "Session started\n"; 
        return 0; 
       } 
       else 
       { 
        std::cout << "Unable to set port\n"; 
        return -3; 
       } 

      } 
      else 
      { 
       std::cout << "Protocol option log verbosity unable to set\n"; 
       return -2; 
      } 
     } 
     else 
     { 
      std::cout << "Unable to set Host address\n"; 
      return -1; 
     } 
    } 
    catch (...) 
    { 
     std::cout << "Unknown exception occurred\n"; 
     return -8; 
    } 
} 

Io di solito uso se- altre istruzioni con i parametri di stato, ma tendo a finire con nidi grandi di istruzioni if-else se ci sono più di una o due funzioni coinvolte. C'è un modo più leggibile per scrivere qualcosa del genere? Diventa un nido di topi molto rapidamente.

MODIFICA: Grazie per tutte le risposte. Penso di avere alcune idee su come strutturare meglio il mio codice. Apprezzo tutti i suggerimenti diligenti.

+2

penso eccezioni sono i migliori in generale. Ma assicurati che il tuo codice sia [eccezionalmente sicuro] (https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Resource_Acquisition_Is_Initialization). –

+0

È più forte di "penso". Le eccezioni sono * il * metodo raccomandato e standard per comunicare un errore quando ci si aspetta che un'operazione abbia successo. –

risposta

6

Nella moderna programmazione C++, in genere, se si verifica un errore in cui il programma non può continuare, quindi penso che sia meglio fare un'eccezione a throw.

Quindi la vostra funzione non restituirebbe nulla (vale a dire void). Ogni volta che si è imbattuto in una situazione di non poter continuare, si dovrebbe throw un'eccezione che indica quale sia l'errore. Il codice chiamante gestirà quindi l'errore.

Il vantaggio di questo è che è possibile scegliere dove per gestire l'errore. Ad esempio, lo stack può scaricare tutto fino a main.

codice Si potrebbe assomigliare a questo:

void Session::cBeginSession() 
{ 
    if (ssh_options_set(mySession, SSH_OPTIONS_HOST, &hostIP)) 
    { 
     // throw an exception 
    } 
    if (ssh_options_set(mySession, SSH_OPTIONS_LOG_VERBOSITY, SSH_LOG_PROTOCOL)) 
    { 
     // throw an exception 
    } 
    if (ssh_options_set(mySession, SSH_OPTIONS_PORT, &port)) 
    { 
     // throw an exception 
    } 
} 

Una volta a ottenere il blocco di codifica con le eccezioni, il codice tende ad essere più pulito e più robusto visto che non stai sempre preoccuparsi di codice di verifica di ritorno.

EDIT

Per rispondere si commento. Puoi scegliere come e quando gestire l'errore. Puoi semplicemente catturare l'eccezione sopra la tua chiamata. Ma, in generale, se vuoi fare qualcosa che può fallire (ma non terminare un programma) puoi fare un'altra funzione che restituisce uno stato booleano.

bool Session::tryCBeginSession()

Ora, la funzione originale void Session::cBeginSession() sarebbe stata attuata in termini di questa nuova funzione. Ho scoperto che nella maggior parte dei casi la scrittura di queste doppie funzioni viene eseguita solo in un numero limitato di casi.

+0

"dove il programma non può continuare" è un insieme di casi troppo ristretto. Ce ne sono molti altri –

+0

Forse, ma per quel caso ristretto, sicuramente le eccezioni sono una buona scelta. –

+0

Questa sembra essere la più leggibile di tutte le risposte finora. Una cosa che ho letto è che usare la parte catch di try-catch per reindirizzare il codice è una cattiva pratica. Ci sono pensieri su questo? Sono interessato a come potrei recuperare e riprovare, o andare avanti se qualcosa non funziona. Preferirei che il programma non si arrendesse e uscisse. – Rethipher

1

mi piace per ridurre la nidificazione, in questo modo:

status = fcn1(); 
if (status == 0) 
{ 
    // something good 
    status = fcn2(); 
} 
else 
{ 
    // something bad happened. report, and leave status reporting failure. 
} 

if (status == 0) 
{ 
    // something good 
    status = fcn3(); 
} 
else 
{ 
    // something bad happened. report, and leave status reporting failure. 
} 

if (status == 0) 
{ 
    // something good 
    status = fcn4(); 
} 
else 
{ 
    // something bad happened. report, and leave status reporting failure. 
} 

mi piace che la stampa l'errore è vicino al verificarsi dell'errore. Certo, quando si verifica un errore, il status viene controllato più volte. Ma è un piccolo prezzo da pagare per la semplicità.

Ciò si presta bene anche alla disassegnazione delle risorse e alla chiusura dei file alla fine, indipendentemente da dove si verifica l'errore.

+0

Sembra più C che C++. – jbruni

+0

Hai ragione. Stavo solo mostrando una "struttura"/tecnica che non è limitata a C o C++. Mentre lanciare un'eccezione è il cuore della risposta accettata e specifica del C++, l'utilità è piuttosto limitata. Ogni chiamante di questo metodo deve gestire e segnalare (probabilmente testo) indipendentemente. Con molti chiamanti, questo è un po 'un aspetto negativo. Molto dipende da come il metodo deve essere usato. – donjuedo

1

In questo particolare caso (manipolazione codici di errore), che sto sostenendo per i ritorni presto:

int status = ssh_options_set(mySession, SSH_OPTIONS_HOST, &hostIP); 
if (status != 0) 
{ 
    std::cout << "Unable to set Host address\n"; 
    return -1; 
} 

status = ssh_options_set(mySession, SSH_OPTIONS_LOG_VERBOSITY,      
         SSH_LOG_PROTOCOL); 
if (status != 0) 
{ 
    std::cout << "Protocol option log verbosity unable to set\n"; 
    return -2; 
} 

status = ssh_options_set(mySession, SSH_OPTIONS_PORT, &port); 
if (status != 0) 
{ 
    std::cout << "Unable to set port\n"; 
    return -3; 
} 

std::cout << "Session started\n"; 
return 0; 

trovo il codice molto più leggibile perché ha timore di nidificazione e la gestione degli errori è mantenuto vicino al punto in cui si è verificato l'errore anziché essere sepolto in un altro ramo.

Se si decide che è meglio utilizzare le eccezioni anziché i codici di errore, è possibile mantenere la stessa struttura e sostituire i ritorni con i lanci.

+0

Anche questa è la mia preferenza, purché la pulizia sia semplice. – donjuedo

+0

Perché non renderlo ancora più compatto e assegnare 'status' all'interno delle istruzioni' if'? In realtà prendo quello indietro. Il tuo uso di 'status' è superfluo. –

+0

@PeterM Che lo renderebbe meno leggibile/manutenibile, inoltre (oltre all'avvertimento che un compilatore potrebbe emettere) –

0
  • Non è necessario if-else se si sia o returnthrow nella dichiarazione che segue (questo è un buon argomento per throw ing BTW). Plain if farà.
  • Il tipo di messaggi che si sta stampando di solito si adatta meglio a stderr anziché a stdout (cerr anziché a cout).
  • Se si decide si può mantenere con stati di errore, costanti simboliche (o enumerazioni o definisce) sono di solito preferito su "numeri magici"
1

Questo è uno scenario abbastanza ideale per di gestione delle eccezioni (almeno come era inteso). La gestione delle eccezioni è in genere appropriata per gestire errori di input esterni, come in questo caso in cui l'input esterno proviene da un socket.

Hai già un blocco try/catch, ma ti suggerisco di eliminarlo poiché non c'è alcun codice di ripristino. Tieni i tuoi blocchi try/catch generalmente concentrati intorno alle aree in cui effettui una transazione di modifica. Catturare un'eccezione quindi ripristina le modifiche, riporta il sistema a uno stato valido e, in alcuni casi, emette alcuni messaggi.

Qualcosa di simile a questo:

void Session::cBeginSession() 
{ 
    if (ssh_options_set(mySession, SSH_OPTIONS_HOST, &hostIP) != 0) 
     throw runtime_error("Unable to set Host address"); 

    if (ssh_options_set(mySession, SSH_OPTIONS_LOG_VERBOSITY, SSH_LOG_PROTOCOL) != 0) 
     throw runtime_error("Protocol option log verbosity unable to set."); 

    if (ssh_options_set(mySession, SSH_OPTIONS_PORT, &port) != 0) 
     throw runtime_error("Unable to set port"); 
    std::cout << "Session started\n"; 
} 

Vediamo il codice del client di chiamare questa funzione cattura l'eccezione in un sito dove è appropriato per gestire e recuperare dall'errore. Basta preoccuparsi di lanciare l'eccezione in modo appropriato nel caso di questi errori di input esterni.

Si noti che la gestione delle eccezioni è in genere ultra economica nei casi non eccezionali (dove non si effettuano) con ottimizzazioni come EH a costo zero. Tuttavia, questi tipi di ottimizzazioni del compilatore di gestione delle eccezioni rendono il caso raro molto più lento in cui effettivamente si genera un'eccezione. Quindi le eccezioni dovrebbero essere utilizzate per casi veramente eccezionali derivanti da un qualche tipo di input esterno che il software normalmente non può gestire, come in questo caso.

Un'altra avvertenza rilevante in alcuni tipi di sistemi più grandi (architetture di plug-in, ad esempio), è che in genere le eccezioni non devono essere gettate oltre i limiti del modulo.

Questo è un po 'supponente ma non consiglio di avere molti rami di cattura basati sul tipo di eccezione (come comunemente si trova in Java, ad es.). Spesso non è necessario distinguere il tipo effettivo di un'eccezione tanto quanto inoltrare un messaggio all'utente, ad es. Cattura le eccezioni nel modo più generale/grossolano possibile e mantieni i blocchi try/catch al minimo (mentalità orientata alle transazioni di alto livello: le transazioni hanno esito positivo o falliscono e si ripristinano nel suo complesso).

Altrimenti le eccezioni possono davvero semplificare questo tipo di casi e un'intera parte della libreria C++ (e anche parti della lingua) generano eccezioni normalmente (penso davvero che C++ e gestione delle eccezioni siano inseparabili), quindi può essere utile per farne uso poiché in genere un programma robusto dovrà generalmente catturarli comunque.

+0

L'uso di '! = 0' è analogo al test se un valore booleano == true. –

+0

'operator!' Sarebbe la mia preferenza qui se è solo 1/0, anche se volevo provare una traduzione più diretta del codice originale dell'op. Sembrava preferire l'uso di '== 0', e questa convenzione sembra essere abbastanza comune nel codice C di gestione degli errori come questo per qualche ragione, anche se mi piace molto la forma concisa. –

+0

Oh bene, l'ho aggiornato con 'operator!'! –

0

alcuni suggerimenti annotati nel codice:

// improvement - derive your own descriptive exception FROM A STANDARD EXCEPTION TYPE 
struct session_error : std::runtime_error 
{ 
    using std::runtime_error::runtime_error; 
}; 

// improvement in constructor: initialiser lists 
Session::Session(const char * IPaddress, unsigned int openPort) 
: mySession() 
, hostIP(IPaddress) 
, port(openPort) 
{ 
} 

namespace { 
    // use of anonymous namespace so this wrapper function does not pollute other compilation units 
    void setopt(ssh_session& session, int opt, const void* val, const char* context) 
    { 
     if (ssh_options_set(session, opt, val)) 
      throw session_error(context); 
    } 

} 

void Session::cBeginSession() 
{ 
    // improvement - defer to wrapper function that handles nasty return code logic 
    // and throws a sensible exception. Now your function is readable and succinct. 

    setopt(mySession, SSH_OPTIONS_HOST, &hostIP, "setting host option"); 
    setopt(mySession, SSH_OPTIONS_LOG_VERBOSITY, SSH_LOG_PROTOCOL, "setting verbosity"); 
    setopt(mySession, SSH_OPTIONS_PORT, &port, "can't set port"); 

    std::cout << "Session started\n"; 
} 
+0

A prima vista, penserei che questo è un po 'più di lavoro extra, e overdesign, vs semplicemente usando la funzione in un'istruzione if e chiamando le eccezioni subito dopo. Non mi aspetterei che le eccezioni generate cambino, ma verranno aggiunte anche. Ho sbagliato? Ovviamente non ho molta esperienza nella progettazione di corsi, quindi potrei benissimo mancare alcune ottime ragioni per fare queste cose. – Rethipher

+0

dipende da come si misura il lavoro. è meno linee di codice rispetto all'originale, più succinta, la funzione ora si adatta a una pagina e il metodo segnala gli errori con un'eccezione, sollevando il chiamante dall'onere di controllare che la funzione abbia avuto successo. È semplicemente un fatto che uno sviluppatore spenderà almeno 10 volte più a lungo il debug di un programma che scriverlo. Se la scrivi con quel numero in mente, dividi la logica in unità più piccole. –

+0

Mi sento come sollevare l'utente dal controllo dello stato potrebbe essere cattivo. Ciò significa che devi semplicemente aver fiducia che tutto è andato bene nella chiamata di funzione. Ho scritto molti test automatizzati in MATLAB usando la funzionalità di terze parti, e ricordo di essere sempre stato irritato quando non avevo modo di sapere se la funzione fosse tornata a posto. Oltre a quella parte, vedo il tuo punto. – Rethipher