Balikkan pernyataan "jika" untuk mengurangi bersarang


272

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".


1
Saya percaya bahwa pengecekan pengecekan dan pengembalian di awal adalah baik-baik saja, tetapi saya akan mengubah kondisinya sehingga Anda mengecek pengecualian secara langsung daripada bukan sesuatu (yaitu jika (beberapa condtion) kembali).
bruceatk

36
Tidak, itu tidak akan melakukan apa pun untuk kinerja.
Seth Carnegie

3
Saya akan tergoda untuk melempar ArgumentException jika metode saya diteruskan data yang buruk.
asawyer

1
@asawyer Ya, ada seluruh sisi-diskusi di sini tentang fungsi yang terlalu memaafkan input yang tidak masuk akal - yang bertentangan dengan menggunakan kegagalan pernyataan. Menulis Kode Padat membuka mata saya untuk ini. Dalam hal ini, ini akan menjadi sesuatu seperti ASSERT( exampleParam > 0 ).
Greg Hendershott

4
Pernyataan adalah untuk kondisi internal, bukan parameter. Anda akan mulai dengan memvalidasi parameter, menyatakan bahwa keadaan internal Anda sudah benar, dan kemudian melakukan operasi. Dalam rilis rilis, Anda bisa meninggalkan pernyataan atau memetakannya ke pematian komponen.
Simon Richter

Jawaban:


296

Pengembalian di tengah metode tidak selalu buruk. Mungkin lebih baik untuk kembali segera jika itu membuat maksud kode menjadi lebih jelas. Sebagai contoh:

double getPayAmount() {
    double result;
    if (_isDead) result = deadAmount();
    else {
        if (_isSeparated) result = separatedAmount();
        else {
            if (_isRetired) result = retiredAmount();
            else result = normalPayAmount();
        };
    }
     return result;
};

Dalam hal ini, jika _isDeadbenar, kita dapat segera keluar dari metode. Mungkin lebih baik menyusunnya seperti ini:

double getPayAmount() {
    if (_isDead)      return deadAmount();
    if (_isSeparated) return separatedAmount();
    if (_isRetired)   return retiredAmount();

    return normalPayAmount();
};   

Saya telah mengambil kode ini dari katalog refactoring . Refactoring khusus ini disebut: Ganti Bersarang Bersyarat dengan Klausa Penjaga.


13
Ini adalah contoh yang sangat bagus! Kode refactored lebih dibaca seperti pernyataan kasus.
Otherside

16
Mungkin hanya masalah selera: Saya sarankan mengubah "jika" menjadi "lain" ke-2 dan ke-3 agar lebih mudah dibaca. Jika seseorang mengabaikan pernyataan "pengembalian", masih akan jelas bahwa kasus berikut ini hanya diperiksa jika yang sebelumnya gagal, yaitu bahwa urutan pemeriksaan itu penting.
foraidt

2
Dalam contoh sederhana ini, id setuju bahwa cara ke-2 lebih baik, tetapi hanya karena SO-nya jelas apa yang terjadi.
Andrew Bullock

Sekarang, bagaimana kita mengambil kode yang cantik itu menulis dan menjaga visual studio dari memecahnya dan meletakkan pengembalian pada baris yang berbeda? Itu benar-benar membuatku kesal karena memformat ulang kode seperti itu. Membuat kode ini sangat mudah dibaca UGLY.
Mark T

@ Mark T Ada pengaturan di studio visual untuk mencegahnya memecah kode itu.
Aaron Smith

333

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.


41
satu titik keluar? Siapa yang butuh itu?
sq33G

3
@ sq33G: Pertanyaan tentang SESE (dan jawabannya tentu saja) sangat fantastis. Terima kasih untuk tautannya!
Jon

Saya terus mendengar jawaban bahwa ada beberapa orang yang menganjurkan untuk titik keluar tunggal, tetapi saya tidak pernah melihat seseorang yang menganjurkan itu, terutama dalam bahasa seperti C #.
Thomas Bonini

1
@AndreasBonini: Tidak adanya bukti bukanlah bukti ketidakhadiran. :-)
Jon

Ya tentu saja, saya hanya merasa aneh bahwa semua orang merasa perlu untuk mengatakan bahwa beberapa orang menyukai pendekatan lain jika orang-orang itu tidak merasa perlu untuk mengatakannya sendiri =)
Thomas Bonini

102

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.


2
Pengembalian berganda dalam suatu fungsi memang memengaruhi programmer pemeliharaan. Saat men-debug suatu fungsi, mencari nilai pengembalian yang nakal, pengelola harus meletakkan breakpoint di semua tempat yang bisa kembali.
EvilTeach

