Senyawa styling DAN / ATAU jika pernyataan

13

Bagaimana Anda mendesain senyawa kompleks AND / OR jika pernyataan untuk keterbacaan maksimum? Bagaimana Anda indentasi dan di mana Anda menempatkan garis terputus? Situasi khusus saya adalah seperti berikut ini. Ini jelas lebih baik daripada menghancurkan semuanya menjadi satu baris, tetapi masih terlihat berantakan.

if (
    (
        x == y
        && a != b
        && p.isGood() 
        && (
            i + u == b
            || q >= a
        )
    )
    || k.isSomething()
    || m > n
) {
    doSomething();
}
Jojo
sumber
1
Apakah indentasi yang konyol dan kurung / struktur kurung disengaja atau bagian dari gaya?
Ed S.
Lucu. Saya menanyakan pertanyaan yang sama ini pada SO seminggu yang lalu dan sudah ditutup. Senang melihat pertanyaan ini hidup-hidup di suatu tempat!
Eric Belair

Jawaban:

6

Buat variabel boolean untuk setiap langkah kecil:

bool step1 = i + u == b || q >= a;
bool step2 = a != b && p.isGood() && group1;
bool step3 = group2 || k.isSomething() || m > n;
if (step3) { doSomething(); }

Ini tentu saja mirip dengan jawaban Lacrymology, kecuali dengan nama yang berbeda untuk setiap langkah.

Jika Anda menyebutkan step1, step2dan step3dengan cara yang masuk akal secara konseptual, ini seharusnya yang paling mudah dibaca. p.isGood()dan k.isSomething()kadang-kadang dapat dipanggil dalam situasi di mana itu tidak ada dalam kode asli Anda, jadi ini tidak akan menjadi pilihan jika fungsi-fungsi itu mahal atau jika Anda menjalankan kode ini dalam loop yang sangat ketat.

Di sisi lain, Anda tidak perlu khawatir tentang hit kinerja yang membuat variabel baru mungkin terjadi; kompiler yang baik akan mengoptimalkannya.

Contoh dengan deteksi tabrakan persegi panjang (yang mungkin tidak Anda gunakan karena hit kinerja yang disebutkan di atas):

if((a.x + a.width >= b.x || b.x + b.width >= a.x)
 && (a.y + a.height >= b.y || b.y + b.width >= a.y)
)
{ collision(); }

Mungkin menjadi:

bool horizMatch = a.x + a.width >= b.x || b.x + b.width >= a.x;
bool vertMatch = a.y + a.height >= b.y || b.y + b.width >= a.y;
if(horizMatch && vertMatch) { collision(); }

Juga, jika Anda ingin meninggalkan kode Anda apa adanya, saya pikir itu akan baik-baik saja juga. Jujur saya pikir kode Anda cukup terbaca. Jelas saya tidak tahu apa sebenarnya a b x y i u p k m n, tetapi sejauh struktur berjalan, itu terlihat baik bagi saya.

Rei Miyasaka
sumber
8

Saya biasanya faktor ulang kode saya menjadi lebih modular jika kondisi saya menjadi rumit.

JohnFx
sumber
Saya akan menghindari refractoring dalam situasi ini. Akan konyol untuk menguji fungsi kecil seperti itu dalam isolasi, dan memiliki definisi yang menggantung di luar fungsi membuat kode kurang jelas.
Rei Miyasaka
Itu tergantung pada seberapa baik Anda refactor. Saya berpendapat bahwa itu membuat kode Anda LEBIH jauh lebih jelas untuk memiliki nama fungsi yang bermakna daripada serangkaian persyaratan yang tampak seperti buku teks aljabar yang dilemparkan ke dalam kode Anda.
JohnFx
Namun secara visual, Anda akan memiliki fungsi Anda menggantung di luar, dan tidak akan segera jelas apa fungsi sebenarnya. Ini sementara kotak hitam sampai Anda gulir ke atas. Kecuali jika Anda menggunakan bahasa yang memungkinkan fungsi dalam fungsi, saya tidak berpikir itu akan sangat nyaman bagi pembaca atau penulis terlepas dari seberapa baik Anda refraktor. Dan jika Anda menggunakan bahasa yang memungkinkan fungsi dalam fungsi, maka kemungkinan sintaksnya hampir tidak berbeda dengan mendeklarasikan suatu binding atau variabel sebagai gantinya, misalnya let x = a > batau let f a b = a > b.
Rei Miyasaka
Variabel akan bekerja sama baiknya. Saya akan mempertimbangkan refactoring itu juga.
JohnFx
Ah, baiklah.
Rei Miyasaka
8

