Mengapa suatu fungsi hanya memiliki satu titik keluar? [Tutup]

97

Saya selalu mendengar tentang satu fungsi titik keluar sebagai cara yang buruk untuk membuat kode karena Anda kehilangan keterbacaan dan efisiensi. Saya belum pernah mendengar ada orang yang berdebat di sisi lain.

Saya pikir ini ada hubungannya dengan CS tetapi pertanyaan ini ditolak di cstheory stackexchange.

hex bob-omb
sumber
6
Jawabannya adalah tidak ada jawaban yang selalu benar. Saya sering merasa lebih mudah untuk membuat kode dengan beberapa pintu keluar. Saya juga menemukan (saat memperbarui kode di atas) bahwa memodifikasi / memperluas kode lebih sulit karena beberapa jalan keluar yang sama. Membuat keputusan kasus per kasus ini adalah tugas kita. Ketika keputusan selalu memiliki jawaban "terbaik", kita tidak perlu.
JS.
1
@finnw mod fasis telah menghapus dua pertanyaan terakhir, untuk memastikan mereka harus dijawab lagi, dan lagi, dan lagi
Maarten Bodewes
Terlepas dari kata "membantah" dalam pertanyaan tersebut, menurut saya ini bukanlah pertanyaan yang didasarkan pada opini. Ini cukup relevan dengan desain yang bagus, dll. Saya tidak melihat alasan untuk menutupnya, tetapi w / e.
Ungeheuer
1
Titik keluar tunggal menyederhanakan proses debug, pembacaan, pengukuran dan penyetelan kinerja, pemfaktoran ulang, Ini objektif dan signifikan secara material. Menggunakan pengembalian awal (setelah pemeriksaan argumen sederhana) membuat perpaduan yang masuk akal dari kedua gaya. Mengingat manfaat dari satu titik keluar, mengotori kode Anda dengan nilai yang dikembalikan hanyalah bukti dari programmer yang malas, ceroboh, ceroboh - dan setidaknya mungkin tidak menyukai anak anjing.
Rick O'Shea

Jawaban:

108

Ada berbagai aliran pemikiran, dan sebagian besar tergantung pada preferensi pribadi.

Pertama, tidak terlalu membingungkan jika hanya ada satu titik keluar - Anda memiliki satu jalur melalui metode ini dan Anda tahu di mana mencari jalan keluar. Sisi negatifnya jika Anda menggunakan lekukan untuk merepresentasikan penumpukan, kode Anda akan berakhir menjorok secara masif ke kanan, dan menjadi sangat sulit untuk mengikuti semua cakupan bersarang.

Yang lainnya adalah Anda dapat memeriksa prasyarat dan keluar lebih awal di awal metode, sehingga Anda tahu di dalam isi metode bahwa kondisi tertentu benar, tanpa seluruh isi metode menjorok 5 mil ke kanan. Ini biasanya meminimalkan jumlah cakupan yang perlu Anda khawatirkan, yang membuat kode lebih mudah diikuti.

Ketiga, Anda bisa keluar di mana pun Anda mau. Ini dulu lebih membingungkan di masa lalu, tetapi sekarang kami memiliki editor dan kompiler pewarnaan sintaks yang mendeteksi kode yang tidak dapat dijangkau, itu jauh lebih mudah untuk ditangani.

Saya tepat di kamp tengah. Menerapkan satu titik keluar adalah pembatasan IMHO yang tidak ada gunanya atau bahkan kontraproduktif, sementara keluar secara acak di seluruh metode terkadang dapat menyebabkan logika sulit untuk diikuti, di mana menjadi sulit untuk melihat apakah sedikit kode akan atau tidak akan dieksekusi. Tapi "gerbang" metode Anda memungkinkan untuk menyederhanakan tubuh metode secara signifikan.

Jason Williams
sumber
1
Penyarangan yang dalam dapat disingkirkan dalam singe exitparadigma melalui go topernyataan. Selain itu, seseorang mendapat kesempatan untuk melakukan beberapa proses pasca di bawah Errorlabel lokal fungsi , yang tidak mungkin dilakukan dengan beberapa returns.
Ant_222
2
Biasanya ada solusi bagus yang menghindari kebutuhan untuk pergi ke. Saya lebih memilih untuk 'mengembalikan (Gagal (...))' dan memasukkan kode pembersihan bersama ke dalam metode Gagal. Ini mungkin memerlukan melewati beberapa penduduk setempat untuk memungkinkan memori dibebaskan dll, tetapi kecuali Anda berada dalam kinerja kritis sedikit kode ini biasanya merupakan solusi yang jauh lebih bersih daripada IMO goto. Ini juga memungkinkan beberapa metode untuk berbagi kode pembersihan serupa juga.
Jason Williams
Ada pendekatan yang optimal berdasarkan kriteria obyektif tetapi kita dapat menyetujui ada aliran pemikiran (benar dan salah) dan itu tergantung pada preferensi pribadi (preferensi untuk atau menentang pendekatan yang benar).
Rick O'Shea
39

