Apakah gaya yang buruk untuk memeriksa kondisi secara berlebihan?

10

Saya sering mendapatkan posisi dalam kode saya di mana saya menemukan diri saya memeriksa kondisi tertentu berulang-ulang.

Saya ingin memberi Anda contoh kecil: misalkan ada file teks yang berisi baris yang dimulai dengan "a", baris yang dimulai dengan "b" dan baris lainnya dan saya sebenarnya hanya ingin bekerja dengan dua jenis baris pertama. Kode saya akan terlihat seperti ini (menggunakan python, tetapi membacanya sebagai pseudocode):

# ...
clear_lines() # removes every other line than those starting with "a" or "b"
for line in lines:
    if (line.startsWith("a")):
        # do stuff
    elif (line.startsWith("b")):
        # magic
    else:
        # this else is redundant, I already made sure there is no else-case
        # by using clear_lines()
# ...

Bisa dibayangkan saya tidak hanya memeriksa kondisi ini di sini, tetapi mungkin juga di fungsi lain dan seterusnya.

Apakah Anda menganggapnya sebagai noise atau menambah nilai pada kode saya?

marktani
sumber
5
Ini pada dasarnya tentang apakah Anda coding atau tidak. Apakah Anda melihat kode ini banyak diedit? Apakah mungkin ini akan menjadi bagian dari sistem yang perlu sangat dapat diandalkan? Saya tidak melihat banyak bahaya dalam mendorong assert()di sana untuk membantu dengan pengujian, tetapi di luar itu mungkin berlebihan. Yang mengatakan, itu akan bervariasi tergantung pada situasinya.
Latty
kasus 'lain' Anda pada dasarnya adalah kode mati / tidak dapat dijangkau. Periksa apakah tidak ada persyaratan sistem yang melarang ini.
NWS
@NWS: apakah Anda mengatakan bahwa saya harus menyimpan koper lain? Maaf saya tidak mengerti Anda sepenuhnya.
marktani
2
tidak terutama terkait dengan pertanyaan - tapi saya akan membuat 'pernyataan' menjadi invarian - yang akan membutuhkan kelas "Line" baru (mungkin dengan kelas turunan untuk A & B), daripada memperlakukan garis sebagai string dan memberi tahu mereka apa mereka mewakili dari luar. Saya akan dengan senang hati menjelaskan hal ini di CodeReview
MattDavey
yang Anda maksud elif (line.startsWith("b"))? Ngomong-ngomong, Anda dapat dengan aman menghapus tanda kurung di sekitar kondisi, mereka tidak idiomatik dengan Python.
tokland

Jawaban:

14

Ini adalah praktik yang sangat umum dan cara mengatasinya adalah melalui filter tingkat tinggi .

Pada dasarnya, Anda meneruskan fungsi ke metode filter, bersama dengan daftar / urutan yang ingin Anda filter dan daftar / urutan yang dihasilkan hanya berisi elemen-elemen yang Anda inginkan.

Saya tidak terbiasa dengan sintaks python (walaupun, itu memang mengandung fungsi seperti yang terlihat pada tautan di atas), tetapi dalam c # / f # tampilannya seperti ini:

c #:

var linesWithAB = lines.Where(l => l.StartsWith("a") || l.StartsWith("b"));
foreach (var line in linesWithAB)
{
    /* line is guaranteed to ONLY start with a or b */
}

f # (mengasumsikan ienumerable, jika tidak List.filter akan digunakan):

let linesWithAB = lines
    |> Seq.filter (fun l -> l.StartsWith("a") || l.StartsWith("b"))

for line in linesWithAB do
    /* line is guaranteed to ONLY start with a or b */

Jadi, untuk menjadi jelas: jika Anda menggunakan kode / pola yang dicoba dan diuji, itu adalah gaya yang buruk. Itu, dan mengubah daftar dalam memori dengan cara Anda muncul melalui clear_lines () kehilangan Anda keamanan thread dan harapan paralelisme yang bisa Anda miliki.

