jika pernyataan - evaluasi hubung singkat vs keterbacaan

90

Terkadang, sebuah ifpernyataan bisa jadi agak rumit atau panjang, jadi demi keterbacaan, lebih baik mengekstrak panggilan rumit sebelum if.

misalnya ini:

if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall())
{
    // do stuff
}

ke dalam ini

bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();

if (b1 || b2)
{
    //do stuff
}

(contoh yang diberikan tidak terlalu buruk, itu hanya untuk ilustrasi ... bayangkan panggilan lain dengan banyak argumen, dll.)

Tetapi dengan ekstraksi ini saya kehilangan evaluasi sirkuit pendek (SCE).

  1. Apakah saya benar-benar kehilangan SCE setiap saat? Apakah ada skenario di mana kompilator diizinkan untuk "mengoptimalkannya" dan masih menyediakan SCE?
  2. Adakah cara untuk menjaga agar cuplikan kedua tetap terbaca tanpa kehilangan SCE?
relaxxx
sumber
20
Latihan menunjukkan bahwa sebagian besar jawaban tentang kinerja yang akan Anda lihat di sini atau di tempat lain dalam banyak kasus salah (4 salah 1 benar). Saran saya selalu lakukan profiling dan periksa sendiri, Anda akan terhindar dari "pengoptimalan prematur" dan mempelajari hal-hal baru.
Marek R
25
@MarekR bukan hanya tentang kinerja, ini tentang kemungkinan efek samping di OtherCunctionCall ...
relaxxx
3
@David saat merujuk situs lain, sering kali membantu untuk menunjukkan bahwa pengeposan silang tidak disukai
gnat
7
Jika keterbacaan adalah perhatian utama Anda, jangan panggil fungsi dengan efek samping di dalam jika bersyarat
Morgen
3
Pemilih potensial dekat: baca pertanyaannya lagi. Bagian (1) tidak berbasis opini, sedangkan bagian (2) dapat dengan mudah berhenti menjadi berbasis opini melalui suntingan yang menghilangkan referensi ke "praktik terbaik" yang seharusnya, seperti yang akan saya lakukan.
duplode

Jawaban:

119

Salah satu solusi alami akan terlihat seperti ini:

bool b1 = SomeCondition();
bool b2 = b1 || SomeOtherCondition();
bool b3 = b2 || SomeThirdCondition();
// any other condition
bool bn = bn_1 || SomeFinalCondition();

if (bn)
{
  // do stuff
}

Manfaatnya adalah mudah dipahami, dapat diterapkan pada semua kasus, dan memiliki perilaku korsleting.


Ini adalah solusi awal saya: Pola yang baik dalam pemanggilan metode dan badan loop-for adalah sebagai berikut:

if (!SomeComplicatedFunctionCall())
   return; // or continue

if (!SomeOtherComplicatedFunctionCall())
   return; // or continue

// do stuff

Seseorang mendapat manfaat kinerja bagus yang sama dari evaluasi sirkuit pendek, tetapi kodenya terlihat lebih mudah dibaca.

Horia Coman
sumber
4
@ relaxxx: Saya mengerti, tetapi "lebih banyak hal yang harus dilakukan setelah if" juga merupakan tanda bahwa fungsi atau metode Anda terlalu besar, dan harus dipecah menjadi yang lebih kecil. Itu tidak selalu menjadi cara terbaik tetapi sering kali begitu!
mike3996
2
ini melanggar prinsip daftar putih
JoulinRouge
13
@ JoulinRouge: Menarik, saya belum pernah mendengar tentang prinsip ini. Saya sendiri lebih suka pendekatan "hubung singkat" ini karena manfaatnya pada keterbacaan: ini mengurangi lekukan dan menghilangkan kemungkinan bahwa sesuatu terjadi setelah blok menjorok.
Matthieu M.
2
Apakah lebih mudah dibaca? Beri nama b2dengan benar dan Anda akan mendapatkannya someConditionAndSomeotherConditionIsTrue, tidak terlalu berarti. Juga, saya harus menyimpan banyak variabel di tumpukan mental saya selama latihan ini (dan sampai saya berhenti bekerja dalam ruang lingkup ini). Saya akan menggunakan SJuan76solusi nomor 2 atau hanya memasukkan semuanya ke dalam fungsi.
Nathan Cooper
2
Saya belum membaca semua komentar tetapi setelah pencarian cepat saya tidak menemukan keuntungan besar dari potongan kode pertama yaitu debugging. Menempatkan hal-hal langsung ke dalam pernyataan-if alih-alih menetapkannya ke variabel sebelumnya dan kemudian menggunakan variabel sebagai gantinya membuat debugging lebih sulit daripada yang seharusnya. Menggunakan variabel juga memungkinkan untuk mengelompokkan nilai secara semantik sehingga meningkatkan keterbacaan.
rbaleksandar
30

