Domanda

Mi sono imbattuto molte volte nel seguente tipo di codice e mi chiedo se questa sia una buona pratica (dal punto di vista delle prestazioni) o meno:

try
{
    ... // some code
}
catch (Exception ex)
{
    ... // Do something
    throw new CustomException(ex);
}

Fondamentalmente, ciò che sta facendo il programmatore è racchiudere l'eccezione in un'eccezione personalizzata e lanciarla di nuovo.

In cosa differisce in termini di prestazioni dai due seguenti:

try
{
    ... // some code
}
catch (Exception ex)
{
    .. // Do something
    throw ex;
}

O

try
{
    ... // some code
}
catch (Exception ex)
{
    .. // Do something
    throw;
}

Mettendo da parte eventuali argomentazioni sulle migliori pratiche funzionali o di codifica, c'è qualche differenza di prestazioni tra i 3 approcci?

È stato utile?

Soluzione

@Brad Tutterow

Nel primo caso l'eccezione non viene persa, viene passata al costruttore.Sarò d'accordo con te sul resto, però, il secondo approccio è una pessima idea a causa della perdita di stack trace.Quando lavoravo con .NET, mi sono imbattuto in molti casi in cui altri programmatori hanno fatto proprio questo, e mi ha frustrato moltissimo quando avevo bisogno di vedere la vera causa di un'eccezione, solo per scoprire che veniva rilanciata da un enorme blocco try in cui Ora non ho idea di dove abbia avuto origine il problema.

Condivido anche il commento di Brad secondo cui non dovresti preoccuparti della performance.Questo tipo di microottimizzazione è un'idea ORRIBILE.A meno che tu non stia parlando di lanciare un'eccezione in ogni iterazione di un ciclo for in esecuzione per un lungo periodo, molto probabilmente non incontrerai problemi di prestazioni a causa dell'utilizzo delle eccezioni.

Ottimizza sempre le prestazioni quando disponi di metriche che indicano che DEVI ottimizzare le prestazioni, quindi raggiungi i punti che si sono rivelati colpevoli.

