Apakah praktik yang buruk untuk menggunakan metode pengembalian dalam void?

92

Bayangkan kode berikut:

void DoThis()
{
    if (!isValid) return;

    DoThat();
}

void DoThat() {
    Console.WriteLine("DoThat()");
}

Apakah boleh menggunakan metode pengembalian dalam void? Apakah ada penalti kinerja? Atau akan lebih baik jika menulis kode seperti ini:

void DoThis()
{
    if (isValid)
    {
        DoThat();
    }
}

sumber
1
Bagaimana dengan: void DoThis () {if (isValid) DoThat (); }
Dscoduc
30
bayangkan kodenya? Mengapa? Disebelah sana! :-D
STW
Ini adalah pertanyaan yang bagus, saya selalu berpikir apakah itu praktik yang baik untuk menggunakan return; untuk keluar dari metode atau fungsi. Terutama dalam metode data mining LINQ yang memiliki beberapa hasil <T> IQuerable dan semuanya bergantung satu sama lain. Jika salah satu dari mereka tidak membuahkan hasil, waspada dan keluar.
Cheung

Jawaban:

173

Pengembalian dalam metode void tidak buruk, adalah praktik umum untuk membalikkan ifpernyataan untuk mengurangi penumpukan .

Dan lebih sedikit bersarang pada metode Anda akan meningkatkan keterbacaan kode dan pemeliharaan.

Sebenarnya jika Anda memiliki metode void tanpa pernyataan return, kompilator akan selalu menghasilkan instruksi ret pada akhirnya.

Christian C. Salvadó
sumber
33

Ada alasan bagus lainnya untuk menggunakan penjaga (sebagai lawan dari kode bersarang): Jika programmer lain menambahkan kode ke fungsi Anda, mereka bekerja di lingkungan yang lebih aman.

Mempertimbangkan:

void MyFunc(object obj)
{
    if (obj != null)
    {
        obj.DoSomething();
    }
}

melawan:

void MyFunc(object obj)
{
    if (obj == null)
        return;

    obj.DoSomething();
}

Sekarang, bayangkan programmer lain menambahkan baris: obj.DoSomethingElse ();

void MyFunc(object obj)
{
    if (obj != null)
    {
        obj.DoSomething();
    }

    obj.DoSomethingElse();
}

void MyFunc(object obj)
{
    if (obj == null)
        return;

    obj.DoSomething();
    obj.DoSomethingElse();
}

Jelas ini adalah kasus yang sederhana, tetapi pemrogram telah menambahkan kerusakan ke program pada contoh pertama (kode bersarang). Dalam contoh kedua (keluar awal dengan penjaga), setelah Anda melewati penjaga, kode Anda aman dari penggunaan referensi null yang tidak disengaja.

Tentu, programmer hebat tidak membuat kesalahan seperti ini (sering). Tetapi mencegah lebih baik daripada mengobati - kita dapat menulis kode dengan cara yang menghilangkan potensi sumber kesalahan ini sepenuhnya. Penyusunan menambah kerumitan, jadi praktik terbaik merekomendasikan pemfaktoran ulang kode untuk mengurangi penyarangan.

Jason Williams
sumber
Ya, tetapi di sisi lain, beberapa lapisan bersarang, dengan kondisinya, membuat kode semakin rentan terhadap bug, logika lebih sulit dilacak dan - yang lebih penting - lebih sulit untuk di-debug. Fungsi datar tidak terlalu jahat, IMO.
Skrim
18
Saya berdebat untuk mendukung pengurangan bersarang! :-)
Jason Williams
Saya setuju dengan ini. Selain itu, dari sudut pandang refactor, akan lebih mudah dan lebih aman untuk melakukan refactor metode jika obj menjadi struct atau sesuatu yang dapat Anda jamin tidak akan menjadi null.
Phil Cooper
18

Praktik buruk ??? Tidak mungkin. Faktanya, selalu lebih baik untuk menangani validasi dengan kembali dari metode paling awal jika validasi gagal. Jika tidak, ini akan menghasilkan sejumlah besar if & elses bersarang. Mengakhiri lebih awal meningkatkan keterbacaan kode.