10
Saya akan memasang breakpoint pada brace pembuka. Jika Anda melewati fungsi, Anda tidak hanya dapat melihat pemeriksaan dilakukan secara berurutan, tetapi juga akan sangat jelas pemeriksaan mana fungsi yang digunakan untuk menjalankan itu.
John Dunagan

3
Setuju dengan John di sini ... Saya tidak melihat masalah hanya dengan melangkah melalui seluruh fungsi.
Nailer

5
@ Penjara Sangat terlambat, saya terutama setuju, karena jika fungsi terlalu besar untuk dilalui, itu harus dipisahkan menjadi beberapa fungsi lagian!
Aidiakapi

1
Setuju dengan John juga. Mungkin itu adalah sekolah tua yang dianggap menempatkan break point di bagian bawah tetapi jika Anda ingin tahu apa fungsi yang kembali maka Anda akan melangkah melalui fungsi tersebut untuk melihat mengapa mengembalikan apa yang dikembalikan pula. Jika Anda hanya ingin melihat apa yang dikembalikannya maka letakkan di braket terakhir.
user441521

70

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 doublesebaliknya:

public void myfunction(double exampleParam){
    if(exampleParam > 0){
        //Body will *not* be executed if Double.IsNan(exampleParam)
    }
}

Bandingkan dengan inversi yang tampaknya setara:

public void myfunction(double exampleParam){
    if(exampleParam <= 0)
        return;
    //Body *will* be executed if Double.IsNan(exampleParam)
}

Jadi dalam keadaan tertentu apa yang tampaknya benar terbalik ifmungkin tidak.


4
Juga harus dicatat bahwa pemulihan cepat membalikkan exampleParam> 0 ke exampleParam <0 bukan exampleParam <= 0 yang telah menarik saya keluar.
julukan

1
Dan inilah mengapa cek itu harus di muka dan dikembalikan juga mengingat dev berpikir nan seharusnya menghasilkan bail out.
user441521

51

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.


6
Anda baru tahu mengapa pengecualian adalah UglyAndEvil [tm] ... ;-) Pengecualian adalah goto dalam penyamaran yang mewah dan mahal.
EricSchaefer

16
@ Eric Anda pasti telah terkena kode yang sangat buruk. Ini cukup jelas ketika mereka digunakan secara tidak benar, dan mereka umumnya memungkinkan Anda untuk menulis kode berkualitas lebih tinggi.
Robert Paulson

Ini jawaban yang benar-benar saya cari! Ada jawaban bagus di sini, tetapi kebanyakan dari mereka fokus pada contoh, bukan pada alasan sebenarnya mengapa rekomendasi ini lahir.
Gustavo Mori

Ini adalah jawaban terbaik, karena ini menjelaskan mengapa seluruh argumen ini muncul sejak awal.
Buttle Butkus

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.
v.oddou

30

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


2
Persis. Ini lebih merupakan refactoring. Ini membantu membaca kode lebih mudah seperti yang Anda sebutkan.
rpattabi