Steven Evers
sumber
3
Sebagai catatan, sintaks python untuk ini akan menjadi generator ekspresi: (line for line in lines if line.startswith("a") or line.startswith("b")).
Latty
1
+1 untuk menunjukkan bahwa implementasi imperatif (yang tidak perlu) clear_linessebenarnya adalah ide yang buruk. Dengan Python Anda mungkin akan menggunakan generator untuk menghindari memuat file lengkap dalam memori.
tokland
Apa yang terjadi ketika file input lebih besar dari memori yang tersedia?
Blrfl
@ Bllfl: baik, jika istilah generator konsisten antara c # / f # / python, lalu apa yang diterjemahkan oleh @tokland dan @ Lattyware ke dalam c # / f # hasil dan / atau hasil! pernyataan. Ini sedikit lebih jelas dalam contoh f # saya karena Seq.filter hanya dapat diterapkan pada koleksi IEnumerable <T> tetapi kedua contoh kode akan bekerja jika linesmerupakan koleksi yang dihasilkan.
Steven Evers
@mcwise: Ketika Anda mulai melihat semua fungsi lain yang tersedia yang beroperasi dengan cara ini mulai menjadi sangat seksi dan sangat ekspresif karena mereka semua dapat dirantai dan disusun bersama. Lihatlah skip, take, reduce( aggregateNET), map( selectNET), dan masih ada lagi tapi itu awal yang benar-benar solid.
Steven Evers
14

Baru-baru ini saya harus mengimplementasikan programmer firmware menggunakan format S-record Motorola , sangat mirip dengan yang Anda gambarkan. Karena kami memiliki beberapa tekanan waktu, draft pertama saya mengabaikan redudansi dan membuat penyederhanaan berdasarkan subset yang sebenarnya saya perlukan untuk digunakan dalam aplikasi saya. Itu lulus ujian saya dengan mudah, tetapi gagal keras begitu orang lain mencobanya. Tidak ada petunjuk apa masalahnya. Semua berhasil tetapi gagal pada akhirnya.

Jadi saya tidak punya pilihan selain menerapkan semua cek yang berlebihan, untuk mempersempit di mana masalahnya. Setelah itu, saya butuh sekitar dua detik untuk menemukan masalah.

Mungkin butuh dua jam ekstra untuk melakukannya dengan cara yang benar, tetapi menghabiskan satu hari dari waktu orang lain juga dalam pemecahan masalah. Sangat jarang bahwa beberapa siklus prosesor bernilai satu hari pemecahan masalah yang sia-sia.

Yang sedang berkata, di mana membaca file yang bersangkutan, itu sering bermanfaat untuk merancang perangkat lunak Anda untuk bekerja dengan membacanya dan memprosesnya satu baris pada satu waktu, daripada membaca seluruh file ke dalam memori dan memprosesnya dalam memori. Dengan begitu masih bisa bekerja pada file yang sangat besar.

Karl Bielefeldt
sumber
"Sangat jarang bahwa beberapa siklus prosesor bernilai satu hari pemecahan masalah yang sia-sia." Terima kasih atas jawabannya, Anda punya poin bagus.
marktani
5

Anda dapat mengajukan pengecualian elsejika ada. Dengan cara ini tidak mubazir. Pengecualian adalah hal-hal yang tidak seharusnya terjadi tetapi tetap diperiksa.

