Praktik terbaik untuk pengecualian

Penanganan pengecualian yang tepat sangat penting untuk keandalan aplikasi. Anda sengaja dapat menangani pengecualian yang diharapkan untuk mencegah aplikasi Mengalami crash. Namun, aplikasi yang mengalami crash lebih andal dan dapat didiagnosis daripada aplikasi dengan perilaku yang tidak terdefinisi.

Artikel ini menjelaskan praktik terbaik untuk menangani dan membuat pengecualian.

Menangani pengecualian

Praktik terbaik berikut ini menyangkut cara Anda menangani pengecualian:

Menggunakan blok try/catch/finally untuk memulihkan dari kesalahan atau melepaskan sumber daya

Untuk kode yang berpotensi menghasilkan pengecualian, dan kapan aplikasi Anda dapat pulih dari pengecualian tersebut, gunakan try/catch blok di sekitar kode. Dalam blok catch, selalu urutkan pengecualian dari yang paling turunan ke yang paling tidak diturunkan. (Semua pengecualian berasal dari Exception kelas . Pengecualian yang lebih turunan tidak ditangani oleh catch klausul yang didahului oleh catch klausul untuk kelas pengecualian dasar.) Saat kode Anda tidak dapat pulih dari pengecualian, jangan menangkap pengecualian tersebut. Aktifkan metode lebih lanjut ke tumpukan panggilan untuk memulihkan jika memungkinkan.

Bersihkan sumber daya yang dialokasikan dengan using pernyataan atau finally blok. Lebih memilih pernyataan using untuk membersihkan sumber daya secara otomatis saat pengecualian dilemparkan. Gunakan blok finally untuk membersihkan sumber daya yang tidak menerapkan IDisposable. Kode dalam klausul finally hampir selalu dijalankan bahkan ketika pengecualian dilemparkan.

Menangani kondisi umum untuk menghindari pengecualian

Untuk kondisi yang kemungkinan akan terjadi tetapi mungkin memicu pengecualian, pertimbangkan untuk menanganinya dengan cara yang menghindari pengecualian. Misalnya, jika Anda mencoba menutup koneksi yang sudah ditutup, Anda akan mendapatkan InvalidOperationException. Anda dapat menghindarinya dengan menggunakan pernyataan if untuk memeriksa status koneksi sebelum mencoba menutupnya.

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

Jika Anda tidak memeriksa status koneksi sebelum menutup, Anda dapat menangkap InvalidOperationException pengecualian.

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

Pendekatan untuk memilih tergantung pada seberapa sering Anda mengharapkan peristiwa terjadi.

  • Gunakan penanganan pengecualian jika peristiwa tidak sering terjadi, yaitu, jika peristiwa benar-benar luar biasa dan menunjukkan kesalahan, seperti akhir file yang tidak terduga. Saat Anda menggunakan penanganan pengecualian, lebih sedikit kode yang dijalankan dalam kondisi normal.

  • Periksa kondisi kesalahan dalam kode jika peristiwa terjadi secara rutin dan dapat dianggap sebagai bagian dari eksekusi normal. Saat Anda memeriksa kondisi kesalahan umum, lebih sedikit kode dijalankan karena Anda menghindari pengecualian.

    Catatan

    Pemeriksaan di muka menghilangkan pengecualian sebagian besar waktu. Namun, mungkin ada kondisi balapan di mana kondisi yang dijaga berubah antara pemeriksaan dan operasi, dan dalam hal ini, Anda masih dapat dikenakan pengecualian.

Metode panggilan Try* untuk menghindari pengecualian

Jika biaya performa pengecualian dilarang, beberapa metode pustaka .NET menyediakan bentuk alternatif penanganan kesalahan. Misalnya, Int32.Parse melempar OverflowException jika nilai yang akan diurai terlalu besar untuk diwakili oleh Int32. Namun, Int32.TryParse tidak melemparkan pengecualian ini. Sebaliknya, ia mengembalikan Boolean dan memiliki out parameter yang berisi bilangan bulat valid yang diurai setelah berhasil. Dictionary<TKey,TValue>.TryGetValue memiliki perilaku serupa untuk mencoba mendapatkan nilai dari kamus.

Menangkap pembatalan dan pengecualian asinkron

Lebih baik menangkap OperationCanceledException alih-alih TaskCanceledException, yang berasal dari OperationCanceledException, ketika Anda memanggil metode asinkron. Banyak metode asinkron melemparkan OperationCanceledException pengecualian jika pembatalan diminta. Pengecualian ini memungkinkan eksekusi dihentikan secara efisien dan tumpukan panggilan dibatalkan setelah permintaan pembatalan diamati.

