Come trattare gli spaghetti HRESULT?

3

Attualmente sto scrivendo un'applicazione audio usando WASAPI, che è davvero una grande esperienza di apprendimento, tuttavia ho questo strano problema, non so davvero cosa fare su tutti questi codici HRESULT in un buon modo e sto cercando per alternative al mio attuale approccio che è if_else chains.

Poiché la documentazione MSDN menziona che "i codici di errore non sono limitati a ciò che elenciamo in queste tabelle" mi sento come se non avessi altra scelta che essere un buon cittadino e controllare ogni singola chiamata di funzione per errore e poi se fallisce fai una pulizia e chiudi il programma.

Questo porta ad una quantità oscena di codice di gestione degli errori. Ho preso in considerazione solo l'utilizzo di eccezioni, ma ho questo pregiudizio contro di loro a causa di uno sfondo di sviluppo del gioco in cui sono stato adeguatamente indottrinato per trattarli come un hellspawn che gonfia il programma e le prestazioni degradanti, quindi sono riluttante su quello anteriore.

Qual è un buon modo per gestire un codice di grandi quantità che restituisce HRESULT nel senso di rendere il codice facile da gestire e leggere? (preferibilmente qualcosa di diverso dalle enormi catene if_else che ho ora)

    
posta vlind 05.06.2017 - 00:47
fonte

2 risposte

1

Di solito avvolgo HRESULT s che indica errore in std::system_error eccezioni.

Questo è un modo lungo per ripulire il codice. Il valore di ritorno sarà disponibile per i dati effettivi elaborati dalle funzioni e la gestione degli errori è separata dalla logica del programma (almeno per il chiamante delle funzioni).

Trovo particolarmente utile poter aggiungere informazioni di contesto alle eccezioni che mi consentono di risalire da dove ha origine l'errore. Molto spesso quando scriviamo una funzione dobbiamo fare una catena di chiamate API. Nel codice C dovremmo semplicemente restituire HRESULT dalla nostra funzione, ma il contesto andrebbe perso.

I've been considering just using exceptions however I have this prejudice against them due to coming from a game development background where I've been properly indoctrinated to treat them as hellspawn bloating up the program and degrading performance

Le prestazioni sono degradate solo per il percorso del codice eccezionale . Sapendo questo, le eccezioni non dovrebbero essere utilizzate nei casi in cui il fallimento si verifica altrettanto spesso quanto il successo. Quindi non è più un'emulazione e il codice di errore dovrebbe essere restituito normalmente.

Ho una semplice funzione che chiamo per il valore di ritorno delle funzioni in cui non mi aspetto un errore durante il normale funzionamento:

// Throw a std::system_error if the HRESULT indicates failure.
template< typename T >
void ThrowIfFailed( HRESULT hr, T&& msg )
{
    if( FAILED( hr ) )
        throw std::system_error{ hr, std::system_category(), std::forward<T>( msg ) };
}

Molto probabilmente lo compilerà il compilatore in modo che non ci siano sovraccarichi per una chiamata di funzione nel caso non eccezionale .

Lo uso in questo modo con informazioni di contesto sotto forma di stringa letterale ...

ThrowIfFailed(
    SomeApiThatReturnsHRESULT(),
    "Context information for this API" );

... o con informazioni di contesto sotto forma di std::string :

ThrowIfFailed(
    SomeApiThatReturnsHRESULT(),
    "Context information for this API" + std::to_string( additionalErrorInfo ) );

Ecco un esempio completo .

Puoi ricavare le tue classi di eccezione da std::system_error per classificare ulteriormente gli errori, ad esempio:

struct WASAPI_error : public std::system_error
{
    // Inherit constructors.
    using std::system_error::system_error;
};
    
risposta data 11.06.2017 - 02:12
fonte
-1

Alcuni suggerimenti generali:

  • NON gestire errori non previsti. Significato, se dovessi ricevere un HRESULT che non è incluso tra i tuoi possibili scenari, fai sicuro che dopo la pulizia, il programma viene chiuso e il codice di errore viene registrato. Mi ringrazierai più tardi.
  • FAILED può almeno dirti la gravità di il risultato, quindi se non è un errore, ma non sai quale codice rappresenta, puoi ancora loggarlo per dopo senza chiudere il programma.

A questo punto, se si tratta di un errore atteso che è possibile gestire, è possibile creare una classe astratta ErrorHandler con metodi non dichiarati canHandle (HRESULT) e handle (HRESULT). Idealmente avresti una classe estesa di ErrorHandler per ogni risposta diversa a un errore, con canHandle che risponde a tutti i valori HRESULT specifici che vengono gestiti allo stesso modo. Ovviamente puoi modificare la firma del metodo di handle e canHandle per accompagnare i puntatori aggiuntivi ai controller che consentono al gestore di eseguire varie azioni, se necessario.

Avresti quindi una factory che restituirà un ErrorHandler che gestisce il particolare HRESULT passato, e dovresti chiamare solo handle con i parametri appropriati. Se non ci sono ErrorHandler per adattarsi a HRESULT, puoi gestirlo in due modi. Uno è verificare che ErrorHandler restituito dalla fabbrica sia non nullo e lasciare che maneggi un errore imprevisto al chiamante, ma una soluzione più pulita sarebbe semplicemente avere un ErrorHandler specificamente per gestire questo caso all'interno della stessa Fabbrica (controlli di fabbrica se il valore di ritorno è nullo e, in tal caso, restituisce un'istanza di UnexpectedErrorHandler in modo che sia sempre un ErrorHandler, anche se ErrorHandler deve gestire un errore imprevisto). Assicurati solo che, in questo caso, il programma sia stato chiuso in modo responsabile e che il codice di errore sia registrato.

Questa soluzione è un sistema di gestione degli errori molto pulito che ti consente di gestire i nuovi errori con relativa facilità. Non c'è nulla che ti impedisca di estendere le classi ErrorHandler esistenti e di eseguire azioni oltre a quelle eseguite nella classe genitore.

Spero che ti aiuti!

    
risposta data 05.06.2017 - 11:02
fonte

Leggi altre domande sui tag