Ketika aliran kode seperti ini:
if(check())
{
...
...
if(check())
{
...
...
if(check())
{
...
...
}
}
}
Saya biasanya melihat pekerjaan ini untuk menghindari aliran kode yang berantakan di atas:
do {
if(!check()) break;
...
...
if(!check()) break;
...
...
if(!check()) break;
...
...
} while(0);
Apa sajakah cara yang lebih baik yang menghindari ini bekerja-sekitar / hack sehingga menjadi kode tingkat yang lebih tinggi (tingkat industri)?
Setiap saran yang ada di luar kotak dipersilahkan!
goto
- tapi saya yakin seseorang akan menandai saya karena menyarankan itu, jadi saya tidak menulis jawaban untuk efek itu. Memiliki PANJANGdo ... while(0);
sepertinya adalah hal yang salah.goto
, jujurlah tentang hal itu dan lakukan di tempat terbuka, jangan sembunyikan dengan menggunakanbreak
dando ... while
goto
upaya rehabilitasi Anda . :)Jawaban:
Ini dianggap praktik yang dapat diterima untuk mengisolasi keputusan ini dalam suatu fungsi dan menggunakan
return
s bukannyabreak
s. Sementara semua pemeriksaan ini sesuai dengan tingkat abstraksi yang sama dengan fungsi, itu adalah pendekatan yang cukup logis.Sebagai contoh:
sumber
return
lebih bersih karena pembaca mana pun segera menyadari bahwa itu berfungsi dengan benar dan apa fungsinya. Dengangoto
Anda harus melihat sekeliling untuk melihat apa itu untuk, dan untuk memastikan bahwa tidak ada kesalahan yang dilakukan. Penyamaran adalah manfaatnya.forceinline
.Ada saat-saat ketika menggunakan
goto
sebenarnya jawaban yang BENAR - setidaknya bagi mereka yang tidak dibesarkan dalam kepercayaan agama bahwa "goto
tidak akan pernah bisa menjadi jawaban, tidak peduli apa pertanyaannya" - dan ini adalah salah satu kasus.Kode ini menggunakan retasan
do { ... } while(0);
untuk tujuan berdandangoto
abreak
. Jika Anda akan menggunakangoto
, maka terbuka tentang hal itu. Tidak ada gunanya membuat kode LEBIH KERAS untuk dibaca.Situasi tertentu hanya ketika Anda memiliki banyak kode dengan kondisi yang cukup kompleks:
Ini benar-benar dapat membuatnya lebih jelas bahwa kode itu benar dengan tidak memiliki logika yang rumit.
Sunting: Untuk memperjelas, saya sama sekali tidak mengusulkan penggunaan
goto
sebagai solusi umum. Tetapi ada beberapa kasus di managoto
solusi yang lebih baik daripada solusi lainnya.Bayangkan misalnya kita sedang mengumpulkan beberapa data, dan kondisi berbeda yang sedang diuji adalah semacam "ini adalah akhir dari data yang dikumpulkan" - yang tergantung pada semacam penanda "lanjut / akhir" yang bervariasi tergantung di mana Anda berada di aliran data.
Sekarang, setelah selesai, kita perlu menyimpan data ke file.
Dan ya, sering ada solusi lain yang dapat memberikan solusi yang masuk akal, tetapi tidak selalu.
sumber
goto
mungkin punya tempat, tetapigoto cleanup
tidak. Pembersihan dilakukan dengan RAII.goto
tidak pernah menjadi jawaban yang tepat. Saya akan menghargai jika ada komentar ...goto
karena Anda harus berpikir untuk menggunakannya / untuk memahami suatu program yang menggunakannya ... Mikroprosesor, di sisi lain, dibangunjumps
danconditional jumps
... jadi masalahnya ada pada beberapa orang, bukan dengan logika atau hal lain.goto
. RAII bukanlah solusi yang tepat jika kesalahan diharapkan (tidak luar biasa), karena itu akan menjadi penyalahgunaan pengecualian.Anda dapat menggunakan pola kelanjutan sederhana dengan
bool
variabel:Rantai eksekusi ini akan berhenti segera setelah
checkN
pengembalian afalse
. Tidak adacheck...()
panggilan lebih lanjut yang akan dilakukan karena hubungan arus pendek dari&&
operator. Selain itu, mengoptimalkan kompiler yang cukup pintar untuk mengenali pengaturan yanggoOn
kefalse
adalah jalan satu arah, dan masukkan hilanggoto end
untuk Anda. Akibatnya, kinerja kode di atas akan identik dengan ado
/while(0)
, hanya tanpa pukulan menyakitkan untuk keterbacaannya.sumber
if
kondisi terlihat sangat mencurigakan.if
tidak peduli apagoOn
yang bahkan jika yang awal gagal (sebagai lawan melompat / pecah) ... tapi saya baru saja menjalankan tes dan VS2012 setidaknya cukup pintar untuk melakukan hubungan arus pendek setelah kesalahan pertama. Saya akan menggunakan ini lebih sering. Catatan: jika Anda menggunakangoOn &= checkN()
makacheckN()
akan selalu berjalan bahkan jikagoOn
itufalse
di awalif
(yaitu, jangan lakukan itu).if
s.Cobalah untuk mengekstrak kode menjadi fungsi yang terpisah (atau mungkin lebih dari satu). Kemudian kembali dari fungsi jika pemeriksaan gagal.
Jika terlalu erat dengan kode di sekitarnya untuk melakukan itu, dan Anda tidak dapat menemukan cara untuk mengurangi kopling, lihat kode setelah blok ini. Agaknya, itu membersihkan beberapa sumber daya yang digunakan oleh fungsi. Cobalah untuk mengelola sumber daya ini menggunakan objek RAII ; kemudian ganti setiap dodgy
break
denganreturn
(atauthrow
, jika itu lebih tepat) dan biarkan destructor objek bersih untuk Anda.Jika aliran program (harus) begitu berlekuk sehingga Anda benar-benar membutuhkannya
goto
, maka gunakan itu alih-alih memberikan penyamaran yang aneh.Jika Anda memiliki aturan pengkodean yang dilarang secara membabi buta
goto
, dan Anda benar-benar tidak dapat menyederhanakan aliran program, maka Anda mungkin harus menyamarkannya dengando
hack.sumber
goto
jika itu benar-benar pilihan yang lebih baik.TLDR : RAII , kode transaksional (hanya mengatur hasil atau mengembalikan barang saat sudah dihitung) dan pengecualian.
Jawaban panjang:
Dalam C , praktik terbaik untuk kode semacam ini adalah dengan menambahkan label EXIT / CLEANUP / lainnya dalam kode, tempat pembersihan sumber daya lokal terjadi dan kode kesalahan (jika ada) dikembalikan. Ini adalah praktik terbaik karena memecah kode secara alami menjadi inisialisasi, perhitungan, komit dan kembali:
Dalam C, di sebagian codebases, yang
if(error_ok != ...
dangoto
kode biasanya tersembunyi di balik beberapa macro kenyamanan (RET(computation_result)
,ENSURE_SUCCESS(computation_result, return_code)
, dll).C ++ menawarkan alat tambahan lebih dari C :
Fungsi blok pembersihan dapat diimplementasikan sebagai RAII, yang berarti Anda tidak lagi memerlukan seluruh
cleanup
blok dan memungkinkan kode klien untuk menambahkan pernyataan pengembalian awal.Anda melempar kapan pun Anda tidak bisa melanjutkan, mengubah semua itu
if(error_ok != ...
menjadi panggilan langsung.Kode C ++ Setara:
Ini adalah praktik terbaik karena:
Itu eksplisit (yaitu, sementara penanganan kesalahan tidak eksplisit, aliran utama algoritma adalah)
Sangat mudah untuk menulis kode klien
Minimal
Sederhana saja
Ini tidak memiliki konstruksi kode berulang
Tidak menggunakan makro
Itu tidak menggunakan
do { ... } while(0)
konstruksi anehIni dapat digunakan kembali dengan sedikit usaha (yaitu, jika saya ingin menyalin panggilan ke
computation2();
fungsi yang berbeda, saya tidak harus memastikan saya menambahkando { ... } while(0)
kode baru, atau#define
makro pembungkus goto dan label pembersihan, atau ada yang lain).sumber
shared_ptr
dengan custom deleter dapat melakukan banyak hal. Lebih mudah dengan lambdas di C ++ 11.namespace xyz { typedef shared_ptr<some_handle> shared_handle; shared_handle make_shared_handle(a, b, c); };
Dalam hal ini (denganmake_handle
menetapkan tipe deleter yang benar pada konstruksi), nama tipe tidak lagi menunjukkan itu adalah pointer .Saya menambahkan jawaban demi kelengkapan. Sejumlah jawaban lain menunjukkan bahwa blok kondisi besar dapat dipecah menjadi fungsi yang terpisah. Tetapi seperti yang telah ditunjukkan beberapa kali adalah bahwa pendekatan ini memisahkan kode kondisional dari konteks aslinya. Ini adalah salah satu alasan mengapa lambdas ditambahkan ke bahasa dalam C ++ 11. Menggunakan lambdas disarankan oleh orang lain tetapi tidak ada sampel eksplisit yang diberikan. Saya telah memasukkan satu dalam jawaban ini. Apa yang mengejutkan saya adalah bahwa itu terasa sangat mirip dengan
do { } while(0)
pendekatan dalam banyak hal - dan mungkin itu berarti masihgoto
menyamar ....sumber
Tentu saja tidak dengan jawaban, tapi jawaban (demi kelengkapan)
Dari pada :
Anda bisa menulis:
Ini masih merupakan kebohongan yang terselubung, tetapi setidaknya ini bukan loop lagi. Yang berarti Anda tidak perlu hati-hati memeriksa tidak ada beberapa yang terus disembunyikan di suatu tempat di blok.
Konstruksinya juga cukup sederhana sehingga Anda bisa berharap kompiler akan mengoptimalkannya.
Seperti yang disarankan oleh @jamesdlin, Anda bahkan dapat menyembunyikan itu di belakang seperti makro
Dan gunakan itu seperti
Ini dimungkinkan karena sintaks bahasa C mengharapkan pernyataan setelah beralih, bukan blok yang ditandai dan Anda dapat menempatkan label kasus sebelum pernyataan itu. Sampai sekarang saya tidak melihat gunanya membiarkan itu, tetapi dalam kasus khusus ini berguna untuk menyembunyikan saklar di balik makro yang bagus.
sumber
define BLOCK switch (0) case 0:
dan menggunakannya sukaBLOCK { ... break; }
.Saya akan merekomendasikan pendekatan yang mirip dengan jawaban Mats dikurangi yang tidak perlu
goto
. Hanya menempatkan logika kondisional dalam fungsi. Kode apa pun yang selalu berjalan harus masuk sebelum atau setelah fungsi dipanggil dalam pemanggil:sumber
func
, Anda perlu faktor-out fungsi lain (per pola Anda). Jika semua fungsi yang terisolasi ini membutuhkan data yang sama, Anda hanya akan menyalin argumen tumpukan yang sama berulang-ulang, atau memutuskan untuk mengalokasikan argumen Anda di heap dan meneruskan sebuah pointer di sekitar, tidak memanfaatkan fitur bahasa yang paling dasar ( argumen fungsi). Singkatnya, saya tidak percaya solusi ini menskala untuk kasus terburuk di mana setiap kondisi diperiksa setelah mendapatkan sumber daya baru yang harus dibersihkan. Perhatikan komentar Nawaz tentang lambdas.func()
dan membiarkan destruktornya menangani sumber daya yang bebas? Jika ada yang di luar darifunc()
kebutuhan akses ke sumber daya yang sama itu harus dinyatakan pada heap sebelum memanggilfunc()
oleh manajer sumber daya yang tepat.Aliran kode itu sendiri sudah merupakan bau kode yang banyak terjadi dalam fungsi. Jika tidak ada solusi langsung untuk itu (fungsinya adalah fungsi pemeriksaan umum), maka gunakan RAII sehingga Anda dapat kembali alih-alih melompat ke bagian akhir fungsi mungkin lebih baik.
sumber
Jika Anda tidak perlu memperkenalkan variabel lokal selama eksekusi maka Anda dapat sering meratakan ini:
sumber
Mirip dengan jawaban dasblinkenlight, tetapi menghindari tugas di dalam
if
yang bisa diperbaiki oleh pengkaji kode:...
Saya menggunakan pola ini ketika hasil suatu langkah perlu diperiksa sebelum langkah berikutnya, yang berbeda dari situasi di mana semua pemeriksaan dapat dilakukan di depan dengan
if( check1() && check2()...
pola tipe besar .sumber
Gunakan pengecualian. Kode Anda akan terlihat jauh lebih bersih (dan pengecualian dibuat untuk menangani kesalahan dalam alur eksekusi suatu program). Untuk membersihkan sumber daya (deskriptor file, koneksi database, dll,), baca artikel Mengapa C ++ tidak menyediakan konstruksi "akhirnya"? .
sumber
check()
kondisi gagal, dan itu adalah asumsi ANDA, tidak ada konteks dalam sampel. Dengan asumsi bahwa program tidak dapat dilanjutkan, menggunakan pengecualian adalah cara yang harus dilakukan.Bagi saya
do{...}while(0)
itu baik-baik saja. Jika Anda tidak ingin melihatnyado{...}while(0)
, Anda dapat menentukan kata kunci alternatif untuk mereka.Contoh:
Saya pikir kompiler akan menghapus
while(0)
kondisi yang tidak perlu di Windowsdo{...}while(0)
dalam versi biner dan mengubah istirahat menjadi lompatan tanpa syarat. Anda dapat memastikan versi bahasa assemblynya untuk memastikan.Menggunakan
goto
juga menghasilkan kode yang lebih bersih dan langsung dengan logika kondisi-kemudian-lompat. Anda dapat melakukan hal berikut:Perhatikan label ditempatkan setelah penutupan
}
. Ini adalah salah satu masalah yang mungkin terjadigoto
karena secara tidak sengaja menempatkan kode di antaranya karena Anda tidak melihat label. Sekarang sepertido{...}while(0)
tanpa kode kondisi.Untuk membuat kode ini lebih bersih dan lebih mudah dipahami, Anda dapat melakukan ini:
Dengan ini, Anda dapat melakukan blok bersarang dan menentukan di mana Anda ingin keluar / melompat.
Kode spaghetti bukan kesalahan
goto
, itu kesalahan programmer. Anda masih dapat menghasilkan kode spageti tanpa menggunakangoto
.sumber
goto
lebih dari memperpanjang sintaks bahasa menggunakan preprocessor satu juta kali lipat.Ini adalah masalah yang terkenal dan diselesaikan dari perspektif pemrograman fungsional - mungkin monad.
Menanggapi komentar yang saya terima di bawah ini, saya telah mengedit pengantar saya di sini: Anda dapat menemukan detail lengkap tentang penerapan C ++ monads di berbagai tempat yang memungkinkan Anda mencapai apa yang disarankan Rotsor. Butuh beberapa saat untuk grok monads jadi alih-alih saya akan menyarankan di sini mekanisme cepat "orang miskin" yang mirip monad yang perlu Anda ketahui tentang tidak lebih dari boost :: opsional.
Siapkan langkah perhitungan Anda sebagai berikut:
Setiap langkah komputasi jelas dapat melakukan sesuatu seperti mengembalikan
boost::none
jika opsi yang diberikannya kosong. Jadi misalnya:Kemudian rangkai mereka:
Yang menyenangkan tentang ini adalah bahwa Anda dapat menulis unit test yang terdefinisi dengan jelas untuk setiap langkah komputasi. Doa juga berbunyi seperti bahasa Inggris biasa (seperti biasanya dengan gaya fungsional).
Jika Anda tidak peduli tentang kekekalan dan lebih mudah untuk mengembalikan objek yang sama setiap kali Anda dapat membuat beberapa variasi menggunakan shared_ptr atau sejenisnya.
sumber
optional<EnabledContext> enabled(Context); optional<EnergisedContext> energised(EnabledContext);
dan menggunakan operasi komposisi monadik ('bind') daripada aplikasi fungsi.Bagaimana dengan memindahkan pernyataan if ke fungsi ekstra yang menghasilkan hasil numerik atau enum?
sumber
Sesuatu seperti ini mungkin
atau gunakan pengecualian
Menggunakan pengecualian, Anda juga dapat meneruskan data.
sumber
ever
akan sangat tidak bahagia ...Saya tidak terlalu suka menggunakan
break
ataureturn
dalam kasus seperti itu. Mengingat bahwa biasanya ketika kita menghadapi situasi seperti itu, itu biasanya merupakan metode yang relatif panjang.Jika kita memiliki beberapa titik keluar, itu dapat menyebabkan kesulitan ketika kita ingin tahu apa yang akan menyebabkan logika tertentu untuk dieksekusi: Biasanya kita terus naik blok melampirkan potongan logika itu, dan kriteria dari blok penutup memberitahu kita situasi:
Sebagai contoh,
Dengan melihat melampirkan blok, mudah untuk mengetahui bahwa
myLogic()
hanya terjadi kapanconditionA and conditionB and conditionC
itu benar.Itu menjadi jauh kurang terlihat ketika ada pengembalian awal:
Kami tidak dapat lagi menavigasi dari
myLogic()
, melihat melampirkan blok untuk mengetahui kondisi.Ada beberapa solusi yang saya gunakan. Ini salah satunya:
(Tentu saja disambut menggunakan variabel yang sama untuk menggantikan semua
isA isB isC
.)Pendekatan semacam itu setidaknya akan memberikan pembaca kode, yang
myLogic()
dieksekusi ketikaisB && conditionC
. Pembaca diberi petunjuk bahwa ia perlu mencari lebih jauh apa yang menyebabkan isB menjadi benar.sumber
Bagaimana tentang itu?
sumber
return condition;
, kalau tidak saya pikir ini bisa dipertahankan.Pola lain yang berguna jika Anda memerlukan langkah-langkah pembersihan berbeda tergantung di mana kegagalannya:
sumber
Saya bukan programmer C ++ , jadi saya tidak akan menulis kode apa pun di sini, tapi sejauh ini tidak ada yang menyebutkan solusi berorientasi objek. Jadi, inilah tebakan saya tentang itu:
Memiliki antarmuka generik yang menyediakan metode untuk mengevaluasi satu kondisi. Sekarang Anda dapat menggunakan daftar implementasi dari kondisi-kondisi di objek Anda yang berisi metode yang dimaksud. Anda mengulangi daftar dan mengevaluasi setiap kondisi, mungkin keluar awal jika salah satu gagal.
Yang baik adalah bahwa desain seperti itu menempel dengan sangat baik pada prinsip terbuka / tertutup , karena Anda dapat dengan mudah menambahkan kondisi baru selama inisialisasi objek yang berisi metode yang dimaksud. Anda bahkan dapat menambahkan metode kedua ke antarmuka dengan metode untuk evaluasi kondisi mengembalikan deskripsi kondisi. Ini dapat digunakan untuk sistem dokumentasi diri.
The downside, bagaimanapun, adalah bahwa ada sedikit lebih banyak overhead yang terlibat karena penggunaan lebih banyak objek dan iterasi di atas daftar.
sumber
Ini adalah cara saya melakukannya.
sumber
Pertama, contoh singkat untuk menunjukkan mengapa
goto
ini bukan solusi yang baik untuk C ++:Cobalah untuk mengkompilasi ini menjadi file objek dan lihat apa yang terjadi. Lalu coba yang setara
do
+break
+while(0)
.Itu adalah samping. Poin utama berikut.
Mereka potongan kecil kode sering membutuhkan beberapa jenis pembersihan harus seluruh fungsi gagal. Pembersihan itu biasanya ingin terjadi dalam urutan yang berlawanan dari potongan itu sendiri, ketika Anda "melepaskan" perhitungan yang sebagian selesai.
Salah satu opsi untuk mendapatkan semantik ini adalah RAII ; lihat jawaban @ utnapistim. C ++ menjamin bahwa destruktor otomatis berjalan dalam urutan yang berlawanan dengan konstruktor, yang secara alami memasok "pelepasan".
Tapi itu membutuhkan banyak kelas RAII. Terkadang opsi yang lebih sederhana adalah dengan menggunakan stack:
...dan seterusnya. Ini mudah untuk diaudit, karena menempatkan kode "undo" di sebelah kode "do". Audit mudah itu bagus. Itu juga membuat aliran kontrol sangat jelas. Ini juga merupakan pola yang berguna untuk C.
Mungkin diperlukan
calc
fungsi untuk mengambil banyak argumen, tetapi itu biasanya tidak masalah jika kelas / struct Anda memiliki kohesi yang baik. (Yaitu, barang-barang yang dimiliki bersama hidup dalam satu objek tunggal, sehingga fungsi-fungsi ini dapat mengambil pointer atau referensi ke sejumlah kecil objek dan masih melakukan banyak pekerjaan yang bermanfaat.)sumber
Jika kode Anda memiliki blok panjang if..else if..else pernyataan, Anda dapat mencoba dan menulis ulang seluruh blok dengan bantuan
Functors
ataufunction pointers
. Ini mungkin bukan solusi yang tepat selalu tetapi cukup sering.http://www.cprogramming.com/tutorial/functors-function-objects-in-c++.html
sumber
Saya kagum dengan banyaknya jawaban berbeda yang disajikan di sini. Tapi, akhirnya dalam kode yang harus saya ubah (yaitu menghapus
do-while(0)
hack ini atau apa pun), saya melakukan sesuatu yang berbeda dari jawaban yang disebutkan di sini dan saya bingung mengapa tidak ada yang memikirkan hal ini. Inilah yang saya lakukan:Kode awal:
Sekarang:
Jadi, apa yang telah dilakukan di sini adalah bahwa barang-barang finishing telah diisolasi dalam suatu fungsi dan hal-hal tiba-tiba menjadi sangat sederhana dan bersih!
Saya pikir solusi ini layak disebutkan, jadi berikan di sini.
sumber
Konsolidasikan ke dalam satu
if
pernyataan:Ini adalah pola yang digunakan dalam bahasa seperti Jawa tempat kata kunci goto dihapus.
sumber
true
. Evaluasi hubung singkat akan menjamin bahwa Anda tidak pernah menjalankan hal-hal di antaranya yang tidak lulus semua tes prasyarat.(/*do other stuff*/, /*next condition*/)
, Anda bahkan dapat memformatnya dengan baik. Hanya saja, jangan berharap orang menyukainya. Tapi jujur, ini hanya untuk menunjukkan bahwa itu adalah kesalahan bagi Jawa untuk menghapusgoto
pernyataan itu ...Jika menggunakan penangan kesalahan yang sama untuk semua kesalahan, dan setiap langkah mengembalikan bool yang menunjukkan keberhasilan:
(Mirip dengan jawaban tyzoid, tetapi kondisinya adalah tindakan, dan && mencegah tindakan tambahan terjadi setelah kegagalan pertama.)
sumber
Mengapa metode penandaan tidak menjawabnya digunakan sejak lama.
sumber