Periksa juga tanggapan pada pertanyaan serupa: Haruskah saya menggunakan pernyataan return / continue daripada if-else?

SO User
sumber
8

Ini bukan praktik yang buruk (untuk semua alasan yang telah disebutkan). Namun, semakin banyak pengembalian yang Anda miliki dalam suatu metode, semakin besar kemungkinan itu harus dipecah menjadi metode logis yang lebih kecil.

Mike Hall
sumber
8

Contoh pertama adalah menggunakan pernyataan penjaga. Dari Wikipedia :

Dalam pemrograman komputer, penjaga adalah ekspresi boolean yang harus bernilai true jika eksekusi program akan dilanjutkan di cabang yang dimaksud.

Saya pikir memiliki sekelompok penjaga di bagian atas metode adalah cara yang dapat dimengerti untuk memprogram. Ini pada dasarnya mengatakan "jangan jalankan metode ini jika salah satu dari ini benar".

Jadi secara umum akan seperti ini:

void DoThis()
{
  if (guard1) return;
  if (guard2) return;
  ...
  if (guardN) return;

  DoThat();
}

Saya pikir itu jauh lebih mudah dibaca:

void DoThis()
{
  if (guard1 && guard2 && guard3)
  {
    DoThat();
  }
}
cdmckay.dll
sumber
3

Tidak ada penalti kinerja, namun potongan kode kedua lebih mudah dibaca dan karenanya lebih mudah dipelihara.

Russell
sumber
Russell I tidak setuju dengan pendapat Anda, tetapi Anda seharusnya tidak diremehkan. +1 untuk meratakannya. Btw, saya percaya bahwa tes boolean dan kembali dalam satu baris diikuti dengan baris kosong adalah indikasi yang jelas tentang apa yang terjadi. misalnya contoh pertama Rodrigo.
Paul Sasik
Saya tidak setuju dengan ini. Meningkatkan penyarangan tidak meningkatkan keterbacaan. Bagian pertama dari kode menggunakan pernyataan "penjaga", yang merupakan pola yang dapat dimengerti dengan sempurna.
cdmckay
Saya juga tidak setuju. Klausa penjaga yang menalangi suatu fungsi lebih awal umumnya dianggap sebagai Hal yang Baik saat ini dalam membantu pembaca untuk memahami penerapannya.
Pete Hodgson
2

Dalam kasus ini, contoh kedua Anda adalah kode yang lebih baik, tetapi itu tidak ada hubungannya dengan kembali dari fungsi void, itu hanya karena kode kedua lebih langsung. Tapi kembali dari fungsi kosong sama sekali tidak masalah.

Imagist
sumber
0

Tidak apa-apa dan tidak ada 'penalti kinerja', tetapi jangan pernah menulis pernyataan 'jika' tanpa tanda kurung.

Selalu

if( foo ){
    return;
}

Ini jauh lebih mudah dibaca; dan Anda tidak akan pernah secara tidak sengaja berasumsi bahwa beberapa bagian kode ada dalam pernyataan itu padahal tidak.

