Saya bertemu dengan Tanya Jawab ini beberapa kali, dan ingin berkontribusi jawaban yang lebih komprehensif. Saya pikir cara terbaik untuk berpikir tentang ini adalah bagaimana mengembalikan kesalahan kepada penelepon, dan apa yang Anda kembali.
Bagaimana
Ada 3 cara untuk mengembalikan informasi dari suatu fungsi:
- Nilai Pengembalian
- Argumen Keluar
- Out of Band, yang mencakup goto non-lokal (setjmp / longjmp), file atau variabel cakupan global, sistem file, dll.
Nilai Pengembalian
Anda hanya dapat mengembalikan nilai sebagai objek tunggal, namun, itu bisa berupa kompleks arbitrer. Berikut adalah contoh fungsi pengembalian kesalahan:
enum error hold_my_beer();
Salah satu manfaat dari nilai-nilai pengembalian adalah memungkinkan chaining panggilan untuk penanganan kesalahan yang tidak terlalu mengganggu:
!hold_my_beer() &&
!hold_my_cigarette() &&
!hold_my_pants() ||
abort();
Ini bukan hanya tentang keterbacaan, tetapi juga memungkinkan pemrosesan array dari pointer fungsi tersebut dengan cara yang seragam.
Argumen Keluar
Anda dapat mengembalikan lebih dari satu objek melalui argumen, tetapi praktik terbaik menyarankan agar jumlah argumen tetap rendah (katakanlah, <= 4):
void look_ma(enum error *e, char *what_broke);
enum error e;
look_ma(e);
if(e == FURNITURE) {
reorder(what_broke);
} else if(e == SELF) {
tell_doctor(what_broke);
}
Keluar dari Band
Dengan setjmp () Anda menentukan tempat dan bagaimana Anda ingin menangani nilai int, dan Anda mentransfer kontrol ke lokasi itu melalui longjmp (). Lihat penggunaan praktis setjmp dan longjmp di C .
Apa
- Indikator
- Kode
- Obyek
- Telepon balik
Indikator
Indikator kesalahan hanya memberitahu Anda bahwa ada masalah tetapi tidak ada tentang sifat dari masalah tersebut:
struct foo *f = foo_init();
if(!f) {
/// handle the absence of foo
}
Ini adalah cara yang paling tidak ampuh untuk fungsi untuk mengkomunikasikan keadaan kesalahan, bagaimanapun, sempurna jika penelepon tidak dapat menanggapi kesalahan dengan cara apa pun.
Kode
Kode kesalahan memberi tahu penelepon tentang sifat masalah, dan memungkinkan respons yang sesuai (dari atas). Ini bisa berupa nilai balik, atau seperti contoh look_ma () di atas argumen kesalahan.
Obyek
Dengan objek kesalahan, penelepon dapat diberitahu tentang masalah rumit yang sewenang-wenang. Misalnya, kode kesalahan dan pesan yang dapat dibaca manusia yang sesuai. Ini juga dapat memberi tahu penelepon bahwa banyak hal yang salah, atau kesalahan per item saat memproses koleksi:
struct collection friends;
enum error *e = malloc(c.size * sizeof(enum error));
...
ask_for_favor(friends, reason);
for(int i = 0; i < c.size; i++) {
if(reason[i] == NOT_FOUND) find(friends[i]);
}
Alih-alih pra-alokasi array kesalahan, Anda juga dapat (kembali) mengalokasikannya secara dinamis sesuai kebutuhan tentu saja.
Telepon balik
Callback adalah cara paling ampuh untuk menangani kesalahan, karena Anda bisa memberi tahu fungsi itu perilaku apa yang ingin Anda lihat terjadi ketika terjadi kesalahan. Argumen panggilan balik dapat ditambahkan ke setiap fungsi, atau jika kustomisasi uis hanya diperlukan per instance dari struct seperti ini:
struct foo {
...
void (error_handler)(char *);
};
void default_error_handler(char *message) {
assert(f);
printf("%s", message);
}
void foo_set_error_handler(struct foo *f, void (*eh)(char *)) {
assert(f);
f->error_handler = eh;
}
struct foo *foo_init() {
struct foo *f = malloc(sizeof(struct foo));
foo_set_error_handler(f, default_error_handler);
return f;
}
struct foo *f = foo_init();
foo_something();
Salah satu manfaat menarik dari callback adalah dapat dipanggil beberapa kali, atau tidak ada sama sekali karena tidak ada kesalahan di mana tidak ada overhead di jalur bahagia.
Namun, ada inversi kontrol. Kode panggilan tidak tahu apakah panggilan balik itu dipanggil. Karena itu, masuk akal juga menggunakan indikator.