'kembali' tidak cukup dan mengerikan untuk klausa penjaga. Saya akan melakukan: jika (ex <= 0) melempar WrongParamValueEx ("[MethodName] Nilai input param 1 buruk {0} ... dan Anda harus menangkap pengecualian dan menulis ke dalam log aplikasi Anda
JohnJohnGa

3
@ John. Anda benar jika ini sebenarnya adalah kesalahan. Tetapi seringkali tidak. Dan bukannya memeriksa sesuatu di setiap tempat di mana metode disebut metode, hanya memeriksa dan kembali melakukan apa-apa.
Piotr Perak

3
Saya akan benci membaca metode yang dua layar kode, titik, ifatau tidak. lol
jpmc26

1
Saya pribadi lebih suka pengembalian awal karena alasan ini (juga: menghindari bersarang). Namun, ini tidak terkait dengan pertanyaan awal tentang kinerja dan mungkin harus menjadi komentar.
Patrick M

22

Itu tidak hanya mempengaruhi estetika, tetapi juga mencegah kode bersarang.

Ini sebenarnya dapat berfungsi sebagai prasyarat untuk memastikan bahwa data Anda juga valid.


18

Ini tentu saja subjektif, tetapi saya pikir ini sangat meningkat pada dua poin:

  • Sekarang segera jelas bahwa fungsi Anda tidak ada lagi yang harus dilakukan jika condition ditahan.
  • Itu membuat tingkat sarang turun. Nesting lebih mudah dibaca daripada yang Anda kira.

15

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| | finallymembangun dan usingmemblokir, 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.


Itu bukan satu-satunya alasan. Ada alasan akademis yang merujuk pada kode semu yang tidak ada hubungannya dengan pertimbangan praktis seperti membersihkan barang. Alasan akustik ini berkaitan dengan menghormati bentuk dasar konstruksi imperatif. Cara yang sama Anda tidak meletakkan loop keluar di tengahnya. Dengan cara ini, invarian dapat dideteksi dengan ketat dan perilaku dapat dibuktikan. Atau pemutusan hubungan kerja dapat dibuktikan.
v.oddou

1
Berita: sebenarnya saya telah menemukan alasan yang sangat praktis mengapa istirahat dini dan pengembaliannya buruk, dan itu adalah konsekuensi langsung dari apa yang saya katakan, analisis statis. ini dia, kertas panduan penggunaan kompiler intel C ++: d3f8ykwhia686p.cloudfront.net/1live/intel/… . Frasa kunci:a loop that is not vectorizable due to a second data-dependent exit
v.oddou

12

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.


11

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 ifpernyataan. Jadi daripada mengatakan:

if (something) {
    // a lot of indented code
}

Anda membalikkan kondisi dan mematahkan jika kondisi terbalik itu terpenuhi

if (!something) return false; // or another value to show your other code the function did not execute

// all the code from before, save a lot of tabs

return sama sekali tidak kotor goto . 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:

if (something) {
    do-something();
    if (something-else) {
        do-another-thing();
    } else {
        do-something-else();
    }
}

vs:

if (!something) return;
do-something();

if (!something-else) return do-something-else();
do-another-thing();

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.


6
Saya kira saya salah satu dari sedikit itu. Saya merasa lebih mudah membaca versi pertama. Jika bersarang membuat pohon keputusan lebih jelas. Di sisi lain, jika ada beberapa pra-kondisi, saya setuju lebih baik meletakkan semuanya di atas fungsi.
Otherside

@Otherside: sepenuhnya setuju. pengembalian yang ditulis dengan mode serial membuat otak Anda perlu membuat serial jalan yang memungkinkan. Ketika if-tree dapat dipetakan langsung ke beberapa logika transicent, misalnya ia dapat dikompilasi ke lisp tetapi mode pengembalian serial akan membutuhkan kompiler yang jauh lebih sulit untuk dilakukan. Poin di sini jelas tidak ada hubungannya dengan skenario hipotetis ini, itu ada hubungannya dengan analisis kode, peluang optimasi, bukti koreksi, bukti terminasi dan deteksi invarian.
v.oddou

1
Tidak mungkin ada orang yang lebih mudah membaca kode dengan lebih banyak sarang. Semakin banyak blok seperti sesuatu, semakin mudah untuk membaca sekilas. Tidak ada cara contoh pertama di sini saya lebih mudah dibaca dan hanya berjalan 2 sarang ketika sebagian besar kode seperti ini di dunia nyata masuk lebih dalam dan dengan setiap sarang dibutuhkan lebih banyak kekuatan otak untuk mengikuti.
user441521

1
Jika sarangnya dua kali lebih dalam, berapa lama waktu yang Anda butuhkan untuk menjawab pertanyaan: "Kapan Anda 'melakukan-sesuatu-yang lain'?" Saya pikir Anda akan kesulitan untuk menjawabnya tanpa pena dan kertas. Tapi Anda bisa menjawabnya dengan mudah jika semuanya dijaga ketat.
David Storfer

9

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.


8

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 ifpernyataan, 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.


7

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.

 bool PerformDefaultOperation()
 {
      bool succeeded = false;

      DataStructure defaultParameters;
      if ((defaultParameters = this.GetApplicationDefaults()) != null)
      {
           succeeded = this.DoSomething(defaultParameters);
      }

      return succeeded;
 }

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).


4
bool PerformDefaultOperation () {DataStructure defaultParameters = this.GetApplicationDefaults (); return (defaultParameters! = NULL && this.DoSomething (defaultParameters);} Di sana, perbaiki untuk Anda. :)
tchen

1
Contoh ini sangat mendasar dan melewatkan poin dari pola ini. Dapatkan sekitar 4 pemeriksaan lain di dalam fungsi ini dan kemudian beri tahu kami bahwa itu lebih mudah dibaca, karena tidak.
user441521

5

Banyak alasan bagus tentang bagaimana kode itu terlihat . Tetapi bagaimana dengan hasil ?

Mari kita lihat beberapa kode C # dan formulir yang dikompilasi IL-nya:


using System;

public class Test {
    public static void Main(string[] args) {
        if (args.Length == 0) return;
        if ((args.Length+2)/3 == 5) return;
        Console.WriteLine("hey!!!");
    }
}

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:

  1. Jika kondisi pertama salah, lompat ke kode tempat yang kedua.
  2. Jika benar melompat ke instruksi terakhir. (Catatan: instruksi terakhir adalah pengembalian).
  3. Dalam kondisi kedua hal yang sama terjadi setelah hasilnya dihitung. Bandingkan dan: dapatkan ke Console.WriteLine jika false atau sampai akhir jika ini benar.
  4. Cetak pesan dan kembali.