Metode asinkron menyimpan pengecualian yang dilemparkan selama eksekusi dalam tugas yang mereka kembalikan. Jika pengecualian disimpan ke dalam tugas yang dikembalikan, pengecualian tersebut akan dilemparkan saat tugas ditunggu. Pengecualian penggunaan, seperti ArgumentException, masih dilemparkan secara sinkron. Untuk informasi selengkapnya, lihat Pengecualian asinkron.

Kelas desain sehingga pengecualian dapat dihindari

Kelas dapat menyediakan metode atau properti yang memungkinkan Anda menghindari panggilan yang akan memicu pengecualian. Misalnya, FileStream kelas menyediakan metode yang membantu menentukan apakah akhir file telah tercapai. Anda dapat memanggil metode ini untuk menghindari pengecualian yang dilemparkan jika Anda membaca melewati akhir file. Contoh berikut menunjukkan cara membaca ke akhir file tanpa memicu pengecualian:

class FileRead
{
    public static void ReadAll(FileStream fileToRead)
    {
        ArgumentNullException.ThrowIfNull(fileToRead);

        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 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

Cara lain untuk menghindari pengecualian adalah dengan mengembalikan null (atau default) untuk kasus kesalahan yang paling umum alih-alih melemparkan pengecualian. Kasus kesalahan umum dapat dianggap sebagai aliran kontrol normal. Dengan mengembalikan null (atau default) dalam kasus ini, Anda meminimalkan dampak performa ke aplikasi.

Untuk jenis nilai, pertimbangkan apakah akan menggunakan Nullable<T> atau default sebagai indikator kesalahan untuk aplikasi Anda. Dengan menggunakan Nullable<Guid>, default menjadi null, bukan Guid.Empty. Terkadang, menambahkan Nullable<T> dapat membuatnya lebih jelas ketika nilai ada atau tidak ada. Di lain waktu, menambahkan Nullable<T> dapat membuat kasus tambahan untuk memeriksa yang tidak diperlukan dan hanya berfungsi untuk membuat sumber kesalahan potensial.

Status pemulihan ketika metode tidak selesai karena pengecualian

Pemanggil harus dapat mengasumsikan bahwa tidak ada efek samping ketika pengecualian dilemparkan dari metode. Misalnya, jika Anda memiliki kode yang mentransfer uang dengan menarik dari satu akun dan menyetorkan di akun lain, dan pengecualian dilemparkan saat mengeksekusi deposit, Anda tidak ingin penarikan tetap berlaku.

public void TransferFunds(Account from, Account to, decimal amount)
{
    from.Withdrawal(amount);
    // If the deposit fails, the withdrawal shouldn't remain in effect.
    to.Deposit(amount);
}
Public Sub TransferFunds(from As Account, [to] As Account, amount As Decimal)
    from.Withdrawal(amount)
    ' If the deposit fails, the withdrawal shouldn't remain in effect.
    [to].Deposit(amount)
End Sub

Metode sebelumnya tidak secara langsung melemparkan pengecualian apa pun. Namun, Anda harus menulis metode sehingga penarikan dibalik jika operasi setoran gagal.

Salah satu cara untuk menangani situasi ini adalah dengan menangkap pengecualian yang dilemparkan oleh transaksi deposit dan menggulung balik penarikan.

private static void TransferFunds(Account from, Account to, decimal amount)
{
    string withdrawalTrxID = from.Withdrawal(amount);
    try
    {
        to.Deposit(amount);
    }
    catch
    {
        from.RollbackTransaction(withdrawalTrxID);
        throw;
    }
}
Private Shared Sub TransferFunds(from As Account, [to] As Account, amount As Decimal)
    Dim withdrawalTrxID As String = from.Withdrawal(amount)
    Try
        [to].Deposit(amount)
    Catch
        from.RollbackTransaction(withdrawalTrxID)
        Throw
    End Try
End Sub

Contoh ini menggambarkan penggunaan throw untuk menumbuhkan kembali pengecualian asli, sehingga memudahkan penelepon untuk melihat penyebab sebenarnya dari masalah tanpa harus memeriksa InnerException properti. Alternatifnya adalah melemparkan pengecualian baru dan menyertakan pengecualian asli sebagai pengecualian dalam.

catch (Exception ex)
{
    from.RollbackTransaction(withdrawalTrxID);
    throw new TransferFundsException("Withdrawal failed.", innerException: ex)
    {
        From = from,
        To = to,
        Amount = amount
    };
}
Catch ex As Exception
    from.RollbackTransaction(withdrawalTrxID)
    Throw New TransferFundsException("Withdrawal failed.", innerException:=ex) With
    {
        .From = from,
        .[To] = [to],
        .Amount = amount
    }
End Try

Menangkap dan menumbuhkan kembali pengecualian dengan benar

Setelah pengecualian dilemparkan, bagian dari informasi yang dikandungnya adalah jejak tumpukan. Pelacakan tumpukan adalah daftar hierarki panggilan metode yang dimulai dengan metode yang melempar pengecualian dan berakhir dengan metode yang menangkap pengecualian. Jika Anda menumbuhkan kembali pengecualian dengan menentukan pengecualian dalam throw pernyataan, misalnya, throw e, jejak tumpukan dimulai ulang pada metode saat ini dan daftar panggilan metode antara metode asli yang melemparkan pengecualian dan metode saat ini hilang. Untuk menyimpan informasi pelacakan tumpukan asli dengan pengecualian, ada dua opsi yang bergantung pada tempat Anda menggulung kembali pengecualian dari:

  • Jika Anda menumbuhkan kembali pengecualian dari dalam handler (catch blok) yang menangkap instans pengecualian, gunakan throw pernyataan tanpa menentukan pengecualian. Aturan analisis kode CA2200 membantu Anda menemukan tempat dalam kode di mana Anda mungkin secara tidak sengaja kehilangan informasi pelacakan tumpukan.
  • Jika Anda menumbuhkan kembali pengecualian dari suatu tempat selain handler (catch blok), gunakan ExceptionDispatchInfo.Capture(Exception) untuk menangkap pengecualian di handler dan ExceptionDispatchInfo.Throw() kapan Anda ingin memutusnya kembali. Anda dapat menggunakan ExceptionDispatchInfo.SourceException properti untuk memeriksa pengecualian yang ditangkap.

Contoh berikut menunjukkan bagaimana ExceptionDispatchInfo kelas dapat digunakan, dan seperti apa outputnya.

ExceptionDispatchInfo? edi = null;
try
{
    var txt = File.ReadAllText(@"C:\temp\file.txt");
}
catch (FileNotFoundException e)
{
    edi = ExceptionDispatchInfo.Capture(e);
}

// ...

Console.WriteLine("I was here.");

if (edi is not null)
    edi.Throw();

Jika file dalam kode contoh tidak ada, output berikut diproduksi:

I was here.
Unhandled exception. System.IO.FileNotFoundException: Could not find file 'C:\temp\file.txt'.
File name: 'C:\temp\file.txt'
   at Microsoft.Win32.SafeHandles.SafeFileHandle.CreateFile(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options)
   at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
   at System.IO.Strategies.OSFileStreamStrategy..ctor(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
   at System.IO.Strategies.FileStreamHelpers.ChooseStrategyCore(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
   at System.IO.StreamReader.ValidateArgsAndOpenPath(String path, Encoding encoding, Int32 bufferSize)
   at System.IO.File.ReadAllText(String path, Encoding encoding)
   at Example.ProcessFile.Main() in C:\repos\ConsoleApp1\Program.cs:line 12
--- End of stack trace from previous location ---
   at Example.ProcessFile.Main() in C:\repos\ConsoleApp1\Program.cs:line 24

Melemparkan pengecualian

Praktik terbaik berikut menyangkut bagaimana Anda melemparkan pengecualian:

Gunakan jenis pengecualian yang telah ditentukan sebelumnya

Perkenalkan kelas pengecualian baru hanya jika kelas yang telah ditentukan sebelumnya tidak berlaku. Contohnya:

  • Jika kumpulan properti atau panggilan metode tidak sesuai mengingat status objek saat ini, berikan InvalidOperationException pengecualian.
  • Jika parameter yang tidak valid diteruskan, berikan ArgumentException pengecualian atau salah satu kelas yang telah ditentukan sebelumnya yang berasal dari ArgumentException.

Catatan

Meskipun yang terbaik adalah menggunakan jenis pengecualian yang telah ditentukan sebelumnya jika memungkinkan, Anda tidak boleh menaikkan beberapa jenis pengecualian yang dipesan , seperti AccessViolationException, IndexOutOfRangeException, NullReferenceException dan StackOverflowException. Untuk informasi selengkapnya, lihat CA2201: Jangan naikkan jenis pengecualian yang dipesan.

Menggunakan metode penyusun pengecualian

Adalah umum bagi kelas untuk melemparkan pengecualian yang sama dari berbagai tempat dalam implementasinya. Untuk menghindari kode yang berlebihan, buat metode pembantu yang membuat pengecualian dan mengembalikannya. Contohnya:

class FileReader
{
    private readonly string _fileName;

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

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

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

        return new FileReaderException(description);
    }
}
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

Beberapa jenis pengecualian .NET utama memiliki metode pembantu statis throw seperti itu yang mengalokasikan dan melemparkan pengecualian. Anda harus memanggil metode ini alih-alih membangun dan melemparkan jenis pengecualian yang sesuai:

Tip

Aturan analisis kode berikut dapat membantu Anda menemukan tempat dalam kode tempat Anda dapat memanfaatkan pembantu statis throw ini: CA1510, CA1511, CA1512, dan CA1513.

Jika Anda menerapkan metode asinkron, panggil CancellationToken.ThrowIfCancellationRequested() alih-alih memeriksa apakah pembatalan diminta dan kemudian membuat dan melemparkan OperationCanceledException. Untuk informasi selengkapnya, lihat CA2250.

Menyertakan pesan string yang dilokalkan

Pesan kesalahan yang dilihat pengguna berasal dari Exception.Message properti pengecualian yang dilemparkan, dan bukan dari nama kelas pengecualian. Biasanya, Anda menetapkan nilai ke properti Exception.Message dengan meneruskan string pesan ke argumen messageKonstruktor pengecualian.

Untuk aplikasi yang dilokalkan, Anda harus menyediakan string pesan yang dilokalkan untuk setiap pengecualian yang dapat dilemparkan aplikasi Anda. Anda menggunakan file sumber daya untuk menyediakan pesan kesalahan yang dilokalkan. Untuk informasi tentang melokalisasi aplikasi dan mengambil string yang dilokalkan, lihat artikel berikut ini:

Gunakan tata bahasa yang tepat

Tulis kalimat yang jelas dan sertakan tanda baca akhir. Setiap kalimat dalam string yang ditetapkan ke properti Exception.Message harus diakhiri satu titik. Misalnya, "Tabel log telah meluap." menggunakan tata bahasa dan tanda baca yang benar.

Letakkan pernyataan lemparan dengan baik

Tempatkan pernyataan lemparan di mana pelacakan tumpukan akan membantu. Jejak tumpukan dimulai pada pernyataan tempat pengecualian dilemparkan dan berakhir pada pernyataan catch yang menangkap pengecualian.

Jangan memunculkan pengecualian dalam klausul akhirnya

Jangan memunculkan pengecualian dalam finally klausul. Untuk informasi selengkapnya, lihat aturan analisis kode CA2219.

Jangan memunculkan pengecualian dari tempat yang tidak terduga

Beberapa metode, seperti Equals, , GetHashCodedan ToString metode, konstruktor statis, dan operator kesetaraan, tidak boleh melemparkan pengecualian. Untuk informasi selengkapnya, lihat aturan analisis kode CA1065.

Melempar pengecualian validasi argumen secara sinkron

Dalam metode pengembalian tugas, Anda harus memvalidasi argumen dan melemparkan pengecualian yang sesuai, seperti ArgumentException dan ArgumentNullException, sebelum memasukkan bagian asinkron dari metode . Pengecualian yang dilemparkan di bagian asinkron dari metode disimpan dalam tugas yang dikembalikan dan tidak muncul sampai, misalnya, tugas ditunggu. Untuk informasi selengkapnya, lihat Pengecualian dalam metode pengembalian tugas.

Jenis pengecualian kustom

Praktik terbaik berikut menyangkut jenis pengecualian kustom:

Akhiri nama kelas pengecualian dengan Exception

Ketika pengecualian kustom diperlukan, beri nama dengan tepat dan dapatkan dari kelas Exception. Contohnya:

public class MyFileNotFoundException : Exception
{
}
Public Class MyFileNotFoundException
    Inherits Exception
End Class

Sertakan tiga konstruktor

Gunakan setidaknya tiga konstruktor umum saat membuat kelas pengecualian Anda sendiri: konstruktor tanpa parameter, konstruktor yang mengambil pesan string, dan konstruktor yang mengambil pesan string dan pengecualian dalam.

Misalnya, lihat Cara: Membuat pengecualian yang ditentukan pengguna.

Menyediakan properti tambahan sesuai kebutuhan

Berikan properti tambahan untuk pengecualian (selain string pesan kustom) hanya ketika ada skenario terprogram tempat informasi tambahan menjadi berguna. Misalnya FileNotFoundException memberikan properti FileName.

Lihat juga