Saya cenderung memecah kondisi menjadi beberapa baris, yaitu:

if( SomeComplicatedFunctionCall()
 || OtherComplicatedFunctionCall()
  ) {

Bahkan saat berurusan dengan banyak operator (&&), Anda hanya perlu memajukan indensi dengan setiap pasangan tanda kurung. SCE masih bekerja - tidak perlu menggunakan variabel. Menulis kode dengan cara ini membuatnya lebih siap untuk saya selama bertahun-tahun. Contoh yang lebih kompleks:

if( one()
 ||( two()> 1337
  &&( three()== 'foo'
   || four()
    )
   )
 || five()!= 3.1415
  ) {
AmigoJack
sumber
28

Jika Anda memiliki rantai kondisi yang panjang dan apa yang menyebabkan beberapa korsleting, maka Anda dapat menggunakan variabel sementara untuk menggabungkan beberapa kondisi. Mengambil contoh Anda adalah mungkin untuk melakukan mis

bool b = SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
if (b && some_other_expression) { ... }

Jika Anda memiliki kompiler berkemampuan C ++ 11, Anda dapat menggunakan ekspresi lambda untuk menggabungkan ekspresi menjadi fungsi, mirip dengan di atas:

auto e = []()
{
    return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
};

if (e() && some_other_expression) { ... }
Beberapa programmer
sumber
21

1) Ya, Anda tidak lagi memiliki SCE. Jika tidak, Anda akan memilikinya

bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();

bekerja dengan satu cara atau yang lain tergantung apakah ada if pernyataan nanti. Terlalu rumit.

2) Ini berbasis opini, tetapi untuk ekspresi yang cukup kompleks, Anda dapat melakukan:

if (SomeComplicatedFunctionCall()
    || OtherComplicatedFunctionCall()) {

Jika terlalu rumit, solusi yang pasti adalah membuat fungsi yang mengevaluasi ekspresi dan memanggilnya.

SJuan76
sumber
21

Anda juga bisa menggunakan:

bool b = someComplicatedStuff();
b = b || otherComplicatedStuff(); // it has to be: b = b || ...;  b |= ...; is bitwise OR and SCE is not working then 

dan SCE akan bekerja.

Tapi itu tidak lebih mudah dibaca dari pada contoh:

if (
    someComplicatedStuff()
    ||
    otherComplicatedStuff()
   )
KIIV
sumber
3
Saya tidak tertarik untuk menggabungkan boolean dengan operator bitwise. Itu sepertinya tidak diketik dengan baik bagi saya. Umumnya saya menggunakan apa pun yang terlihat paling mudah dibaca kecuali saya bekerja pada level yang sangat rendah dan siklus prosesor dihitung.
Semut
3
Saya telah menggunakan secara b = b || otherComplicatedStuff();spesifik dan @SargeBorsch melakukan pengeditan untuk menghapus SCE. Terima kasih telah memperhatikan saya tentang perubahan itu @Ant.
KIIV
14

1) Apakah saya benar-benar kehilangan SCE setiap saat? Apakah kompilator diperbolehkan untuk "mengoptimalkannya" dan masih menyediakan SCE?

Saya tidak berpikir pengoptimalan seperti itu diperbolehkan; terutama OtherComplicatedFunctionCall()mungkin memiliki beberapa efek samping.

2) Apa praktik terbaik dalam situasi seperti itu? Apakah hanya kemungkinan (ketika saya ingin SCE) untuk memiliki semua yang saya butuhkan secara langsung di dalam jika dan "hanya memformatnya agar dapat dibaca semaksimal mungkin"?

Saya lebih suka merefaktornya menjadi satu fungsi atau satu variabel dengan nama deskriptif; yang akan menjaga evaluasi hubung singkat dan keterbacaan:

bool getSomeResult() {
    return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
}

...

if (getSomeResult())
{
    //do stuff
}

Dan saat kami menerapkan getSomeResult()berdasarkan SomeComplicatedFunctionCall()dan OtherComplicatedFunctionCall(), kami dapat menguraikannya secara rekursif jika masih rumit.

songyuanyao
sumber
2
Saya suka ini karena Anda dapat memperoleh beberapa keterbacaan dengan memberikan fungsi pembungkus nama deskriptif (meskipun mungkin bukan getSomeResult), terlalu banyak jawaban lain tidak benar-benar menambah nilai
aw04
9

