Praktik terbaik jika / kembali

60

Saya ingin tahu apa yang dianggap cara yang lebih baik untuk kembali ketika saya memiliki ifpernyataan.

Contoh 1:

public bool MyFunction()
{
   // Get some string for this example
   string myString = GetString();

   if (myString == null)
   {
      return false;
   }
   else
   {
      myString = "Name " + myString;
      // Do something more here...
      return true;
   }
}

Contoh 2:

public bool MyFunction()
{
   // Get some string for this example
   string myString = GetString();

   if (myString == null)
   {
      return false;
   }

   myString = "Name " + myString;
   // Do something more here...
   return true;
}

Seperti yang Anda lihat pada kedua contoh, fungsi akan kembali true/falsetetapi apakah ide yang baik untuk meletakkan elsepernyataan seperti pada contoh pertama atau lebih baik tidak meletakkannya?

sed
sumber
7
Jika Anda hanya memeriksa kesalahan di 'jika' pertama, Anda lebih baik tidak memasukkan 'lain' karena kesalahan tidak boleh dianggap sebagai bagian dari logika aktual.
Mert Akcakaya
2
secara pribadi saya akan lebih khawatir tentang fungsi yang menyebabkan efek samping, tetapi saya kira ini hanya contoh yang buruk?
jk.
14
Bagi saya, versi pertama adalah seperti mencabut semua siswa di ruang yang tidak menyelesaikan pekerjaan rumah mereka, dan kemudian setelah mereka pergi, mengatakan ke seluruh siswa "Sekarang jika Anda tidak menyelesaikan pekerjaan rumah Anda ...". Masuk akal, tetapi tidak perlu. Karena conditional tidak benar - benar conditional lagi, saya cenderung menjatuhkan else.
Nicole
1
Apa 'Lakukan sesuatu yang lain di sini' yang ingin Anda lakukan? Itu bisa sepenuhnya mengubah cara Anda mendesain fungsi Anda.
Benediktus

Jawaban:

81

Contoh 2 dikenal sebagai blok penjaga. Lebih baik mengembalikan / melempar pengecualian lebih awal jika terjadi kesalahan (parameter salah atau keadaan tidak valid). Dalam aliran logika normal, lebih baik menggunakan Contoh 1

Kemoda
sumber
+1 - perbedaan yang bagus, dan apa yang akan saya jawab.
Telastyn
3
@ pR0Ps memiliki jawaban yang sama di bawah ini dan memberikan contoh kode mengapa akhirnya menjadi jalan yang lebih bersih untuk diikuti. +1 untuk jawaban yang bagus.
Pertanyaan ini telah ditanyakan berkali-kali pada SO dan meskipun dapat diperdebatkan, ini adalah jawaban yang bagus. Banyak orang yang dilatih untuk hanya kembali dari akhir memiliki beberapa masalah dengan konsep ini.
Bill K
2
+1. Penghindaran blok penjaga yang tidak tepat cenderung menyebabkan kode panah.
Brian
Bukankah kedua contoh tersebut menunjukkan blok penjaga?
ernie
44

Gaya pribadi saya adalah menggunakan tunggal ifuntuk blok penjaga, dan if/ elsedalam kode pemrosesan metode aktual.

Dalam hal ini, Anda menggunakan kondisi myString == nullsebagai penjaga, jadi saya cenderung menggunakan ifpola tunggal .

Pertimbangkan kode yang sedikit lebih rumit:

Contoh 1:

public bool MyFunction(myString: string){

    //guard block
    if (myString == null){
        return false;
    }
    else{
        //processing block
        myString = escapedString(myString);

        if (myString == "foo"){
            //some processing here
            return false;
        }
        else{
            myString = "Name " + myString;
            //other stuff
            return true;
        }
    }
}

Contoh 2:

public bool MyFunction(myString: string){

    //guard block
    if (myString == null){
        return false;
    }

    //processing block
    myString = escapedString(myString);

    if (myString == "foo"){
        //some processing here
        return false;
    }
    else{
        myString = "Name " + myString;
        //other stuff
        return true;
    }
}