Rekomendasi umum saya adalah bahwa pernyataan pengembalian harus, jika praktis, ditempatkan sebelum kode pertama yang memiliki efek samping, atau setelah kode terakhir yang memiliki efek samping. Saya akan mempertimbangkan sesuatu seperti:

  if (! argument) // Periksa apakah bukan null
    kembali ERR_NULL_ARGUMENT;
  ... memproses argumen bukan nol
  jika (ok)
    kembali 0;
  lain
    kembali ERR_NOT_OK;

lebih jelas dari:

  int return_value;
  if (argumen) // Bukan-null
  {
    .. memproses argumen bukan nol
    .. atur hasil dengan tepat
  }
  lain
    hasil = ERR_NULL_ARGUMENT;
  hasil pengembalian;

Jika kondisi tertentu mencegah suatu fungsi melakukan apa pun, saya lebih suka mengembalikan fungsi lebih awal di tempat di atas titik di mana fungsi tersebut akan melakukan apa pun. Setelah fungsi menjalankan tindakan dengan efek samping, saya lebih suka kembali dari bawah, untuk menjelaskan bahwa semua efek samping harus ditangani.

supercat
sumber
Contoh pertama Anda, mengelola okvariabel, terlihat sebagai pendekatan pengembalian tunggal bagi saya. Selain itu, blok if-else dapat dengan mudah ditulis ulang menjadi:return ok ? 0 : ERR_NOT_OK;
Melebius
2
Contoh pertama memiliki returndi awal sebelum semua kode yang melakukan segalanya. Untuk penggunaan ?:operator, menuliskannya pada baris terpisah memudahkan banyak IDE untuk memasang breakpoint debug ke skenario not-ok. BTW, kunci sebenarnya untuk "titik keluar tunggal" terletak pada pemahaman bahwa yang penting adalah untuk setiap panggilan tertentu ke fungsi normal, titik keluar adalah titik segera setelah panggilan . Programmer saat ini menerima begitu saja, tetapi hal-hal tidak selalu demikian. Dalam beberapa kasus yang jarang, kode mungkin harus bertahan tanpa ruang tumpukan, yang mengarah ke fungsi ...
supercat
... yang keluar melalui gotos bersyarat atau dihitung. Umumnya apa pun dengan sumber daya yang cukup untuk diprogram dalam bahasa apa pun selain bahasa assembly akan dapat mendukung tumpukan, tetapi saya telah menulis kode assembly yang harus beroperasi di bawah batasan yang sangat ketat (hingga NOL byte RAM dalam satu kasus), dan memiliki beberapa titik keluar dapat membantu dalam kasus seperti itu.
supercat
1
Contoh yang disebut lebih jelas jauh lebih tidak jelas dan sulit dibaca. Satu titik keluar selalu lebih mudah dibaca, lebih mudah dikelola, lebih mudah di-debug.
GuidoG
8
@GuidoG: Salah satu pola mungkin lebih mudah dibaca, bergantung pada apa yang muncul di bagian yang dihilangkan. Menggunakan "return x;" memperjelas bahwa jika pernyataan itu tercapai, nilai kembaliannya adalah x. Menggunakan "result = x;" membiarkan terbuka kemungkinan bahwa sesuatu yang lain mungkin mengubah hasil sebelum dikembalikan. Itu bisa berguna jika memang menjadi perlu untuk mengubah hasil, tetapi programmer yang memeriksa kode harus memeriksanya untuk melihat bagaimana hasil mungkin berubah bahkan jika jawabannya "tidak bisa".
supercat
15

Titik masuk dan keluar tunggal adalah konsep asli dari pemrograman terstruktur vs Spaghetti Coding langkah demi langkah. Ada keyakinan bahwa beberapa fungsi titik keluar memerlukan lebih banyak kode karena Anda harus membersihkan ruang memori yang dialokasikan untuk variabel dengan benar. Pertimbangkan skenario di mana fungsi mengalokasikan variabel (sumber daya) dan keluar dari fungsi lebih awal dan tanpa pembersihan yang tepat akan mengakibatkan kebocoran sumber daya. Selain itu, melakukan pembersihan sebelum setiap pintu keluar akan menghasilkan banyak kode yang berlebihan.

