Apakah blok lain meningkatkan kompleksitas kode? [Tutup]

18

Ini adalah contoh yang sangat sederhana . Ini belum tentu merupakan pertanyaan khusus bahasa, dan saya meminta Anda mengabaikan banyak cara lain fungsi dapat ditulis, dan perubahan yang dapat dilakukan untuk itu. . Warna adalah jenis yang unik

string CanLeaveWithoutUmbrella()
{
    if(sky.Color.Equals(Color.Blue))
    {
        return "Yes you can";
    }
    else
    {
        return "No you can't";
    }
}

Banyak orang yang saya temui, ReSharper, dan orang ini (yang komentarnya mengingatkan saya bahwa saya sudah lama ingin menanyakan hal ini) akan merekomendasikan refactoring kode untuk menghapus elseblok meninggalkan ini:

(Saya tidak ingat apa yang dikatakan mayoritas, saya mungkin tidak menanyakan hal ini jika tidak)

string CanLeaveWithoutUmbrella()
{
    if(sky.Color.Equals(Color.Blue))
    {
        return "Yes you can";
    }
    return "No you can't";
}

Pertanyaan: Apakah ada peningkatan kompleksitas yang dimasukkan dengan tidak menyertakan elseblok?

Saya mendapat kesan yang elselebih langsung menyatakan maksud, dengan menyatakan fakta bahwa kode di kedua blok terkait langsung.

Selain itu saya menemukan bahwa saya dapat mencegah kesalahan halus dalam logika, terutama setelah modifikasi kode di kemudian hari.

Ambil variasi contoh sederhana saya ini (Mengabaikan fakta oroperator karena ini adalah contoh yang sengaja disederhanakan):

bool CanLeaveWithoutUmbrella()
{
    if(sky.Color != Color.Blue)
    {
        return false;
    }
    return true;
}

Seseorang sekarang dapat menambahkan ifblok baru berdasarkan kondisi setelah contoh pertama tanpa segera mengenali dengan benar kondisi pertama menempatkan kendala pada kondisi mereka sendiri.

Jika ada elseblok, siapa pun yang menambahkan kondisi baru akan dipaksa untuk memindahkan isi elseblok (dan jika entah bagaimana mereka mengabaikannya heuristik akan menunjukkan kode tidak dapat dijangkau, yang tidak dalam kasus salah satu ifmembatasi) .

Tentu saja ada cara lain contoh spesifik harus didefinisikan, yang semuanya mencegah situasi itu, tetapi itu hanya contoh.

Panjang contoh yang saya berikan dapat memiringkan aspek visual dari ini, jadi anggaplah bahwa ruang yang digunakan untuk kurung relatif tidak signifikan terhadap sisa metode.

Saya lupa menyebutkan kasus di mana saya setuju dengan penghilangan blok lain, dan ketika menggunakan ifblok untuk menerapkan kendala yang harus dipenuhi secara logis untuk semua kode berikut, seperti cek kosong (atau pengawal lainnya) .

Selali Adobor
sumber
Pada kenyataannya, ketika ada pernyataan "jika" dengan "kembali", itu dapat dilihat sebagai "blok penjaga" di> 50% dari semua kasus. Jadi saya pikir kesalahpahaman Anda di sini bahwa "blok penjaga" adalah kasus luar biasa, dan contoh Anda kasus biasa.
Doc Brown
2
@AssortedTrailmix: Saya setuju bahwa kasus seperti di atas mungkin lebih mudah dibaca ketika menulis elseklausa (sesuai selera saya, akan lebih mudah dibaca ketika meninggalkan tanda kurung yang tidak perlu. Tapi saya pikir contohnya tidak dipilih dengan baik, karena dalam kasus di atas saya akan menulis return sky.Color == Color.Blue(tanpa if / else). Contoh dengan tipe pengembalian yang berbeda dari bool mungkin akan membuat ini lebih jelas
Doc Brown
1
seperti yang ditunjukkan dalam komentar di atas, contoh yang disarankan terlalu sederhana, mengundang jawaban spekulatif. Adapun bagian dari pertanyaan yang dimulai dengan "Seseorang sekarang dapat menambahkan baru jika blok ..." , itu telah ditanyakan dan dijawab berkali-kali sebelumnya, lihat misalnya Pendekatan untuk memeriksa beberapa kondisi? dan beberapa pertanyaan yang terkait dengannya.
nyamuk
1
Saya gagal melihat apa lagi yang harus dilakukan blok dengan prinsip KERING. KERING lebih berkaitan dengan abstraksi dan referensi ke kode yang biasa digunakan dalam bentuk fungsi, metode, dan objek daripada apa yang Anda minta. Pertanyaan Anda terkait dengan keterbacaan kode dan kemungkinan konvensi atau idiom.
Shashank Gupta
2
Voting untuk dibuka kembali. Jawaban yang bagus untuk pertanyaan ini tidak terlalu berdasarkan pada pendapat. Jika contoh yang diberikan dalam pertanyaan tidak memadai, memperbaikinya dengan mengeditnya akan menjadi hal yang wajar untuk dilakukan, bukan memilih untuk menutup karena alasan yang sebenarnya tidak benar.
Michael Shaw

Jawaban:

27

Dalam pandangan saya, blok eksplisit lain lebih disukai. Ketika saya melihat ini:

if (sky.Color != Blue) {
   ...
}  else {
   ...
}

Saya tahu bahwa saya berurusan dengan opsi yang saling eksklusif. Saya tidak perlu membaca apa yang ada di dalam blok if untuk mengetahui.

Ketika saya melihat ini:

if (sky.Color != Blue) {
   ...
} 
return false;

Pada pandangan pertama, terlihat bahwa ia mengembalikan false terlepas dan memiliki efek samping opsional langit tidak biru.

Seperti yang saya lihat, struktur kode kedua tidak mencerminkan apa yang sebenarnya dilakukan oleh kode tersebut. Itu buruk. Sebaliknya, pilih opsi pertama yang mencerminkan struktur logis. Saya tidak ingin harus memeriksa kembali / istirahat / melanjutkan logika di setiap blok untuk mengetahui aliran logis.

Mengapa ada orang yang lebih suka yang lain adalah misteri bagiku, semoga seseorang akan mengambil posisi sebaliknya akan menerangi aku dengan jawaban mereka.

Saya lupa menyebutkan kasus di mana saya setuju dengan penghilangan blok lain, dan ketika menggunakan blok if untuk menerapkan kendala yang harus dipenuhi secara logis untuk semua kode berikut, seperti cek-nol (atau pengawal lainnya) ).

Saya akan mengatakan bahwa kondisi penjaga baik-baik saja jika Anda meninggalkan jalan bahagia. Telah terjadi kesalahan atau kegagalan yang mencegah eksekusi biasa dari melanjutkan. Ketika ini terjadi, Anda harus melempar pengecualian, mengembalikan kode kesalahan, atau apa pun yang sesuai untuk bahasa Anda.

Tak lama setelah versi asli dari jawaban ini, kode seperti ini ditemukan di proyek saya:

Foobar merge(Foobar left, Foobar right) {
   if(!left.isEmpty()) {
      if(!right.isEmpty()) {
          Foobar merged = // construct merged Foobar
          // missing return merged statement
      }
      return left;
   }
   return right;
}

Dengan tidak memasukkan return ke dalam elses, fakta bahwa statement return tidak ada telah diabaikan. Kalau sudah dipekerjakan, kode itu bahkan tidak akan dikompilasi. (Tentu saja, kekhawatiran yang jauh lebih besar yang saya miliki adalah bahwa tes yang ditulis pada kode ini sangat buruk untuk tidak mendeteksi masalah ini.)

