Ketika saya menjalankan ReSharper pada kode saya, misalnya:
if (some condition)
{
Some code...
}
ReSharper memberi saya peringatan di atas (Balikkan pernyataan "jika" untuk mengurangi bersarang), dan menyarankan koreksi berikut:
if (!some condition) return;
Some code...
Saya ingin mengerti mengapa itu lebih baik. Saya selalu berpikir bahwa menggunakan "kembali" di tengah metode bermasalah, agak seperti "goto".
ASSERT( exampleParam > 0 )
.Jawaban:
Pengembalian di tengah metode tidak selalu buruk. Mungkin lebih baik untuk kembali segera jika itu membuat maksud kode menjadi lebih jelas. Sebagai contoh:
Dalam hal ini, jika
_isDead
benar, kita dapat segera keluar dari metode. Mungkin lebih baik menyusunnya seperti ini:Saya telah mengambil kode ini dari katalog refactoring . Refactoring khusus ini disebut: Ganti Bersarang Bersyarat dengan Klausa Penjaga.
sumber
Ini tidak hanya estetika , tetapi juga mengurangi tingkat bersarang maksimum di dalam metode ini. Ini umumnya dianggap sebagai nilai tambah karena membuat metode lebih mudah dipahami (dan memang, banyak alat analisis statis memberikan ukuran ini sebagai salah satu indikator kualitas kode).
Di sisi lain, itu juga membuat metode Anda memiliki beberapa titik keluar, sesuatu yang menurut kelompok orang lain adalah tidak-tidak.
Secara pribadi, saya setuju dengan ReSharper dan grup pertama (dalam bahasa yang memiliki pengecualian, saya merasa konyol untuk membahas "beberapa titik keluar"; hampir semua hal dapat dilempar, sehingga ada banyak titik keluar potensial dalam semua metode).
Mengenai kinerja : kedua versi harus setara (jika tidak di tingkat IL, maka tentunya setelah jitter selesai dengan kode) dalam setiap bahasa. Secara teoritis ini tergantung pada kompiler, tetapi secara praktis setiap kompiler yang banyak digunakan saat ini mampu menangani kasus optimasi kode jauh lebih maju daripada ini.
sumber
Ini sedikit argumen agama, tapi saya setuju dengan ReSharper bahwa Anda sebaiknya tidak terlalu bersarang. Saya percaya bahwa ini melebihi negatif memiliki beberapa jalur kembali dari suatu fungsi.
Alasan utama untuk memiliki lebih sedikit sarang adalah untuk meningkatkan keterbacaan kode dan pemeliharaan . Ingatlah bahwa banyak pengembang lain akan perlu membaca kode Anda di masa depan, dan kode dengan sedikit lekukan pada umumnya lebih mudah dibaca.
Prasyarat adalah contoh yang bagus di mana tidak apa-apa untuk kembali lebih awal pada awal fungsi. Mengapa keterbacaan sisa fungsi dipengaruhi oleh adanya pemeriksaan prasyarat?
Adapun negatif tentang kembali beberapa kali dari suatu metode - debuggers cukup kuat sekarang, dan sangat mudah untuk mengetahui dengan tepat di mana dan kapan fungsi tertentu kembali.
Memiliki beberapa pengembalian dalam suatu fungsi tidak akan memengaruhi pekerjaan programmer maintainance.
Pembacaan kode yang buruk akan.
sumber
Seperti yang disebutkan orang lain, seharusnya tidak ada hit kinerja, tetapi ada pertimbangan lain. Selain dari kekhawatiran yang sahih, ini juga dapat membuka Anda terhadap gotcha dalam beberapa keadaan. Misalkan Anda berurusan dengan yang
double
sebaliknya:Bandingkan dengan inversi yang tampaknya setara:
Jadi dalam keadaan tertentu apa yang tampaknya benar terbalik
if
mungkin tidak.sumber
Gagasan untuk hanya kembali di akhir fungsi muncul kembali dari hari-hari sebelum bahasa memiliki dukungan untuk pengecualian. Itu memungkinkan program untuk mengandalkan kemampuan menempatkan kode pembersihan di akhir metode, dan kemudian memastikan itu akan dipanggil dan beberapa programmer lain tidak akan menyembunyikan pengembalian metode yang menyebabkan kode pembersihan dilewati . Kode pembersihan yang dilompati dapat menyebabkan kebocoran memori atau sumber daya.
Namun, dalam bahasa yang mendukung pengecualian, itu tidak memberikan jaminan seperti itu. Dalam bahasa yang mendukung pengecualian, eksekusi pernyataan atau ekspresi apa pun dapat menyebabkan aliran kontrol yang menyebabkan metode berakhir. Ini berarti pembersihan harus dilakukan melalui penggunaan kata kunci yang terakhir atau menggunakan kata kunci.
Bagaimanapun, saya katakan saya pikir banyak orang mengutip pedoman 'hanya kembali di akhir metode' tanpa memahami mengapa itu adalah hal yang baik untuk dilakukan, dan bahwa mengurangi sarang untuk meningkatkan keterbacaan mungkin merupakan tujuan yang lebih baik.
sumber
If you've got deep nesting, maybe your function is trying to do too many things.
=> ini bukan konsekuensi yang benar dari frasa Anda sebelumnya. Karena tepat sebelum Anda mengatakan bahwa Anda dapat mengubah perilaku A dengan kode C menjadi perilaku A dengan kode D. kode D lebih bersih, memang, tetapi "terlalu banyak hal" merujuk pada perilaku, yang TIDAK berubah. Karena itu Anda tidak masuk akal dengan kesimpulan ini.Saya ingin menambahkan bahwa ada nama untuk mereka yang terbalik - Penjaga Klausul. Saya menggunakannya kapan pun saya bisa.
Saya benci membaca kode di mana ada jika di awal, dua layar kode dan tidak ada lagi. Balikkan jika dan kembali. Dengan begitu tidak ada yang akan membuang waktu menggulir.
http://c2.com/cgi/wiki?GuardClause
sumber
if
atau tidak. lolItu tidak hanya mempengaruhi estetika, tetapi juga mencegah kode bersarang.
Ini sebenarnya dapat berfungsi sebagai prasyarat untuk memastikan bahwa data Anda juga valid.
sumber
Ini tentu saja subjektif, tetapi saya pikir ini sangat meningkat pada dua poin:
condition
ditahan.sumber
Beberapa poin kembali adalah masalah dalam C (dan pada tingkat lebih rendah C ++) karena mereka memaksa Anda untuk menduplikasi kode pembersihan sebelum masing-masing poin kembali. Dengan pengumpulan sampah,
try
| |finally
membangun danusing
memblokir, sebenarnya tidak ada alasan mengapa Anda harus takut pada mereka.Pada akhirnya, itu tergantung pada apa yang Anda dan kolega Anda temukan lebih mudah dibaca.
sumber
a loop that is not vectorizable due to a second data-dependent exit
Dari sisi kinerja, tidak akan ada perbedaan mencolok antara kedua pendekatan tersebut.
Tetapi pengkodean lebih dari sekedar kinerja. Kejelasan dan pemeliharaan juga sangat penting. Dan, dalam kasus seperti ini di mana itu tidak mempengaruhi kinerja, itu adalah satu-satunya hal yang penting.
Ada sekolah-sekolah pemikiran yang bersaing mengenai pendekatan mana yang lebih disukai.
Satu pandangan adalah yang lain telah disebutkan: pendekatan kedua mengurangi tingkat bersarang, yang meningkatkan kejelasan kode. Ini wajar dalam gaya imperatif: ketika Anda tidak memiliki apa-apa lagi untuk dilakukan, Anda sebaiknya kembali lebih awal.
Pandangan lain, dari perspektif gaya yang lebih fungsional, adalah bahwa suatu metode seharusnya hanya memiliki satu titik keluar. Segala sesuatu dalam bahasa fungsional adalah ekspresi. Jadi, jika pernyataan harus selalu memiliki klausa lain. Kalau tidak, ekspresi if tidak selalu memiliki nilai. Jadi dalam gaya fungsional, pendekatan pertama lebih alami.
sumber
Penjaga klausa atau pra-kondisi (seperti yang mungkin Anda lihat) periksa untuk melihat apakah kondisi tertentu terpenuhi dan kemudian memutus aliran program. Mereka bagus untuk tempat-tempat di mana Anda benar-benar hanya tertarik pada satu hasil
if
pernyataan. Jadi daripada mengatakan:Anda membalikkan kondisi dan mematahkan jika kondisi terbalik itu terpenuhi
return
sama sekali tidak kotorgoto
. Ini memungkinkan Anda untuk melewatkan nilai untuk menunjukkan sisa kode Anda bahwa fungsi tidak dapat berjalan.Anda akan melihat contoh terbaik di mana ini dapat diterapkan dalam kondisi bersarang:
vs:
Anda akan menemukan beberapa orang yang berargumen bahwa yang pertama lebih bersih tetapi tentu saja, ini sangat subjektif. Beberapa programmer ingin mengetahui kondisi apa yang sesuatu beroperasi di bawah lekukan, sementara saya lebih suka menjaga aliran metode linear.
Saya tidak akan menyarankan untuk sesaat bahwa prekursor akan mengubah hidup Anda atau membuat Anda santai tetapi Anda mungkin menemukan kode Anda sedikit lebih mudah untuk dibaca.
sumber
Ada beberapa poin bagus yang dibuat di sini, tetapi beberapa poin kembali bisa tidak dapat dibaca juga, jika metode ini sangat panjang. Yang sedang berkata, jika Anda akan menggunakan beberapa poin kembali hanya pastikan bahwa metode Anda pendek, jika tidak, bonus keterbacaan dari beberapa poin kembali mungkin hilang.
sumber
Kinerja ada dalam dua bagian. Anda memiliki kinerja ketika perangkat lunak dalam produksi, tetapi Anda juga ingin memiliki kinerja saat mengembangkan dan debugging. Hal terakhir yang diinginkan pengembang adalah "menunggu" untuk sesuatu yang sepele. Pada akhirnya, kompilasi ini dengan optimasi yang diaktifkan akan menghasilkan kode yang serupa. Jadi ada baiknya mengetahui trik-trik kecil ini yang membuahkan hasil di kedua skenario.
Kasus dalam pertanyaan sudah jelas, ReSharper benar. Daripada membuat
if
pernyataan, dan membuat ruang lingkup baru dalam kode, Anda menetapkan aturan yang jelas di awal metode Anda. Ini meningkatkan keterbacaan, akan lebih mudah untuk mempertahankan, dan mengurangi jumlah aturan yang harus disaring untuk menemukan ke mana mereka ingin pergi.sumber
Secara pribadi saya lebih suka hanya 1 titik keluar. Sangat mudah untuk dicapai jika Anda menjaga metode Anda singkat dan langsung ke sasaran, dan itu memberikan pola yang dapat diprediksi untuk orang berikutnya yang bekerja pada kode Anda.
misalnya.
Ini juga sangat berguna jika Anda hanya ingin memeriksa nilai-nilai variabel lokal tertentu dalam suatu fungsi sebelum keluar. Yang perlu Anda lakukan adalah menempatkan breakpoint pada pengembalian akhir dan Anda dijamin akan memukulnya (kecuali ada pengecualian).
sumber
Banyak alasan bagus tentang bagaimana kode itu terlihat . Tetapi bagaimana dengan hasil ?
Mari kita lihat beberapa kode C # dan formulir yang dikompilasi IL-nya:
Cuplikan sederhana ini dapat dikompilasi. Anda dapat membuka file .exe yang dihasilkan dengan ildasm dan memeriksa apa hasilnya. Saya tidak akan memposting semua assembler tetapi saya akan menjelaskan hasilnya.
Kode IL yang dihasilkan melakukan hal berikut:
Jadi sepertinya kode itu akan melompat ke akhir. Bagaimana jika kita melakukan normal jika dengan kode bersarang?
Hasilnya sangat mirip dalam instruksi IL. Perbedaannya adalah bahwa sebelum ada lompatan per kondisi: jika salah pergi ke bagian kode berikutnya, jika benar pergi kemudian berakhir. Dan sekarang kode IL mengalir lebih baik dan memiliki 3 lompatan (kompiler mengoptimalkan ini sedikit): 1. Lompatan pertama: ketika Panjang adalah 0 ke bagian di mana kode melompat lagi (lompatan ketiga) ke akhir. 2. Kedua: di tengah kondisi kedua untuk menghindari satu instruksi. 3. Ketiga: jika kondisi kedua salah, lompat ke ujung.
Bagaimanapun, penghitung program akan selalu melompat.
sumber
Secara teori, pembalikan
if
dapat menghasilkan kinerja yang lebih baik jika meningkatkan hit rate prediksi cabang. Dalam praktiknya, saya pikir sangat sulit untuk mengetahui dengan tepat bagaimana prediksi cabang akan berperilaku, terutama setelah mengkompilasi, jadi saya tidak akan melakukannya dalam pengembangan saya sehari-hari, kecuali jika saya menulis kode perakitan.Lebih lanjut tentang prediksi cabang di sini .
sumber
Itu cukup kontroversial. Tidak ada "perjanjian di antara programmer" tentang pertanyaan pengembalian awal. Itu selalu subjektif, sejauh yang saya tahu.
Ada kemungkinan untuk membuat argumen kinerja, karena lebih baik memiliki kondisi yang ditulis sehingga paling sering benar; bisa juga dikatakan lebih jelas. Itu, di sisi lain, membuat tes bersarang.
Saya tidak berpikir Anda akan mendapatkan jawaban konklusif untuk pertanyaan ini.
sumber
Sudah ada banyak jawaban mendalam di sana, tapi tetap saja, saya akan mengarahkan ke situasi yang sedikit berbeda: Alih-alih prasyarat, yang memang harus diletakkan di atas fungsi, pikirkan inisialisasi langkah-demi-langkah, di mana Anda harus memeriksa setiap langkah untuk berhasil dan kemudian melanjutkan dengan langkah berikutnya. Dalam hal ini, Anda tidak dapat memeriksa semuanya di atas.
Saya menemukan kode saya benar-benar tidak dapat dibaca ketika menulis aplikasi host ASIO dengan ASIOSDK Steinberg, ketika saya mengikuti paradigma bersarang. Kedalamannya delapan tingkat, dan saya tidak bisa melihat cacat desain di sana, seperti yang disebutkan oleh Andrew Bullock di atas. Tentu saja, saya bisa mengemas beberapa kode dalam ke fungsi lain, dan kemudian menyarangkan level yang tersisa di sana untuk membuatnya lebih mudah dibaca, tetapi ini tampaknya agak acak bagi saya.
Dengan mengganti sarang dengan klausa penjaga, saya bahkan menemukan kesalahpahaman saya mengenai bagian kode pembersihan yang seharusnya terjadi jauh lebih awal dalam fungsi daripada di akhir. Dengan cabang bersarang, saya tidak akan pernah melihat itu, Anda bahkan bisa mengatakan mereka menyebabkan kesalahpahaman saya.
Jadi ini mungkin situasi lain di mana ifs terbalik dapat berkontribusi pada kode yang lebih jelas.
sumber
Menghindari beberapa titik keluar dapat menyebabkan peningkatan kinerja. Saya tidak yakin tentang C # tetapi dalam C ++ Optimasi Nilai Pengembalian Bernama (Salin Penghilangan, ISO C ++ '03 12.8 / 15) tergantung pada memiliki satu titik keluar. Pengoptimalan ini menghindari penyalinan yang menyusun nilai pengembalian Anda (dalam contoh spesifik Anda, itu tidak masalah). Ini bisa menghasilkan keuntungan besar dalam kinerja di loop ketat, karena Anda menyimpan konstruktor dan destruktor setiap kali fungsi dipanggil.
Tetapi untuk 99% kasus yang menghemat panggilan konstruktor dan destruktor tambahan tidak sepadan dengan hilangnya pembacaan
if
blok bersarang yang diperkenalkan (seperti yang telah ditunjukkan orang lain).sumber
Ini masalah pendapat.
Pendekatan normal saya adalah untuk menghindari seandainya baris tunggal, dan kembali di tengah metode.
Anda tidak akan menginginkan garis seperti yang disarankan di mana-mana dalam metode Anda, tetapi ada sesuatu yang bisa dikatakan untuk memeriksa sekelompok asumsi di bagian atas metode Anda, dan hanya melakukan pekerjaan Anda yang sebenarnya jika semuanya lulus.
sumber
Menurut pendapat saya pengembalian awal baik-baik saja jika Anda baru saja mengembalikan batal (atau kode pengembalian tidak berguna Anda tidak akan pernah memeriksa) dan itu mungkin meningkatkan keterbacaan karena Anda menghindari bersarang dan pada saat yang sama Anda membuat eksplisit bahwa fungsi Anda selesai.
Jika Anda benar-benar mengembalikan returnValue - bersarang biasanya merupakan cara yang lebih baik karena Anda mengembalikan returnValue Anda hanya di satu tempat (di akhir - duh), dan itu mungkin membuat kode Anda lebih mudah dikelola dalam banyak kasus.
sumber
Saya tidak yakin, tapi saya pikir, R # mencoba menghindari lompatan jauh. Ketika Anda memiliki IF-ELSE, kompiler melakukan sesuatu seperti ini:
Kondisi salah -> lompat jauh ke false_condition_label
true_condition_label: instruction1 ... instruction_n
false_condition_label: instruction1 ... instruction_n
blok akhir
Jika kondisinya benar tidak ada lompatan dan tidak ada peluncuran L1 cache, tetapi lompat ke false_condition_label bisa sangat jauh dan prosesor harus meluncurkan cache sendiri. Menyinkronkan cache itu mahal. R # mencoba mengganti lompatan jauh menjadi lompatan pendek dan dalam hal ini ada kemungkinan lebih besar, bahwa semua instruksi sudah ada dalam cache.
sumber
Saya pikir itu tergantung pada apa yang Anda sukai, seperti yang disebutkan, tidak ada kesepakatan umum afaik. Untuk mengurangi gangguan, Anda dapat mengurangi peringatan semacam ini menjadi "Petunjuk"
sumber
Gagasan saya adalah bahwa pengembalian "di tengah fungsi" seharusnya tidak begitu "subyektif". Alasannya cukup sederhana, ambil kode ini:
Mungkin "pengembalian" pertama bukan SO intuitif, tapi itu benar-benar menyelamatkan. Ada terlalu banyak "ide" tentang kode bersih, yang hanya perlu lebih banyak latihan untuk kehilangan ide buruk "subyektif" mereka.
sumber
Ada beberapa keuntungan untuk pengkodean semacam ini, tetapi bagi saya kemenangan besar adalah, jika Anda dapat kembali dengan cepat, Anda dapat meningkatkan kecepatan aplikasi Anda. Yaitu saya tahu bahwa karena Prasyarat X saya dapat kembali dengan cepat dengan kesalahan. Ini menyingkirkan kasus kesalahan terlebih dahulu dan mengurangi kompleksitas kode Anda. Dalam banyak kasus karena cpu pipeline dapat menjadi lebih bersih maka dapat menghentikan crash atau switch pipeline. Kedua jika Anda berada dalam satu lingkaran, memecahkan atau mengembalikan dengan cepat dapat menghemat banyak cpu. Beberapa programmer menggunakan invarian loop untuk melakukan semacam ini keluar cepat tetapi dalam hal ini Anda dapat mematahkan pipa cpu Anda dan bahkan membuat masalah mencari memori dan berarti cpu perlu memuat dari cache luar. Tapi pada dasarnya saya pikir Anda harus melakukan apa yang Anda inginkan, yang mengakhiri loop atau fungsi tidak membuat jalur kode yang kompleks hanya untuk mengimplementasikan beberapa gagasan abstrak kode yang benar. Jika satu-satunya alat yang Anda miliki adalah palu maka semuanya terlihat seperti paku.
sumber