Apakah kondisi yang berlebihan memeriksa praktik terbaik?

16

Saya telah mengembangkan perangkat lunak selama tiga tahun terakhir, tetapi saya baru-baru ini terbangun betapa bodohnya praktik yang baik. Ini membuat saya mulai membaca buku Clean Code , yang mengubah hidup saya menjadi lebih baik, tetapi saya berjuang untuk mendapatkan wawasan tentang beberapa pendekatan terbaik untuk menulis program saya.

Saya memiliki program Python di mana saya ...

  1. gunakan argparse required=Trueuntuk menegakkan dua argumen, yang keduanya merupakan nama file. yang pertama adalah nama file input, yang kedua adalah nama file output
  2. memiliki fungsi readFromInputFileyang pertama memeriksa untuk melihat bahwa nama file input telah dimasukkan
  3. memiliki fungsi writeToOutputFileyang pertama kali memeriksa untuk melihat bahwa nama file output dimasukkan

Program saya cukup kecil sehingga saya percaya bahwa memeriksa # 2 dan # 3 berlebihan dan harus dihapus, sehingga membebaskan kedua fungsi dari ifkondisi yang tidak perlu . Namun, saya juga dituntun untuk percaya bahwa "pemeriksaan ganda adalah ok" dan mungkin merupakan solusi yang tepat dalam program di mana fungsi-fungsi dapat dipanggil dari lokasi yang berbeda di mana penguraian argumen tidak terjadi.

(Juga, jika baca atau tulis gagal, saya memiliki try exceptdi setiap fungsi untuk memunculkan pesan kesalahan yang sesuai.)

Pertanyaan saya adalah: apakah yang terbaik untuk menghindari semua pengecekan kondisi berlebihan? Haruskah logika suatu program begitu solid sehingga pemeriksaan hanya perlu dilakukan sekali? Apakah ada contoh bagus yang menggambarkan hal ini atau sebaliknya?

EDIT: Terima kasih semua atas jawabannya! Saya telah belajar sesuatu dari masing-masing. Melihat begitu banyak perspektif memberi saya pemahaman yang jauh lebih baik tentang bagaimana mendekati masalah ini dan menentukan solusi berdasarkan persyaratan saya. Terima kasih!

tesis
sumber
Ini adalah versi umum dari pertanyaan Anda: softwareengineering.stackexchange.com/questions/19549/… . Saya tidak akan mengatakan itu duplikat karena memiliki fokus yang cukup besar, tetapi mungkin bisa membantu.
Doc Brown

Jawaban:

15

Apa yang Anda minta disebut "ketahanan", dan tidak ada jawaban benar atau salah. Itu tergantung pada ukuran dan kompleksitas program, jumlah orang yang bekerja di dalamnya, dan pentingnya mendeteksi kegagalan.

Dalam program kecil yang Anda tulis sendiri dan hanya untuk diri Anda sendiri, ketahanan biasanya merupakan masalah yang jauh lebih kecil daripada ketika Anda akan menulis program kompleks yang terdiri dari beberapa komponen, mungkin ditulis oleh tim. Dalam sistem seperti itu, ada batas antara komponen dalam bentuk API publik, dan pada setiap batas, sering kali ide yang baik untuk memvalidasi parameter input, bahkan jika "logika program harus sangat solid sehingga pemeriksaan tersebut berlebihan ". Itu membuat deteksi bug lebih mudah dan membantu menjaga waktu debugging lebih kecil.

Dalam kasus Anda, Anda harus memutuskan sendiri, jenis siklus hidup yang Anda harapkan untuk program Anda. Apakah ini sebuah program yang Anda harapkan akan digunakan dan dikelola selama bertahun-tahun? Kemudian menambahkan cek berlebihan mungkin lebih baik, karena bukan tidak mungkin kode Anda akan di-refactored di masa depan dan readdan writefungsi Anda dan mungkin digunakan dalam konteks yang berbeda.

Atau itu program kecil hanya untuk tujuan belajar atau bersenang-senang? Maka cek ganda itu tidak diperlukan.

Dalam konteks "Kode Bersih", orang bisa bertanya apakah pemeriksaan ganda melanggar prinsip KERING. Sebenarnya, kadang-kadang memang demikian, setidaknya sampai tingkat tertentu: validasi input dapat diartikan sebagai bagian dari logika bisnis suatu program, dan memiliki hal ini di dua tempat dapat menyebabkan masalah pemeliharaan biasa yang disebabkan oleh pelanggaran KERING. Robustness vs DRY sering merupakan tradeoff - robustness membutuhkan redundansi dalam kode, sementara DRY mencoba meminimalkan redundansi. Dan dengan meningkatnya kompleksitas program, ketahanan menjadi lebih dan lebih penting daripada menjadi KERING dalam validasi.