Winston Ewert
sumber
Itu tentang meringkas pikiranku di atasnya, aku senang aku bukan satu-satunya yang berpikir seperti ini. Saya bekerja dengan programmer, alat, dan IDE lebih memilih penghapusan elseblok (itu bisa menyusahkan ketika saya terpaksa melakukannya hanya untuk tetap sejalan dengan konvensi untuk basis kode). Saya juga tertarik untuk melihat titik konter di sini.
Selali Adobor
1
Saya bervariasi dalam hal ini. Kadang-kadang orang lain tidak mendapatkan Anda banyak karena itu tidak mungkin memicu kesalahan logika halus OP menyebutkan - itu hanya kebisingan. Tapi kebanyakan saya setuju, dan kadang-kadang saya akan melakukan sesuatu seperti di } // elsemana yang lain biasanya pergi sebagai kompromi antara keduanya.
Telastyn
3
@ Telastyn, tapi apa yang Anda dapatkan dari melewatkan yang lain? Saya setuju manfaatnya sangat kecil dalam banyak situasi, tetapi bahkan untuk sedikit manfaat pun saya pikir itu layak dilakukan. Tentu saja, tampaknya aneh bagi saya untuk memasukkan sesuatu ke dalam komentar ketika itu bisa menjadi kode.
Winston Ewert
2
Saya pikir Anda memiliki kesalahpahaman yang sama dengan OP - "jika" dengan "kembali" sebagian besar dapat dilihat sebagai blok penjaga, jadi Anda membahas di sini kasus luar biasa, sedangkan kasus blok penjaga yang lebih sering adalah IMHO lebih mudah dibaca tanpa "lain".
Doc Brown
... jadi apa yang Anda tulis tidak salah, tetapi IMHO tidak sepadan dengan banyak upvotes yang Anda dapatkan.
Doc Brown
23

Alasan utama untuk menghapus elseblok yang saya temukan adalah indentasi berlebih. Hubungan arus pendek elseblok memungkinkan struktur kode yang lebih datar.

Banyak ifpernyataan pada dasarnya penjaga. Mereka mencegah dereferencing dari null pointer dan lainnya "jangan lakukan ini!" kesalahan. Dan mereka mengarah pada penghentian cepat fungsi saat ini. Sebagai contoh:

if (obj === NULL) {
    return NULL;
}
// further processing that now can assume obj attributes are set

Sekarang tampaknya elseklausa akan sangat masuk akal di sini:

if (obj === NULL) {
    return NULL;
}
else if (obj.color != Blue) {
   // handle case that object is not blue
}

Dan memang, jika hanya itu yang Anda lakukan, itu tidak lebih menjorok dari contoh di atas. Tapi, ini jarang semua yang Anda lakukan dengan struktur data multi-level nyata (suatu kondisi yang terjadi setiap saat ketika memproses format umum seperti JSON, HTML, dan XML). Jadi jika Anda ingin memodifikasi teks semua anak dari simpul HTML yang diberikan, ucapkan:

elements = tree.xpath(".//p");
if (elements === NULL) {
    return NULL;
}
p = elements[0]
if ((p.children === NULL) or (p.children.length == 0)) {
    return NULL;
}
for (c in p.children) {
    c.text = c.text.toUpperCase();
}
return p;

Para penjaga tidak meningkatkan indentasi kode. Dengan sebuah elseklausa, semua pekerjaan aktual mulai bergerak ke kanan:

elements = tree.xpath(".//p");
if (elements === NULL) {
    return NULL;
}
else {
    p = elements[0]
    if ((p.children === NULL) or (p.children.length == 0)) {
        return NULL;
    }
    else {
        for (c in p.children) {
            c.text = c.text.toUpperCase();
        }
        return p;
    }
}

Ini mulai memiliki gerakan ke kanan yang signifikan, dan ini adalah contoh yang cukup sepele, dengan hanya dua kondisi penjaga. Setiap penjaga tambahan menambahkan tingkat lekukan lain. Kode nyata yang saya tulis memproses XML atau mengonsumsi umpan JSON terperinci dapat dengan mudah menumpuk 3, 4, atau lebih banyak kondisi penjaga. Jika Anda selalu menggunakan else, Anda memiliki indentasi 4, 5, atau 6 level. Mungkin lebih. Dan itu pasti berkontribusi pada rasa kompleksitas kode, dan lebih banyak waktu yang dihabiskan untuk memahami apa yang sejalan dengan apa. Gaya keluar cepat, datar, dan cepat menghilangkan beberapa "struktur berlebih" itu, dan tampak lebih sederhana.

Addendum Komentar jawaban ini membuat saya menyadari bahwa itu tidak mungkin jelas bahwa NULL / penanganan kosong tidak satu-satunya alasan untuk conditional penjaga. Tidak hampir! Sementara contoh sederhana ini berfokus pada NULL / penanganan kosong dan tidak mengandung alasan lain, mencari struktur dalam seperti XML dan AST untuk konten "relevan", misalnya, sering memiliki serangkaian tes khusus untuk menghilangkan node yang tidak relevan dan tidak menarik. kasus. Serangkaian tes "jika node ini tidak relevan, kembali" akan menyebabkan drift kanan yang sama dengan NULL / blank guard. Dan dalam praktiknya, tes relevansi berikutnya sering digabungkan dengan NULL / penjaga kosong untuk struktur data yang lebih dalam dicari - sebuah whammy ganda untuk pergeseran ke kanan.

Jonathan Eunice
sumber
2
Ini adalah jawaban yang terperinci dan valid, tetapi saya akan menambahkan ini ke pertanyaan (awalnya terlintas di benak saya); Ada "pengecualian" di mana saya selalu menemukan elseblok berlebihan, ketika menggunakan ifblok untuk menerapkan kendala yang harus dipenuhi secara logis untuk semua kode berikut, seperti cek-nol (atau penjaga lainnya).
Selali Adobor
@AssortedTrailmix Saya percaya jika Anda mengecualikan kasus di mana Anda dapat secara rutin membuat keluar awal dalam thenklausa (seperti pernyataan penjaga berturut-turut), maka tidak ada nilai tertentu untuk menghindari elseklausa tertentu . Memang, itu akan mengurangi kejelasan dan berpotensi berbahaya (dengan gagal hanya menyatakan struktur alternatif yang ada / seharusnya ada).
Jonathan Eunice
Saya pikir masuk akal untuk menggunakan kondisi penjaga untuk mengeluarkan begitu Anda meninggalkan jalan bahagia. Namun, saya juga menemukan untuk kasus pemrosesan XML / JSON bahwa jika Anda memvalidasi input terhadap suatu skema terlebih dahulu, Anda mendapatkan pesan kesalahan yang lebih baik dan kode pembersih.
Winston Ewert
Ini adalah penjelasan sempurna dari kasus-kasus di mana hal lain dihindari.
Chris Schiffhauer
2
Saya akan membungkus API untuk tidak menghasilkan NULL konyol sebagai gantinya.
Winston Ewert
4

Ketika saya melihat ini:

if(something){
    return lamp;
}
return table;

Saya melihat idiom yang umum . Pola umum , yang di kepala saya diterjemahkan menjadi:

if(something){
    return lamp;
}
else return table;

Karena ini adalah ungkapan yang sangat umum dalam pemrograman, programmer yang terbiasa melihat kode semacam ini memiliki 'terjemahan' implisit di kepala mereka setiap kali mereka melihatnya, yang 'mengubahnya' menjadi makna yang tepat.

Saya tidak perlu yang eksplisit else, kepala saya 'meletakkannya di sana' untuk saya karena ia mengenali polanya secara keseluruhan . Saya mengerti arti dari keseluruhan pola umum, jadi saya tidak perlu detail kecil untuk memperjelasnya.

Misalnya, baca teks berikut:

masukkan deskripsi gambar di sini

Apakah kamu membaca ini?

Saya suka Paris di musim semi

Jika demikian, Anda salah. Baca teks lagi. Apa yang sebenarnya ditulis adalah

Saya suka Paris di dalam musim semi

Ada dua kata "the" dalam teks. Jadi mengapa Anda melewatkan salah satu dari keduanya?