Jadi sepertinya kode itu akan melompat ke akhir. Bagaimana jika kita melakukan normal jika dengan kode bersarang?

using System;

public class Test {
    public static void Main(string[] args) {
        if (args.Length != 0 && (args.Length+2)/3 != 5) 
        {
            Console.WriteLine("hey!!!");
        }
    }
}

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.


5
Ini adalah info yang bagus - tetapi secara pribadi saya tidak peduli jika IL "mengalir lebih baik" karena orang-orang yang akan mengelola kode tidak akan melihat IL
JohnIdol

1
Anda benar-benar tidak dapat mengantisipasi bagaimana optimasi lolos dalam pembaruan C # compiler selanjutnya akan berfungsi dibandingkan dengan apa yang Anda lihat hari ini. Secara pribadi, saya tidak akan menghabiskan waktu untuk mencoba mengubah kode C # untuk menghasilkan hasil yang berbeda di IL, kecuali jika tes menunjukkan bahwa Anda memiliki masalah kinerja SEVERE dalam beberapa loop ketat di suatu tempat dan Anda kehabisan ide lain . Bahkan kemudian, saya akan berpikir lebih keras tentang opsi lain. Dalam nada yang sama, saya ragu Anda memiliki ide seperti apa jenis optimasi yang dilakukan oleh kompiler JIT dengan IL yang diumpankan ke sana untuk setiap platform ...
Craig

5

Secara teori, pembalikan ifdapat 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 .


4

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.


Saya tidak mengerti argumen kinerja Anda. Kondisinya boolean jadi apa pun hasilnya, selalu ada dua hasil ... Saya tidak melihat mengapa membalikkan pernyataan (atau tidak) akan mengubah hasil. (Kecuali jika Anda mengatakan menambahkan "TIDAK" ke kondisi tersebut menambahkan jumlah pemrosesan yang terukur ...
Oli

2
Optimalisasi bekerja seperti ini: Jika Anda memastikan bahwa kasus yang paling umum adalah di blok if daripada yang lain, maka CPU biasanya sudah memiliki pernyataan dari blok if yang dimuat dalam pipeline-nya. Jika kondisi kembali salah, CPU harus mengosongkan pipa dan ...
Otherside

Saya juga suka melakukan apa yang dikatakan orang lain hanya untuk keterbacaan, saya benci memiliki kasus negatif di blok "lalu" daripada "yang lain". Masuk akal untuk melakukan ini bahkan tanpa mengetahui atau mempertimbangkan apa yang dilakukan CPU
Andrew Bullock

3

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.


3

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 ifblok bersarang yang diperkenalkan (seperti yang telah ditunjukkan orang lain).


2
sana. saya butuh 3 bacaan panjang dari puluhan jawaban dalam 3 pertanyaan menanyakan hal yang sama (2 di antaranya dibekukan), untuk akhirnya menemukan seseorang yang menyebutkan NRVO. Wah ... terima kasih.
v.oddou

2

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.


2

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.


1

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.


0

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"


0

Gagasan saya adalah bahwa pengembalian "di tengah fungsi" seharusnya tidak begitu "subyektif". Alasannya cukup sederhana, ambil kode ini:

    fungsi do_something (data) {

      if (! is_valid_data (data)) 
            return false;


       do_something_that_take_an_hour (data);

       istance = new object_with_very_painful_constructor (data);

          if (istance tidak valid) {
               pesan eror( );
                kembali;

          }
       connect_to_database ();
       get_some_other_data ();
       kembali;
    }

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.


Dengan bahasa pengecualian Anda tidak memiliki masalah ini.
user441521

0

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.


Membaca jawaban graffic, sepertinya kode bersarang dioptimalkan oleh kompiler sedemikian rupa sehingga kode yang dieksekusi lebih cepat daripada menggunakan beberapa pengembalian. Namun, meskipun beberapa pengembalian lebih cepat, pengoptimalan ini mungkin tidak akan mempercepat aplikasi Anda sebanyak itu ... :)
hangy

1
Saya tidak setuju - Hukum Pertama adalah tentang mendapatkan algoritma yang benar. Tetapi kita semua adalah Insinyur, yaitu menyelesaikan masalah yang benar dengan sumber daya yang kita miliki dan lakukan dalam anggaran yang tersedia. Aplikasi yang terlalu lambat tidak cocok untuk tujuan.
David Allan Finch
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.