Dalam Contoh 1, pelindung dan sisa metode ada di if/ elseform. Bandingkan dengan Contoh 2, di mana blok penjaga berada dalam ifbentuk tunggal , sedangkan metode lainnya menggunakan if/ elseform. Secara pribadi, saya menemukan contoh 2 lebih mudah dimengerti, sedangkan contoh 1 terlihat berantakan dan terlalu banyak indentasi.

Perhatikan bahwa ini adalah contoh yang dibuat-buat dan Anda bisa menggunakan else ifpernyataan untuk membersihkannya, tapi saya bertujuan untuk menunjukkan perbedaan antara blok penjaga dan kode pemrosesan fungsi yang sebenarnya.

Kompiler yang layak harus menghasilkan output yang sama untuk keduanya. Satu-satunya alasan untuk menggunakan satu atau yang lain adalah preferensi pribadi atau untuk menyesuaikan dengan gaya kode yang ada.

pR0Ps
sumber
18
Contoh 2 akan lebih mudah dibaca jika Anda menyingkirkan yang lain.
briddums
18

secara pribadi, saya lebih suka metode kedua. Saya merasa seolah itu lebih pendek, lebih sedikit lekukan, dan lebih mudah dibaca.

GSto
sumber
+1 untuk lebih sedikit lekukan. Sikap ini tidak jelas jika Anda melakukan beberapa pemeriksaan
d.raev
15

Latihan pribadi saya adalah sebagai berikut:

  • Saya tidak suka fungsi dengan beberapa titik keluar, saya merasa sulit untuk mempertahankan dan mengikuti, modifikasi kode terkadang mematahkan logika internal karena secara inheren agak ceroboh. Ketika ini merupakan perhitungan yang rumit, saya membuat nilai kembali di awal dan mengembalikannya di akhir. Ini memaksa saya untuk mengikuti setiap if-else, switch, dll jalur dengan hati-hati, mengatur nilainya dengan benar di lokasi yang tepat. Saya juga menghabiskan sedikit waktu untuk memutuskan apakah akan menetapkan nilai pengembalian default, atau membiarkannya tidak diinisialisasi di awal. Metode ini juga membantu ketika logika atau tipe nilai balik atau makna berubah.

Sebagai contoh:

public bool myFunction()
{
   // First parameter loading
   String myString = getString();

   // Location of "quick exits", see the second example
   // ...

   // declaration of external resources that MUST be released whatever happens
   // ...

   // the return variable (should think about giving it a default value or not) 
   // if you have no default value, declare it final! You will get compiler 
   // error when you try to set it multiple times or leave uninitialized!
   bool didSomething = false;

   try {
     if (myString != null)
     {
       myString = "Name " + myString;
       // Do something more here...

       didSomething = true;
     } else {
       // get other parameters and data
       if ( other conditions apply ) {
         // do something else
         didSomething = true;
       }
     }

     // Edit: previously forgot the most important advantage of this version
     // *** HOUSEKEEPING!!! ***

   } finally {

     // this is the common place to release all resources, reset all state variables

     // Yes, if you use try-finally, you will get here from any internal returns too.
     // As I said, it is only my taste that I like to have one, straightforward path 
     // leading here, and this works even if you don't use the try-finally version.

   }

   return didSomething;
}
  • Satu-satunya pengecualian: "keluar cepat" di awal (atau dalam kasus yang jarang terjadi, di dalam proses). Jika logika kalkulasi nyata tidak dapat menangani kombinasi tertentu dari parameter input dan status internal, atau memiliki solusi yang mudah tanpa menjalankan algoritma, itu tidak membantu untuk memiliki semua kode dienkapsulasi dalam (kadang-kadang dalam) jika blok. Ini adalah "keadaan luar biasa", bukan bagian dari logika inti, jadi saya harus keluar dari perhitungan segera setelah saya mendeteksi. Dalam hal ini tidak ada cabang lain, dalam kondisi normal eksekusi hanya berjalan. (Tentu saja, "keadaan luar biasa" lebih baik diekspresikan dengan melemparkan pengecualian, tetapi kadang-kadang itu berlebihan.)