Itu karena otak Anda melihat pola umum , dan segera menerjemahkannya ke maknanya yang diketahui. Itu tidak perlu membaca keseluruhan hal dengan detail untuk mengetahui artinya, karena sudah mengenali pola setelah melihatnya. Inilah sebabnya mengapa ia mengabaikan "si" tambahan .

Hal yang sama dengan idiom di atas. Programmer yang telah membaca banyak kode digunakan untuk melihat pola ini , dari if(something) {return x;} return y;. Mereka tidak perlu penjelasan elseuntuk memahami maknanya, karena otak mereka 'menempatkannya di sana untuk mereka'. Mereka mengenalinya sebagai pola dengan makna yang diketahui: "apa yang setelah kurung kurawal akan berjalan hanya sebagai implisit elsedari yang sebelumnya if".

Jadi menurut saya, elseitu tidak perlu. Tapi gunakan saja apa yang tampaknya lebih mudah dibaca.

Aviv Cohn
sumber
2

Mereka menambahkan kekacauan visual dalam kode, jadi ya, mereka mungkin menambah kompleksitas.

Namun, kebalikannya juga benar, refactoring yang berlebihan untuk mengurangi panjang kode juga dapat menambah kompleksitas (tidak dalam kasus yang sederhana ini tentu saja).

Saya setuju dengan pernyataan bahwa elsemaksud menyatakan blok. Tetapi kesimpulan saya berbeda, karena Anda menambahkan kompleksitas dalam kode Anda untuk mencapai ini.

Saya tidak setuju dengan pernyataan Anda bahwa itu memungkinkan Anda untuk mencegah kesalahan yang halus dalam logika.

Mari kita lihat contoh, sekarang seharusnya hanya mengembalikan true jika Langit adalah Biru dan ada Kelembaban tinggi, Kelembaban tidak tinggi atau jika Langit Merah. Tapi kemudian, Anda salah mengaitkan satu penyangga dan menempatkannya di salah else:

bool CanLeaveWithoutUmbrella() {
    if(sky.Color == Color.Blue)
    {
        if (sky.Humidity == Humidity.High) 
        {
            return true;
        } 
    else if (sky.Humidity != Humidity.High)
    {
        return true;
    } else if (sky.Color == Color.Red) 
    {
            return true;
    }
    } else 
    {
       return false;
    }
}

Kami telah melihat semua kesalahan konyol dalam kode produksi ini dan bisa sangat sulit dideteksi.

Saya akan menyarankan yang berikut ini:

Saya menemukan ini lebih mudah dibaca, karena saya dapat melihat secara default false.

Juga, gagasan memaksa untuk memindahkan isi blok untuk memasukkan klausa baru, rentan terhadap kesalahan. Ini entah bagaimana berkaitan dengan prinsip terbuka / tertutup (kode harus terbuka untuk ekstensi tetapi ditutup untuk modifikasi). Anda dipaksa untuk memodifikasi kode yang ada (mis. Koder mungkin memperkenalkan kesalahan dalam penyeimbangan kawat gigi).

Akhirnya, Anda mengarahkan orang untuk menjawab dengan cara yang telah Anda tentukan sebelumnya yang menurut Anda benar. Sebagai contoh, Anda memberikan contoh yang sangat sederhana tetapi setelah itu, Anda menyatakan bahwa orang tidak harus mempertimbangkannya. Namun, kesederhanaan contoh mempengaruhi keseluruhan pertanyaan:

  • Jika kasusnya sesederhana yang disajikan, maka jawaban saya adalah menghilangkan if elseklausa sama sekali.

    return (sky.Color == Color.Blue);

  • Jika kasusnya sedikit lebih rumit, dengan berbagai klausa, saya akan menjawab:

    bool CanLeaveWithoutUmbrella () {

    if(sky.Color == Color.Blue && sky.Humidity == Humidity.High) {
        return true;
    } else if (sky.Humidity != Humidity.High) {
        return true;
    } else if (sky.Color == Color.Red) {
        return true;
    }
    
    return false;
    

    }

  • Jika kasusnya rumit dan tidak semua klausa mengembalikan true (mungkin mereka mengembalikan dua kali lipat), dan saya ingin memperkenalkan beberapa perintah breakpoint atau loggin, saya akan mempertimbangkan sesuatu seperti:

    double CanLeaveWithoutUmbrella() {
    
        double risk = 0.0;
    
        if(sky.Color == Color.Blue && sky.Humidity == Humidity.High) {
            risk = 1.0;
        } else if (sky.Humidity != Humidity.High) {
            risk = 0.5;
        } else if (sky.Color == Color.Red) {
            risk = 0.8;
        }
    
        Log("value of risk:" + risk);
        return risk;
    

    }

  • Jika kita tidak berbicara tentang metode yang mengembalikan sesuatu, melainkan blok if-else yang menetapkan beberapa variabel atau memanggil metode, maka ya, saya akan menyertakan elseklausa akhir .

Oleh karena itu, kesederhanaan contoh juga merupakan faktor yang perlu dipertimbangkan.

FranMowinckel
sumber
Aku merasa seperti Anda membuat kesalahan yang sama orang lain telah dibuat dan morphing contoh kecil yang terlalu banyak, sehingga memeriksa masalah baru, tapi aku harus mengambil kedua dan memeriksa proses mendapatkan untuk contoh Anda, jadi untuk sekarang terlihat berlaku untuk saya. Dan catatan tambahan, ini tidak terkait dengan prinsip terbuka / tertutup . Cakupannya terlalu sempit untuk menerapkan idiom ini. Tetapi menarik untuk dicatat aplikasinya pada tingkat yang lebih tinggi akan menghapus seluruh aspek dari masalah (dengan menghapus kebutuhan untuk setiap modifikasi fungsi).
Selali Adobor
1
Tetapi jika kita tidak dapat memodifikasi contoh yang terlalu sederhana ini menambahkan klausa, atau memodifikasi bagaimana itu ditulis, atau membuat perubahan apa pun, maka Anda harus mempertimbangkan bahwa pertanyaan ini hanya berhubungan dengan baris kode yang tepat dan itu bukan pertanyaan umum lagi. Dalam hal ini, Anda sepenuhnya benar, itu tidak menambah kerumitan (atau menghapusnya) klausa lain karena tidak ada kompleksitas untuk memulai. Namun, Anda tidak adil, karena Andalah yang menyarankan gagasan bahwa klausul baru dapat ditambahkan.
FranMowinckel
Dan seperti yang saya perhatikan, saya tidak menyatakan ini berkaitan dengan prinsip buka / tutup yang terkenal. Saya pikir gagasan mengarahkan orang untuk mengubah kode Anda seperti yang Anda sarankan cenderung rentan terhadap kesalahan. Saya hanya mengambil ide ini dari prinsip itu.
FranMowinckel
Tunggu suntingan, Anda sedang
mengerjakan
1

Meskipun kasus if-else yang dideskripsikan memiliki kompleksitas yang lebih besar, dalam sebagian besar (tetapi tidak semua) situasi praktis tidak masalah banyak.

Bertahun-tahun yang lalu ketika saya mengerjakan perangkat lunak yang digunakan untuk industri penerbangan (harus melalui proses sertifikasi), pertanyaan Anda muncul. Ternyata ada biaya keuangan untuk pernyataan 'lain' itu karena menambah kompleksitas kode.

Inilah sebabnya.

Kehadiran 'orang lain' menciptakan kasus ke-3 implisit yang harus dievaluasi - bahwa tidak ada 'jika' maupun 'orang lain' diambil. Anda mungkin berpikir (seperti saya saat itu) WTF?

Penjelasannya seperti ini ...

Dari sudut pandang logis, hanya ada dua opsi - 'jika' dan 'lain'. Namun, yang kami sertifikasi adalah file objek yang dihasilkan oleh kompiler. Oleh karena itu, ketika ada 'yang lain', analisis harus dilakukan untuk mengkonfirmasi bahwa kode percabangan yang dihasilkan sudah benar dan bahwa jalur ke-3 yang misterius tidak muncul.