Akhirnya, izinkan saya memberi contoh apa artinya itu dalam kasus Anda. Mari kita asumsikan kebutuhan Anda berubah menjadi sesuatu seperti

  • program juga akan bekerja dengan satu argumen, nama file input, jika tidak ada nama file output yang diberikan, itu secara otomatis dibangun dari nama file input dengan mengganti akhiran.

Apakah itu membuat Anda perlu mengubah validasi rangkap di dua tempat? Mungkin tidak, persyaratan seperti itu mengarah pada satu perubahan saat memanggil argparse, tetapi tidak ada perubahan writeToOutputFile: fungsi itu masih membutuhkan nama file. Jadi dalam kasus Anda, saya akan memilih untuk melakukan validasi input dua kali, risiko mendapatkan masalah pemeliharaan karena memiliki dua tempat untuk berubah adalah IMHO jauh lebih rendah daripada risiko mendapatkan masalah pemeliharaan karena kesalahan bertopeng yang disebabkan oleh terlalu sedikit pemeriksaan.

Doc Brown
sumber
"... batas antar komponen dalam bentuk API publik ..." Saya amati bahwa "kelas melompati batas". Jadi yang dibutuhkan adalah kelas; kelas domain bisnis yang koheren. Saya menyimpulkan dari OP ini bahwa prinsip "sederhana jadi tidak perlu kelas" di mana-mana sedang bekerja di sini. Mungkin ada kelas sederhana yang membungkus "objek utama", menegakkan aturan bisnis seperti "file harus memiliki nama" yang tidak hanya KERING ke atas kode yang ada tetapi tetap KERING di masa depan.
radarbob
@radarbob: apa yang saya tulis tidak terbatas pada OOP atau komponen dalam bentuk kelas. Ini juga berlaku untuk pustaka sewenang-wenang dengan API publik, berorientasi objek atau tidak.
Doc Brown
5

Redundansi bukanlah dosa. Redundansi yang tidak perlu adalah.

  1. Jika readFromInputFile()dan writeToOutputFile()merupakan fungsi publik (dan oleh konvensi penamaan Python mereka karena nama mereka tidak dimulai dengan dua garis bawah) maka fungsi mungkin suatu hari nanti dapat digunakan oleh seseorang yang menghindari argparse sama sekali. Itu berarti ketika mereka meninggalkan argumen mereka tidak bisa melihat pesan kesalahan argparse kustom Anda.

  2. Jika readFromInputFile()dan writeToOutputFile()periksa sendiri parameternya, Anda bisa menampilkan pesan kesalahan khusus yang menjelaskan perlunya nama file.

  3. Jika readFromInputFile()dan writeToOutputFile()tidak memeriksa parameter sendiri, tidak ada pesan kesalahan khusus ditampilkan. Pengguna harus mengetahui pengecualian yang dihasilkan sendiri.

Semuanya turun ke 3. Tulis beberapa kode yang benar-benar menggunakan fungsi-fungsi ini menghindari argparse dan menghasilkan pesan kesalahan. Bayangkan Anda belum melihat ke dalam fungsi-fungsi ini sama sekali dan hanya mempercayai nama mereka untuk memberikan pemahaman yang cukup untuk digunakan. Ketika hanya itu yang Anda ketahui, adakah cara untuk dikacaukan oleh pengecualian? Apakah ada kebutuhan untuk pesan kesalahan yang disesuaikan?

Mematikan bagian otak Anda yang mengingat bagian dalam dari fungsi-fungsi itu sulit. Sedemikian rupa sehingga beberapa merekomendasikan untuk menulis menggunakan kode sebelum kode yang digunakan. Dengan begitu Anda sampai pada masalah yang sudah tahu seperti apa benda-benda itu dari luar. Anda tidak harus melakukan TDD untuk melakukan itu tetapi jika Anda melakukannya TDD Anda sudah akan datang dari luar terlebih dahulu.

candied_orange
sumber
4

Sejauh mana Anda membuat metode Anda berdiri sendiri dan dapat digunakan kembali adalah hal yang baik. Itu berarti metode harus memaafkan dalam apa yang mereka terima dan mereka harus memiliki output yang terdefinisi dengan baik (tepat dalam apa mereka kembali). Itu juga berarti bahwa mereka harus dapat dengan anggun menangani segala sesuatu yang diberikan kepada mereka dan tidak membuat asumsi tentang sifat input, kualitas, waktu dll.