Sebagai contoh:

public bool myFunction()
{
   String myString = getString();

   if (null == myString)
   {
     // there is nothing to do if myString is null
     return false;
   } 

   myString = "Name " + myString;
   // Do something more here...

   // not using return value variable now, because the operation is straightforward.
   // if the operation is complex, use the variable as the previous example.

   return true;
}

Aturan "satu jalan keluar" juga membantu ketika perhitungan membutuhkan sumber daya eksternal yang harus Anda lepaskan, atau menyatakan bahwa Anda harus mengatur ulang sebelum meninggalkan fungsi; terkadang mereka ditambahkan kemudian selama pengembangan. Dengan beberapa pintu keluar di dalam algoritma, jauh lebih sulit untuk memperluas semua cabang dengan benar. (Dan jika pengecualian dapat terjadi, rilis / reset harus diletakkan di blok akhirnya juga, untuk menghindari efek samping dalam kasus luar biasa yang jarang terjadi ...).

Kasing Anda sepertinya termasuk dalam kategori "jalan keluar cepat sebelum pekerjaan nyata", dan saya akan menulisnya seperti versi Contoh 2 Anda.

Lorand Kedves
sumber
9
+1 untuk "Saya tidak suka fungsi dengan beberapa titik keluar"
Corv1nus
@LorandKedves - menambahkan contoh - harap Anda tidak keberatan.
Matthew Flynn
@ MatthewFlynn baik, jika Anda tidak keberatan bahwa itu bertentangan dengan apa yang saya sarankan di akhir jawaban saya ;-) Saya melanjutkan contohnya, saya berharap akhirnya akan baik bagi kita berdua :-)
Lorand Kedves
@MatthewFlynn (maaf, saya hanya seorang bajingan pemarah, dan terima kasih telah membantu saya untuk mengklarifikasi pendapat saya. Saya harap Anda menyukai versi saat ini.)
Lorand Kedves
13
Dari fungsi dengan beberapa titik keluar dan fungsi dengan variabel hasil yang bisa berubah, yang pertama menurut saya jauh lebih kecil dari dua kejahatan.
Jon Purdy
9

Saya lebih suka mempekerjakan penjaga blok di mana pun saya bisa karena dua alasan:

  1. Mereka memungkinkan keluar cepat dengan syarat tertentu.
  2. Hapus keharusan untuk pernyataan kompleks dan tidak perlu jika nanti dalam kode.

Secara umum saya lebih suka melihat metode di mana fungsi inti dari metode ini jelas dan minimal. Blok penjaga membantu mewujudkan hal ini secara visual.

drekka
sumber
5

Saya suka pendekatan "Fall Through":

public bool MyFunction()
{
   string myString = GetString();

   if (myString != null)
   {
     myString = "Name " + myString;
     return true;
    }
    return false;
}

Tindakan memiliki kondisi tertentu, yang lainnya hanyalah pengembalian "jatuh melalui" default.

Malam gelap
sumber
1

Jika saya punya satu jika kondisi saya tidak akan menghabiskan terlalu banyak waktu merenungkan gaya. Tetapi jika saya memiliki beberapa kondisi penjaga saya lebih suka style2

Picuture ini. Asumsikan bahwa tes ini rumit dan Anda benar-benar tidak ingin mengikatnya menjadi satu kondisi jika-ORed untuk menghindari kompleksitas:

//Style1
if (this1 != Right)
{ 
    return;
}
else if(this2 != right2)
{
    return;
}
else if(this3 != right2)
{
    return;
}
else
{
    //everything is right
    //do something
    return;
}

melawan

//Style 2
if (this1 != Right)
{ 
   return;
}
if(this2 != right2)
{
    return;
}
if(this3 != right2)
{
    return;
}