Bandingkan ini dengan kasus di mana hanya ada 'jika' dan tidak ada 'yang lain'. Dalam hal ini, hanya ada dua opsi - jalur 'jika' dan jalur 'non-jika'. Dua kasus untuk dievaluasi kurang kompleks dari tiga.

Semoga ini membantu.

Sparky
sumber
dalam file objek, tidakkah kedua kasus akan ditampilkan sebagai jmps identik?
Winston Ewert
@ WinstonEwert - Orang akan berharap mereka sama. Namun, apa yang dirender dan apa yang diharapkan akan diberikan adalah dua hal yang berbeda, terlepas dari seberapa banyak mereka tumpang tindih.
Sparky
(Saya kira SE memakan komentar saya ...) Ini adalah jawaban yang sangat menarik. Sementara saya akan mengatakan bahwa case yang diekspos agak niche, itu menunjukkan bahwa beberapa alat akan menemukan perbedaan antara keduanya (Bahkan metrik kode paling umum berdasarkan aliran yang saya lihat, kompleksitas cyclomatic, akan mengukur tidak ada perbedaan.
Selali Adobor
1

Tujuan kode yang paling penting adalah agar dapat dimengerti sebagai komitmen (bukan kebalikan dari refactored yang mudah, yang bermanfaat tetapi kurang penting). Contoh Python yang sedikit lebih kompleks dapat digunakan untuk menggambarkan ini:

def value(key):
    if key == FROB:
        return FOO
    elif key == NIK:
        return BAR
    [...]
    else:
        return BAZ

Ini cukup jelas - ini setara dengan casepencarian pernyataan atau kamus, versi tingkat yang lebih tinggi dari return foo == bar:

KEY_VALUES = {FROB: FOO, NIK: BAR, [...]}
DEFAULT_VALUE = BAZ

def value(key):
    return KEY_VALUES.get(key, default=DEFAULT_VALUE)

Ini lebih jelas, karena meskipun jumlah token lebih tinggi (32 vs 24 untuk kode asli dengan dua pasangan kunci / nilai), semantik fungsi sekarang eksplisit: Ini hanya pencarian.

(Ini memiliki konsekuensi untuk refactoring - jika Anda ingin memiliki efek samping jika key == NIK, Anda memiliki tiga pilihan:

  1. Kembalikan ke gaya if/ elif/ elsedan masukkan satu baris ke key == NIKcase.
  2. Simpan hasil pencarian ke nilai dan tambahkan ifpernyataan valueuntuk kasus khusus sebelum kembali.
  3. Masukkan ifpernyataan di pemanggil.

Sekarang kita melihat mengapa fungsi pencarian adalah penyederhanaan yang kuat: Ini membuat opsi ketiga jelas solusi paling sederhana, karena selain menghasilkan perbedaan yang lebih kecil dari opsi pertama, itu adalah satu-satunya yang memastikan bahwa valuehanya melakukan satu hal. )

Kembali ke kode OP, saya pikir ini dapat berfungsi sebagai panduan untuk kompleksitas elsepernyataan: Kode dengan elsepernyataan eksplisit secara obyektif lebih kompleks, tetapi yang lebih penting adalah apakah itu membuat semantik kode menjadi jelas dan sederhana. Dan itu harus dijawab berdasarkan kasus per kasus, karena setiap bagian kode memiliki arti yang berbeda.

l0b0
sumber
Saya suka tabel pencarian Anda menulis ulang dari saklar sederhana. Dan saya tahu itu idiom Python yang disukai. Namun dalam praktiknya, saya sering menemukan bahwa kondisional multi-arah (alias caseatau switchpernyataan) kurang homogen. Beberapa opsi hanyalah pengembalian nilai langsung, tetapi kemudian satu atau dua kasing memerlukan pemrosesan ekstra. Dan ketika Anda menemukan strategi pencarian tidak sesuai, Anda perlu menulis ulang dalam if elif... elsesintaks yang sama sekali berbeda .
Jonathan Eunice
Itu sebabnya saya pikir pencarian biasanya harus satu-liner atau fungsi, sehingga logika di sekitar hasil pencarian dipisahkan dari logika mencari itu di tempat pertama.
l0b0
Pertanyaan ini membawa pandangan tingkat yang lebih tinggi ke pertanyaan yang pasti harus disebutkan dan diingat, tetapi saya merasa bahwa cukup banyak kendala telah ditempatkan pada kasus yang Anda dapat merasa bebas untuk memperluas pada sikap Anda sendiri
Selali Adobor
1

Saya pikir itu tergantung pada ifpernyataan apa yang Anda gunakan. Beberapa ifpernyataan dapat dilihat sebagai ekspresi, dan yang lainnya hanya dapat dilihat sebagai pernyataan aliran kontrol.

Contoh Anda terlihat seperti ungkapan bagi saya. Dalam bahasa mirip-C, operator ternary ?:sangat mirip dengan ifpernyataan, tetapi merupakan ekspresi, dan bagian lainnya tidak dapat dihilangkan. Masuk akal bahwa cabang lain tidak dapat dihilangkan dalam ekspresi, karena ekspresi harus selalu memiliki nilai.

Menggunakan operator ternary, contoh Anda akan terlihat seperti ini:

bool CanLeaveWithoutUmbrella()
{
    return (sky.Color == Color.Blue)
               ? true
               : false;
}

Karena ini adalah ekspresi boolean, itu benar-benar harus disederhanakan sampai selesai

bool CanLeaveWithoutUmbrella()
{
    return (sky.Color == Color.Blue);
}

Termasuk klausul eksplisit lain membuat maksud Anda lebih jelas. Penjaga bisa masuk akal dalam beberapa situasi, tetapi dalam memilih antara dua nilai yang mungkin, yang keduanya tidak luar biasa atau tidak biasa, mereka tidak membantu.

Michael Shaw
sumber
+1, OP adalah IMHO yang membahas kasus patologis yang harus ditulis dengan lebih baik seperti yang Anda tunjukkan di atas. Kasus jauh lebih sering untuk "jika" dengan "kembali" adalah pengalaman saya "penjaga" kasus.
Doc Brown
Saya tidak tahu mengapa ini terus terjadi, tetapi orang-orang terus mengabaikan paragraf pertama dari pertanyaan itu. Faktanya, Anda semua menggunakan baris kode yang persis sama dengan yang saya katakan secara eksplisit untuk tidak digunakan. I ask that you ignore the many other ways the function can be written, and changes that can be made to it. (I know most people are itching to write return sky.Color == Color.Blue.)
Selali Adobor
Dan membaca kembali pertanyaan terakhir Anda, Anda sepertinya salah paham tentang maksud pertanyaan itu.
Selali Adobor
Anda menggunakan contoh yang praktis meminta agar disederhanakan. Saya membaca dan memperhatikan paragraf pertama Anda, itulah sebabnya blok kode pertama saya ada di sana, daripada melewatkan langsung ke cara terbaik untuk melakukan contoh khusus itu. Jika Anda pikir saya (atau orang lain) telah salah memahami maksud pertanyaan Anda, mungkin Anda harus mengeditnya untuk memperjelas maksud Anda.
Michael Shaw
Saya akan mengeditnya, tetapi saya tidak akan tahu bagaimana membuatnya lebih jelas karena saya menggunakan baris kode yang tepat yang Anda lakukan dan berkata, "jangan lakukan ini". Ada banyak cara tak terbatas untuk menulis ulang kode dan menghapus pertanyaan, saya tidak bisa membahas semuanya, namun begitu banyak orang telah memahami pernyataan saya dan menghormatinya ...
Selali Adobor
1

Satu hal lain untuk dipikirkan dalam argumen ini adalah kebiasaan yang dipromosikan oleh setiap pendekatan.

  • jika / lain membingkai ulang sampel pengkodean dalam hal cabang. Seperti yang Anda katakan, terkadang kesaksian ini bagus. Sebenarnya ada dua jalur eksekusi yang mungkin dan kami ingin menyoroti itu.

  • Dalam kasus lain, hanya ada satu jalur eksekusi dan kemudian semua hal luar biasa yang harus dikhawatirkan oleh programmer. Seperti yang Anda sebutkan, klausa penjaga sangat bagus untuk ini.

  • Ada, tentu saja, 3 kasus atau lebih (n), di mana kami biasanya mendorong meninggalkan rantai if / else dan menggunakan pernyataan case / switch atau polimorfisme.

Prinsip panduan yang saya ingat di sini adalah bahwa metode atau fungsi seharusnya hanya memiliki satu tujuan. Kode apa pun dengan pernyataan if / else atau switch hanya untuk percabangan. Jadi harus pendek kepalang (4 baris) dan hanya mendelegasikan ke metode yang benar atau menghasilkan hasil yang jelas (seperti contoh Anda). Ketika kode Anda sesingkat itu, sulit untuk melewatkan beberapa pengembalian :) Seperti halnya "dianggap berbahaya", pernyataan percabangan apa pun dapat disalahgunakan, sehingga memasukkan beberapa pemikiran kritis ke dalam pernyataan lain biasanya sepadan. Seperti yang Anda sebutkan, hanya memiliki blok lain menyediakan tempat bagi kode untuk menggumpal. Anda dan tim Anda harus memutuskan bagaimana perasaan Anda tentang hal itu.

