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 ...
- gunakan argparse
required=True
untuk menegakkan dua argumen, yang keduanya merupakan nama file. yang pertama adalah nama file input, yang kedua adalah nama file output - memiliki fungsi
readFromInputFile
yang pertama memeriksa untuk melihat bahwa nama file input telah dimasukkan - memiliki fungsi
writeToOutputFile
yang 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 if
kondisi 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 except
di 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!
sumber
Jawaban:
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
read
danwrite
fungsi 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
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 perubahanwriteToOutputFile
: 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.sumber
Redundansi bukanlah dosa. Redundansi yang tidak perlu adalah.
Jika
readFromInputFile()
danwriteToOutputFile()
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.Jika
readFromInputFile()
danwriteToOutputFile()
periksa sendiri parameternya, Anda bisa menampilkan pesan kesalahan khusus yang menjelaskan perlunya nama file.Jika
readFromInputFile()
danwriteToOutputFile()
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.
sumber
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 .
sumber
Asumsikan Anda memiliki fungsi (dalam C)
Dan Anda tidak dapat menemukan dokumentasi tentang jalur. Dan kemudian Anda melihat implementasinya dan dikatakan
Ini tidak hanya menguji input ke fungsi, tetapi juga memberi tahu pengguna fungsi bahwa jalur tidak boleh NULL atau string kosong.
sumber
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:
argparse
modul. Sering kali merupakan ide yang buruk untuk menggunakan perpustakaan dan kemudian melakukan tugasnya sendiri. Mengapa menggunakan perpustakaan itu?sumber
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.
sumber
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:
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.
sumber
Saya berpendapat bahwa tes tidak berlebihan.
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.
Saya menggunakan dua aturan kapan harus melakukan tindakan:
sumber
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.
sumber