Ini niggle kecil, tetapi setiap kali saya harus kode sesuatu seperti ini, pengulangan mengganggu saya, tapi saya tidak yakin bahwa salah satu solusi tidak lebih buruk.
if(FileExists(file))
{
contents = OpenFile(file); // <-- prevents inclusion in if
if(SomeTest(contents))
{
DoSomething(contents);
}
else
{
DefaultAction();
}
}
else
{
DefaultAction();
}
- Apakah ada nama untuk jenis logika ini?
- Apakah saya sedikit terlalu OCD?
Saya terbuka untuk saran kode jahat, jika hanya karena penasaran ...
coding-style
conditions
Benjol
sumber
sumber
DefaultAction
panggilan melanggar prinsip KERINGmake sure I have valid data for DoSomething(), and then DoSomething() with it. Otherwise, take DefaultAction()
. Detail penting untuk memastikan Anda memiliki data untuk DoSomething () berada pada level abstraksi yang lebih rendah, dan karenanya harus dalam fungsi yang berbeda. Fungsi ini akan memiliki nama di tingkat abstraksi yang lebih tinggi, dan implementasinya akan tingkat rendah. Jawaban yang baik di bawah mengatasi masalah ini.Jawaban:
Ekstrak untuk memisahkan fungsi (metode) dan gunakan
return
pernyataan:Atau, mungkin lebih baik, pisahkan konten yang didapat dan prosesnya:
Pembaruan:
Mengapa tidak pengecualian, mengapa
OpenFile
tidak melempar pengecualian IO:Saya pikir itu benar-benar pertanyaan umum, bukan pertanyaan tentang file IO. Nama-nama seperti
FileExists
,OpenFile
dapat membingungkan, tetapi jika menggantinya denganFoo
,,Bar
dll, - akan lebih jelas yangDefaultAction
dapat dipanggil sesering mungkinDoSomething
, jadi ini mungkin kasus yang tidak biasa. Péter Török menulis tentang ini di akhir jawabannyaMengapa ada operator kondisional ternary dalam varian ke-2:
Jika akan ada tag [C ++], saya telah menulis
if
pernyataan dengan deklarasicontents
di bagian kondisinya:Tetapi untuk bahasa lain (seperti C),
if(contents) ...; else ...;
persis sama dengan pernyataan ekspresi dengan operator kondisional ternary, tetapi lebih lama. Karena bagian utama dari kode adalahget_contents
fungsi, saya hanya menggunakan versi yang lebih pendek (dan jugacontents
tipe yang dihilangkan ). Bagaimanapun, itu melampaui pertanyaan ini.sumber
Jika bahasa pemrograman yang Anda gunakan (0) perbandingan binary hubung singkat (mis. Jika tidak memanggil
SomeTest
jikaFileExists
mengembalikan false) dan (1) tugas mengembalikan nilai (hasilOpenFile
ditugaskan kecontents
dan kemudian nilai tersebut diteruskan sebagai argumen toSomeTest
), Anda dapat menggunakan sesuatu seperti yang berikut ini, tetapi masih disarankan bagi Anda untuk mengomentari kode yang mencatat bahwa single=
disengaja.Bergantung pada seberapa berbelit-belitnya jika, mungkin lebih baik memiliki variabel flag (yang memisahkan pengujian kondisi keberhasilan / kegagalan dengan kode yang menangani kesalahan
DefaultAction
dalam kasus ini)sumber
if
pernyataan, menurut saya.Lebih serius daripada pengulangan panggilan ke DefaultAction adalah gaya itu sendiri karena kode ditulis non-ortogonal (lihat jawaban ini untuk alasan yang baik untuk menulis ortogonal).
Untuk menunjukkan mengapa kode non-ortogonal buruk, perhatikan contoh asli, ketika persyaratan baru bahwa kita tidak boleh membuka file jika disimpan pada disk jaringan diperkenalkan. Nah, kalau begitu kita bisa memperbarui kodenya sebagai berikut:
Tapi kemudian ada juga persyaratan bahwa kita tidak boleh membuka file besar lebih dari 2Gb. Baiklah, kami baru saja memperbarui lagi:
Seharusnya sangat jelas bahwa gaya kode seperti itu akan sangat menyulitkan pemeliharaan.
Di antara jawaban di sini yang ditulis dengan benar secara orthogonal adalah contoh kedua Abyx dan jawaban Jan Hudec , jadi saya tidak akan mengulanginya, hanya menunjukkan bahwa menambahkan dua persyaratan dalam jawaban itu hanya akan menjadi
(atau
goto notexists;
alih-alihreturn null;
), tidak memengaruhi kode lain selain baris yang ditambahkan . Misal orthogonal.Saat menguji, aturan umum harus menguji pengecualian, bukan kasus normal .
sumber
Jelas:
Kamu bilang kamu terbuka bahkan untuk solusi jahat, jadi menggunakan hitungan keburukan jahat, bukan?
Sebenarnya tergantung pada konteksnya solusi ini mungkin tidak terlalu jahat daripada kejahatan melakukan tindakan dua kali atau variabel ekstra jahat. Saya membungkusnya dalam suatu fungsi, karena itu pasti tidak akan OK di tengah fungsi yang panjang (paling tidak karena pengembalian di tengah). Tetapi dari fungsi panjang tidak OK, titik.
Ketika Anda memiliki pengecualian, mereka akan lebih mudah dibaca, terutama jika Anda dapat memiliki OpenFile dan DoSomething dengan hanya membuang pengecualian jika kondisinya tidak terpenuhi, jadi Anda tidak memerlukan pemeriksaan eksplisit sama sekali. Di sisi lain dalam C ++, Java dan C # melempar pengecualian adalah operasi yang lambat, jadi dari titik kinerja, goto masih lebih disukai.
Catatan tentang "jahat": C ++ FAQ 6.15 mendefinisikan "jahat" sebagai:
Dan itu berlaku
goto
dalam konteks ini. Konstruksi kontrol aliran terstruktur lebih baik sebagian besar waktu, tetapi ketika Anda mendapatkan dalam situasi di mana mereka mengumpulkan terlalu banyak kejahatan mereka sendiri, seperti penugasan dalam kondisi, bersarang lebih dari sekitar 3 level dalam, kode duplikat atau kondisi panjang,goto
mungkin berakhir menjadi kurang jahat.sumber
for(;;) { if(!FileExists(file)) break; contents = OpenFile(file); if(!SomeTest(contents)) break; DoSomething(contents); return; } /* broken out */ DefaultAction();
goto
, karena Anda menyalahgunakanbreak
dengan cara yang tak seorang pun mengharapkannya untuk disalahgunakan, sehingga orang yang membaca kode akan lebih banyak kesulitan melihat ke mana keretakan membawa mereka daripada dengan goto yang mengatakannya secara eksplisit. Selain itu Anda menggunakan loop tak terbatas yang hanya akan berjalan sekali, yang akan agak membingungkan. Sayangnyado { ... } while(0)
tidak sepenuhnya dapat dibaca, karena Anda hanya melihat itu hanya blok lucu ketika Anda sampai di akhir dan C tidak mendukung pemecahan dari blok lain (tidak seperti perl)....
sumber
SomeTest
dapat memiliki semantik yang sama dengan keberadaan file jikaSomeTest
memeriksa jenis file, misalnya memeriksa bahwa .gif benar-benar file GIF.contents && f(contents)
. Dua fungsi untuk menyelamatkan yang lain ?!Satu kemungkinan:
Tentu saja, ini membuat kode sedikit lebih kompleks dengan cara yang berbeda. Jadi itu sebagian besar pertanyaan gaya.
Pendekatan yang berbeda akan menggunakan pengecualian, misalnya:
Ini terlihat lebih sederhana, namun itu hanya berlaku jika
DefaultAction()
cocok untuk masing-masingSomeTest()
jelas merupakan kondisi yang salah, sehingga sangat cocok untuk melempar pengecualian.sumber
(function () { ... })()
dalam Javascript,{ flag = false; ... }
dalam C-like dll.Ini berada pada level abstraksi yang lebih tinggi:
Dan ini mengisi rinciannya.
sumber
Beberapa orang menganggap pendekatan itu sedikit ekstrem, tetapi juga sangat bersih. Izinkan saya memberi ilustrasi dengan Python:
Ketika dia mengatakan fungsi harus melakukan satu hal, dia berarti satu hal.
processFile()
memilih tindakan berdasarkan hasil tes, dan hanya itu yang dilakukannya.fileMeetsTest()
menggabungkan semua kondisi tes, dan hanya itu yang dilakukannya.contentsTest()
mentransfer baris pertama kefirstLineTest()
, dan hanya itu yang dilakukannya.Sepertinya banyak fungsi, tetapi bisa dibilang seperti bahasa Inggris langsung:
Memang, itu sedikit bertele-tele, tetapi perhatikan bahwa jika pengelola tidak peduli dengan detailnya, ia dapat berhenti membaca setelah hanya 4 baris kode
processFile()
, dan ia masih akan memiliki pengetahuan tingkat tinggi yang baik tentang apa fungsi tersebut.sumber
Berkenaan dengan apa ini disebut, itu dapat dengan mudah berkembang menjadi pola anti panah saat kode Anda tumbuh untuk menangani lebih banyak persyaratan (seperti yang ditunjukkan oleh jawaban yang diberikan di https://softwareengineering.stackexchange.com/a/122625/33922 ) dan kemudian jatuh ke dalam perangkap memiliki bagian besar kode dengan pernyataan kondisional bersarang yang menyerupai panah.
Lihat Tautan seperti;
http://codinghorror.com/blog/2006/01/flattening-arrow-code.html
http://lostechies.com/chrismissal/2009/05/27/anti-patterns-and-worst-practices-the-arrowhead-anti-pattern/
Ada banyak hal lain tentang ini dan pola anti lainnya yang dapat ditemukan di Google.
Beberapa tips hebat yang Jeff berikan di blog-nya tentang ini adalah;
Lihat beberapa komentar di blog Jeff mengenai saran Steve McConnells tentang pengembalian awal juga;
Saya selalu berlangganan 1 entri / keluar per teori fungsi karena apa yang saya diajarkan 15 tahun yang lalu. Saya merasa ini hanya membuat kode jadi lebih mudah dibaca dan seperti yang Anda sebutkan lebih mudah dikelola
sumber
Ini sesuai dengan aturan KERING, no-goto, dan no-multiple-return, dapat diukur dan dibaca menurut saya:
sumber
if
di dalam yang lainif
. Juga, kode untuk menangani kasus yang gagal (DefaultAction()
) hanya di satu tempat dan untuk tujuan debugging kode tidak melompat di sekitar fungsi pembantu dan menambahkan breakpoints ke garis-garis di manasuccess
variabel diubah dengan cepat dapat menunjukkan tes mana yang lulus (di atas dipicu breakpoint) dan mana yang belum diuji (di bawah).success
menjadiok_so_far
:)Saya akan mengekstraknya ke metode terpisah dan kemudian:
yang juga memungkinkan untuk
maka mungkin Anda bisa menghapus
DefaultAction
panggilan dan meninggalkan mengeksekusiDefaultAction
untuk si penelepon:Saya suka pendekatan Jeanne Pindar juga.
sumber
Untuk kasus khusus ini, jawabannya cukup mudah ...
Ada kondisi lomba antara
FileExists
danOpenFile
: apa yang terjadi jika file dihapus?Satu-satunya cara yang waras untuk menangani kasus khusus ini adalah dengan melewati
FileExists
:Ini dengan rapi menyelesaikan masalah ini dan membuat kode lebih bersih.
Secara umum: Cobalah untuk memikirkan kembali masalah dan mencari solusi lain yang menghindari masalah sepenuhnya.
sumber
Kemungkinan lain jika Anda tidak suka melihat terlalu banyak yang lain adalah untuk menghentikan penggunaan yang lain sama sekali dan memberikan pernyataan pengembalian ekstra. Lain adalah jenis berlebihan kecuali Anda memerlukan logika yang lebih kompleks untuk menentukan apakah ada lebih dari dua kemungkinan tindakan.
Dengan demikian contoh Anda bisa menjadi:
Secara pribadi saya tidak keberatan menggunakan klausa lain karena menyatakan secara eksplisit bagaimana logika seharusnya bekerja, dan dengan demikian meningkatkan keterbacaan kode Anda. Namun beberapa alat kode kecantikan lebih suka untuk menyederhanakan pernyataan tunggal jika untuk mencegah logika bersarang.
sumber
Kasus yang ditunjukkan dalam kode sampel biasanya dapat direduksi menjadi satu
if
pernyataan. Pada banyak sistem, fungsi buka file akan mengembalikan nilai yang tidak valid jika file belum ada. Terkadang ini adalah perilaku default; di lain waktu, harus ditentukan melalui argumen. Ini berartiFileExists
pengujian dapat dibatalkan, yang juga dapat membantu dengan kondisi balapan yang dihasilkan dari penghapusan file antara pengujian keberadaan dan pembukaan file.Ini tidak secara langsung mengatasi masalah pencampuran tingkat abstraksi karena menghindari masalah beberapa tes yang tidak dapat dipasangkan secara keseluruhan, meskipun menghapus dengan uji keberadaan file tidak tidak kompatibel dengan pemisahan tingkat abstraksi. Dengan asumsi bahwa pegangan file tidak valid sama dengan "false" dan handle file ditutup ketika mereka keluar dari ruang lingkup:
sumber
Saya setuju dengan frozenkoi, bagaimanapun, untuk C # lagian, saya pikir itu akan membantu untuk mengikuti sintaks dari metode TryParse.
sumber
Kode Anda jelek karena Anda melakukan terlalu banyak dalam satu fungsi. Anda ingin memproses file atau mengambil tindakan default, jadi mulailah dengan mengatakan bahwa:
Programmer Perl dan Ruby menulis
processFile(file) || defaultAction()
Sekarang, tulislah ProcessFile:
sumber
Tentu saja Anda hanya bisa melangkah sejauh ini dalam skenario seperti ini, tetapi berikut ini caranya:
Anda mungkin ingin filter tambahan. Kemudian lakukan ini:
Meskipun ini mungkin masuk akal juga:
Mana yang terbaik? Itu tergantung pada masalah dunia nyata yang Anda hadapi.
Tetapi hal yang perlu diambil adalah: Anda dapat melakukan banyak hal dengan komposisi dan polimorfisme.
sumber
Apa yang salah dengan yang jelas
Sepertinya cukup standar bagi saya? Untuk prosedur besar semacam itu di mana banyak hal kecil harus terjadi, kegagalan yang mana pun akan mencegah yang terakhir. Pengecualian membuatnya sedikit lebih bersih jika itu pilihan.
sumber
Saya tahu bahwa ini adalah pertanyaan lama, tetapi saya perhatikan pola yang belum disebutkan; terutama, menetapkan variabel untuk kemudian menentukan metode yang ingin Anda panggil (di luar if ... else ...).
Ini sama seperti sudut lain yang harus dilihat untuk membuat kode lebih mudah untuk dikerjakan. Ini juga memungkinkan ketika Anda mungkin ingin menambahkan metode lain untuk dipanggil atau mengubah metode yang sesuai yang perlu dipanggil dalam situasi tertentu.
Daripada harus mengganti semua penyebutan metode (dan mungkin melewatkan beberapa skenario), mereka semua tercantum di akhir blok if ... else ... dan lebih mudah dibaca dan diubah. Saya cenderung menggunakan ini ketika misalnya, beberapa metode dapat dipanggil, tetapi di dalam bersarang jika ... lain ... metode dapat dipanggil dalam beberapa pertandingan.
Jika Anda menetapkan variabel yang menentukan status, Anda bisa memiliki banyak opsi yang bersarang secara mendalam, dan memperbarui status saat sesuatu akan dilakukan (atau tidak dilakukan).
Ini dapat digunakan seperti dalam contoh yang diajukan dalam pertanyaan di mana kami memeriksa apakah 'DoSomething' telah terjadi, dan jika tidak, lakukan tindakan default. Atau Anda dapat meminta negara untuk setiap metode yang ingin Anda panggil, atur jika berlaku, lalu panggil metode yang berlaku di luar ... jika ... lain ...
Di akhir pernyataan bersarang jika ... lain ..., Anda memeriksa status dan bertindak sesuai. Ini berarti Anda hanya perlu menyebutkan satu metode saja daripada semua lokasi yang harus diterapkan.
sumber
Untuk mengurangi IF bersarang:
1 / pengembalian awal;
2 / ekspresi majemuk (sirkuit pendek sadar)
Jadi, contoh Anda dapat di refactored seperti ini:
sumber
Saya melihat banyak contoh dengan "kembali" yang saya gunakan juga, tetapi kadang-kadang saya ingin menghindari membuat fungsi baru dan menggunakan loop:
Jika Anda ingin menulis lebih sedikit baris atau Anda membenci loop tak terbatas seperti saya, Anda dapat mengubah jenis loop menjadi "do ... while (0)" dan hindari "break" terakhir.
sumber
Bagaimana dengan solusi ini:
Saya membuat asumsi bahwa OpenFile mengembalikan sebuah pointer, tetapi ini bisa bekerja juga dengan tipe nilai kembali dengan menetapkan beberapa nilai default tidak dapat dikembalikan (kode kesalahan atau sesuatu seperti itu).
Tentu saja saya tidak mengharapkan beberapa tindakan yang mungkin melalui metode SomeTest pada pointer NULL (tetapi Anda tidak pernah tahu), jadi ini bisa juga dilihat sebagai pemeriksaan tambahan untuk pointer NULL untuk panggilan SomeTest (isi).
sumber
Jelas, solusi yang paling elegan dan ringkas adalah menggunakan makro preprosesor.
Yang memungkinkan Anda untuk menulis kode yang indah seperti ini:
Mungkin sulit untuk mengandalkan pemformatan otomatis jika Anda sering menggunakan teknik ini, dan beberapa IDE mungkin sedikit berteriak kepada Anda tentang apa yang salah duga. Dan seperti kata pepatah, semuanya merupakan tradeoff, tapi saya kira itu bukan harga yang buruk untuk membayar untuk menghindari kejahatan kode berulang.
sumber
Karena Anda bertanya karena penasaran, dan pertanyaan Anda tidak ditandai dengan bahasa tertentu (meskipun jelas Anda memiliki bahasa yang sangat penting), mungkin ada baiknya menambahkan bahwa bahasa yang mendukung evaluasi malas memungkinkan untuk pendekatan yang sama sekali berbeda. Dalam bahasa-bahasa itu, ekspresi hanya dievaluasi saat dibutuhkan, sehingga Anda dapat mendefinisikan "variabel" dan menggunakannya hanya ketika masuk akal untuk melakukannya. Misalnya, dalam bahasa fiksi dengan malas
let
/in
struktur Anda lupa tentang kontrol aliran dan tulis:sumber