Suggerimenti per gestire le eccezioni

Un insieme ben progettato di blocchi di codice per la gestione degli errori può rendere un programma più affidabile e meno soggetto ad arresti anomali perché l'applicazione è in grado di gestire tali errori. Di seguito sono indicati alcuni suggerimenti per la gestione delle eccezioni:

  • Sapere quando impostare un blocco try/catch. È ad esempio possibile controllare a livello di codice una condizione che è probabile si verifichi senza ricorrere alla gestione delle eccezioni. In altre situazioni è invece preferibile utilizzare la gestione delle eccezioni per intercettare una condizione di errore.

    Nell'esempio seguente viene utilizzata un'istruzione if per controllare se una connessione è chiusa. È possibile utilizzare questo metodo anziché definire la generazione di un'eccezione in caso di connessione non chiusa.

If conn.State <> ConnectionState.Closed Then
    conn.Close()
End IF
if (conn.State != ConnectionState.Closed)
{
    conn.Close();
}
if (conn->State != ConnectionState::Closed)
{
    conn->Close();
}

Nell'esempio che segue viene generata un'eccezione se la connessione non è chiusa.

Try
    conn.Close()
Catch ex As InvalidOperationException
    Console.WriteLine(ex.GetType().FullName)
    Console.WriteLine(ex.Message)
End Try
try
{
    conn.Close();
}
catch (InvalidOperationException ex)
{
    Console.WriteLine(ex.GetType().FullName);
    Console.WriteLine(ex.Message);
}
try
{
    conn->Close();
}
catch (InvalidOperationException^ ex)
{
    Console::WriteLine(ex->GetType()->FullName);
    Console::WriteLine(ex->Message);
}

Nella scelta del metodo è necessario considerare la frequenza con cui si prevede che l'evento possa verificarsi. Se l'evento è davvero eccezionale e rappresenta un errore, quale la fine imprevista di un file, è preferibile utilizzare la gestione delle eccezioni perché in condizioni normali viene eseguito meno codice. Se l'evento si verifica invece con una certa regolarità, è meglio adottare il metodo a livello di codice per il controllo degli errori. In questo caso, infatti, se si verificasse un'eccezione, la gestione richiederebbe tempi più lunghi.

  • Utilizzare blocchi try/finally attorno al codice che potrebbe, potenzialmente, generare un'eccezione e riunire le istruzioni catch in un'unica posizione centrale. In questo modo l'istruzione try genera l'eccezione, l'istruzione finally chiude o dealloca risorse e l'istruzione catch gestisce l'eccezione da una posizione centrale.

  • Ordinare sempre le eccezioni in blocchi catch dalla più specifica alla meno specifica. In questo modo l'eccezione specifica viene gestita prima di essere passata a un blocco catch più generico.

  • Terminare i nomi delle classi di eccezioni con la parola "Exception". Di seguito è riportato un esempio:

Public Class MyFileNotFoundException
    Inherits Exception
End Class
public class MyFileNotFoundException : Exception
{
}
public ref class MyFileNotFoundException : public Exception
{
};
  • Quando si creano eccezioni definite dall'utente, è necessario garantire che i metadati relativi alle eccezioni siano disponibili per il codice eseguito in posizione remota, incluso il caso di eccezioni che si verificano a livello di più domini applicazione. Si supponga ad esempio che nel dominio applicazione A venga creato il dominio applicazione B, nel quale viene eseguito codice che genera un'eccezione. Per rilevare e gestire correttamente l'eccezione, il dominio applicazione A deve essere in grado di individuare l'assembly contenente l'eccezione generata dal dominio applicazione B. Se il dominio applicazione B genera un'eccezione contenuta in un assembly nella base dell'applicazione di tale dominio, ma non nella base dell'applicazione del dominio applicazione A, quest'ultimo non sarà in grado di individuare l'eccezione e Common Language Runtime genererà un'eccezione FileNotFoundException. Per evitare che questo si verifichi è possibile distribuire l'assembly contenente le informazioni sull'eccezione in due modi:

    • Inserendo l'assembly in una base applicativa comune condivisa da entrambi i domini applicazione

      - oppure -

    • Se i domini non condividono alcuna base applicativa comune, firmando l'assembly contenente le informazioni sull'eccezione con un nome sicuro e distribuendo tale assembly nella Global Assembly Cache.

  • In C# e C++ utilizzare almeno i tre costruttori comuni quando si creano classi di eccezione personalizzate. Per un esempio, vedere Procedura: creare eccezioni definite dall'utente.

  • Nella maggior parte dei casi utilizzare i tipi di eccezione predefiniti. Definire nuovi tipi di eccezioni solo per l'utilizzo a livello di codice. Introdurre una nuova classe di eccezione per consentire a un programmatore di eseguire un'operazione differente nel codice in base alla classe di eccezione.

  • Per la maggior parte delle applicazioni, derivare le eccezioni personalizzate dalla classe Exception . L'idea iniziale di far derivare le eccezioni personalizzate dalla classe ApplicationException non ha prodotto alcun valore significativo in termini pratici.

  • Includere in ciascuna eccezione una stringa descrittiva localizzata. Quando viene visualizzato un messaggio di errore per l'utente, tale messaggio verrà tratto dalla stringa descrittiva dell'eccezione generata, anziché dalla classe di eccezione.

  • Utilizzare messaggi di errore corretti dal punto di vista grammaticale, inclusa la punteggiatura finale. È necessario che ogni frase della stringa descrittiva di un'eccezione termini con un punto.

  • Fornire proprietà Exception per l'accesso a livello di codice. Oltre alla stringa descrittiva, includere in un'eccezione informazioni supplementari solo se effettivamente utili nel relativo contesto di codice.

  • Restituire il valore null per i casi di errore estremamente comuni. Open, ad esempio, restituisce null se è impossibile trovare un file, ma genera un'eccezione se il file è bloccato.

  • Progettare le classi in modo che non venga mai generata un'eccezione nell'utilizzo normale. Con la classe FileStream, ad esempio, è possibile determinare in modo diverso se è stata raggiunta la fine del file. Questo consente di evitare l'eccezione che verrebbe generata se si continua la lettura oltre il termine del file. Nell'esempio che segue viene illustrato come terminare la lettura in corrispondenza della fine del file.

