Masalah:
Sejak lama, saya khawatir dengan exceptions
mekanismenya, karena saya merasa tidak benar-benar menyelesaikan apa yang seharusnya.
KLAIM: Ada perdebatan panjang di luar tentang topik ini, dan kebanyakan dari mereka kesulitan membandingkan dan exceptions
mengembalikan kode kesalahan. Ini jelas bukan topik di sini.
Mencoba mendefinisikan kesalahan, saya setuju dengan CppCoreGuidelines, dari Bjarne Stroustrup & Herb Sutter
Kesalahan berarti bahwa fungsi tidak dapat mencapai tujuan yang diiklankan
KLAIM: exception
Mekanisme ini adalah semantik bahasa untuk menangani kesalahan.
KLAIM: Bagi saya, ada "tidak ada alasan" untuk suatu fungsi untuk tidak mencapai tugas: Entah kita salah mendefinisikan kondisi sebelum / sesudah sehingga fungsi tidak dapat memastikan hasil, atau beberapa kasus luar biasa tertentu tidak dianggap cukup penting untuk menghabiskan waktu dalam mengembangkan sebuah solusi. Menimbang bahwa, IMO, perbedaan antara kode normal dan penanganan kode kesalahan adalah (sebelum implementasi) garis yang sangat subyektif.
KLAIM: Menggunakan pengecualian untuk menunjukkan ketika kondisi pra atau pasca tidak disimpan adalah tujuan lain dari exception
mekanisme, terutama untuk tujuan debugging. Saya tidak menargetkan penggunaan ini di exceptions
sini.
Dalam banyak buku, tutorial, dan sumber lain, mereka cenderung menunjukkan penanganan kesalahan sebagai ilmu yang cukup objektif, yang diselesaikan dengan exceptions
dan Anda hanya perlu catch
mereka untuk memiliki perangkat lunak yang kuat, dapat pulih dari situasi apa pun. Tetapi beberapa tahun saya sebagai pengembang membuat saya melihat masalah dari pendekatan yang berbeda:
- Pemrogram cenderung untuk menyederhanakan tugas mereka dengan melemparkan pengecualian ketika kasus spesifik tampaknya terlalu jarang untuk diimplementasikan dengan hati-hati. Kasus khas ini adalah: kehabisan masalah memori, masalah disk penuh, masalah file rusak, dll. Ini mungkin cukup, tetapi tidak selalu diputuskan dari tingkat arsitektur.
- Pemrogram cenderung tidak membaca dokumentasi dengan hati-hati tentang pengecualian di perpustakaan, dan biasanya tidak mengetahui yang mana dan kapan suatu fungsi dilemparkan. Lebih jauh lagi, bahkan ketika mereka tahu, mereka tidak benar-benar mengelolanya.
- Programmer cenderung tidak menangkap pengecualian cukup awal, dan ketika mereka melakukannya, sebagian besar untuk login dan melempar lebih jauh. (lihat poin pertama).
Ini memiliki dua konsekuensi:
- Kesalahan yang terjadi sering terdeteksi pada awal pengembangan dan debugged (yang bagus).
- Pengecualian langka tidak dikelola dan membuat sistem macet (dengan pesan log yang bagus) di rumah pengguna. Beberapa kali kesalahan dilaporkan, atau bahkan tidak.
Mempertimbangkan itu, IMO tujuan utama dari mekanisme kesalahan harus:
- Buat terlihat dalam kode di mana beberapa kasus tertentu tidak dikelola.
- Komunikasikan masalah runtime ke kode terkait (setidaknya penelepon) ketika situasi ini terjadi.
- Menyediakan mekanisme pemulihan
Kelemahan utama dari exception
semantik sebagai mekanisme penanganan kesalahan adalah IMO: mudah untuk melihat di mana a throw
berada dalam kode sumber, tetapi sama sekali tidak jelas untuk mengetahui apakah fungsi tertentu dapat melempar dengan melihat pada deklarasi. Ini membawa semua masalah yang saya perkenalkan di atas.
Bahasa tidak menegakkan dan memeriksa kode kesalahan seketat yang dibuat untuk aspek lain dari bahasa (misalnya jenis variabel yang kuat)
Mencoba solusi
Untuk memperbaiki hal ini, saya mengembangkan sistem penanganan kesalahan yang sangat sederhana, yang mencoba menempatkan penanganan kesalahan pada tingkat kepentingan yang sama dengan kode normal.
Idenya adalah:
- Setiap fungsi (yang relevan) menerima referensi ke objek yang
success
sangat ringan, dan dapat mengaturnya ke status kesalahan jika terjadi. Objek sangat ringan sampai kesalahan dengan teks disimpan. - Suatu fungsi didorong untuk melewati tugasnya jika objek yang disediakan sudah mengandung kesalahan.
- Kesalahan tidak boleh ditimpa.
Desain lengkap jelas mempertimbangkan dengan seksama setiap aspek (sekitar 10 halaman), juga bagaimana menerapkannya pada OOP.
Contoh Success
kelas:
class Success
{
public:
enum SuccessStatus
{
ok = 0, // All is fine
error = 1, // Any error has been reached
uninitialized = 2, // Initialization is required
finished = 3, // This object already performed its task and is not useful anymore
unimplemented = 4, // This feature is not implemented already
};
Success(){}
Success( const Success& v);
virtual ~Success() = default;
virtual Success& operator= (const Success& v);
// Comparators
virtual bool operator==( const Success& s)const { return (this->status==s.status && this->stateStr==s.stateStr);}
virtual bool operator!=( const Success& s)const { return (this->status!=s.status || this->stateStr==s.stateStr);}
// Retrieve if the status is not "ok"
virtual bool operator!() const { return status!=ok;}
// Retrieve if the status is "ok"
operator bool() const { return status==ok;}
// Set a new status
virtual Success& set( SuccessStatus status, std::string msg="");
virtual void reset();
virtual std::string toString() const{ return stateStr;}
virtual SuccessStatus getStatus() const { return status; }
virtual operator SuccessStatus() const { return status; }
private:
std::string stateStr;
SuccessStatus status = Success::ok;
};
Pemakaian:
double mySqrt( Success& s, double v)
{
double result = 0.0;
if (!s) ; // do nothing
else if (v<0.0) s.set(Error, "Square root require non-negative input.");
else result = std::sqrt(v);
return result;
}
Success s;
mySqrt(s, 144.0);
otherStuff(s);
saveStuff(s);
if (s) /*All is good*/;
else cout << s << endl;
Saya menggunakan itu dalam banyak kode saya (sendiri) dan memaksa programmer (saya) untuk berpikir lebih jauh tentang kemungkinan kasus luar biasa dan bagaimana menyelesaikannya (baik). Namun, ia memiliki kurva belajar dan tidak terintegrasi dengan baik dengan kode yang sekarang menggunakannya.
Pertanyaan
Saya ingin lebih memahami implikasi penggunaan paradigma semacam itu dalam proyek:
- Apakah premis untuk masalah itu benar? atau Apakah saya melewatkan sesuatu yang relevan?
- Apakah solusinya ide arsitektur yang bagus? atau harganya terlalu tinggi?
EDIT:
Perbandingan antara metode:
//Exceptions:
// Incorrect
File f = open("text.txt"); // Could throw but nothing tell it! Will crash
save(f);
// Correct
File f;
try
{
f = open("text.txt");
save(f);
}
catch( ... )
{
// do something
}
//Error code (mixed):
// Incorrect
File f = open("text.txt"); //Nothing tell you it may fail! Will crash
save(f);
// Correct
File f = open("text.txt");
if (f) save(f);
//Error code (pure);
// Incorrect
File f;
open(f, "text.txt"); //Easy to forget the return value! will crash
save(f);
//Correct
File f;
Error er = open(f, "text.txt");
if (!er) save(f);
//Success mechanism:
Success s;
File f;
open(s, "text.txt");
save(s, f); //s cannot be avoided, will never crash.
if (s) ... //optional. If you created s, you probably don't forget it.