Sutra Siang
sumber
2
mudah dibaca adalah subjektif. imho, apa pun yang ditambahkan ke kode yang tidak perlu membuatnya kurang dapat dibaca ... (Saya harus membaca lebih lanjut, dan kemudian saya bertanya-tanya mengapa kode itu ada di sana dan membuang waktu untuk memastikan saya tidak melewatkan sesuatu) ... tapi itu milik saya pendapat subjektif
Charles Bretana
10
Alasan yang lebih baik untuk selalu memasukkan kawat gigi adalah lebih sedikit tentang keterbacaan dan lebih banyak tentang keamanan. Tanpa tanda kurung kurawal itu mudah bagi seseorang nanti untuk memperbaiki bug yang membutuhkan pernyataan tambahan sebagai bagian dari if, tidak cukup memperhatikan dan menambahkannya tanpa juga menambahkan kurung kurawal. Dengan selalu menyertakan kawat gigi, risiko ini dihilangkan.
Scott Dorman
2
Silky, silakan tekan enter sebelum Anda {. Ini sejajar {dengan Anda }di kolom yang sama, yang sangat membantu keterbacaan (jauh lebih mudah untuk menemukan kurung buka / tutup yang sesuai).
Imagist
1
@Imagist Saya akan menyerahkan itu ke preferensi pribadi; dan itu dilakukan dengan cara yang saya suka :)
Noon Silk
1
Jika setiap tanda kurung kurawal dicocokkan dengan kurung kurawal buka yang ditempatkan pada tingkat indentasi yang sama , maka membedakan secara visual ifpernyataan mana yang memerlukan kurung kurawal tutup akan mudah, dan dengan demikian memiliki ifpernyataan yang mengontrol satu pernyataan akan aman. Mendorong brace terbuka kembali ke garis dengan ifmenghemat garis spasi vertikal pada setiap pernyataan multi if, tetapi akan membutuhkan penggunaan garis brace dekat yang tidak perlu.
supercat
0

Saya akan tidak setuju dengan semua orang bodoh muda yang satu ini.

Menggunakan kembali di tengah metode, batal atau sebaliknya, adalah praktik yang sangat buruk, karena alasan yang diartikulasikan dengan cukup jelas, hampir empat puluh tahun yang lalu, oleh almarhum Edsger W. Dijkstra, dimulai dari "Pernyataan GOTO yang Dianggap Berbahaya ", dan dilanjutkan di" Pemrograman Terstruktur ", oleh Dahl, Dijkstra, dan Hoare.

Aturan dasarnya adalah bahwa setiap struktur kontrol, dan setiap modul, harus memiliki tepat satu entri dan satu pintu keluar. Pengembalian eksplisit di tengah modul melanggar aturan itu, dan membuatnya lebih sulit untuk bernalar tentang status program, yang pada gilirannya membuat lebih sulit untuk mengatakan apakah program itu benar atau tidak (yang merupakan properti yang jauh lebih kuat daripada "apakah itu tampak berfungsi atau tidak").

"Pernyataan GOTO Dianggap Berbahaya" dan "Pemrograman Terstruktur" memulai revolusi "Pemrograman Terstruktur" pada tahun 1970-an. Kedua bagian tersebut adalah alasan kami memiliki if-then-else, while-do, dan konstruksi kontrol eksplisit lainnya saat ini, dan mengapa pernyataan GOTO dalam bahasa tingkat tinggi ada di daftar Spesies Terancam Punah. (Pendapat pribadi saya adalah bahwa mereka harus ada dalam daftar Spesies Punah.)

Perlu dicatat bahwa Message Flow Modulator, perangkat lunak militer pertama yang PERNAH lulus pengujian penerimaan pada percobaan pertama, tanpa penyimpangan, pengabaian, atau kata-kata "ya, tetapi", ditulis dalam bahasa yang bahkan tidak memiliki pernyataan GOTO.

Perlu juga disebutkan bahwa Nicklaus Wirth mengubah semantik pernyataan RETURN di Oberon-07, versi terbaru dari bahasa pemrograman Oberon, membuatnya menjadi bagian akhir dari deklarasi prosedur yang diketik (yaitu, fungsi), daripada sebuah pernyataan yang dapat dieksekusi dalam tubuh fungsi. Penjelasan tentang perubahan mengatakan bahwa dia melakukannya justru karena bentuk sebelumnya WS pelanggaran prinsip satu-keluar dari Pemrograman Terstruktur.