Jika seorang programmer memiliki kebiasaan menulis metode yang membuat asumsi tentang apa yang diteruskan, berdasarkan ide-ide seperti "jika ini rusak, kita memiliki hal-hal yang lebih besar untuk dikhawatirkan" atau "parameter X tidak dapat memiliki nilai Y karena sisanya kode mencegahnya ", maka tiba-tiba Anda tidak benar-benar memiliki komponen yang terpisah dan terpisah. Komponen Anda pada dasarnya tergantung pada sistem yang lebih luas. Itu adalah semacam kopling ketat yang halus dan mengarah pada peningkatan total biaya kepemilikan secara eksponensial seiring dengan meningkatnya kompleksitas sistem.

Perhatikan bahwa ini mungkin berarti Anda memvalidasi informasi yang sama lebih dari sekali. Tapi ini tidak masalah. Setiap komponen bertanggung jawab atas validasinya sendiri dengan caranya sendiri . Ini bukan pelanggaran KERING, karena validasinya adalah dengan memisahkan komponen independen, dan perubahan validasi dalam satu tidak harus harus direplikasi persis di yang lain. Tidak ada redundansi di sini. X memiliki tanggung jawab untuk memeriksa inputnya untuk kebutuhannya sendiri dan memberikannya kepada Y. Y memiliki tanggung jawabnya sendiri untuk memeriksa inputnya sendiri untuk kebutuhannya .

Brad Thomas
sumber
1

Asumsikan Anda memiliki fungsi (dalam C)

void readInputFile (const char* path);

Dan Anda tidak dapat menemukan dokumentasi tentang jalur. Dan kemudian Anda melihat implementasinya dan dikatakan

void readInputFile (const char* path)
{
    assert (path != NULL && strlen (path) > 0);

Ini tidak hanya menguji input ke fungsi, tetapi juga memberi tahu pengguna fungsi bahwa jalur tidak boleh NULL atau string kosong.

gnasher729
sumber
0

Secara umum, mengecek tidak selalu baik atau buruk. Selalu ada banyak aspek pertanyaan dalam kasus khusus Anda yang menjadi dasar masalah ini. Dalam kasus Anda:

  • Seberapa besar programnya? Semakin kecil, semakin jelas penelepon melakukan hal yang benar. Ketika program Anda tumbuh lebih besar, menjadi lebih penting untuk menentukan dengan tepat apa prasyarat dan kondisi akhir setiap rutinitas.
  • argumen sudah diperiksa oleh argparsemodul. Sering kali merupakan ide yang buruk untuk menggunakan perpustakaan dan kemudian melakukan tugasnya sendiri. Mengapa menggunakan perpustakaan itu?
  • Seberapa besar kemungkinan metode Anda akan digunakan kembali dalam konteks di mana penelepon tidak memeriksa argumen? Semakin besar kemungkinannya, semakin penting untuk memvalidasi argumen.
  • Apa yang terjadi jika argumen tidak hilang? Tidak menemukan file input mungkin akan berhenti memproses langsung. Itu mungkin mode kegagalan yang jelas yang mudah diperbaiki. Jenis kesalahan berbahaya adalah kesalahan di mana program dengan riang terus bekerja dan menghasilkan hasil yang salah tanpa Anda sadari .
Kilian Foth
sumber
0

Pemeriksaan ganda Anda tampaknya berada di tempat yang jarang digunakan. Jadi pemeriksaan ini hanya membuat program Anda lebih kuat:

Cek yang terlalu banyak tidak akan sakit, satu terlalu sedikit.

Namun, jika Anda memeriksa di dalam lingkaran yang sering diulang, Anda harus berpikir tentang menghapus redundansi, bahkan jika cek itu sendiri dalam banyak waktu tidak mahal dibandingkan dengan apa yang mengikuti setelah pemeriksaan.

qwerty_so
sumber
Dan karena Anda sudah memilikinya, itu tidak sepadan dengan upaya menghapus, kecuali itu dalam satu lingkaran atau sesuatu.
StarWeaver
0

Mungkin Anda bisa mengubah sudut pandang Anda:

Jika ada yang salah, apa hasilnya? Apakah itu akan membahayakan aplikasi Anda / pengguna?

Tentu saja Anda selalu bisa berdebat, apakah lebih atau kurang cek lebih baik atau lebih buruk tetapi itu adalah pertanyaan yang agak skolastik. Dan karena Anda berurusan dengan perangkat lunak dunia nyata , ada konsekuensi dunia nyata.

Dari konteks yang Anda berikan:

  • satu file input A
  • satu file keluaran B

Saya berasumsi Anda melakukan transformasi dari A ke B . Jika A dan B kecil dan transformasi kecil, apa konsekuensinya?

1) Anda lupa menentukan dari mana harus membaca: Maka hasilnya tidak ada apa - apanya . Dan waktu eksekusi akan lebih pendek dari yang diharapkan. Anda melihat hasilnya - atau lebih baik: mencari hasil yang hilang, melihat bahwa Anda memohon perintah dengan cara yang salah, memulai kembali dan semuanya baik-baik saja lagi