Class FileRead
    Public Sub ReadAll(fileToRead As FileStream)
        ' This if statement is optional
        ' as it is very unlikely that
        ' the stream would ever be null.
        If fileToRead Is Nothing Then
            Throw New System.ArgumentNullException()
        End If

        Dim b As Integer

        ' Set the stream position to the beginning of the file.
        fileToRead.Seek(0, SeekOrigin.Begin)

        ' Read each byte to the end of the file.
        For i As Integer = 0 To fileToRead.Length - 1
            b = fileToRead.ReadByte()
            Console.Write(b.ToString())
            ' Or do something else with the byte.
        Next i
    End Sub
End Class
class FileRead
{
    public void ReadAll(FileStream fileToRead)
    {
        // This if statement is optional
        // as it is very unlikely that
        // the stream would ever be null.
        if (fileToRead == null)
        {
            throw new System.ArgumentNullException();
        }

        int b;

        // Set the stream position to the beginning of the file.
        fileToRead.Seek(0, SeekOrigin.Begin);

        // Read each byte to the end of the file.
        for (int i = 0; i < fileToRead.Length; i++)
        {
            b = fileToRead.ReadByte();
            Console.Write(b.ToString());
            // Or do something else with the byte.
        }
    }
}
class FileRead
{
public:
    void ReadAll(FileStream^ fileToRead)
    {
        // This if statement is optional
        // as it is very unlikely that
        // the stream would ever be null.
        if (fileToRead == nullptr)
        {
            throw gcnew System::ArgumentNullException();
        }

        int b;

        // Set the stream position to the beginning of the file.
        fileToRead->Seek(0, SeekOrigin::Begin);

        // Read each byte to the end of the file.
        for (int i = 0; i < fileToRead->Length; i++)
        {
            b = fileToRead->ReadByte();
            Console::Write(b.ToString());
            // Or do something else with the byte.
        }
    }
};

Generare un'eccezione InvalidOperationException se un insieme di proprietà o una chiamata al metodo non è adatta allo stato corrente dell'oggetto.

Generare un'eccezione ArgumentException o una classe derivata da ArgumentException se vengono passati parametri non validi.

La traccia dello stack inizia in corrispondenza dell'istruzione in cui l'eccezione viene generata e termina in corrispondenza dell'istruzione catch che intercetta l'eccezione. Tenere presente questo fatto quando si decide in che punto inserire un'istruzione throw.

Utilizzare metodi per la creazione di eccezioni. Una classe genera spesso la stessa eccezione da punti diversi dell'implementazione. Per evitare codice di dimensioni eccessive, utilizzare metodi di supporto che creano l'eccezione e la restituiscono. Esempio:

Class FileReader
    Private fileName As String


    Public Sub New(path As String)
        fileName = path
    End Sub

    Public Function Read(bytes As Integer) As Byte()
        Dim results() As Byte = FileUtils.ReadFromFile(fileName, bytes)
        If results Is Nothing
            Throw NewFileIOException()
        End If
        Return results
    End Function

    Function NewFileIOException() As FileReaderException
        Dim description As String = "My NewFileIOException Description"

        Return New FileReaderException(description)
    End Function
End Class
class FileReader
{
    private string fileName;

    public FileReader(string path)
    {
        fileName = path;
    }

    public byte[] Read(int bytes)
    {
        byte[] results = FileUtils.ReadFromFile(fileName, bytes);
        if (results == null)
        {
            throw NewFileIOException();
        }
        return results;
    }

    FileReaderException NewFileIOException()
    {
        string description = "My NewFileIOException Description";

        return new FileReaderException(description);
    }
}
ref class FileReader
{
private:
    String^ fileName;

public:
    FileReader(String^ path)
    {
        fileName = path;
    }

    array<Byte>^ Read(int bytes)
    {
        array<Byte>^ results = FileUtils::ReadFromFile(fileName, bytes);
        if (results == nullptr)
        {
            throw NewFileIOException();
        }
        return results;
    }

    FileReaderException^ NewFileIOException()
    {
        String^ description = "My NewFileIOException Description";

        return gcnew FileReaderException(description);
    }
};

In alternativa, utilizzare il costruttore dell'eccezione per compilare l'eccezione. Questo è opportuno soprattutto per le classi di eccezione globali, quale ArgumentException.

  • Generare eccezioni anziché restituire un codice di errore o HRESULT.

  • Eliminare i risultati intermedi quando si genera un'eccezione. I chiamanti dovrebbero avere la garanzia che non si verifichino effetti secondari quando un'eccezione viene generata da un metodo.

Vedere anche

Concetti

Gestione e generazione di eccezioni