1) Apakah saya benar-benar kehilangan SCE setiap saat? Apakah compiler adalah beberapa skenario diperbolehkan untuk "mengoptimalkannya" dan masih menyediakan SCE?

Tidak, tidak, tetapi diterapkan secara berbeda:

if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall())
{
    // do stuff
}

Di sini, kompilator bahkan tidak akan berjalan OtherComplicatedFunctionCall()jika SomeComplicatedFunctionCall()mengembalikan nilai true.

bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();

if (b1 || b2)
{
    //do stuff
}

Di sini, kedua fungsi akan berjalan karena harus disimpan ke dalam b1dan b2. Ff b1 == truemaka b2tidak akan dievaluasi (SCE). Tapi OtherComplicatedFunctionCall()sudah dijalankan.

Jika b2tidak digunakan di tempat lain, kompilator mungkin cukup pintar untuk menyebariskan pemanggilan fungsi di dalam fungsi jika tidak memiliki efek samping yang dapat diamati.

2) Apa praktik terbaik dalam situasi seperti itu? Apakah hanya kemungkinan (ketika saya ingin SCE) untuk memiliki semua yang saya butuhkan secara langsung di dalam jika dan "hanya memformatnya agar dapat dibaca sebisa mungkin"?

Itu tergantung. Apakah Anda perlu OtherComplicatedFunctionCall() menjalankan karena efek samping atau kinerja hit fungsi minimal maka Anda harus menggunakan pendekatan kedua untuk keterbacaan. Jika tidak, tetap gunakan SCE melalui pendekatan pertama.

Hatted Rooster
sumber
8

Kemungkinan lain yang korsleting dan memiliki kondisi di satu tempat:

bool (* conditions [])()= {&a, &b, ...}; // list of conditions
bool conditionsHold = true;
for(int i= 0; i < sizeOf(conditions); i ++){
     if (!conditions[i]()){;
         conditionsHold = false;
         break;
     }
}
//conditionsHold is true if all conditions were met, otherwise false

Anda dapat menempatkan loop ke dalam fungsi dan membiarkan fungsi menerima daftar kondisi dan mengeluarkan nilai boolean.

levilime.dll
sumber
1
@Erbureth Tidak, mereka tidak. Elemen-elemen dari array adalah penunjuk fungsi, mereka tidak akan dieksekusi sampai fungsi dipanggil dalam loop.
Barmar
Terima kasih Barmar, tetapi saya telah membuat pengeditan, Erbureth benar, sebelum pengeditan (saya pikir pengeditan saya akan ditampilkan secara visual lebih langsung).
levilime
4

Sangat aneh: Anda berbicara tentang keterbacaan ketika tidak ada yang menyebutkan penggunaan komentar di dalam kode:

if (somecomplicated_function() || // let me explain what this function does
    someother_function())         // this function does something else
...

Selain itu, saya selalu mengawali fungsi saya dengan beberapa komentar, tentang fungsi itu sendiri, tentang input dan outputnya, dan terkadang saya memberikan contoh, seperti yang Anda lihat di sini:

/*---------------------------*/
/*! interpolates between values
* @param[in] X_axis : contains X-values
* @param[in] Y_axis : contains Y-values
* @param[in] value  : X-value, input to the interpolation process
* @return[out]      : the interpolated value
* @example          : interpolate([2,0],[3,2],2.4) -> 0.8
*/
int interpolate(std::vector<int>& X_axis, std::vector<int>& Y_axis, int value)

Jelas sekali pemformatan yang digunakan untuk komentar Anda mungkin bergantung pada lingkungan pengembangan Anda (Visual studio, JavaDoc di bawah Eclipse, ...)

Sejauh menyangkut SCE, saya berasumsi bahwa yang Anda maksud adalah sebagai berikut:

bool b1;
b1 = somecomplicated_function(); // let me explain what this function does
bool b2 = false;
if (!b1) {                       // SCE : if first function call is already true,
                                 // no need to spend resources executing second function.
  b2 = someother_function();     // this function does something else
}

if (b1 || b2) {
...
}
Dominique
sumber
-7

Keterbacaan diperlukan jika Anda bekerja di perusahaan dan kode Anda akan dibaca oleh orang lain. Jika Anda menulis program untuk diri sendiri, terserah Anda jika Anda ingin mengorbankan kinerja demi kode yang dapat dipahami.

br0lly
sumber
23
Ingatlah bahwa "kamu dalam waktu enam bulan" pasti adalah "orang lain", dan "kamu besok" terkadang bisa. Saya tidak akan pernah mengorbankan keterbacaan untuk kinerja sampai saya memiliki beberapa bukti kuat bahwa ada masalah kinerja.
Martin Bonner mendukung Monica