Saya sering berbicara dengan programmer yang mengatakan " Jangan letakkan banyak pernyataan pengembalian dalam metode yang sama. " Ketika saya meminta mereka untuk memberi tahu alasannya, yang saya dapatkan hanyalah " Standar pengkodean mengatakan demikian. " Atau " Ini membingungkan. " Ketika mereka menunjukkan solusi kepada saya dengan pernyataan pengembalian tunggal, kode tersebut terlihat lebih buruk bagi saya. Sebagai contoh:
if (condition)
return 42;
else
return 97;
" Ini jelek, kamu harus menggunakan variabel lokal! "
int result;
if (condition)
result = 42;
else
result = 97;
return result;
Bagaimana kode 50% mengasapi ini membuat program lebih mudah dimengerti? Secara pribadi, saya merasa lebih sulit, karena ruang keadaan baru saja meningkat oleh variabel lain yang dapat dengan mudah dicegah.
Tentu saja, biasanya saya hanya menulis:
return (condition) ? 42 : 97;
Tetapi banyak programmer menghindari operator bersyarat dan lebih suka bentuk panjang.
Dari mana gagasan "satu kembali saja" ini berasal? Adakah alasan historis mengapa kebaktian ini terjadi?
sumber
Jawaban:
"Single Entry, Single Exit" ditulis ketika sebagian besar pemrograman dilakukan dalam bahasa assembly, FORTRAN, atau COBOL. Ini telah banyak disalahtafsirkan secara luas, karena bahasa modern tidak mendukung praktik-praktik yang Dijkstra peringatkan.
"Entri Tunggal" berarti "jangan membuat titik entri alternatif untuk fungsi". Dalam bahasa assembly, tentu saja, dimungkinkan untuk memasukkan fungsi pada instruksi apa pun. FORTRAN mendukung banyak entri ke fungsi dengan
ENTRY
pernyataan:"Single Exit" berarti bahwa suatu fungsi hanya boleh kembali ke satu tempat: pernyataan segera setelah panggilan. Itu tidak berarti bahwa suatu fungsi hanya boleh kembali dari satu tempat. Ketika Pemrograman Terstruktur ditulis, itu adalah praktik umum untuk fungsi untuk menunjukkan kesalahan dengan kembali ke lokasi alternatif. FORTRAN mendukung ini melalui "pengembalian alternatif":
Kedua teknik ini sangat rawan kesalahan. Penggunaan entri alternatif sering menyebabkan beberapa variabel tidak diinisialisasi. Penggunaan pengembalian alternatif memiliki semua masalah pernyataan GOTO, dengan komplikasi tambahan bahwa kondisi cabang tidak berdekatan dengan cabang, tetapi di suatu tempat dalam subrutin.
sumber
const
sejak sebelum banyak pengguna di sini lahir, sehingga tidak perlu untuk konstanta modal lagi bahkan di C. Tapi Jawa mempertahankan semua kebiasaan buruk C yang lama itu .setjmp/longjmp
,?)Gagasan Single Entry, Single Exit (SESE) ini berasal dari bahasa dengan manajemen sumber daya eksplisit , seperti C dan assembly. Dalam C, kode seperti ini akan membocorkan sumber daya:
Dalam bahasa tersebut, Anda pada dasarnya memiliki tiga opsi:
Replikasi kode pembersihan.
Ugh. Redundansi selalu buruk.
Gunakan a
goto
untuk melompat ke kode pembersihan.Ini membutuhkan kode pembersihan untuk menjadi hal terakhir dalam fungsi. (Dan inilah mengapa beberapa orang berargumentasi bahwa
goto
ada tempatnya. Dan memang ada - dalam C.)Memperkenalkan variabel lokal dan memanipulasi aliran kontrol melalui itu.
Kelemahannya adalah bahwa aliran kontrol dimanipulasi melalui sintaks (berpikir
break
,return
,if
,while
) jauh lebih mudah untuk mengikuti dari aliran kontrol dimanipulasi melalui keadaan variabel (karena variabel tersebut tidak memiliki negara ketika Anda melihat algoritma).Dalam perakitan itu bahkan lebih aneh, karena Anda dapat melompat ke alamat mana pun dalam suatu fungsi ketika Anda memanggil fungsi itu, yang secara efektif berarti Anda memiliki jumlah titik masuk yang hampir tidak terbatas untuk fungsi apa pun. (Kadang-kadang ini membantu. Pemukul seperti itu adalah teknik yang umum bagi kompiler untuk mengimplementasikan
this
penyesuaian pointer yang diperlukan untuk memanggilvirtual
fungsi dalam beberapa skenario warisan di C ++.)Ketika Anda harus mengelola sumber daya secara manual, mengeksploitasi opsi memasukkan atau keluar dari fungsi di mana saja mengarah ke kode yang lebih kompleks, dan dengan demikian ke bug. Karena itu, muncul aliran pemikiran yang menyebarkan SESE, untuk mendapatkan kode yang lebih bersih dan lebih sedikit bug.
Namun, ketika suatu bahasa memiliki pengecualian, (hampir) fungsi apa pun mungkin keluar sebelum waktunya di (hampir) titik mana pun, jadi Anda harus tetap membuat ketentuan untuk pengembalian prematur. (Saya pikir
finally
ini terutama digunakan untuk itu di Jawa danusing
(ketika menerapkanIDisposable
,finally
sebaliknya) di C #; C ++ sebagai gantinya mempekerjakan RAII .) Setelah Anda melakukan ini, Anda tidak dapat gagal untuk membersihkan setelah Anda sendiri karenareturn
pernyataan awal , jadi apa yang mungkin argumen terkuat yang mendukung SESE telah menghilang.Itu membuat keterbacaan. Tentu saja, fungsi 200 LoC dengan setengah lusin
return
pernyataan ditaburkan secara acak di atasnya bukan gaya pemrograman yang baik dan tidak membuat kode yang dapat dibaca. Tetapi fungsi seperti itu tidak akan mudah dipahami tanpa pengembalian prematur itu.Dalam bahasa di mana sumber daya tidak atau tidak seharusnya dikelola secara manual, ada sedikit atau tidak ada nilai dalam mematuhi konvensi SESE lama. OTOH, seperti yang saya katakan di atas, SESE sering membuat kode lebih kompleks . Ini adalah dinosaurus yang (kecuali untuk C) tidak cocok dengan sebagian besar bahasa saat ini. Alih-alih membantu pemahaman kode, justru menghambatnya.
Mengapa programmer Java tetap pada ini? Saya tidak tahu, tetapi dari POV (luar) saya, Java mengambil banyak konvensi dari C (di mana mereka masuk akal) dan menerapkannya ke dunia OO (di mana mereka tidak berguna atau sangat buruk), di mana sekarang menempel mereka, tidak peduli berapa biayanya. (Suka konvensi untuk mendefinisikan semua variabel Anda di awal ruang lingkup.)
Programmer berpegang pada semua jenis notasi aneh karena alasan irasional. (Pernyataan struktural yang sangat bersarang - "panah" - dalam bahasa seperti Pascal, pernah dipandang sebagai kode yang indah.) Menerapkan penalaran logis murni untuk ini tampaknya gagal meyakinkan mayoritas dari mereka untuk menyimpang dari cara yang telah mereka tetapkan. Cara terbaik untuk mengubah kebiasaan seperti itu mungkin mengajar mereka sejak dini untuk melakukan yang terbaik, bukan apa yang konvensional. Anda, sebagai guru pemrograman, memilikinya di tangan Anda.
:)
sumber
finally
klausa di mana ia dieksekusi terlepas dari awalreturn
atau pengecualian.finally
banyak waktu.malloc()
danfree()
menjadi komentar sebagai contoh), saya berbicara tentang sumber daya secara umum. Saya juga tidak menyiratkan bahwa GC akan menyelesaikan masalah ini. (Saya memang menyebutkan C ++, yang tidak memiliki GC di luar kotak.) Dari apa yang saya mengerti, di Jawafinally
digunakan untuk memecahkan masalah ini.Di satu sisi, pernyataan pengembalian tunggal membuat logging lebih mudah, serta bentuk debugging yang mengandalkan logging. Saya ingat banyak kali saya harus mengurangi fungsi menjadi pengembalian tunggal hanya untuk mencetak nilai pengembalian pada satu titik.
Di sisi lain, Anda bisa mengubah ini menjadi
function()
panggilan itu_function()
dan mencatat hasilnya.sumber
_function()
, denganreturn
s di tempat-tempat yang tepat, dan pembungkus bernamafunction()
yang menangani penebangan asing, daripada memiliki satufunction()
dengan logika terdistorsi untuk membuat semua pengembalian masuk ke satu jalan keluar -titik supaya saya bisa memasukkan pernyataan tambahan sebelum titik itu."Single Entry, Single Exit" berasal dari revolusi Pemrograman Terstruktur pada awal 1970-an, yang dimulai oleh surat Edsger W. Dijkstra kepada Editor " Pernyataan GOTO Dianggap Berbahaya ". Konsep-konsep di balik pemrograman terstruktur dituangkan secara rinci dalam buku klasik "Structured Programming" oleh Ole Johan-Dahl, Edsger W. Dijkstra, dan Charles Anthony Richard Hoare.
"Pernyataan GOTO Dianggap Berbahaya" wajib dibaca, bahkan hari ini. "Structured Programming" sudah ketinggalan zaman, tetapi masih sangat, sangat bermanfaat, dan harus berada di puncak daftar "Harus Dibaca" pengembang, jauh di atas apa pun dari misalnya Steve McConnell. (Bagian Dahl menjabarkan dasar-dasar kelas dalam Simula 67, yang merupakan dasar teknis untuk kelas-kelas dalam C ++ dan semua pemrograman berorientasi objek.)
sumber
goto
benar-benar dapat pergi ke mana saja , seperti langsung ke beberapa titik acak di fungsi lain, melewati setiap gagasan tentang prosedur, fungsi, tumpukan panggilan, dll. Tidak ada bahasa waras yang mengizinkan hari-hari ini dengan garis lurusgoto
. Csetjmp
/longjmp
adalah satu-satunya kasus semi-luar biasa yang saya ketahui, dan bahkan itu membutuhkan kerja sama dari kedua ujungnya. (Semi-ironis bahwa saya menggunakan kata "luar biasa" di sana, mengingat bahwa pengecualian melakukan hal yang hampir sama ...) Pada dasarnya, artikel tersebut mengecilkan praktik yang sudah lama mati.Selalu mudah untuk menautkan Fowler.
Salah satu contoh utama yang bertentangan dengan SESE adalah klausa penjaga:
Ganti Nested Conditional dengan Guard Clauses
sumber
_isSeparated
dan_isRetired
dapat keduanya benar (dan mengapa itu tidak mungkin?) Anda mengembalikan jumlah yang salah.Saya menulis posting blog tentang topik ini beberapa waktu lalu.
Intinya adalah bahwa aturan ini berasal dari usia bahasa yang tidak memiliki pengumpulan sampah atau penanganan pengecualian. Tidak ada studi formal yang menunjukkan bahwa aturan ini mengarah pada kode yang lebih baik dalam bahasa modern. Jangan ragu untuk mengabaikannya kapan pun ini akan menyebabkan kode lebih pendek atau lebih mudah dibaca. Orang-orang Jawa bersikeras tentang ini secara membabi buta dan tidak mempertanyakan mengikuti aturan usang, tidak berguna.
Pertanyaan ini juga ditanyakan pada Stackoverflow
sumber
Satu pengembalian membuat refactoring lebih mudah. Cobalah untuk melakukan "ekstrak metode" ke tubuh bagian dalam loop for yang berisi kembali, istirahat atau melanjutkan. Ini akan gagal karena Anda telah merusak aliran kendali Anda.
Intinya adalah: Saya kira tidak ada yang pura-pura menulis kode yang sempurna. Jadi kode biasanya sedang dalam refactoring untuk "ditingkatkan" dan diperluas. Jadi tujuan saya adalah untuk menjaga kode saya sebagai refactoring ramah mungkin.
Seringkali saya menghadapi masalah bahwa saya harus memformulasi ulang fungsi sepenuhnya jika mengandung pemutus aliran kontrol dan jika saya ingin menambahkan sedikit fungsionalitas saja. Ini sangat rawan kesalahan saat Anda mengubah keseluruhan aliran kendali alih-alih memperkenalkan jalur baru ke sarang yang terisolasi. Jika Anda hanya memiliki satu pengembalian tunggal di akhir atau jika Anda menggunakan penjaga untuk keluar dari loop Anda tentu saja memiliki lebih banyak kode bersarang dan lebih banyak. Tetapi Anda mendapatkan compiler dan IDE yang mendukung kemampuan refactoring.
sumber
Pertimbangkan fakta bahwa banyak pernyataan pengembalian setara dengan memiliki GOTO untuk pernyataan pengembalian tunggal. Ini adalah kasus yang sama dengan pernyataan break. Karena itu, beberapa orang, seperti saya, menganggapnya GOTO untuk semua maksud dan tujuan.
Namun, saya tidak menganggap jenis GOTO ini berbahaya dan tidak akan ragu untuk menggunakan GOTO yang sebenarnya dalam kode saya jika saya menemukan alasan yang bagus untuk itu.
Aturan umum saya adalah bahwa GOTO hanya untuk kontrol aliran. Mereka tidak boleh digunakan untuk perulangan apa pun, dan Anda tidak boleh GOTO 'ke atas' atau 'ke belakang'. (yang merupakan cara istirahat / pengembalian bekerja)
Seperti yang telah disebutkan orang lain, berikut ini adalah harus dibaca Pernyataan GOTO Dianggap Berbahaya
Namun, perlu diingat bahwa ini ditulis pada tahun 1970 ketika GOTO terlalu banyak digunakan. Tidak setiap GOTO berbahaya dan saya tidak akan mengecilkan penggunaannya selama Anda tidak menggunakannya daripada konstruksi normal, melainkan dalam kasus aneh bahwa menggunakan konstruksi normal akan sangat merepotkan.
Saya menemukan bahwa menggunakannya dalam kasus kesalahan di mana Anda perlu melarikan diri dari suatu daerah karena kegagalan yang seharusnya tidak pernah terjadi dalam kasus normal yang berguna di kali. Tetapi Anda juga harus mempertimbangkan untuk menempatkan kode ini ke dalam fungsi yang terpisah sehingga Anda bisa kembali lebih awal daripada menggunakan GOTO ... tapi terkadang itu juga merepotkan.
sumber
Kompleksitas Siklomatik
Saya telah melihat SonarCube menggunakan pernyataan pengembalian berganda untuk menentukan kompleksitas siklomatik. Jadi semakin banyak pernyataan pengembalian, semakin tinggi kompleksitas siklomatiknya
Return Type Change
Pengembalian berganda berarti kita perlu mengubah di beberapa tempat dalam fungsi saat kita memutuskan untuk mengubah jenis pengembalian kita.
Beberapa Keluar
Lebih sulit untuk di-debug karena logika perlu dipelajari dengan cermat bersama dengan pernyataan kondisional untuk memahami apa yang menyebabkan nilai yang dikembalikan.
Solusi Refactored
Solusi untuk beberapa pernyataan pengembalian adalah menggantinya dengan polimorfisme yang memiliki pengembalian tunggal setelah menyelesaikan objek implementasi yang diperlukan.
sumber