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:
- Gunakan blok coba/tangkap/akhirnya untuk memulihkan dari kesalahan atau melepaskan sumber daya
- Menangani kondisi umum untuk menghindari pengecualian
- Menangkap pembatalan dan pengecualian asinkron
- Kelas desain sehingga pengecualian dapat dihindari
- Status pemulihan ketika metode tidak selesai karena pengecualian
- Menangkap dan menumbuhkan kembali pengecualian dengan benar
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, gunakanthrow
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
- Menggunakan metode penyusun pengecualian
- Menyertakan pesan string yang dilokalkan
- Gunakan tata bahasa yang tepat
- Letakkan pernyataan lemparan dengan baik
- Jangan memunculkan pengecualian dalam klausul akhirnya
- Jangan memunculkan pengecualian dari tempat yang tidak terduga
- Melempar pengecualian validasi argumen secara sinkron
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:
- ArgumentNullException.ThrowIfNull
- ArgumentException.ThrowIfNullOrEmpty(String, String)
- ArgumentException.ThrowIfNullOrWhiteSpace(String, String)
- ArgumentOutOfRangeException.ThrowIfZero<T>(T, String)
- ArgumentOutOfRangeException.ThrowIfNegative<T>(T, String)
- ArgumentOutOfRangeException.ThrowIfEqual<T>(T, T, String)
- ArgumentOutOfRangeException.ThrowIfLessThan<T>(T, T, String)
- ArgumentOutOfRangeException.ThrowIfNotEqual<T>(T, T, String)
- ArgumentOutOfRangeException.ThrowIfNegativeOrZero<T>(T, String)
- ArgumentOutOfRangeException.ThrowIfGreaterThan<T>(T, T, String)
- ArgumentOutOfRangeException.ThrowIfLessThanOrEqual<T>(T, T, String)
- ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual<T>(T, T, String)
- ObjectDisposedException.ThrowIf
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 message
Konstruktor 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:
- Cara: Membuat pengecualian yang ditentukan pengguna dengan pesan pengecualian yang dilokalkan
- Sumber daya di aplikasi .NET
- System.Resources.ResourceManager
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
, , GetHashCode
dan 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
- Sertakan tiga konstruktor
- Menyediakan properti tambahan sesuai kebutuhan
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.
- Exception(), yang menggunakan nilai default.
- Exception(String), yang menerima pesan string.
- Exception(String, Exception), yang menerima 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.