//everything is right
//do something
return;

Di sini ada dua keunggulan utama

  1. Anda memisahkan kode dalam satu fungsi menjadi dua blok logcal visual: blok validasi (kondisi penjaga) dan blok yang lebih rendah dari kode runnable.

  2. Jika Anda harus menambah / menghapus satu syarat, Anda mengurangi kemungkinan mengacaukan seluruh tangga if-elseif-else.

Keuntungan kecil lainnya adalah Anda memiliki satu set kawat gigi yang lebih sedikit untuk dirawat.

DPD
sumber
0

Ini harus menjadi masalah yang terdengar lebih baik.

If condition
  do something
else
  do somethingelse

mengekspresikan diri dengan lebih baik

if condition
  do something
do somethingelse

untuk metode kecil itu tidak akan banyak perbedaan, tetapi untuk metode senyawa yang lebih besar dapat membuatnya lebih sulit untuk dipahami karena tidak akan terpisah dengan benar

José Valente
sumber
3
Jika suatu metode begitu panjang sehingga bentuk kedua sulit untuk dipahami, itu terlalu panjang.
kevin cline
7
Dua kasing Anda hanya setara jika do somethingmenyertakan pengembalian. Jika tidak, kode kedua akan dieksekusi do somethingelseterlepas dari ifpredikat yang bukan cara kerja blok pertama.
Adnan
Mereka juga menjelaskan OP dalam contoh-contohnya. Hanya mengikuti garis pertanyaan
José Valente
0

Saya menemukan contoh 1 menjengkelkan, karena pernyataan pengembalian yang hilang di akhir fungsi pengembalian nilai segera memicu "Tunggu, ada yang salah" -flag. Jadi dalam kasus seperti ini, saya akan menggunakan contoh 2.

Namun, biasanya ada yang lebih terlibat, tergantung pada tujuan fungsi, misalnya penanganan kesalahan, penebangan, dll. Jadi saya dengan jawaban Lorand Kedves mengenai hal ini, dan biasanya memiliki satu titik keluar pada akhirnya, bahkan pada biaya dari variabel bendera tambahan. Itu membuat pemeliharaan dan ekstensi lebih mudah dalam kebanyakan kasus.

Aman
sumber
0

Ketika ifpernyataan Anda selalu kembali maka tidak ada alasan untuk menggunakan yang lain untuk kode yang tersisa dalam fungsi. Melakukan hal itu menambah garis ekstra dan lekukan ekstra. Menambahkan kode yang tidak perlu membuatnya lebih sulit untuk dibaca dan seperti semua orang tahu Membaca kode itu Sulit .

pengantin
sumber
0

Dalam contoh Anda, elsejelas tidak perlu, TAPI ...

Ketika membaca sekilas kode-kode dengan cepat, mata seseorang biasanya melihat kawat gigi dan lekukan untuk mendapatkan gagasan tentang aliran kode; sebelum mereka benar-benar puas dengan kode itu sendiri. Oleh karena itu, menulis else, dan menempatkan kawat gigi dan lekukan di sekitar blok kode kedua sebenarnya membuatnya lebih cepat bagi pembaca untuk melihat bahwa ini adalah situasi "A atau B", di mana satu blok atau yang lain akan berjalan. Artinya, pembaca melihat kawat gigi dan lekukan SEBELUM mereka melihat returnsetelah inisial if.

Menurut pendapat saya, ini adalah salah satu kasus langka di mana menambahkan sesuatu yang berlebihan ke kode sebenarnya membuatnya LEBIH dapat dibaca, tidak kurang.

Dawood ibn Kareem
sumber
0

Saya lebih suka contoh 2 karena jelas bahwa fungsi mengembalikan sesuatu . Tetapi bahkan lebih dari itu, saya lebih suka kembali dari satu tempat, seperti ini:

public bool MyFunction()
{
    bool result = false;

    string myString = GetString();

    if (myString != nil) {
        myString = "Name " + myString;

        result = true;
    }

    return result;
}

