Saya benar-benar ragu untuk menanyakan hal ini, karena saya tidak ingin "meminta debat, argumen, jajak pendapat, atau diskusi panjang" tetapi saya baru mengenal C dan ingin mendapatkan lebih banyak wawasan tentang pola umum yang digunakan dalam bahasa.
Saya baru-baru ini mendengar beberapa kebencian untuk goto
perintah, tetapi saya juga baru-baru ini menemukan kasus penggunaan yang layak untuk itu.
Kode seperti ini:
error = function_that_could_fail_1();
if (!error) {
error = function_that_could_fail_2();
if (!error) {
error = function_that_could_fail_3();
...to the n-th tab level!
} else {
// deal with error, clean up, and return error code
}
} else {
// deal with error, clean up, and return error code
}
Jika bagian pembersihan semuanya sangat mirip, dapat ditulis sedikit lebih cantik (menurut saya?) Seperti ini:
error = function_that_could_fail_1();
if(error) {
goto cleanup;
}
error = function_that_could_fail_2();
if(error) {
goto cleanup;
}
error = function_that_could_fail_3();
if(error) {
goto cleanup;
}
...
cleanup:
// deal with error if it exists, clean up
// return error code
Apakah ini kasus penggunaan yang umum atau dapat diterima goto
di C? Apakah ada cara berbeda / lebih baik untuk melakukan ini?
c
design-patterns
goto
Robz
sumber
sumber
goto hell;
Jawaban:
The
goto
pernyataan (dan label yang sesuai) adalah kontrol aliran primitif (bersama dengan eksekusi bersyarat dari pernyataan). Maksud saya, mereka ada di sana untuk memungkinkan Anda membangun jaringan kendali aliran program. Anda dapat menganggap mereka sebagai pemodelan panah di antara simpul diagram alur.Beberapa di antaranya dapat dioptimalkan segera, di mana ada aliran linier langsung (Anda hanya menggunakan urutan pernyataan dasar). Pola lain yang terbaik diganti dengan konstruksi pemrograman terstruktur di mana ini tersedia; jika terlihat seperti
while
loop, gunakanwhile
loop , OK? Pola pemrograman terstruktur jelas setidaknya berpotensi lebih jelas niat daripada kekacauangoto
pernyataan.Namun C tidak mencakup semua kemungkinan konstruksi pemrograman terstruktur. (Tidak jelas bagi saya bahwa semua yang relevan telah ditemukan; tingkat penemuannya lambat sekarang, tapi saya ragu untuk mengatakan bahwa semua telah ditemukan.) Dari yang kita ketahui, C jelas tidak memiliki
try
/catch
/finally
struktur (dan pengecualian juga). Ini juga tidak memiliki multi-levelbreak
-dari-loop. Ini adalah hal-hal yanggoto
dapat digunakan untuk diterapkan. Ini mungkin untuk menggunakan skema lain untuk melakukan ini juga - kita tahu bahwa C memiliki cukup set nongoto
primitif - tetapi ini sering melibatkan pembuatan variabel flag dan kondisi loop atau guard yang jauh lebih kompleks; meningkatkan keterjeratan analisis kontrol dengan analisis data membuat program lebih sulit untuk dipahami secara keseluruhan. Ini juga membuat lebih sulit bagi kompiler untuk mengoptimalkan dan bagi CPU untuk mengeksekusi dengan cepat (sebagian besar konstruksi kontrol aliran - dan tentunyagoto
- sangat murah).Jadi, sementara Anda tidak boleh menggunakan
goto
kecuali diperlukan, Anda harus menyadari bahwa itu ada dan mungkin diperlukan, dan jika Anda membutuhkannya, Anda seharusnya tidak merasa terlalu buruk. Contoh kasus di mana diperlukan adalah alokasi sumber daya ketika fungsi yang dipanggil mengembalikan kondisi kesalahan. (Yaitu,try
/finally
.) Adalah mungkin untuk menulis itu tanpagoto
tetapi melakukan itu dapat memiliki kelemahan sendiri, seperti masalah mempertahankannya. Contoh kasus:Kode bisa lebih pendek, tetapi cukup untuk menunjukkan intinya.
sumber
try/catch/finally
untukgoto
terlepas dari mirip namun lebih luas (karena dapat menyebar ke seluruh beberapa fungsi / modul) berupa kode spaghetti itu mungkin menggunakan,try/catch/finally
?Iya.
Ini digunakan dalam, misalnya, kernel linux. Berikut ini email dari ujung utas dari hampir satu dekade lalu , cetak tebal milik saya:
Karena itu, diperlukan banyak disiplin untuk menjaga diri Anda dari membuat kode spaghetti setelah Anda terbiasa menggunakan goto, jadi kecuali Anda menulis sesuatu yang membutuhkan kecepatan dan jejak memori yang rendah (seperti kernel atau sistem embedded) Anda harus benar - benar pikirkan dulu sebelum Anda menulis goto pertama.
sumber
Menurut pendapat saya, kode yang Anda posting adalah contoh penggunaan yang valid
goto
, karena Anda hanya melompat ke bawah dan hanya menggunakannya seperti pengendali pengecualian primitif.Namun , karena perdebatan lama, para programmer telah menghindari
goto
selama 40 tahun dan karena itu mereka tidak terbiasa membaca kode dengan goto. Ini adalah alasan yang sah untuk menghindari kebotakan: itu bukan standar.Saya akan menulis ulang kode sebagai sesuatu yang lebih mudah dibaca oleh programmer C:
Keuntungan dari desain ini:
sumber
Sebuah makalah terkenal yang menggambarkan kasus penggunaan yang valid adalah Pemrograman Terstruktur dengan Pernyataan GOTO oleh Donald E. Knuth (Stanford University). Makalah ini muncul pada hari-hari di mana penggunaan GOTO dianggap dosa oleh sebagian orang dan ketika gerakan untuk Pemrograman Terstruktur adalah pada puncaknya. Anda mungkin ingin melihat GoTo Dianggap Berbahaya.
sumber
Di Jawa Anda akan melakukannya seperti ini:
Saya sering menggunakan ini. Seperti yang saya tidak suka
goto
, di sebagian besar bahasa C-style lainnya saya menggunakan kode Anda; tidak ada cara lain yang baik untuk melakukannya. (Melompat keluar dari loop bersarang adalah kasus serupa; di Jawa saya menggunakan labelbreak
dan di mana pun saya menggunakan agoto
.)sumber
Saya pikir ini adalah kasus penggunaan yang layak, tetapi dalam kasus "kesalahan" tidak lebih dari nilai boolean, ada cara berbeda untuk mencapai apa yang Anda inginkan:
Ini memanfaatkan evaluasi hubung singkat dari operator boolean. Jika ini "lebih baik", terserah selera pribadi Anda dan bagaimana Anda terbiasa dengan idiom itu.
sumber
error
nilai bisa menjadi tidak berarti dengan semua OR'ing.Panduan gaya linux memberikan alasan khusus untuk menggunakan
goto
yang sesuai dengan contoh Anda:https://www.kernel.org/doc/Documentation/process/coding-style.rst
Penafian saya tidak seharusnya membagikan pekerjaan saya. Contoh-contoh di sini sedikit dibuat-buat jadi tolong tolong bersamaku.
Ini bagus untuk manajemen memori. Saya baru-baru ini bekerja pada kode yang secara dinamis mengalokasikan memori (misalnya
char *
dikembalikan oleh suatu fungsi). Fungsi yang melihat jalur dan memastikan apakah jalur tersebut valid dengan menguraikan token jalur:Sekarang bagi saya, kode berikut ini jauh lebih baik dan lebih mudah dirawat jika Anda perlu menambahkan
varNplus1
:Sekarang kode memiliki segala macam masalah lain dengan itu, yaitu bahwa N berada di suatu tempat di atas 10, dan fungsinya lebih dari 450 baris, dengan 10 tingkat bersarang di beberapa tempat.
Tapi saya menawarkan penyelia saya untuk memperbaiki itu, yang saya lakukan dan sekarang banyak fungsi yang semuanya singkat, dan mereka semua memiliki gaya linux
Jika kami menganggap yang setara tanpa
goto
s:Bagi saya, dalam kasus pertama, jelas bagi saya bahwa jika fungsi pertama kembali
NULL
, kami keluar dari sini dan kami kembali0
. Dalam kasus kedua, saya harus gulir ke bawah untuk melihat bahwa if berisi seluruh fungsi. Memang yang pertama menunjukkan ini kepada saya dengan gaya (nama "out
") dan yang kedua melakukannya secara sintaksis. Yang pertama masih lebih jelas.Juga, saya lebih suka memiliki
free()
pernyataan di akhir fungsi. Itu sebagian karena, dalam pengalaman saya,free()
pernyataan di tengah fungsi berbau tidak enak dan menunjukkan kepada saya bahwa saya harus membuat subrutin. Dalam hal ini, saya membuatvar1
dalam fungsi saya dan tidak bisafree()
dalam subrutin, tapi itu sebabnyagoto out_free
, gaya goto out sangat praktis.Saya pikir pemrogram perlu dibesarkan dengan keyakinan bahwa
goto
itu jahat. Kemudian, ketika mereka sudah cukup dewasa, mereka harus menelusuri kode sumber Linux dan membaca panduan gaya linux.Saya harus menambahkan bahwa saya menggunakan gaya ini dengan sangat konsisten, setiap fungsi memiliki int
retval
,out_free
label dan label out. Karena konsistensi gaya, keterbacaan ditingkatkan.Bonus: Istirahat dan berlanjut
Katakanlah Anda memiliki loop sementara
Ada hal lain yang salah dengan kode ini, tetapi satu hal adalah pernyataan berlanjut. Saya ingin menulis ulang semuanya, tetapi saya ditugaskan untuk memodifikasinya dengan cara yang kecil. Butuh waktu berhari-hari untuk memperbaikinya dengan cara yang memuaskan saya, tetapi perubahan sebenarnya adalah pekerjaan setengah hari. Masalahnya adalah bahwa bahkan jika
continue
kita masih harus bebasvar1
danvar2
. Saya harus menambahkanvar3
, dan itu membuat saya ingin muntah harus mencerminkan pernyataan bebas ().Saya adalah pekerja magang yang relatif baru pada saat itu, tetapi saya telah melihat kode sumber linux untuk bersenang-senang beberapa waktu lalu, jadi saya bertanya kepada penyelia saya apakah saya bisa menggunakan pernyataan goto. Dia berkata ya, dan saya melakukan ini:
Saya pikir terus baik-baik saja tetapi bagi saya itu seperti goto dengan label yang tidak terlihat. Hal yang sama berlaku untuk istirahat. Saya masih lebih suka melanjutkan atau merusak kecuali, seperti yang terjadi di sini, itu memaksa Anda untuk mencerminkan modifikasi di banyak tempat.
Dan saya juga harus menambahkan bahwa penggunaan
goto next;
dannext:
label ini tidak memuaskan bagi saya. Mereka hanya lebih baik daripada mencerminkan pernyataanfree()
dancount++
pernyataan.goto
Hampir selalu salah, tetapi orang harus tahu kapan itu baik untuk digunakan.Satu hal yang tidak saya diskusikan adalah penanganan kesalahan yang telah dicakup oleh jawaban lain.
Performa
Seseorang dapat melihat implementasi strtok () http://opensource.apple.com//source/Libc/Libc-167/string.subproj/strtok.c
Harap perbaiki saya jika saya salah, tetapi saya percaya bahwa
cont:
label dangoto cont;
pernyataannya ada untuk kinerja (mereka pasti tidak membuat kode lebih mudah dibaca). Mereka dapat diganti dengan kode yang dapat dibaca dengan melakukanuntuk melewati pembatas. Tetapi untuk secepat mungkin dan menghindari pemanggilan fungsi yang tidak perlu, mereka melakukannya dengan cara ini.
Saya membaca koran oleh Dijkstra dan saya merasa cukup esoterik.
google "pernyataan goto dijkstra dianggap berbahaya" karena saya tidak memiliki reputasi yang cukup untuk memposting lebih dari 2 tautan.
Saya telah melihatnya dikutip sebagai alasan untuk tidak menggunakan goto dan membacanya tidak mengubah apa pun sejauh penggunaan goto saya disetujui.
Adendum :
Saya telah datang dengan aturan yang rapi sambil memikirkan semua ini tentang terus dan rusak.
Itu tidak selalu mungkin karena masalah ruang lingkup tetapi saya telah menemukan bahwa melakukan ini membuatnya lebih mudah untuk alasan tentang kode saya. Saya telah memperhatikan bahwa setiap kali loop sementara istirahat atau melanjutkan itu memberi saya perasaan buruk.
sumber
Secara pribadi saya akan refactor lebih seperti ini:
Itu akan lebih termotivasi dengan menghindari sarang yang dalam daripada menghindari kebotakan (IMO masalah yang lebih buruk dengan sampel kode pertama), dan tentu saja akan bergantung pada CleanupAfterError menjadi mungkin di luar ruang lingkup (dalam hal ini "params" bisa menjadi struct yang berisi beberapa memori yang dialokasikan yang harus Anda bebaskan, FILE * yang perlu Anda tutup atau apa pun).
Satu keuntungan utama yang saya lihat dengan pendekatan ini adalah lebih mudah dan lebih bersih untuk menempatkan langkah tambahan di masa depan yang hipotetis antara, katakanlah, FTCF2 dan FTCF3 (atau hapus langkah saat ini), sehingga lebih cocok untuk pemeliharaan (dan orang yang mewarisi kode saya tidak ingin lynch saya!) - kesampingkan, versi bersarang kurang itu.
sumber
Lihatlah panduan pengkodean C MISRA (Asosiasi Keandalan Perangkat Lunak Industri Motor) yang memungkinkan goto di bawah kriteria ketat (yang memenuhi contoh Anda)
Di mana saya bekerja, kode yang sama akan ditulis - tidak perlu kebersamaan - Menghindari perdebatan agama yang tidak perlu tentang mereka adalah nilai tambah yang besar di setiap rumah perangkat lunak.
atau untuk "goto in drag" - sesuatu yang bahkan lebih cerdik dari goto, tetapi berkeliling "No goto Ever !!!" camp) "Tentunya itu harus OK, tidak menggunakan Goto" ....
Jika fungsi memiliki tipe parameter yang sama, masukkan ke dalam tabel dan gunakan satu loop -
sumber
if(flag)
dalam satu salinan mengambil cabang "jika", dan meminta setiap pernyataan terkait dalam salinan lain mengambil " lain". Tindakan yang mengatur dan menghapus bendera benar-benar "goto" yang melompat di antara dua versi kode. Ada saat-saat ketika penggunaan bendera lebih bersih daripada alternatif lain, tetapi menambahkan bendera untuk menyimpan satugoto
target bukanlah pertukaran yang baik.Saya juga menggunakan
goto
jikado/while/continue/break
peretasan alternatif kurang dapat dibaca.goto
Ada keuntungan bahwa target mereka memiliki nama dan mereka membacagoto something;
. Ini mungkin lebih mudah dibaca daripadabreak
ataucontinue
jika Anda tidak benar-benar menghentikan atau melanjutkan sesuatu.sumber
do ... while(0)
atau konstruksi lain yang bukan merupakan loop aktual tetapi upaya yang salah untuk mencegah penggunaan agoto
.sumber
break
bekerja persis sepertigoto
, meskipun tanpa stigma.Akan selalu ada kamp yang mengatakan satu cara dapat diterima dan yang lain tidak. Perusahaan tempat saya bekerja telah mengernyit atau sangat tidak suka penggunaan goto. Secara pribadi, saya tidak dapat memikirkan kapan saya menggunakannya, tetapi itu tidak berarti mereka buruk , hanya cara lain dalam melakukan sesuatu.
Di C, saya biasanya melakukan hal berikut:
Untuk pemrosesan, menggunakan contoh goto Anda, saya akan melakukan ini:
error = function_that_could_fail_1 (); if (! error) {error = function_that_could_fail_2 (); } if (! error) {error = function_that_could_fail_3 (); }
Tidak ada sarang, dan di dalam klausa if, Anda dapat melakukan pelaporan kesalahan apa pun, jika langkah tersebut menghasilkan kesalahan. Jadi, itu tidak harus "lebih buruk" daripada metode menggunakan gotos.
Saya belum menemukan kasus di mana seseorang memiliki goto yang tidak dapat dilakukan dengan metode lain dan hanya bisa dibaca / dimengerti dan itulah kuncinya, IMHO.
sumber