Apa yang benar-benar dikeluhkan oleh alat ini adalah kenyataan bahwa Anda memiliki return / break / throw di dalam blok if. Jadi sejauh menyangkut, Anda menulis klausa penjaga tetapi mengacaukannya. Anda dapat memilih untuk membuat alat ini bahagia atau Anda dapat "menghibur" sarannya tanpa menerimanya.

Steve Jackson
sumber
Saya tidak pernah memikirkan fakta alat itu mungkin mempertimbangkan jika menjadi klausa penjaga segera setelah cocok dengan pola untuk satu, itu mungkin terjadi, dan itu menjelaskan alasan untuk satu "kelompok pendukung" karena tidak adanya
Selali Adobor
@AssortedTrailmix Benar, Anda berpikir tentang elsesemantik, alat analisis kode biasanya mencari instruksi yang asing karena mereka sering menyoroti kesalahpahaman aliran kontrol. The elsesetelah ifitu kembali terbuang bit. if(1 == 1)sering memicu jenis peringatan yang sama.
Steve Jackson
1

Saya pikir jawabannya tergantung. Sampel kode Anda (seperti yang Anda tunjukkan secara tidak langsung) hanyalah mengembalikan evaluasi suatu kondisi, dan paling baik disajikan seperti yang Anda ketahui, sebagai satu ekspresi.

Untuk menentukan apakah menambahkan kondisi lain mengklarifikasi atau mengaburkan kode, Anda perlu menentukan apa yang diwakili IF / ELSE. Apakah ini sebuah (seperti dalam contoh Anda) sebuah ekspresi? Jika demikian, ungkapan itu harus diekstraksi dan digunakan. Apakah ini kondisi penjaga? Jika demikian, maka ELSE tidak perlu dan menyesatkan, karena membuat kedua cabang tampak setara. Apakah ini contoh polimorfisme prosedural? Ekstrak itu. Apakah pengaturan prasyarat? Maka itu bisa sangat penting.

Sebelum Anda memutuskan untuk memasukkan atau menghilangkan ELSE, tentukan apa yang diwakilinya, yang akan memberi tahu Anda apakah itu harus ada atau tidak.

jmoreno
sumber
0

Jawabannya agak subyektif. Sebuah elseblok dapat membantu pembacaan dengan mendirikan bahwa satu blok dari mengeksekusi kode secara eksklusif untuk blok lain kode, tanpa perlu membaca kedua blok. Ini bisa membantu jika, katakanlah, kedua blok relatif panjang. Ada beberapa kasus, meskipun di mana memiliki iftanpa elsedapat lebih mudah dibaca. Bandingkan kode berikut dengan sejumlah if/elseblok:

if(a == b) {
    if(c <= d) {
        if(e != f) {
            a.doSomeStuff();
            c.makeSomeThingsWith(b);
            f.undo(d, e);
            return 9;
        } else {
            e++;
            return 3;
        }
    } else {
        return 1;
    }
} else {
    return 0;
}

dengan:

if(a != b) {
    return 0;
}

if(c > d) {
    return 1;
}

if(e != f) {
    e++;
    return 3;
}

a.doSomeStuff();
c.makeSomeThingsWith(b);
f.undo(d, e);
return 9;

Blok kode kedua mengubah ifpernyataan bersarang menjadi klausa penjaga. Ini mengurangi lekukan, dan membuat beberapa kondisi pengembalian lebih jelas ("jika a != b, maka kami kembali 0!")


Telah ditunjukkan dalam komentar bahwa mungkin ada beberapa lubang dalam contoh saya, jadi saya akan menyempurnakannya sedikit lagi.

Kita bisa menulis blok kode pertama di sini seperti agar lebih mudah dibaca:

if(a != b) {
    return 0;
} else if(c > d) {
    return 1;
} else if(e != f) {
    e++;
    return 3;
} else {
    a.doSomeStuff();
    c.makeSomeThingsWith(b);
    f.undo(d, e);
    return 9;
}

Kode ini setara dengan kedua blok di atas, tetapi menyajikan beberapa tantangan. The elseklausul disini menghubungkan ini jika pernyataan bersama-sama. Dalam versi klausa penjaga di atas, klausa penjaga independen satu sama lain. Menambahkan yang baru berarti hanya menulis yang baru ifantara yang terakhir ifdan sisa dari logika. Di sini, itu juga bisa dilakukan, dengan hanya sedikit beban kognitif yang lebih sedikit. Perbedaannya adalah ketika beberapa logika tambahan perlu terjadi sebelum klausa penjaga baru itu. Ketika pernyataan independen, kita bisa melakukan:

...
if(e != f) {
    e++;
    return 3;
}

g.doSomeLogicWith(a);

if(!g.isGood()) {
    return 4;
}

a.doSomeStuff();
...

Dengan if/elserantai yang terhubung , ini tidak mudah dilakukan. Faktanya, jalur termudah untuk dilakukan adalah dengan memutus rantai agar lebih mirip versi klausa penjaga.

Tapi sungguh, ini masuk akal. Menggunakan if/elselemah menyiratkan hubungan antara bagian-bagian. Kita bisa menduga itu a != bada hubungannyac > d dalam if/elseversi dirantai. Anggapan itu akan menyesatkan, seperti yang bisa kita lihat dari versi klausa penjaga bahwa mereka, pada kenyataannya, tidak berhubungan. Itu berarti bahwa kita dapat melakukan lebih banyak dengan klausa independen, seperti mengatur ulang mereka (mungkin untuk mengoptimalkan kinerja permintaan atau untuk meningkatkan aliran logis). Kita juga bisa mengabaikan mereka secara kognitif begitu kita melewati mereka. Dalam sebuah if/elserantai, saya kurang yakin bahwa hubungan kesetaraan antara adan btidak akan muncul kembali nanti.

Hubungan yang tersirat dengan elsejuga terlihat dalam kode contoh yang sangat bersarang, tetapi dengan cara yang berbeda. The elsepernyataan dipisahkan dari bersyarat mereka melekat, dan mereka yang terkait dalam terbalik Agar conditional (yang pertama elsemelekat terakhirif , terakhir elsemelekat pertama if). Ini menciptakan disonansi kognitif di mana kita harus menelusuri kembali kode, mencoba untuk meluruskan indentasi dalam otak kita. Ini seperti mencoba untuk mencocokkan sekelompok kurung. Bahkan menjadi lebih buruk jika lekukan salah. Kawat gigi opsional juga tidak membantu.