Dengan gaya ini saya dapat:

  1. Lihat segera bahwa saya mengembalikan sesuatu.

  2. Tangkap hasil dari setiap pemanggilan fungsi hanya dengan satu breakpoint.

Caleb
sumber
Ini mirip dengan bagaimana saya akan mendekati masalah. Satu-satunya perbedaan adalah saya akan menggunakan variabel hasil untuk menangkap evaluasi myString dan menggunakannya sebagai nilai balik.
Chuck Conway
@ ChuckConway Setuju, tapi saya mencoba untuk tetap berpegang pada prototipe OP.
Caleb
0

Gaya pribadi saya cenderung berjalan

function doQuery(string) {
    if (!query(string)) {
        query("ABORT");
        return false;
    } // else
    if(!anotherquery(string) {
        query("ABORT");
        return false;
    } // else
    return true;
}

Menggunakan pernyataan komentar elseuntuk menunjukkan aliran program dan membuatnya tetap mudah dibaca, tetapi menghindari indentasi besar-besaran yang dapat dengan mudah menjangkau seluruh layar jika banyak langkah yang terlibat.

Shadur
sumber
1
Secara pribadi, saya harap saya tidak perlu lagi bekerja dengan kode Anda.
CVn
Jika Anda memiliki beberapa pemeriksaan, metode ini bisa agak lama. Mengembalikan masing-masing jika klausa mirip dengan menggunakan pernyataan goto untuk melewati bagian kode.
Chuck Conway
Jika itu adalah pilihan antara ini dan kode dengan elseklausa yang disertakan, saya akan memilih yang terakhir karena kompiler akan mengabaikannya secara efektif juga. Meskipun itu akan tergantung pada else if tidak meningkatkan lekukan lebih dari asli sekali - jika memang saya setuju dengan Anda. Tapi pilihan saya adalah untuk menjatuhkan elsesepenuhnya jika gaya itu diizinkan.
Mark Hurd
0

saya akan menulis

public bool SetUserId(int id)
{
   // Get user name from id
   string userName = GetNameById(id);

   if (userName != null)
   {
       // update local ID and userName
       _id = id;
       _userNameLabelText = "Name: " + userName;
   }

   return userName != null;
}

Saya lebih suka gaya ini karena paling jelas saya akan kembali userName != null.

tia
sumber
1
Pertanyaannya adalah mengapa Anda lebih suka gaya itu?
Caleb
... Terus terang, saya tidak yakin mengapa Anda menginginkan string dalam kasus ini, belum lagi mengapa Anda menambahkan tes boolean tambahan pada akhirnya bahwa Anda benar-benar tidak perlu untuk alasan apa pun.
Shadur
@ Safad Saya sadar akan tes boolean ekstra dan myStringhasil yang tidak digunakan . Saya hanya menyalin fungsi dari OP. Saya akan mengubahnya menjadi sesuatu yang terlihat lebih nyata. Tes Boolean itu sepele dan seharusnya tidak menjadi masalah.
tia
1
@ Safad Kami tidak melakukan optimasi di sini, kan? Maksud saya adalah membuat kode saya mudah dibaca dan dipelihara, dan secara pribadi saya akan membayarnya dengan biaya satu referensi nol cek.
tia
1
@tia Saya suka semangat kode Anda. Alih-alih mengevaluasi userName dua kali saya akan menangkap evaluasi pertama dalam variabel dan mengembalikannya.
Chuck Conway
-1

Aturan praktis saya adalah melakukan if-return tanpa yang lain (sebagian besar untuk kesalahan) atau melakukan rantai if-else-if penuh atas semua kemungkinan.

Dengan cara ini saya menghindari mencoba untuk menjadi terlalu mewah dengan percabangan saya, karena setiap kali saya akhirnya melakukan itu saya menyandikan logika aplikasi ke ifs saya, membuat mereka lebih sulit untuk dipertahankan (Misalnya, jika enum OK atau ERR dan saya menulis semua ifs saya mengambil keuntungan dari fakta itu! OK <-> ERR menjadi susah payah untuk menambahkan opsi ketiga ke enum waktu berikutnya.)

Dalam kasus Anda, misalnya, saya akan menggunakan dataran jika, karena "kembali jika nol" pasti akan menjadi pola umum dan tidak mungkin Anda perlu khawatir tentang kemungkinan ketiga (selain nol / tidak nol) di masa depan.

Namun, jika tes itu adalah sesuatu yang melakukan pemeriksaan lebih mendalam terhadap data, saya akan melakukan kesalahan jika ada yang sebaliknya.

hugomg
sumber
-1

Saya pikir ada banyak jebakan pada Contoh 2, bahwa di jalan dapat menyebabkan kode yang tidak diinginkan. Fokus pertama di sini didasarkan pada logika yang mengelilingi variabel 'myString'. Oleh karena itu untuk menjadi eksplisit, semua pengujian kondisi harus terjadi dalam akuntansi blok kode untuk logika yang dikenal dan standar / tidak diketahui .

Bagaimana jika nanti kode diperkenalkan secara tidak sengaja ke contoh 2 yang mengubah ouput secara signifikan:

   if (myString == null)
   {
      return false;
   }

   //add some v1 update code here...
   myString = "And the winner is: ";
   //add some v2 update code here...
   //buried in v2 updates the following line was added
   myString = null;
   //add some v3 update code here...
   //Well technically this should not be hit because myString = null
   //but we already passed that logic
   myString = "Name " + myString;
   // Do something more here...
   return true;

Saya pikir dengan elseblok segera setelah cek untuk nol akan membuat programmer yang menambahkan perangkat tambahan ke versi masa depan menambahkan semua logika bersama karena sekarang kami memiliki serangkaian logika yang tidak diinginkan untuk aturan asli (mengembalikan jika nilainya adalah batal).

Saya meminjamkan kepercayaan saya pada ini banyak ke beberapa pedoman C # pada Codeplex (tautan ke sini: http://csharpguidelines.codeplex.com/ ) yang menyatakan sebagai berikut:

"Tambahkan komentar deskriptif jika blok default (yang lain) seharusnya kosong. Selain itu, jika blok itu tidak seharusnya dicapai, lontarkan InvalidOperationException untuk mendeteksi perubahan di masa mendatang yang mungkin jatuh pada case yang ada. Ini memastikan kode yang lebih baik, karena semua jalur yang dapat dilalui oleh kode telah dipikirkan. "

Saya pikir ini adalah praktik pemrograman yang baik ketika menggunakan blok logika seperti ini untuk selalu memiliki blok default ditambahkan (jika-selain itu, case: default) untuk menjelaskan semua jalur kode secara eksplisit dan tidak membiarkan kode terbuka untuk konsekuensi logika yang tidak diinginkan.

atconway
sumber
Bagaimana tidak memiliki yang lain dalam contoh dua tidak mempertimbangkan semua kasus? Hasil yang dimaksudkan IS melanjutkan eksekusi. Menambahkan klausa lain dalam kasus ini hanya meningkatkan kompleksitas metode.
Chuck Conway
Saya tidak setuju berdasarkan pada kemampuan untuk tidak memiliki semua logika yang bersangkutan dalam blok ringkas & deterministik. Fokusnya adalah pada memanipulasi myStringnilai & kode yang berhasil ifblok adalah logika yang dihasilkan jika string! = Null. Oleh karena itu karena fokusnya adalah pada logika tambahan yang memanipulasi nilai tertentu saya pikir itu harus dienkapsulasi dalam sebuah elseblok. Kalau tidak, ini membuka potensi ketika saya menunjukkan untuk secara tidak sengaja memisahkan logika & memperkenalkan konsekuensi yang tidak diinginkan. Mungkin tidak terjadi sepanjang waktu, tapi ini benar-benar pertanyaan gaya pendapat praktik terbaik .
atconway