2) Anda lupa menentukan outputfile. Ini menghasilkan berbagai skenario:

a) Input dibaca sekaligus. Daripada transformasi dimulai dan hasilnya harus ditulis, tetapi Anda justru menerima kesalahan. Tergantung pada waktu, pengguna Anda harus menunggu (tergantung pada massa data yang dapat diproses) ini bisa mengganggu.

b) Input dibaca langkah demi langkah. Kemudian proses penulisan segera berhenti seperti pada (1) dan pengguna memulai lagi.

Pemeriksaan yang ceroboh dapat dilihat sebagai OK dalam beberapa keadaan. Itu sepenuhnya tergantung pada usecase Anda dan apa niat Anda.

Selain itu: Anda harus menghindari paranoia dan tidak melakukan terlalu banyak pemeriksaan ganda.

Thomas Junk
sumber
0

Saya berpendapat bahwa tes tidak berlebihan.

  • Anda memiliki dua fungsi publik yang memerlukan nama file sebagai parameter input. Sangat tepat untuk memvalidasi parameter mereka. Fungsi-fungsi tersebut berpotensi digunakan dalam program apa pun yang membutuhkan fungsionalitasnya.
  • Anda memiliki program yang memerlukan dua argumen yang harus berupa nama file. Kebetulan menggunakan fungsi. Sangat tepat bagi program untuk memeriksa parameternya.

Sementara nama file sedang diperiksa dua kali, mereka sedang diperiksa untuk tujuan yang berbeda. Dalam sebuah program kecil di mana Anda dapat mempercayai parameter ke fungsi telah diverifikasi, pemeriksaan dalam fungsi dapat dianggap berlebihan.

Solusi yang lebih kuat akan memiliki satu atau dua validator nama file.

  • Untuk file input, Anda mungkin ingin memverifikasi bahwa parameter menentukan file yang dapat dibaca.
  • Untuk file output, Anda mungkin ingin memverifikasi bahwa parameter adalah file yang dapat ditulis atau nama file yang valid yang dapat dibuat dan ditulis.

Saya menggunakan dua aturan kapan harus melakukan tindakan:

  • Lakukan sedini mungkin. Ini bekerja dengan baik untuk hal-hal yang akan selalu dibutuhkan. Dari sudut pandang program ini, ini adalah pemeriksaan pada nilai-nilai argv, dan validasi selanjutnya dalam logika program akan berlebihan. Jika fungsi-fungsi dipindahkan ke perpustakaan, maka mereka tidak lagi mubazir, karena perpustakaan tidak dapat percaya bahwa semua pemanggil telah memvalidasi parameter.
  • Lakukan selambat-lambatnya. Ini bekerja sangat baik untuk hal-hal yang jarang diperlukan. Dari sudut pandang program ini, ini adalah pemeriksaan pada parameter fungsi.
BillThor
sumber
0

Cek itu berlebihan. Namun untuk memperbaiki ini, Anda harus menghapus readFromInputFile dan writeToOutputFile dan menggantinya dengan readFromStream dan writeToStream.

Pada titik di mana kode menerima aliran file, Anda tahu Anda memiliki aliran yang valid terhubung ke file yang valid atau apa pun aliran dapat terhubung. Ini menghindari pemeriksaan yang berlebihan.

Anda mungkin kemudian bertanya, yah, Anda masih perlu membuka aliran di suatu tempat. Ya, tetapi itu terjadi secara internal dalam metode parsing argumen. Anda memiliki dua pemeriksaan di sana, satu untuk memeriksa bahwa nama file diperlukan, yang lain adalah pemeriksaan bahwa file yang ditunjuk oleh nama file adalah file yang valid dalam konteks yang diberikan (mis. File input ada, direktori output dapat ditulisi). Itu adalah tipe pemeriksaan yang berbeda, sehingga tidak berlebihan dan terjadi dalam metode penguraian argumen (batas aplikasi) daripada dalam aplikasi inti.

Lie Ryan
sumber