Saya lupa menyebutkan kasus di mana saya setuju dengan penghilangan blok lain, dan ketika menggunakan blok if untuk menerapkan kendala yang harus dipenuhi secara logis untuk semua kode berikut, seperti cek-nol (atau pengawal lainnya) ).

Ini adalah konsesi yang bagus, tetapi benar-benar tidak membatalkan jawaban apa pun. Saya bisa mengatakan itu dalam contoh kode Anda:

bool CanLeaveWithoutUmbrella()
{
    if(sky.Color != Color.Blue)
    {
        return false;
    }
    return true;
}

interpretasi yang valid untuk menulis kode seperti ini adalah bahwa "kita hanya harus pergi dengan payung jika langit tidak biru." Di sini ada batasan bahwa langit harus biru agar kode berikut valid untuk dijalankan. Saya telah memenuhi definisi konsesi Anda.

Bagaimana jika saya mengatakan bahwa jika warna langit hitam, itu adalah malam yang cerah, jadi tidak perlu payung. (Ada cara yang lebih sederhana untuk menulis ini, tetapi ada cara yang lebih sederhana untuk menulis seluruh contoh.)

bool CanLeaveWithoutUmbrella()
{
    if(sky.Color == Color.Blue)
    {
        return true;
    }

    if(sky.Color == Color.Black)
    {
        return true;
    }

    return false;
}

Seperti pada contoh yang lebih besar di atas, saya bisa menambahkan klausa baru tanpa banyak pemikiran yang diperlukan. Dalam sebuahif/else , saya tidak bisa begitu yakin saya tidak akan mengacaukan sesuatu, terutama jika logikanya lebih rumit.

Saya juga akan menunjukkan bahwa contoh sederhana Anda juga tidak begitu sederhana. Tes untuk nilai non-biner tidak sama dengan kebalikan dari tes itu dengan hasil terbalik.

cbojar
sumber
1
Suntingan saya untuk pertanyaan membahas kasus ini. Ketika kendala yang harus dipenuhi secara logis untuk semua kode berikut, seperti cek-nol (atau penjaga lainnya), saya setuju bahwa penghilangan elseblok sangat penting untuk keterbacaan.
Selali Adobor
1
Versi pertama Anda sulit dipahami, tetapi saya rasa itu tidak perlu dengan menggunakan if / else. Lihat bagaimana saya menulisnya: gist.github.com/winstonewert/c9e0955bc22ce44930fb .
Winston Ewert
@ WinstonEwert Lihat suntingan untuk menanggapi komentar Anda.
cbojar
Suntingan Anda menghasilkan beberapa poin yang valid. Dan saya tentu berpikir bahwa klausa penjaga memiliki tempat. Tetapi dalam kasus umum, saya mendukung klausa lain.
Winston Ewert
0

Anda terlalu menyederhanakan contoh Anda. Salah satu alternatif mungkin lebih mudah dibaca, tergantung pada situasi yang lebih besar: khususnya, itu tergantung pada apakah salah satu dari dua cabang kondisi tersebut tidak normal . Biarkan saya menunjukkan kepada Anda: dalam kasus di mana salah satu cabang sama-sama mungkin, ...

void DoOneOfTwoThings(bool which)
{
    if (which)
        DoThingOne();
    else
        DoThingTwo();
}

... lebih mudah dibaca daripada ...

void DoOneOfTwoThings(bool which)
{
    if (which) {
        DoThingOne();
        return;
    }
    DoThingTwo();
}

... karena pameran pertama struktur paralel dan yang kedua tidak. Tetapi, ketika sebagian besar fungsi dikhususkan untuk satu tugas dan Anda hanya perlu memeriksa beberapa prasyarat terlebih dahulu, ...

void ProcessInputOfTypeOne(String input)
{
    if (InputIsReallyTypeTwo(input)) {
        ProcessInputOfTypeTwo(input);
        return;
    }
    ProcessInputDefinitelyOfTypeOne(input);

}

... lebih mudah dibaca daripada ...

void ProcessInputOfTypeOne(String input)
{
    if (InputIsReallyTypeTwo(input))
        ProcessInputOfTypeTwo(input);
    else
        ProcessInputDefinitelyOfTypeOne(input);
}

... karena dengan sengaja tidak membuat struktur paralel memberikan petunjuk bagi pembaca masa depan bahwa mendapatkan input yang "benar-benar tipe dua" di sini adalah tidak normal . (Dalam kehidupan nyata, ProcessInputDefinitelyOfTypeOnetidak akan menjadi fungsi yang terpisah, jadi perbedaannya akan lebih mencolok.)

zwol
sumber
Mungkin lebih baik untuk mengambil contoh yang lebih konkret untuk kasus kedua. Reaksi langsung saya terhadap hal itu adalah, "tidak mungkin menjadi abnormal jika Anda melanjutkan dan memprosesnya."
Winston Ewert
@ WinstonEwert abnormal mungkin istilah yang salah. Tetapi saya setuju dengan Zack, bahwa jika Anda memiliki default (case1) dan satu pengecualian, itu harus ditulis sebagai » if-> Lakukan yang luar biasa dan tinggalkan« sebagai strategi pengembalian-awal . Pikirkan kode, yang standarnya mencakup lebih banyak pemeriksaan, akan lebih cepat menangani kasus luar biasa terlebih dahulu.
Thomas Junk
Ini sepertinya jatuh ke dalam kasus yang saya bicarakan when using an if block to apply a constraint that must be logically satisfied for all following code, such as a null-check (or any other guards). Secara logis, pekerjaan apaProcessInputOfTypeOne pun tergantung pada fakta itu !InputIsReallyTypeTwo(input), jadi kita perlu menangkapnya di luar pemrosesan normal kita. Biasanya ProcessInputOfTypeTwo(input);akan benar-benar menjadi throw ICan'tProccessThatExceptionyang akan membuat kesamaan lebih jelas.
Selali Adobor
@ WinstonEwert Saya mungkin bisa mengatakan itu lebih baik, ya. Saya sedang memikirkan situasi yang banyak muncul ketika mengkodekan tokenizers dengan tangan: di CSS, misalnya, urutan karakter " u+" biasanya memperkenalkan token "unicode-range", tetapi jika karakter berikutnya bukan hex digit, maka orang perlu membuat cadangan dan memproses 'u' sebagai pengidentifikasi. Jadi loop pemindai utama mengirimkan untuk "memindai token jangkauan-unicode" agak tidak tepat, dan itu dimulai dengan "... jika kita berada dalam kasus khusus yang tidak biasa ini, buat cadangan, lalu panggil subroutine pemindaian dan pengidentifikasi sebagai gantinya."
zwol
@AssortedTrailmix Bahkan jika contoh yang saya pikirkan tidak berasal dari basis kode lama di mana pengecualian tidak dapat digunakan, ini bukan kondisi kesalahan , hanya saja kode yang memanggil ProcessInputOfTypeOnemenggunakan pemeriksaan tidak tepat tapi cepat yang kadang-kadang menangkap tipe dua sebagai gantinya.
zwol
0