È molto meglio avere codice leggibile con semplici funzionalità di debug (IE che non nasconde l'analisi dello stack) piuttosto che far funzionare qualcosa un nanosecondo più velocemente.

Un'ultima nota sull'inclusione delle eccezioni in un'eccezione personalizzata...questo può essere un costrutto molto utile, soprattutto quando si ha a che fare con le UI.È possibile racchiudere ogni caso eccezionale noto e ragionevole in un'eccezione personalizzata di base (o in una che si estende da detta eccezione di base) e quindi l'interfaccia utente può semplicemente rilevare questa eccezione di base.Una volta rilevata, l'eccezione dovrà fornire mezzi per visualizzare informazioni all'utente, ad esempio una proprietà ReadableMessage o qualcosa del genere.Pertanto, ogni volta che l'interfaccia utente non rileva un'eccezione, è a causa di un bug che è necessario correggere e ogni volta che rileva un'eccezione, si tratta di una condizione di errore nota che può e deve essere gestita correttamente dall'interfaccia utente.

Altri suggerimenti

Ovviamente incorri nella penalità di creare nuovi oggetti (la nuova Eccezione) quindi, esattamente come fai con ogni riga di codice che aggiungi al tuo programma, devi decidere se la migliore categorizzazione delle eccezioni ripaga il lavoro extra.

Come consiglio per prendere tale decisione, se i tuoi nuovi oggetti non contengono informazioni aggiuntive sull'eccezione, puoi dimenticare di costruire nuove eccezioni.

Tuttavia, in altre circostanze, avere una gerarchia di eccezioni è molto conveniente per l'utente delle tue classi.Supponiamo che tu stia implementando il modello Facade, nessuno degli scenari finora considerati è buono:

  1. non è positivo sollevare ogni eccezione come oggetto Exception perché stai perdendo (probabilmente) informazioni preziose
  2. non è bene neppure sollevare ogni tipo di oggetto che si capita perché così facendo si fallisce nel creare la facciata

In questo caso ipotetico, la cosa migliore da fare è creare una gerarchia di classi di eccezioni che, astraendo gli utenti dalle complessità interne del sistema, permetta loro di sapere qualcosa sul tipo di eccezione prodotta.

Come nota a margine:

Personalmente non mi piace l'uso delle eccezioni (gerarchie di classi derivate dalla classe Exception) per implementare la logica.Come nel caso:

try {
        // something that will raise an exception almost half the time
} catch( InsufficientFunds e) {
        // Inform the customer is broke
} catch( UnknownAccount e ) {
        // Ask for a new account number
}

Come David, suppongo che il secondo e il terzo abbiano prestazioni migliori.Ma qualcuno dei tre si comporterebbe così male da dedicare del tempo a preoccuparsene?Penso che ci siano problemi più grandi delle prestazioni di cui preoccuparsi.

FxCop consiglia sempre il terzo approccio rispetto al secondo in modo che l'analisi dello stack originale non venga persa.

Modificare:Sono state rimosse cose che erano semplicemente sbagliate e Mike è stato così gentile da farlo notare.

Non fare:

try
{
    // some code
}
catch (Exception ex) { throw ex; }

Poiché ciò perderà l'analisi dello stack.

Invece fai:

try
{
    // some code
}
catch (Exception ex) { throw; }

Basterà il lancio, devi solo passare la variabile di eccezione se vuoi che sia l'eccezione interna su una nuova eccezione personalizzata.

Come altri hanno affermato, le prestazioni migliori provengono da quello in basso poiché stai semplicemente rilanciando un oggetto esistente.Quello centrale è il meno corretto perché perde lo stack.

Personalmente utilizzo le eccezioni personalizzate se desidero disaccoppiare determinate dipendenze nel codice.Ad esempio, ho un metodo che carica i dati da un file XML.Questo può andare storto in molti modi diversi.

Potrebbe non riuscire a leggere dal disco (FileIOException), l'utente potrebbe provare ad accedervi da qualche parte non consentita (SecurityException), il file potrebbe essere danneggiato (XmlParseException), i dati potrebbero essere nel formato sbagliato (DeserialisationException).

In questo caso, quindi è più semplice per la classe chiamante dare un senso a tutto ciò, tutte queste eccezioni riproducono una singola eccezione personalizzata (FileOperationException) in modo che ciò significhi che il chiamante non ha bisogno di riferimenti a System.IO o System.Xml, ma può comunque accedere all'errore verificatosi tramite un'enumerazione e a qualsiasi informazione importante.

Come affermato, non provare a micro-ottimizzare qualcosa di simile, l'atto di lanciare un'eccezione è la cosa più lenta che si verifica qui.Il miglior miglioramento da apportare è provare a evitare del tutto un'eccezione.

public bool Load(string filepath)
{
  if (File.Exists(filepath)) //Avoid throwing by checking state
  {
    //Wrap anyways in case something changes between check and operation
    try { .... }
    catch (IOException ioFault) { .... }
    catch (OtherException otherFault) { .... }
    return true; //Inform caller of success
  }
  else { return false; } //Inform caller of failure due to state
}

Il lancio nel tuo primo esempio ha il sovraccarico della creazione di un nuovo oggetto CustomException.

La ripetizione nel secondo esempio genererà un'eccezione di tipo Exception.

La ripetizione nel terzo esempio genererà un'eccezione dello stesso tipo lanciata dal tuo "codice".

Quindi il secondo e il terzo esempio utilizzano meno risorse.

Aspettare....perché ci preoccupiamo delle prestazioni se viene lanciata un'eccezione?A meno che non utilizziamo le eccezioni come parte del normale flusso dell'applicazione (che è MOLTO contrario alle migliori pratiche).

Ho visto i requisiti prestazionali solo per quanto riguarda il successo, ma mai per quanto riguarda il fallimento.

Da un punto di vista puramente prestazionale, direi che il terzo caso è il più performante.Gli altri due devono estrarre una traccia dello stack e costruire nuovi oggetti, entrambi i quali richiedono potenzialmente molto tempo.

Detto questo questi tre blocchi di codice hanno molto diversi comportamenti (esterni), quindi confrontarli è come chiedere se QuickSort è più efficiente dell'aggiunta di un elemento a un albero rosso-nero.Non è così importante quanto scegliere la cosa giusta da fare.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top