John R. Strohm
sumber
2
@ John: kami mengatasi perintah Dykstra tentang beberapa pengembalian tepat saat kami melupakan Pascal (kebanyakan dari kami, bagaimanapun juga).
John Saunders
Kasus di mana banyak pengembalian diperlukan seringkali merupakan tanda bahwa suatu metode mencoba melakukan terlalu banyak dan harus dikupas. Saya tidak akan membahas sejauh John dengan ini, dan pernyataan pengembalian sebagai bagian dari validasi parameter mungkin merupakan pengecualian yang masuk akal, tetapi saya mendapatkan dari mana idenya berasal.
kyoryu
@nairdaen: Masih ada kontroversi tentang pengecualian di kuartal tersebut. Pedoman saya adalah ini: Jika sistem yang sedang dikembangkan HARUS memperbaiki masalah yang menyebabkan kondisi luar biasa aslinya, dan saya tidak keberatan membuat marah orang yang harus menulis kode itu, saya akan memberikan pengecualian. Kemudian saya dimarahi dalam rapat, karena orang itu tidak repot-repot menangkap pengecualian, dan aplikasi macet dalam pengujian, dan saya menjelaskan MENGAPA dia harus memperbaiki masalah, dan segalanya kembali tenang.
John R. Strohm
Ada perbedaan besar antara pernyataan penjaga dan gotos. Kejahatan dari gotos adalah mereka bisa melompat kemana saja, jadi bisa sangat membingungkan untuk diurai dan diingat. Pernyataan penjaga adalah kebalikannya - mereka memberikan entri yang terjaga keamanannya ke suatu metode, setelah itu Anda tahu bahwa Anda bekerja di lingkungan yang "aman", mengurangi jumlah hal yang harus Anda pertimbangkan saat Anda menulis sisa kode (mis. "Saya tahu pointer ini tidak akan pernah null, jadi saya tidak perlu menangani kasus itu di seluruh kode").
Jason Williams
@Jason: Pertanyaan asli tidak secara khusus tentang pernyataan penjaga, tetapi tentang pernyataan pengembalian acak di tengah metode. Contoh yang diberikan tampaknya menjadi penjaga. Masalah utamanya adalah, di situs pengembalian, Anda ingin dapat bernalar tentang apa yang dilakukan atau tidak dilakukan metode tersebut, dan pengembalian acak membuatnya lebih sulit, karena alasan yang PERSIS sama dengan GOTO acak membuatnya lebih sulit. Lihat: Dijkstra, "Pernyataan GOTO Dianggap Berbahaya". Di sisi sintaks, cdmckay memberikan, dalam jawaban lain, sintaks pilihannya untuk penjaga; Saya tidak setuju dengan pendapatnya tentang bentuk mana yang lebih mudah dibaca.
John R. Strohm
0

Saat menggunakan penjaga, pastikan Anda mengikuti pedoman tertentu agar tidak membingungkan pembaca.

  • fungsinya melakukan satu hal
  • penjaga hanya diperkenalkan sebagai yang pertama logika dalam fungsi tersebut
  • bagian yang tidak terisi berisi maksud inti fungsi

Contoh

// guards point you to the core intent
void Remove(RayCastResult rayHit){

  if(rayHit== RayCastResult.Empty)
    return
    ;
  rayHit.Collider.Parent.Remove();
}

// no guards needed: function split into multiple cases
int WonOrLostMoney(int flaw)=>
  flaw==0 ? 100 :
  flaw<10 ? 30 :
  flaw<20 ? 0 :
  -20
;
Jeruk
sumber
-3

Lempar pengecualian alih-alih tidak mengembalikan apa-apa saat objek nol, dll.

Metode Anda mengharapkan objek bukan null dan bukan kasusnya, jadi Anda harus membuang pengecualian dan membiarkan pemanggil menanganinya.

Tetapi kepulangan awal bukanlah praktik yang buruk sebaliknya.

Dhananjay
sumber
1
Jawabannya tidak menjawab pertanyaan itu. Pertanyaannya adalah metode kosong sehingga tidak ada yang dikembalikan. Selain itu, metode tidak memiliki parameter. Saya mendapatkan titik tidak mengembalikan null jika tipe pengembalian adalah objek tetapi itu tidak berlaku untuk pertanyaan ini.
Luke Hammer