Ya, ada peningkatan dalam kompleksitas karena klausa lain berlebihan . Oleh karena itu lebih baik meninggalkan kode agar tetap ramping dan berarti. Itu pada akor yang sama dengan menghilangkan thiskualifikasi yang berlebihan (Java, C ++, C #) dan menghilangkan tanda kurung direturn pernyataan, dan sebagainya.

Seorang programmer yang baik berpikir "apa yang bisa saya tambahkan?" sementara programmer yang hebat berpikir "apa yang bisa saya hapus?".

vidstige
sumber
1
Ketika saya melihat penghilangan elseblok, jika dijelaskan dalam satu-liner (yang tidak sering) sering dengan alasan semacam ini, jadi +1 untuk menyajikan argumen "default" yang saya lihat, tapi saya tidak merasa itu alasan yang cukup kuat. A good programmer things "what can I add?" while a great programmer things "what can I remove?".tampaknya menjadi pepatah umum bahwa banyak orang melakukan overextend tanpa pertimbangan yang matang, dan saya pribadi menemukan ini sebagai salah satu contohnya.
Selali Adobor
Ya, membaca pertanyaan Anda, saya langsung merasa Anda sudah mengambil keputusan, tetapi ingin konfirmasi. Jawaban ini jelas bukan yang Anda inginkan, tetapi terkadang penting untuk mempertimbangkan pendapat yang tidak Anda setujui. Mungkin ada sesuatu juga?
vidstige
-1

Saya perhatikan, mengubah pikiran saya pada kasus ini.

Ketika ditanya pertanyaan ini sekitar setahun yang lalu, saya akan menjawab sesuatu seperti:

»Seharusnya hanya ada satu entrydan satu exitke metode« Dan dengan mengingat hal ini, saya akan menyarankan untuk refactor kode ke:

bool CanLeaveWithoutUmbrella()
{
    bool result

    if(sky.Color == Color.Blue)
    {
        result = true;
    }
    else
    {
        result = false;
    }

    return result;
}

Dari sudut pandang komunikasi , jelas , apa yang harus dilakukan. Ifsesuatu terjadi, memanipulasi hasilnya dengan satu cara , else memanipulasi dengan cara lain .

Tapi ini melibatkan yang tidak perlu else . Yang elsemengekspresikan case default . Jadi pada langkah kedua, saya bisa refactor

bool CanLeaveWithoutUmbrella()
{
    bool result=false

    if(sky.Color == Color.Blue)
    {
        result = true;
    }
    return result;
}

Kemudian muncul pertanyaan: Mengapa menggunakan variabel sementara? Langkah refactorisasi selanjutnya mengarah ke:

bool CanLeaveWithoutUmbrella()
{

    if(sky.Color == Color.Blue)
    {
        return true;
    }
    return false;
}

Jadi ini jelas dalam apa fungsinya. Saya bahkan akan melangkah lebih jauh:

bool CanLeaveWithoutUmbrella()
{

    if(sky.Color == Color.Blue) return true;
    return false;
}

Atau untuk yang lemah hati :

bool CanLeaveWithoutUmbrella()
{

    if(sky.Color == Color.Blue) { return true; }
    return false;
}

Anda bisa membacanya seperti itu: »CanLeaveWithoutUmbrella mengembalikan bool . Jika tidak ada yang istimewa terjadi, ia mengembalikan false «Ini adalah bacaan pertama, dari atas ke bawah.

Dan kemudian Anda "mem-parsing" kondisinya »Jika kondisinya terpenuhi, ia mengembalikan true « Anda benar-benar dapat melewatkan kondisional dan telah memahami, apa fungsi ini dalam huruf default . Mata dan pikiran Anda hanya perlu membaca beberapa huruf di sisi kiri, tanpa membaca bagian di sebelah kanan.

Saya mendapat kesan yang lain lebih langsung menyatakan niat, dengan menyatakan fakta bahwa kode di kedua blok terkait langsung.

Ya itu benar. Tetapi informasi itu berlebihan . Anda memiliki case default dan satu "exception" yang dicakup oleh if-statement.

Ketika berhadapan dengan struktur yang lebih kompleks, saya akan memilih strategi lain, menghilangkan ifatau itu saudara jelek switch.

Thomas Junk
sumber
+1 redundansi jelas menunjukkan peningkatan tidak hanya kompleksitas, tetapi juga kebingungan. Saya tahu saya selalu harus menggandakan kode yang ditulis seperti ini persis karena redundansi.
dietbuddha
1
Bisakah Anda menghapus kasus setelah kasus dimulai dengan So this is clear in what it does...dan mengembangkannya? (Kasus-kasus sebelumnya juga relevan dipertanyakan). Saya mengatakan ini karena pertanyaannya adalah pertanyaan yang kami periksa. Anda telah menyatakan kuda-kuda Anda, menghilangkan balok orang lain, dan sebenarnya kuda-kuda itu yang membutuhkan penjelasan lebih lanjut (dan orang yang membuat saya mengajukan pertanyaan ini). Dari pertanyaan:I ask that you ignore the many other ways the function can be written, and changes that can be made to it. (I know most people are itching to write return sky.Color == Color.Blue.)
Selali Adobor
@AssortedTrailmix Sure! Apa tepatnya yang harus saya uraikan? Karena pertanyaan awal Anda adalah »Apakah ada lagi blok yang menambah kompleksitas kode?« Dan jawaban saya adalah: ya. Dalam hal ini bisa dihilangkan.
Thomas Junk
Dan tidak! Saya tidak mengerti downvote.
Thomas Junk
-1

Singkatnya, dalam hal ini, menyingkirkan elsekata kunci adalah hal yang baik. Ini benar-benar berlebihan di sini, dan tergantung pada bagaimana Anda memformat kode Anda, itu akan menambahkan satu hingga tiga baris yang sama sekali tidak berguna untuk kode Anda. Pertimbangkan apa yang ada di ifblok:

return true;

Oleh karena itu, jika ifblok dimasukkan ke dalam, seluruh fungsi, secara definisi, tidak dieksekusi. Tetapi jika tidak dimasukkan ke dalam, karena tidak ada ifpernyataan lebih lanjut , seluruh fungsi dijalankan. Akan sangat berbeda jika returnpernyataan tidak ada dalamif blok, dalam hal ini ada atau tidak adanyaelse blok akan berdampak, tetapi di sini, itu tidak akan berdampak.

Dalam pertanyaan Anda, setelah membalikkan ifkondisi Anda dan menghilangkan else, Anda menyatakan yang berikut:

Seseorang sekarang dapat menambahkan blok if baru berdasarkan kondisi setelah contoh pertama tanpa segera mengenali dengan benar kondisi pertama menempatkan kendala pada kondisi mereka sendiri.

Mereka perlu membaca kode sedikit lebih dekat. Kami menggunakan bahasa tingkat tinggi dan elsepengaturan kode yang jelas untuk mengurangi masalah ini, tetapi menambahkan blok yang sepenuhnya berlebihan menambah "noise" dan "clutter" ke kode, dan kami tidak harus membalikkan atau membalikkan ifkondisi kami juga. Jika mereka tidak dapat membedakannya, maka mereka tidak tahu apa yang mereka lakukan. Jika mereka dapat membedakannya, maka mereka selalu dapat membalikkan ifkondisi aslinya sendiri.

Saya lupa menyebutkan kasus di mana saya setuju dengan penghilangan blok lain, dan ketika menggunakan blok if untuk menerapkan kendala yang harus dipenuhi secara logis untuk semua kode berikut, seperti cek-nol (atau pengawal lainnya) ).

Pada dasarnya itulah yang terjadi di sini. Anda kembali dari ifblok, sehingga ifpenilaian kondisi false merupakan kendala yang harus dipenuhi secara logis untuk semua kode berikut.

Apakah ada peningkatan kompleksitas yang dimasukkan dengan tidak menyertakan blok else?

Tidak, ini akan merupakan penurunan kompleksitas dalam kode Anda, dan bahkan mungkin merupakan penurunan dalam kode yang dikompilasi, tergantung pada apakah optimasi super sepele tertentu akan atau tidak akan terjadi.

Panzercrisis
sumber
2
"Mereka perlu membaca kode sedikit lebih dekat." Gaya pengkodean ada sehingga programmer dapat kurang memperhatikan detail nitpicky dan lebih memperhatikan apa yang sebenarnya mereka lakukan. Jika gaya Anda membuat Anda tidak perlu memperhatikan detail itu, itu adalah gaya yang buruk. "... garis yang sama sekali tidak berguna ..." Mereka tidak berguna - mereka membiarkan Anda melihat sekilas apa strukturnya, tanpa harus membuang waktu untuk memikirkan apa yang akan terjadi jika bagian ini atau bagian itu dieksekusi.
Michael Shaw
@MichaelShaw Saya pikir jawaban Aviv Cohn masuk ke apa yang saya bicarakan.
Panzercrisis
-2