Saya akan melakukan sesuatu yang lebih seperti ini, pada tingkat kerumitan ini

bool doIt = x == y && a != b && p.isGood();
doIt &= ( i + u == b || q >= a);
doIt |= k.isSomething() || m > n;

if(doIt)
{
    doSomething();
}

itu jelek, tapi itu bisa dibaca dan saya cukup yakin kompiler akan tahu bagaimana cara membuatnya.

Di sisi lain, jika saya pernah melihat diri saya dalam situasi menulis pernyataan IF seperti itu, saya memikirkan kembali solusinya, karena saya TERTENTU ada cara untuk melakukannya secara lebih sederhana, atau setidaknya mengabstraksikan beberapa kondisi itu (misalnya: mungkin x == y && a != b && p.isGood()benar-benar hanya berarti this->isPolygon()dan saya dapat membuat metode itu;

Lacrymology
sumber
4

Saya menjadi kurang terobsesi dengan perataan vertikal dari waktu ke waktu, tetapi bentuk umum saya dengan ekspresi multisaluran ...

if (   (   (expr1 == expr2)
        || (expr3 == expr4)
        || (expr5 == expr6)
       )
    && (   (expr7 == expr8)
        || (expr9 == expra)
       )
   )
{
  blah;
}

Poin-poin penting ...

  • Paren tutup sejajar secara vertikal dengan paren terbuka, seperti dengan kawat gigi.
  • Sub-ekspresi yang sesuai dalam satu baris berada dalam satu baris, dan disejajarkan secara vertikal di sebelah kiri. Di mana itu membantu keterbacaan, operator infiks dalam bagian-bagian single-line juga selaras secara vertikal.
  • Kawat gigi penutup secara alami membuat garis yang hampir kosong, membantu mengelompokkan berbagai hal secara visual.

Terkadang, saya akan memformat +dan *atau beberapa operator lain juga seperti ini. Cukup banyak ekspresi kompleks yang mengambil bentuk penjumlahan-produk-atau-jumlah-produk (yang dapat merujuk pada "jumlah" dan "produk" boolean) sehingga mungkin cukup umum bahwa gaya yang konsisten untuk itu bermanfaat.

Berhati-hatilah dengan ini. Sering kali lebih baik untuk refactor (memindahkan bagian dari ekspresi ke dalam suatu fungsi, atau menghitung dan menyimpan bagian perantara dalam suatu variabel) daripada menggunakan lekukan untuk mencoba membuat ekspresi overcomplex lebih mudah dibaca.

Jika Anda lebih suka menumpuk parens dekat Anda di sisi kanan, saya tidak membencinya , tapi saya kira itu tidak terlalu buruk. Terlalu jauh, Anda mengambil risiko bahwa kesalahan dapat membuat lekukan salah menggambarkan apa yang dilakukan oleh kurung.

if (   (   (expr1 == expr2)
        || (expr3 == expr4)
        || (expr5 == expr6))

    && (   (expr7 == expr8)
        || (expr9 == expra)))
{
  blah;
}
Steve314
sumber
+1 Saya suka gaya Anda. Itu menjawab pertanyaan saya secara langsung, tetapi saya pikir Rei Miyasaka memakukan akar masalahnya. Jika saya malas melakukan metode Rei, saya akan menggunakan gaya Anda.
JoJo
Wah ini sangat bagus.
Rei Miyasaka
1

http://www.codinghorror.com/blog/2006/01/flattening-arrow-code.html

Saya setuju dengan jawaban JohnFx dan juga oleh Lacrymology. Saya akan membangun banyak fungsi (lebih disukai statis) yang mencapai tujuan kecil dan kemudian membangunnya dengan cara yang cerdas.

Jadi, bagaimana dengan yang seperti ini? Catatan, ini bukan solusi yang sempurna, tetapi berhasil. Ada cara untuk membersihkan ini lebih lanjut, tetapi informasi yang lebih spesifik diperlukan. Catatan: kode ini harus berjalan sama cepatnya, karena kompilernya pintar.

// Currently based on members or global vars
// (which is often a bad idea too)
function doSomethingCondirionally()
{
  if (k.isSomething() || m > n)
  {
    doSomething();
    return;
  }

  // Else ... 
  if (x != y) return;
  if (a == b) return;
  if (!p.isGood()) return;

  // Final, positive check
  if (i + u == b || q >= a)
  {
    doSomething();
  }
}
Pekerjaan
sumber
Jika hanya memiliki satu titik keluar dari suatu fungsi adalah sesuatu yang Anda hargai (mis. Jika Anda menggunakan bahasa fungsional), ini mungkin bukan opsi terbaik, atau bahkan yang tersedia. Yang mengatakan, ya, inilah yang sering saya lakukan.
Rei Miyasaka
Bagaimana jika itu bahasa yang ditafsirkan seperti Javascript?
JoJo
@ Rei Miyasaka, betapa saya menghargainya tergantung pada bahasa. Sementara saya suka keluarga bahasa LISP, saya tidak harus menggunakannya di tempat kerja. Jika saya harus faktor ulang fungsi orang lain tetapi tidak menyentuh kode lain (sering kenyataan), maka saya akan melakukan sesuatu seperti di atas. Jika saya dapat menulis / menulis ulang logika ini dari bawah, maka pendekatan saya akan berbeda, tetapi saya tidak dapat menulis kode seperti itu tanpa memiliki contoh spesifik tentang apa yang penulis coba lakukan di sini.
Pekerjaan
1
@Rei Miyasaka, Orang itu mungkin jenius atau penuh omong kosong. Saya tidak tahu segalanya, tapi saya ingin tahu pertahanan orang itu dari titik keluar tunggal. Ada beberapa diskusi tentang itu di sini dan di SO, dan kesan yang saya dapatkan adalah bahwa pendekatan ini telah populer di kalangan akademisi di tahun 80-an mungkin, tetapi tidak lagi penting, dan pada kenyataannya dapat menghambat keterbacaan. Tentu saja, jika Anda melakukan semuanya dalam gaya fungsional LINQ, maka masalah ini bahkan tidak akan muncul.
Pekerjaan
2
@ Job @Steve Saya pikir ini adalah pedoman yang lebih penting dalam bahasa yang membutuhkan alokasi eksplisit. Jelas tidak untuk setiap fungsi, tetapi mungkin kebiasaan yang diprogram untuk dipegang oleh pemula untuk menghindari lupa sumber daya gratis.
Rei Miyasaka
1

Untuk apa nilainya, saya terkejut melihat bahwa contoh Anda sangat mirip dengan predikat rumit yang saya tulis. Saya setuju dengan orang lain bahwa predikat yang rumit bukanlah yang terbaik untuk pemeliharaan atau keterbacaan, tetapi kadang-kadang muncul.

Biarkan saya menekankan bahwa Anda melakukan bagian ini dengan benar: && a != b JANGAN PERNAH meletakkan konektor logis di akhir baris, terlalu mudah untuk dilewatkan secara visual. Tempat lain di mana Anda TIDAK PERNAH menempatkan operator di akhir baris adalah dalam rangkaian string, dalam bahasa dengan operator seperti itu.

Melakukan hal ini:

String a = b
   + "something"
   + c
   ;

Jangan lakukan ini:

String a = b +
   "something" +
   c;
Bruce Ediger
sumber
Apakah Anda memiliki logika atau studi yang mendukung pernyataan Anda untuk konektor logis, atau hanya preferensi Anda yang dinyatakan sebagai fakta? Saya sudah sering mendengar ini di berbagai tempat, dan tidak pernah memahaminya (tidak seperti persyaratan yoda, yang memiliki alasan [jika salah arah] yang valid).
Caleb Huitt - cjhuitt
@ Caleb - Matematika telah ditetapkan dalam mode itu selama berabad-abad. Saat membaca kode, kita fokus pada sisi kiri setiap baris. Garis yang dimulai dengan operator jelas merupakan kelanjutan dari saluran sebelumnya, dan bukan pernyataan baru yang indentasi salah.
kevin cline
Saya juga suka awalan operator matematika. Saya menyadari hal itu sampai akhir karir pemrograman saya :)
JoJo
0

Jika kondisasinya rumit, biasanya indikasi bahwa itu harus dipecah menjadi beberapa bagian. Mungkin satu klausa dapat ditugaskan ke variabel perantara. Mungkin satu klausa dapat diubah menjadi metode pembantu. Saya biasanya memilih untuk tidak memiliki banyak ands dan ors dalam satu baris.

Zhehao Mao
sumber
0

Anda dapat memecah kode menjadi beberapa pernyataan, sehingga mudah dimengerti. Tapi ninja sungguhan akan melakukan hal seperti ini. :-)

if
(
    (
        x == y
    &&
        a != b
    &&
        p.isGood()
    &&
        (
            i + u == b
        ||
            q >= a
        )
    )
||
    k.isSomething()
||
    m > n
)
{
    doSomething();
}
Daniel Lubarov
sumber
5
Saya penggemar ruang putih, tapi ini terlalu empuk dengan garis hampir kosong untuk seleraku.
Steve314