clear_lines() # removes every other line than those starting with "a" or "b"
for line in lines:
    if (line.startsWith("a)):
        # do stuff
    if (line.startsWith("b")):
        # magic
    else:
        throw BadLineException
# ...
Tulains Córdova
sumber
Saya berpendapat bahwa yang terakhir adalah ide yang buruk, karena kurang eksplisit - jika Anda kemudian memutuskan untuk menambahkan "c", itu bisa menjadi kurang jelas.
Latty
Saran pertama memiliki kelebihan ... yang kedua (anggap "b") adalah ide yang buruk
Andrew
@ Bug, saya meningkatkan jawabannya. Terima kasih atas komentar anda
Tulains Córdova
1
@Andrew saya meningkatkan jawabannya. Terima kasih atas komentar anda
Tulains Córdova
3

Dalam desain berdasarkan kontrak , satu tebakan setiap fungsi harus melakukan tugasnya seperti yang dijelaskan dalam dokumentasinya. Jadi, setiap fungsi memiliki daftar pra-kondisi, yaitu, kondisi pada input fungsi serta kondisi pasca, yaitu, kondisi output fungsi.

Fungsi harus menjamin kepada kliennya bahwa, jika input menghormati pra-kondisi, maka output akan seperti yang dijelaskan oleh pasca-kondisi. Jika setidaknya satu dari pra-kondisi tidak dihormati, fungsi dapat melakukan apa yang diinginkannya (macet, kembalikan hasil apa pun, ...). Oleh karena itu pra dan pasca-kondisi adalah deskripsi fungsi semantik.

Berkat kontrak, suatu fungsi yakin kliennya menggunakannya dengan benar dan klien yakin fungsi itu melakukan tugasnya dengan benar.

Beberapa bahasa menangani kontrak secara asli atau melalui kerangka kerja khusus. Bagi yang lain, yang terbaik adalah memeriksa pra dan pasca kondisi berkat pernyataan, seperti yang dikatakan @Lattyware. Tetapi saya tidak akan menyebut pemrograman defensif itu, karena dalam pikiran saya konsep ini lebih fokus pada perlindungan terhadap input pengguna (manusia).

Jika Anda mengeksploitasi kontrak, Anda dapat menghindari kondisi yang diperiksa secara berlebihan karena fungsi yang dipanggil berfungsi dengan baik dan Anda tidak memerlukan pemeriksaan ganda, atau fungsi yang dipanggil tidak berfungsi dan fungsi panggilan dapat berperilaku seperti yang diinginkan.

Bagian yang lebih sulit adalah menentukan fungsi mana yang bertanggung jawab atas apa, dan mendokumentasikan peran-peran ini secara ketat.

mgoeminne
sumber
1

Anda sebenarnya tidak memerlukan clear_lines () di awal. Jika garisnya bukan "a" atau "b", maka kondisional tidak akan terpicu. Jika Anda ingin menghilangkan garis-garis itu maka buat yang lain menjadi clear_line (). Saat berdiri Anda sedang melakukan dua melewati dokumen Anda. Jika Anda melewatkan clear_lines () di awal dan melakukannya sebagai bagian dari loop foreach maka Anda memotong waktu pemrosesan Anda menjadi dua.

Ini bukan hanya gaya buruk, ini juga buruk komputasi.

Insinyur Dunia
sumber
2
Bisa jadi garis-garis itu sedang digunakan untuk sesuatu yang lain, dan mereka harus ditangani sebelum berurusan dengan garis "a"/ "b". Tidak mengatakan itu mungkin ( nama yang jelas menyiratkan mereka sedang dibuang), hanya saja ada kemungkinan itu diperlukan. Jika rangkaian garis itu berulang kali diulang di masa depan, bisa juga bermanfaat untuk menghapusnya sebelumnya untuk menghindari banyak iterasi yang tidak berguna.
Latty
0

Jika Anda benar-benar ingin melakukan apa pun jika Anda menemukan string yang tidak valid (output debug misalnya) maka saya akan mengatakan itu baik-baik saja. Beberapa baris tambahan dan beberapa bulan ke depan ketika berhenti bekerja karena alasan yang tidak diketahui Anda dapat melihat output untuk mencari tahu mengapa.

Namun, jika aman untuk mengabaikannya saja, atau Anda tahu pasti Anda tidak akan pernah mendapatkan string yang tidak valid maka tidak perlu untuk cabang tambahan.

Secara pribadi saya selalu untuk memasukkan setidaknya jejak output untuk kondisi yang tidak terduga - itu membuat hidup lebih mudah ketika Anda memiliki bug dengan output terlampir memberi tahu Anda apa yang salah.

Bok McDonagh
sumber
0

... misalkan ada file teks yang berisi baris yang dimulai dengan "a", baris yang dimulai dengan "b" dan baris lainnya dan saya sebenarnya hanya ingin bekerja dengan dua jenis baris pertama. Kode saya akan terlihat seperti ini (menggunakan python, tetapi membacanya sebagai pseudocode):

# ...
clear_lines() # removes every other line than those starting with "a" or "b"
for line in lines:
    if ...

Saya benci if...then...elsekonstruksi. Saya akan menghindari seluruh masalah:

process_lines_by_first_character (lines,  
                                  'a' => { |line| ... a code ... },
                                  'b' => { |line| ... b code ... } )
kevin cline
sumber