Dalam contoh spesifik yang Anda berikan, ya.

  • Itu memaksa peninjau untuk membaca kode lagi untuk memastikan itu bukan kesalahan.
  • Ia menambah (tidak perlu) kompleksitas cyclomatic karena ia menambahkan satu simpul ke grafik aliran.

Saya pikir ini tidak bisa lebih sederhana dari ini:

bool CanLeaveWithoutUmbrella()
{
    return (sky.Color == Color.Blue);
}
Tulains Córdova
sumber
6
Saya secara khusus membahas fakta bahwa contoh yang sangat sederhana ini dapat ditulis ulang untuk menghapus ifpernyataan. Sebenarnya saya secara eksplisit mencegah penyederhanaan lebih lanjut menggunakan kode persis yang Anda gunakan. Selain itu kompleksitas siklomatik tidak meningkat oleh elseblok.
Selali Adobor
-4

Saya selalu berusaha menulis kode saya sehingga tujuannya sejelas mungkin. Contoh Anda kurang konteks, jadi saya akan sedikit memodifikasinya:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool CanLeaveWithoutUmbrella()
    {
        if(this.color == Color.Blue)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}

Niat kami tampaknya agar kami bisa pergi tanpa payung saat Langit Biru. Namun, niat sederhana itu hilang dalam lautan kode. Ada beberapa cara yang bisa kita lakukan untuk membuatnya lebih mudah dipahami.

Pertama, ada pertanyaan Anda tentang elsecabang. Saya juga tidak keberatan, tetapi cenderung mengabaikan else. Saya memahami argumen tentang menjadi eksplisit, tetapi pendapat saya adalah bahwa ifpernyataan harus membungkus cabang 'opsional', seperti yang return trueada. Saya tidak akan menyebut return falsecabang 'opsional', karena bertindak sebagai default kami. Bagaimanapun, saya melihat ini sebagai masalah yang sangat kecil, dan jauh lebih penting daripada yang berikut:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool CanLeaveWithoutUmbrella()
    {
        if(this.color == Color.Blue)
        {
            return true;
        }
        return false;
    }
}

Masalah yang jauh lebih penting adalah bahwa cabang Anda melakukan terlalu banyak! Cabang, seperti segalanya, harus 'sesederhana mungkin, tetapi tidak lebih'. Dalam hal ini Anda telah menggunakan ifpernyataan, yang bercabang di antara blok kode (koleksi pernyataan) ketika semua yang Anda butuhkan untuk bercabang di antaranya adalah ekspresi (nilai). Ini jelas karena a) blok kode Anda hanya berisi pernyataan tunggal dan b) mereka adalah pernyataan yang sama ( return). Kita dapat menggunakan operator ternary ?:untuk mempersempit cabang hanya ke bagian yang berbeda:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool CanLeaveWithoutUmbrella()
    {
        return (this.color == Color.Blue)
            ? true
            : false;
    }
}

Sekarang kita mencapai redundansi yang jelas bahwa hasil kita identik dengan this.color == Color.Blue. Saya tahu pertanyaan Anda meminta kami untuk mengabaikan ini, tetapi saya pikir sangat penting untuk menunjukkan bahwa ini adalah cara berpikir prosedural tingkat pertama: Anda meruntuhkan nilai input dan membangun beberapa nilai output. Tidak apa-apa, tetapi jika mungkin jauh lebih kuat untuk berpikir dalam hal hubungan atau transformasi antara input dan output. Dalam hal ini hubungannya jelas bahwa mereka sama, tetapi saya sering melihat kode prosedural yang berbelit-belit seperti ini, yang mengaburkan beberapa hubungan sederhana. Mari kita lanjutkan dan buat penyederhanaan ini:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool CanLeaveWithoutUmbrella()
    {
        return (this.color == Color.Blue);
    }
}

Hal berikutnya yang akan saya tunjukkan adalah bahwa API Anda mengandung double-negative: mungkin CanLeaveWithoutUmbrellasaja false. Ini, tentu saja, menyederhanakan NeedUmbrellamenjadi true, jadi mari kita lakukan itu:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool NeedUmbrella()
    {
        return (this.color != Color.Blue);
    }
}

Sekarang kita dapat mulai berpikir tentang gaya pengkodean Anda. Sepertinya Anda menggunakan bahasa Berorientasi Objek, tetapi dengan menggunakan ifpernyataan untuk bercabang di antara blok kode, Anda akhirnya menulis prosedural kode .

Dalam kode Berorientasi Objek, semua blok kode adalah metode dan semua percabangan dilakukan melalui pengiriman dinamis , misalnya. menggunakan kelas atau prototipe. Dapat diperdebatkan apakah hal itu membuat segalanya lebih sederhana, tetapi seharusnya membuat kode Anda lebih konsisten dengan bagian bahasa lainnya:

class Sky {
    bool NeedUmbrella()
    {
        return true;
    }
}

class BlueSky extends Sky {
    bool NeedUmbrella()
    {
        return false;
    }
}

Perhatikan bahwa menggunakan operator ternary untuk bercabang di antara ekspresi adalah hal biasa dalam pemrograman fungsional .

Warbo
sumber
Paragraf pertama dari jawaban tampaknya berada di jalur untuk menjawab pertanyaan, tetapi segera menyimpang ke topik yang tepat saya secara eksplisit meminta orang mengabaikan contoh yang sangat sederhana ini . Dari pertanyaan: I ask that you ignore the many other ways the function can be written, and changes that can be made to it. (I know most people are itching to write return sky.Color == Color.Blue.). Sebenarnya Anda menggunakan baris kode yang tepat yang saya sebutkan. Selain itu, sementara bagian dari pertanyaan ini di luar topik, saya katakan Anda terlalu jauh dalam kesimpulan. Anda telah mengubah secara radikal apa kode itu.
Selali Adobor
@AssortedTrailmix Ini murni masalah gaya. Masuk akal untuk peduli dengan gaya dan masuk akal untuk mengabaikannya (hanya berfokus pada hasil). Saya rasa tidak masuk akal untuk peduli dengan return false;vs else { return false; }sementara tidak peduli dengan polaritas cabang, pengiriman dinamis, ternaries, dll. Jika Anda menulis kode prosedural dalam bahasa OO, perubahan radikal diperlukan;)
Warbo
Anda dapat mengatakan sesuatu seperti itu secara umum, tetapi itu tidak berlaku ketika Anda menjawab pertanyaan dengan parameter yang terdefinisi dengan baik (terutama ketika tanpa menghormati parameter tersebut, Anda dapat "memisahkan" seluruh pertanyaan). Saya juga mengatakan bahwa kalimat terakhir itu salah. OOP adalah tentang "abstraksi prosedural", bukan "penghapusan prosedural". Saya akan mengatakan fakta Anda beralih dari satu liner ke jumlah kelas yang tak terbatas (Satu untuk setiap warna) menunjukkan alasannya. Selain itu saya masih bertanya-tanya apa yang harus dilakukan oleh pengiriman dinamis dengan if/elsepernyataan, dan apa polaritas cabang.
Selali Adobor
Saya tidak pernah mengatakan solusi OO lebih baik (pada kenyataannya, saya lebih suka pemrograman fungsional), saya hanya mengatakan itu lebih konsisten dengan bahasa. Dengan "polaritas" saya maksudkan hal mana yang "benar" dan yang "salah" (saya menukar dengan "NeedUmbrella"). Dalam desain OO, semua aliran kontrol ditentukan oleh pengiriman dinamis. Misalnya, Smalltalk tidak mengizinkan hal lain en.wikipedia.org/wiki/Smalltalk#Control_structures (juga lihat c2.com/cgi/wiki?HeInventedTheTerm )
Warbo
2
Saya akan memilih ini sampai saya sampai pada paragraf terakhir (tentang OO), yang malah membuatnya downvote! Persis seperti konstruksi OO yang tidak terpikirkan yang menghasilkan kode ravioli , yang sama tidak terbaca dengan spaghetti.
zwol