Saya memiliki switch
struktur yang memiliki beberapa kasus untuk ditangani. Ini switch
beroperasi di atas enum
yang menimbulkan masalah kode duplikat melalui nilai gabungan:
// All possible combinations of One - Eight.
public enum ExampleEnum {
One,
Two, TwoOne,
Three, ThreeOne, ThreeTwo, ThreeOneTwo,
Four, FourOne, FourTwo, FourThree, FourOneTwo, FourOneThree,
FourTwoThree, FourOneTwoThree
// ETC.
}
Saat ini switch
struktur menangani setiap nilai secara terpisah:
// All possible combinations of One - Eight.
switch (enumValue) {
case One: DrawOne; break;
case Two: DrawTwo; break;
case TwoOne:
DrawOne;
DrawTwo;
break;
case Three: DrawThree; break;
...
}
Anda mendapatkan idenya di sana. Saat ini saya memecahnya menjadi if
struktur bertumpuk untuk menangani kombinasi dengan satu baris saja:
// All possible combinations of One - Eight.
if (One || TwoOne || ThreeOne || ThreeOneTwo)
DrawOne;
if (Two || TwoOne || ThreeTwo || ThreeOneTwo)
DrawTwo;
if (Three || ThreeOne || ThreeTwo || ThreeOneTwo)
DrawThree;
Ini menimbulkan masalah evaluasi logis yang sangat panjang yang membingungkan untuk dibaca dan sulit dipertahankan. Setelah mengulangi hal ini, saya mulai berpikir tentang alternatif dan memikirkan gagasan tentang switch
struktur dengan kejatuhan di antara kasus-kasus.
Saya harus menggunakan goto
dalam hal itu karena C#
tidak memungkinkan jatuh. Namun, itu mencegah rantai logika yang sangat panjang meskipun ia melompat di dalam switch
struktur, dan itu tetap membawa duplikasi kode.
switch (enumVal) {
case ThreeOneTwo: DrawThree; goto case TwoOne;
case ThreeTwo: DrawThree; goto case Two;
case ThreeOne: DrawThree; goto default;
case TwoOne: DrawTwo; goto default;
case Two: DrawTwo; break;
default: DrawOne; break;
}
Ini masih bukan solusi yang cukup bersih dan ada stigma yang terkait dengan goto
kata kunci yang ingin saya hindari. Saya yakin harus ada cara yang lebih baik untuk membersihkan ini.
Pertanyaan saya
Apakah ada cara yang lebih baik untuk menangani kasus khusus ini tanpa mempengaruhi keterbacaan dan pemeliharaan?
sumber
goto
ketika struktur tingkat tinggi tidak ada dalam bahasa Anda. Saya terkadang berharap adafallthru
; kata kunci untuk menyingkirkan penggunaan tertentugoto
tapi oh well.goto
bahasa tingkat tinggi seperti C #, maka Anda mungkin mengabaikan banyak alternatif desain dan / atau implementasi lainnya (dan lebih baik). Sangat berkecil hati.Jawaban:
Saya menemukan kode sulit dibaca dengan
goto
pernyataan. Saya akan merekomendasikan penataan yangenum
berbeda. Misalnya, jika Andaenum
adalah bitfield di mana setiap bit mewakili salah satu pilihan, itu bisa terlihat seperti ini:Atribut Flags memberi tahu kompiler bahwa Anda sedang menyiapkan nilai yang tidak tumpang tindih. Kode yang memanggil kode ini dapat mengatur bit yang sesuai. Anda kemudian dapat melakukan sesuatu seperti ini untuk memperjelas apa yang terjadi:
Ini membutuhkan kode yang mengatur
myEnum
untuk mengatur bitfields dengan benar dan ditandai dengan atribut Flags. Tapi Anda bisa melakukannya dengan mengubah nilai enum dalam contoh Anda menjadi:Saat Anda menulis angka dalam formulir
0bxxxx
, Anda menetapkannya dalam bentuk biner. Jadi Anda dapat melihat bahwa kami menetapkan bit 1, 2, atau 3 (well, secara teknis 0, 1, atau 2, tetapi Anda mendapatkan idenya). Anda juga dapat memberi nama kombinasi dengan menggunakan bitwise ATAU jika kombinasi mungkin sering disatukan.sumber
public enum ExampleEnum { One = 1 << 0, Two = 1 << 1, Three = 1 << 2, OneAndTwo = One | Two, OneAndThree = One | Three, TwoAndThree = Two | Three };
. Tidak perlu bersikeras C # 7 +.[Flags]
atribut tidak sinyal apa pun untuk compiler. Inilah sebabnya mengapa Anda masih harus secara eksplisit mendeklarasikan nilai enum sebagai kekuatan 2.HasFlag
adalah istilah deskriptif dan eksplisit untuk operasi yang dilakukan dan abstrak implementasi dari fungsi. Menggunakan&
karena umum di semua bahasa lain tidak masuk akal daripada menggunakanbyte
jenis alih-alih mendeklarasikanenum
.IMO akar masalahnya adalah potongan kode ini seharusnya tidak ada.
Anda tampaknya memiliki tiga kondisi independen, dan tiga tindakan independen yang harus diambil jika kondisi itu benar. Jadi mengapa semua yang disalurkan menjadi satu bagian kode yang membutuhkan tiga bendera Boolean untuk mengatakan apa yang harus dilakukan (apakah Anda mengaburkannya menjadi enum) dan kemudian melakukan kombinasi dari tiga hal independen? Prinsip tanggung jawab tunggal tampaknya memiliki hari libur di sini.
Masukkan panggilan ke tiga fungsi di mana milik (yaitu di mana Anda menemukan perlunya melakukan tindakan) dan mengirimkan kode dalam contoh ini ke tempat sampah.
Jika ada sepuluh flag dan tindakan bukan tiga, apakah Anda akan memperpanjang kode semacam ini untuk menangani 1024 kombinasi berbeda? Saya harap tidak! Jika 1024 terlalu banyak, 8 juga terlalu banyak, untuk alasan yang sama.
sumber
Tidak pernah menggunakan gotos adalah salah satu konsep "kebohongan bagi anak-anak" dari ilmu komputer. Ini adalah saran yang tepat 99% dari waktu, dan waktu tidak begitu jarang dan terspesialisasi bahwa itu jauh lebih baik untuk semua orang jika itu hanya dijelaskan kepada coders baru sebagai "jangan menggunakannya".
Jadi ketika harus mereka digunakan? Ada beberapa skenario , tetapi yang mendasar yang tampaknya Anda pukul adalah: ketika Anda mengkode mesin negara . Jika tidak ada ekspresi yang lebih terorganisir dan terstruktur dari algoritma Anda daripada mesin negara, maka ekspresi alami dalam kode melibatkan cabang yang tidak terstruktur, dan tidak ada banyak yang dapat dilakukan tentang apa yang tidak bisa dibilang membuat struktur nyatakan mesin itu sendiri lebih tidak jelas, daripada kurang.
Penulis kompiler mengetahui hal ini, itulah sebabnya mengapa kode sumber untuk sebagian besar kompiler yang mengimplementasikan parser LALR * berisi gotos. Namun, sangat sedikit orang yang akan benar-benar membuat kode analisis parser dan parser sendiri.
* - IIRC, dimungkinkan untuk menerapkan tata bahasa LALL yang sepenuhnya bersifat rekursif tanpa menggunakan tabel lompatan atau pernyataan kontrol tidak terstruktur lainnya, jadi jika Anda benar-benar anti-goto, ini adalah salah satu jalan keluar.
Sekarang pertanyaan selanjutnya adalah, "Apakah ini contoh salah satu dari kasus itu?"
Apa yang saya lihat ketika melihatnya adalah bahwa Anda memiliki tiga kemungkinan kondisi berikutnya tergantung pada pemrosesan kondisi saat ini. Karena salah satu dari mereka ("default") hanyalah sebaris kode, secara teknis Anda bisa menyingkirkannya dengan menempelkan baris kode itu di akhir status yang berlaku. Itu akan membuat Anda turun ke 2 kemungkinan negara bagian berikutnya.
Salah satu yang tersisa ("Tiga") hanya bercabang dari satu tempat yang bisa saya lihat. Jadi Anda bisa menyingkirkannya dengan cara yang sama. Anda akan berakhir dengan kode yang terlihat seperti ini:
Namun, sekali lagi ini adalah contoh mainan yang Anda berikan. Dalam kasus di mana "default" memiliki jumlah kode non-sepele di dalamnya, "tiga" ditransisikan ke dari beberapa negara, atau (yang paling penting) pemeliharaan lebih lanjut cenderung menambah atau mempersulit negara , Anda benar-benar akan lebih baik menggunakan gotos (dan mungkin bahkan menyingkirkan struktur enum-case yang menyembunyikan sifat mesin negara dari hal-hal, kecuali ada beberapa alasan yang baik perlu tetap).
sumber
goto
.Jawaban terbaik adalah menggunakan polimorfisme .
Jawaban lain, yang, IMO, membuat hal-hal itu lebih jelas dan bisa dibilang lebih pendek :
goto
mungkin pilihan saya yang ke 58 di sini ...sumber
Kenapa tidak ini:
OK, saya setuju, ini peretasan (saya bukan pengembang C # btw, jadi maafkan saya untuk kodenya), tetapi dari sudut pandang efisiensi, ini harus dilakukan? Menggunakan enum sebagai indeks array valid C #.
sumber
int[ExempleEnum.length] COUNTS = { 1, 3, 4, 2, 5, 3 };
?Jika Anda tidak bisa atau tidak ingin menggunakan flag, gunakan fungsi rekursif ekor. Dalam mode rilis 64bit, kompiler akan menghasilkan kode yang sangat mirip dengan
goto
pernyataan Anda . Anda hanya tidak harus menghadapinya.sumber
Solusi yang diterima baik-baik saja dan merupakan solusi konkret untuk masalah Anda. Namun, saya ingin menempatkan alternatif, solusi yang lebih abstrak.
Dalam pengalaman saya, penggunaan enum untuk menentukan aliran logika adalah bau kode karena sering merupakan tanda desain kelas yang buruk.
Saya bertemu dengan contoh dunia nyata dari hal ini terjadi dalam kode yang saya kerjakan tahun lalu. Pengembang asli telah menciptakan kelas tunggal yang melakukan logika impor dan ekspor, dan beralih di antara keduanya berdasarkan enum. Sekarang kodenya mirip dan memiliki beberapa kode duplikat, tetapi cukup berbeda sehingga hal itu membuat kodenya secara signifikan lebih sulit dibaca dan hampir tidak mungkin untuk diuji. Saya akhirnya refactoring itu menjadi dua kelas yang terpisah, yang menyederhanakan keduanya dan benar-benar membiarkan saya melihat dan menghilangkan sejumlah bug yang tidak dilaporkan.
Sekali lagi, saya harus menyatakan bahwa menggunakan enum untuk mengontrol aliran logika sering merupakan masalah desain. Dalam kasus umum, Enums harus digunakan sebagian besar untuk memberikan nilai-nilai aman-ramah-konsumen di mana nilai-nilai yang mungkin didefinisikan dengan jelas. Mereka lebih baik digunakan sebagai properti (misalnya, sebagai ID kolom dalam tabel) daripada sebagai mekanisme kontrol logika.
Mari kita perhatikan masalah yang disajikan dalam pertanyaan. Saya tidak benar-benar tahu konteksnya di sini, atau apa yang diwakili oleh enum ini. Apakah itu kartu gambar? Menggambar? Menggambar darah? Apakah pesanan itu penting? Saya juga tidak tahu betapa pentingnya kinerja. Jika kinerja atau memori sangat penting, maka solusi ini mungkin tidak akan menjadi yang Anda inginkan.
Bagaimanapun, mari kita pertimbangkan enum:
Apa yang kami miliki di sini adalah sejumlah nilai enum yang berbeda yang mewakili konsep bisnis yang berbeda.
Apa yang bisa kita gunakan adalah abstraksi untuk menyederhanakan banyak hal.
Mari kita pertimbangkan antarmuka berikut:
Kami kemudian dapat mengimplementasikan ini sebagai kelas abstrak:
Kita dapat memiliki kelas konkret untuk mewakili Menggambar Satu, dua dan tiga (yang demi argumen memiliki logika yang berbeda). Ini berpotensi menggunakan kelas dasar yang didefinisikan di atas, tapi saya berasumsi bahwa konsep DrawOne berbeda dari konsep yang diwakili oleh enum:
Dan sekarang kami memiliki tiga kelas terpisah yang dapat disusun untuk menyediakan logika untuk kelas-kelas lain.
Pendekatan ini jauh lebih bertele-tele. Tetapi memang memiliki kelebihan.
Pertimbangkan kelas berikut, yang berisi bug:
Seberapa mudah untuk menemukan panggilan drawThree.Draw () yang hilang? Dan jika pesanan penting, urutan panggilan undian juga sangat mudah dilihat dan diikuti.
Kerugian dari pendekatan ini:
Keuntungan dari pendekatan ini:
Pertimbangkan pendekatan ini (atau yang serupa) setiap kali Anda merasa perlu memiliki kode kontrol logika kompleks yang dituliskan dalam pernyataan kasus. Masa depan kamu akan senang kamu lakukan.
sumber
Jika Anda bermaksud menggunakan sakelar di sini, kode Anda sebenarnya akan lebih cepat jika Anda menangani setiap kasing secara terpisah
hanya operasi aritmatika tunggal dilakukan dalam setiap kasus
juga karena orang lain telah menyatakan jika Anda mempertimbangkan untuk menggunakan gotos Anda mungkin harus memikirkan kembali algoritma Anda (meskipun saya akan mengakui bahwa kurangnya kasus C # s meskipun bisa menjadi alasan untuk menggunakan goto). Lihat karya terkenal Edgar Dijkstra "Go To Statement Dianggap Berbahaya"
sumber
Untuk contoh khusus Anda, karena semua yang benar-benar Anda inginkan dari penghitungan adalah indikator do / do-not untuk masing-masing langkah, solusi yang menulis ulang tiga
if
pernyataan Anda lebih disukai daripadaswitch
, dan ada baiknya Anda menjadikannya sebagai jawaban yang diterima .Tetapi jika Anda memiliki beberapa logika yang lebih kompleks yang tidak berjalan dengan sangat bersih, maka saya masih menemukan
goto
s dalamswitch
pernyataan itu membingungkan. Saya lebih suka melihat sesuatu seperti ini:Ini tidak sempurna tapi saya pikir lebih baik begini daripada dengan
goto
s. Jika urutan peristiwa sangat panjang dan saling menduplikasi sehingga tidak masuk akal untuk menguraikan urutan lengkap untuk setiap kasus, saya lebih suka subrutin daripadagoto
untuk mengurangi duplikasi kode.sumber
Alasan membenci
goto
kata kunci adalah kode sukaUps! Jelas itu tidak akan berhasil. The
message
variabel tidak didefinisikan dalam kode jalan. Jadi C # tidak akan lulus itu. Tapi itu mungkin implisit. MempertimbangkanDan anggap itu
display
kemudian memilikiWriteLine
pernyataan di dalamnya.Jenis bug ini mungkin sulit ditemukan karena
goto
mengaburkan jalur kode.Ini adalah contoh yang disederhanakan. Asumsikan bahwa contoh nyata tidak akan terlalu jelas. Mungkin ada lima puluh baris kode antara
label:
dan penggunaanmessage
.Bahasa dapat membantu memperbaikinya dengan membatasi bagaimana
goto
dapat digunakan, hanya turun dari blok. Namun C #goto
tidak terbatas seperti itu. Itu dapat melompati kode. Lebih lanjut, jika Anda akan membatasigoto
, itu juga baik untuk mengubah nama. Bahasa lain digunakanbreak
untuk turun dari blok, baik dengan nomor (blok untuk keluar) atau label (di salah satu blok).Konsep
goto
adalah instruksi bahasa mesin tingkat rendah. Tetapi seluruh alasan mengapa kita memiliki bahasa tingkat yang lebih tinggi adalah agar kita terbatas pada abstraksi tingkat yang lebih tinggi, misalnya ruang lingkup variabel.Semua yang dikatakan, jika Anda menggunakan C #
goto
di dalamswitch
pernyataan untuk melompat dari kasus ke kasus, itu cukup tidak berbahaya. Setiap kasing sudah menjadi titik masuk. Saya masih berpikir bahwa menyebutnyagoto
konyol dalam situasi itu, karena mengonfigurasikan penggunaan tidak berbahaya inigoto
dengan bentuk yang lebih berbahaya. Saya lebih suka mereka menggunakan sesuatu seperticontinue
itu. Tetapi karena suatu alasan, mereka lupa bertanya kepada saya sebelum mereka menulis bahasanya.sumber
C#
bahasa ini Anda tidak dapat melompati deklarasi variabel. Sebagai bukti, luncurkan aplikasi konsol dan masukkan kode yang Anda berikan di pos Anda. Anda akan mendapatkan kesalahan kompiler di label Anda yang menyatakan bahwa kesalahan tersebut menggunakan 'pesan' variabel lokal yang belum ditetapkan . Dalam bahasa yang diizinkan, ini merupakan masalah yang valid, tetapi tidak dalam bahasa C #.Ketika Anda memiliki banyak pilihan ini (dan bahkan lebih, seperti yang Anda katakan), maka mungkin itu bukan kode tetapi data.
Buat pemetaan kamus nilai-nilai enum untuk tindakan, dinyatakan baik sebagai fungsi atau sebagai tipe enum sederhana yang mewakili tindakan. Kemudian kode Anda dapat dirubah menjadi pencarian kamus sederhana, diikuti dengan memanggil nilai fungsi atau beralih pada pilihan yang disederhanakan.
sumber
Gunakan lingkaran dan perbedaan antara istirahat dan melanjutkan.
sumber