Saya terkejut tidak ada yang menyarankan alternatif ini, jadi meskipun pertanyaannya sudah ada beberapa saat saya akan menambahkannya: satu cara yang baik untuk mengatasi masalah ini adalah dengan menggunakan variabel untuk melacak keadaan saat ini. Ini adalah teknik yang dapat digunakan baik goto
digunakan untuk sampai pada kode pembersihan atau tidak . Seperti teknik pengkodean apa pun, ini memiliki pro dan kontra, dan tidak akan cocok untuk setiap situasi, tetapi jika Anda memilih gaya, itu layak dipertimbangkan - terutama jika Anda ingin menghindari goto
tanpa berakhir dengan sangat bertingkat if
.
Ide dasarnya adalah, untuk setiap tindakan pembersihan yang mungkin perlu dilakukan, ada variabel yang nilainya dapat kita ketahui apakah pembersihan perlu dilakukan atau tidak.
Saya akan menunjukkan goto
versinya terlebih dahulu, karena lebih dekat dengan kode dalam pertanyaan awal.
int foo(int bar)
{
int return_value = 0;
int something_done = 0;
int stuff_inited = 0;
int stuff_prepared = 0;
if (do_something(bar)) {
something_done = 1;
} else {
goto cleanup;
}
if (init_stuff(bar)) {
stuff_inited = 1;
} else {
goto cleanup;
}
if (prepare_stuff(bar)) {
stufF_prepared = 1;
} else {
goto cleanup;
}
return_value = do_the_thing(bar);
cleanup:
if (stuff_prepared) {
unprepare_stuff();
}
if (stuff_inited) {
uninit_stuff();
}
if (something_done) {
undo_something();
}
return return_value;
}
Satu keuntungan dari hal ini dibandingkan beberapa teknik lainnya adalah, jika urutan fungsi inisialisasi diubah, pembersihan yang benar masih akan terjadi - misalnya, menggunakan switch
metode yang dijelaskan dalam jawaban lain, jika urutan inisialisasi berubah, maka switch
harus diedit dengan sangat hati-hati untuk menghindari upaya membersihkan sesuatu yang sebenarnya tidak diinisialisasi sejak awal.
Sekarang, beberapa orang mungkin berpendapat bahwa metode ini menambahkan banyak variabel tambahan - dan memang dalam kasus ini itu benar - tetapi dalam praktiknya seringkali variabel yang ada sudah melacak, atau dapat dibuat untuk melacak, status yang diperlukan. Misalnya, jika prepare_stuff()
sebenarnya adalah panggilan ke malloc()
, atau ke open()
, variabel yang menahan pointer atau deskriptor file yang dikembalikan dapat digunakan - misalnya:
int fd = -1;
....
fd = open(...);
if (fd == -1) {
goto cleanup;
}
...
cleanup:
if (fd != -1) {
close(fd);
}
Sekarang, jika kami juga melacak status kesalahan dengan variabel, kami dapat menghindari goto
sepenuhnya, dan masih membersihkan dengan benar, tanpa memiliki lekukan yang semakin dalam dan semakin dalam semakin banyak inisialisasi yang kami butuhkan:
int foo(int bar)
{
int return_value = 0;
int something_done = 0;
int stuff_inited = 0;
int stuff_prepared = 0;
int oksofar = 1;
if (oksofar) {
if (do_something(bar)) {
something_done = 1;
} else {
oksofar = 0;
}
}
if (oksofar) {
if (init_stuff(bar)) {
stuff_inited = 1;
} else {
oksofar = 0;
}
}
if (oksofar) {
if (prepare_stuff(bar)) {
stuff_prepared = 1;
} else {
oksofar = 0;
}
}
if (oksofar) {
return_value = do_the_thing(bar);
}
if (stuff_prepared) {
unprepare_stuff();
}
if (stuff_inited) {
uninit_stuff();
}
if (something_done) {
undo_something();
}
return return_value;
}
Sekali lagi, ada kritik potensial untuk ini:
- Bukankah semua itu "jika" merugikan kinerja? Tidak - karena dalam kasus sukses, Anda tetap harus melakukan semua pemeriksaan (jika tidak, Anda tidak memeriksa semua kasus kesalahan); dan dalam kasus kegagalan, sebagian besar kompiler akan mengoptimalkan urutan
if (oksofar)
pemeriksaan yang gagal ke satu lompatan ke kode pembersihan (tentu saja GCC melakukannya) - dan dalam kasus apa pun, kasus kesalahan biasanya kurang penting untuk kinerja.
Bukankah ini menambahkan variabel lain? Dalam hal ini ya, tetapi seringkali return_value
variabel dapat digunakan untuk memainkan peran yang oksofar
dimainkan di sini. Jika Anda menyusun fungsi Anda untuk mengembalikan kesalahan secara konsisten, Anda bahkan dapat menghindari kesalahan kedua if
dalam setiap kasus:
int return_value = 0;
if (!return_value) {
return_value = do_something(bar);
}
if (!return_value) {
return_value = init_stuff(bar);
}
if (!return_value) {
return_value = prepare_stuff(bar);
}
Salah satu keuntungan dari pengkodean seperti itu adalah bahwa konsistensi berarti bahwa setiap tempat di mana pemrogram asli lupa untuk memeriksa nilai yang dikembalikan tampak seperti jempol yang sakit, membuatnya lebih mudah untuk menemukan (satu kelas dari) bug.
Jadi - ini (masih) satu gaya lagi yang dapat digunakan untuk menyelesaikan masalah ini. Jika digunakan dengan benar, ini memungkinkan kode yang sangat bersih dan konsisten - dan seperti teknik apa pun, di tangan yang salah dapat menghasilkan kode yang bertele-tele dan membingungkan :-)