PSS
sumber
Itu benar-benar bukan masalah dengan RAII
BigSandwich
14

Dengan hampir semua hal, itu tergantung pada kebutuhan pengiriman. Di "masa lalu", kode spaghetti dengan beberapa titik kembali mengundang kebocoran memori, karena pembuat kode yang lebih menyukai metode itu biasanya tidak membersihkan dengan baik. Ada juga masalah dengan beberapa kompiler yang "kehilangan" referensi ke variabel pengembalian karena tumpukan muncul selama pengembalian, dalam kasus kembali dari cakupan bersarang. Masalah yang lebih umum adalah salah satu kode re-entrant, yang mencoba agar status pemanggilan suatu fungsi sama persis dengan status kembaliannya. Mutators of oop melanggar ini dan konsep itu ditangguhkan.

Ada kiriman, terutama kernel, yang membutuhkan kecepatan yang disediakan oleh beberapa titik keluar. Lingkungan ini biasanya memiliki memori dan manajemen prosesnya sendiri, sehingga risiko kebocoran diminimalkan.

Secara pribadi, saya suka memiliki satu titik keluar, karena saya sering menggunakannya untuk memasukkan breakpoint pada pernyataan pengembalian dan melakukan pemeriksaan kode tentang bagaimana kode menentukan solusi itu. Saya bisa pergi ke pintu masuk dan melangkah masuk, yang saya lakukan dengan solusi bersarang dan rekursif secara ekstensif. Sebagai peninjau kode, beberapa pengembalian dalam suatu fungsi memerlukan analisis yang jauh lebih dalam - jadi jika Anda melakukannya untuk mempercepat penerapan, Anda merampok Peter untuk menyelamatkan Paul. Lebih banyak waktu akan dibutuhkan dalam tinjauan kode, membatalkan anggapan implementasi yang efisien.

- 2 sen

Silakan lihat dokumen ini untuk lebih jelasnya: NISTIR 5459

sscheider
sumber
8
multiple returns in a function requires a much deeper analysishanya jika fungsinya sudah besar (> 1 layar), selain itu membuat analisis lebih mudah
dss539
2
beberapa pengembalian tidak pernah membuat analisis lebih mudah, hanya
kebalikannya
1
tautannya mati (404).
fusi
1
@fusi - menemukannya di archive.org dan memperbarui tautannya di sini
sscheider
4

Dalam pandangan saya, saran untuk keluar dari suatu fungsi (atau struktur kontrol lainnya) hanya pada satu titik sering kali oversold. Dua alasan biasanya diberikan untuk keluar hanya pada satu titik:

  1. Kode keluar tunggal seharusnya lebih mudah dibaca dan di-debug. (Saya akui bahwa saya tidak terlalu memikirkan alasan ini, tetapi alasan ini diberikan. Apa yang secara substansial lebih mudah dibaca dan di-debug adalah kode entri tunggal .)
  2. Tautan kode keluar tunggal dan kembali dengan lebih rapi.

Alasan kedua tidak kentara dan memiliki beberapa kelebihan, terutama jika fungsi mengembalikan struktur data yang besar. Namun, saya tidak akan terlalu khawatir tentang itu, kecuali ...

Jika Anda seorang siswa, Anda ingin mendapatkan nilai tertinggi di kelas Anda. Lakukan apa yang disukai instruktur. Dia mungkin memiliki alasan yang bagus dari sudut pandangnya; jadi, paling tidak, Anda akan mempelajari perspektifnya. Ini memiliki nilai tersendiri.

Semoga berhasil.

thb
sumber
4

Saya dulunya adalah penganjur gaya pintu keluar tunggal. Alasan saya sebagian besar berasal dari rasa sakit ...

Keluar tunggal lebih mudah untuk di-debug.

Mengingat teknik dan alat yang kita miliki saat ini, ini adalah posisi yang jauh lebih tidak masuk akal untuk dilakukan karena pengujian unit dan logging dapat membuat jalan keluar tunggal tidak diperlukan. Meskipun demikian, saat Anda perlu melihat kode dieksekusi dalam debugger, akan jauh lebih sulit untuk memahami dan bekerja dengan kode yang berisi beberapa titik keluar.

Ini menjadi benar terutama saat Anda perlu menyela tugas untuk memeriksa status (diganti dengan ekspresi jam tangan di debugger modern). Juga terlalu mudah untuk mengubah aliran kontrol dengan cara yang menyembunyikan masalah atau menghentikan eksekusi sama sekali.

Metode keluar tunggal lebih mudah untuk dilalui di debugger, dan lebih mudah untuk dipisahkan tanpa merusak logika.

Michael Murphree
sumber
0

Jawabannya sangat bergantung pada konteks. Jika Anda membuat GUI dan memiliki fungsi yang menginisialisasi API dan membuka jendela pada awal utama Anda, itu akan penuh dengan panggilan yang dapat menimbulkan kesalahan, yang masing-masing akan menyebabkan program ditutup. Jika Anda menggunakan pernyataan IF bertingkat dan memasukkan kode Anda bisa dengan cepat menjadi sangat miring ke kanan. Mengembalikan kesalahan pada setiap tahap mungkin lebih baik dan sebenarnya lebih mudah dibaca sementara mudah untuk di-debug dengan beberapa tanda di kode.

Namun, jika Anda menguji kondisi yang berbeda dan mengembalikan nilai yang berbeda bergantung pada hasil dalam metode Anda, praktiknya mungkin jauh lebih baik untuk memiliki satu titik keluar. Saya dulu mengerjakan skrip pemrosesan gambar di MATLAB yang bisa menjadi sangat besar. Beberapa titik keluar dapat membuat kode sangat sulit diikuti. Pernyataan Switch jauh lebih tepat.

Hal terbaik untuk dilakukan adalah belajar sambil jalan. Jika Anda menulis kode untuk sesuatu, coba temukan kode orang lain dan lihat bagaimana mereka menerapkannya. Tentukan bit mana yang Anda suka dan mana yang tidak.

Flash_Steel
sumber
-6

Jika Anda merasa perlu beberapa titik keluar dalam suatu fungsi, fungsinya terlalu besar dan melakukan terlalu banyak hal.

Saya akan merekomendasikan membaca bab tentang fungsi dalam buku Robert C. Martin, Clean Code.

Pada dasarnya, Anda harus mencoba menulis fungsi dengan 4 baris kode atau kurang.

Beberapa catatan dari Blog Mike Long :

  • Aturan fungsi pertama: mereka harus kecil
  • Aturan fungsi kedua: mereka harus lebih kecil dari itu
  • Blok di dalam pernyataan if, sementara pernyataan, untuk loop, dll harus sepanjang satu baris
  • … Dan baris kode tersebut biasanya akan menjadi pemanggilan fungsi
  • Harus ada tidak lebih dari satu atau mungkin dua tingkat lekukan
  • Fungsi harus melakukan satu hal
  • Semua pernyataan fungsi harus berada pada tingkat abstraksi yang sama
  • Fungsi harus memiliki tidak lebih dari 3 argumen
  • Argumen keluaran adalah bau kode
  • Meneruskan bendera boolean ke suatu fungsi benar-benar mengerikan. Anda secara definisi melakukan dua hal dalam fungsi tersebut.
  • Efek sampingnya adalah kebohongan.
Roger Dahl
sumber
29
4 baris? Apa yang Anda kodekan yang memungkinkan Anda kesederhanaan seperti itu? Saya sangat ragu bahwa kernel Linux atau git misalnya melakukan itu.
shinzou
7
"Meneruskan bendera boolean ke dalam suatu fungsi benar-benar mengerikan. Anda secara definisi melakukan dua hal - hal dalam fungsi tersebut." Menurut definisi? Tidak ... boolean itu berpotensi hanya memengaruhi salah satu dari empat baris Anda. Juga sementara saya setuju dengan menjaga ukuran fungsi kecil, empat agak terlalu membatasi. Ini harus dianggap sebagai pedoman yang sangat longgar.
Jesse
12
Menambahkan batasan seperti ini pasti akan membuat kode membingungkan. Ini lebih tentang metode menjadi ringkas dan bersih, dan berpegang pada hanya melakukan apa yang seharusnya dilakukan tanpa efek samping yang tidak perlu.
Jesse
10
Salah satu jawaban langka di SO yang saya harap bisa saya downvote berkali-kali.
Steven Rands
8
Sayangnya jawaban ini melakukan banyak hal dan mungkin perlu dipecah menjadi beberapa hal lainnya — semuanya kurang dari empat baris.
Eli