Dalam Clean Code , penulis memberikan contoh
assertExpectedEqualsActual(expected, actual)
vs.
assertEquals(expected, actual)
dengan mantan diklaim lebih jelas karena menghilangkan kebutuhan untuk mengingat ke mana pergolakan dan potensi penyalahgunaan yang berasal dari itu. Namun, saya belum pernah melihat contoh skema penamaan sebelumnya dalam kode apa pun dan melihat yang terakhir sepanjang waktu. Mengapa coders tidak mengadopsi yang pertama jika, seperti yang ditegaskan oleh penulis, lebih jelas daripada yang terakhir?
clean-code
EternalStudent
sumber
sumber
assertEquals()
, metode yang digunakan ratusan kali dalam basis kode sehingga dapat diharapkan bahwa pembaca membiasakan diri dengan konvensi sekali. Kerangka kerja yang berbeda memiliki konvensi yang berbeda (misalnya(actual, expected) or an agnostic
(kiri, kanan) `), tetapi dalam pengalaman saya itu paling banyak sumber kebingungan kecil.assert(a).toEqual(b)
(bahkan jika IMO itu masih tidak perlu bertele-tele) di mana Anda dapat rantai beberapa pernyataan terkait.assertExpectedValueEqualsActualValue
? Tapi tunggu, bagaimana kita mengingat apakah itu menggunakan==
atau.equals
atau tidakObject.equals
? Haruskah begituassertExpectedValueEqualsMethodReturnsTrueWithActualValueParameter
?Jawaban:
Karena lebih banyak mengetik dan lebih banyak membaca
Alasan paling sederhana adalah bahwa orang suka mengetik lebih sedikit, dan menyandikan informasi itu berarti lebih banyak mengetik. Ketika membacanya, setiap kali saya harus membaca semuanya bahkan jika saya tahu apa urutan argumennya. Bahkan jika tidak terbiasa dengan urutan argumen ...
Banyak pengembang menggunakan IDE
IDE sering menyediakan mekanisme untuk melihat dokumentasi untuk metode yang diberikan dengan mengarahkan atau melalui pintasan keyboard. Karena itu, nama parameter selalu tersedia.
Pengkodean argumen menghasilkan duplikasi dan penggandengan
Nama-nama parameter harus sudah mendokumentasikan apa itu. Dengan menuliskan nama-nama dalam nama metode, kami juga menggandakan informasi itu dalam tanda tangan metode. Kami juga membuat sambungan antara nama metode dan parameter. Katakan
expected
danactual
membingungkan pengguna kami. Beralih dariassertEquals(expected, actual)
menjadiassertEquals(planned, real)
tidak perlu mengubah kode klien menggunakan fungsi. Pergi dariassertExpectedEqualsActual(expected, actual)
keassertPlannedEqualsReal(planned, real)
sarana perubahan melanggar ke API. Atau kami tidak mengubah nama metode, yang dengan cepat membingungkan.Gunakan jenis alih-alih argumen yang mendua
Masalah sebenarnya adalah bahwa kita memiliki argumen ambigu yang mudah diubah karena mereka adalah tipe yang sama. Sebagai gantinya, kami dapat menggunakan sistem tipe dan kompiler kami untuk menegakkan urutan yang benar:
Ini kemudian dapat diberlakukan di tingkat kompiler dan menjamin bahwa Anda tidak bisa mendapatkannya mundur. Mendekati dari sudut yang berbeda, ini pada dasarnya adalah apa yang dilakukan perpustakaan Hamcrest untuk tes.
sumber
assertExpectedEqualsActual
"karena lebih banyak mengetik dan lebih banyak membaca", lalu bagaimana Anda bisa mengadvokasiassertEquals(Expected.is(10), Actual.is(x))
?assertExpectedEqualsActual
masih membutuhkan programmer untuk peduli untuk menentukan argumen dalam urutan yang benar. TandaassertEquals(Expected<T> expected, Actual<T> actual)
tangan menggunakan kompiler untuk menegakkan penggunaan yang benar, yang merupakan pendekatan yang sama sekali berbeda. Anda dapat mengoptimalkan pendekatan ini untuk singkatnya, misalnyaexpect(10).equalsActual(x)
, tapi itu bukan pertanyaan ...Anda bertanya tentang perdebatan lama dalam pemrograman. Seberapa banyak verbositas baik? Sebagai jawaban umum, pengembang telah menemukan bahwa verbositas ekstra yang menyebutkan argumen tidak sepadan.
Verbositas tidak selalu berarti lebih jelas. Mempertimbangkan
copyFromSourceStreamToDestinationStreamWithoutBlocking(fileStreamFromChoosePreferredOutputDialog, heuristicallyDecidedSourceFileHandle)
melawan
copy(output, source)
Keduanya mengandung bug yang sama, tetapi apakah kita benar-benar membuatnya lebih mudah untuk menemukan bug itu? Sebagai aturan umum, hal yang paling mudah untuk di-debug adalah ketika semuanya terse maksimal, kecuali beberapa hal yang memiliki bug, dan itu cukup jelas untuk memberi tahu Anda apa yang salah.
Ada sejarah panjang dalam menambahkan verbositas. Misalnya, ada " notasi Hongaria " yang umumnya tidak populer yang memberi kita nama-nama yang bagus
lpszName
. Itu umumnya telah jatuh di pinggir jalan di populasi programmer umum. Namun, menambahkan karakter ke nama variabel anggota (sukamName
ataum_Name
atauname_
) terus memiliki popularitas di beberapa kalangan. Yang lain menjatuhkan itu sepenuhnya. Saya kebetulan bekerja pada basis kode simulasi fisika yang dokumen gaya pengkodeannya mengharuskan setiap fungsi yang mengembalikan vektor harus menentukan bingkai vektor dalam pemanggilan fungsi (getPositionECEF
).Anda mungkin tertarik pada beberapa bahasa yang dipopulerkan oleh Apple. Objective-C menyertakan nama argumen sebagai bagian dari tanda tangan fungsi (Fungsi
[atm withdrawFundsFrom: account usingPin: userProvidedPin]
ditulis dalam dokumentasi sebagaiwithdrawFundsFrom:usingPin:
. Itulah nama fungsi). Swift membuat seperangkat keputusan yang sama, mengharuskan Anda untuk memasukkan nama argumen dalam panggilan fungsi (greet(person: "Bob", day: "Tuesday")
).sumber
copyFromSourceStreamToDestinationStreamWithoutBlocking(fileStreamFromChoosePreferredOutputDialog, heuristicallyDecidedSourceFileHandle)
dituliscopy_from_source_stream_to_destination_stream_without_blocking(file_stream_from_choose_preferred_output_dialog, heuristically_decided_source_file_handle)
. Lihat betapa mudahnya itu ?! Itu karena terlalu mudah untuk melewatkan perubahan kecil di tengah-tengah kata kunci humungousunbroken, dan butuh waktu lebih lama untuk mencari tahu di mana kata batas. Menghancurkan membingungkan.withdrawFundsFrom: account usingPin: userProvidedPin
sebenarnya dipinjam dari SmallTalk.Addingunderscoresnakesthingseasiertoreadnotharderasyousee
memanipulasi argumen. Jawaban di sini menggunakan huruf besar, yang Anda abaikan.AddingCapitalizationMakesThingsEasyEnoughToReadAsYouCanSeeHere
. Kedua, 9 kali dari 10, sebuah nama tidak boleh tumbuh melebihi[verb][adjective][noun]
(di mana setiap blok adalah opsional), sebuah format yang dapat dibaca dengan baik menggunakanReadSimpleName
Penulis "Kode Bersih" menunjukkan masalah yang sah, tetapi solusi yang disarankannya agak tidak sopan. Biasanya ada cara yang lebih baik untuk meningkatkan nama metode yang tidak jelas.
Dia benar bahwa
assertEquals
(dari pustaka uji unit xUnit style) tidak memperjelas argumen mana yang diharapkan dan mana yang sebenarnya. Ini juga menggigitku! Banyak perpustakaan unit test telah mencatat masalah ini dan telah memperkenalkan sintaksis alternatif, seperti:Atau serupa. Yang tentunya jauh lebih jelas daripada
assertEquals
tetapi juga jauh lebih baik daripadaassertExpectedEqualsActual
. Dan itu juga jauh lebih komposable.sumber
fun(x)
menjadi 5 maka apa yang bisa salah jika membalikkan urutan -assert(fun(x), 5)
? Bagaimana itu menggigitmu?expected
danactual
, jadi membalikkannya mungkin menghasilkan pesan yang tidak akurat. Tapi saya setuju bahwa kedengarannya lebih alami :)assert(expected, observed)
atauassert(observed, expected)
. Contoh yang lebih baik adalah sesuatulocateLatitudeLongitude
- jika Anda membalikkan koordinat, itu akan sangat kacau.Anda mencoba untuk mengarahkan jalan Anda antara Scylla dan Charybdis untuk kejelasan, mencoba untuk menghindari kata-kata yang tidak berguna (juga dikenal sebagai rambling tanpa tujuan) serta keringkasan yang berlebihan (juga dikenal sebagai kesederhanaan samar).
Jadi, kita harus melihat antarmuka yang ingin Anda evaluasi, cara untuk melakukan debug-pernyataan bahwa dua objek sama.
Tidak, jadi nama itu sendiri sudah cukup jelas.
Tidak, jadi mari kita abaikan saja. Anda sudah melakukannya? Baik.
Hampir, pada kesalahan pesan menempatkan setiap representasi argumen ke tempat mereka sendiri.
Jadi, mari kita lihat apakah perbedaan kecil itu penting, dan tidak tercakup oleh konvensi kuat yang ada.
Apakah audiens yang dituju tidak nyaman jika argumennya tidak sengaja ditukar?
Tidak, pengembang juga mendapatkan jejak-jejak dan mereka harus memeriksa kode sumber untuk memperbaiki bug.
Bahkan tanpa jejak stack-penuh, posisi pernyataan menyelesaikan pertanyaan itu. Dan bahkan jika itu hilang dan tidak jelas dari pesan yang mana, itu paling banyak menggandakan kemungkinan.
Apakah argumennya mengikuti konvensi?
Tampaknya demikian. Meskipun tampaknya konvensi yang lemah.
Dengan demikian, perbedaannya terlihat sangat tidak signifikan, dan tatanan argumen ditutupi oleh konvensi yang cukup kuat sehingga setiap upaya untuk memasukkannya ke dalam fungsi-nama memiliki utilitas negatif.
sumber
expected
danactual
(setidaknya dengan Strings)assertEquals("foo", "doo")
memberikan pesan kesalahan adalahComparisonFailure: expected:<[f]oo> but was:<[d]oo>
... Menukar nilai akan membalikkan makna pesan, yang terdengar lebih anti simetris bagi saya. Pokoknya seperti yang Anda katakan dev memiliki indikator lain untuk menyelesaikan kesalahan, tetapi itu bisa menyesatkan IMHO dan membutuhkan sedikit waktu debugging.Seringkali itu tidak menambah kejelasan logis.
Bandingkan "Tambah" ke "AddFirstArgumentToSecondArgument".
Jika Anda membutuhkan kelebihan itu, katakanlah, tambahkan tiga nilai. Apa yang lebih masuk akal?
Lain "Tambah" dengan tiga argumen?
atau
"AddFirstAndSecondAndThirdArgument"?
Nama metode harus menyampaikan makna logisnya. Seharusnya tahu apa fungsinya. Memberitahu, pada tingkat mikro, langkah apa yang diambil tidak membuatnya lebih mudah bagi pembaca. Nama-nama argumen akan memberikan detail tambahan jika diperlukan. Jika Anda masih membutuhkan detail lebih lanjut, kodenya akan tersedia untuk Anda.
sumber
Add
menyarankan operasi komutatif. OP prihatin dengan situasi di mana urutan itu penting.sum
adalah kata kerja cromulent sempurna . Ini sangat umum dalam frasa "untuk menyimpulkan".Saya ingin menambahkan sesuatu yang diisyaratkan oleh jawaban lain, tetapi saya tidak berpikir telah disebutkan secara eksplisit:
@puck mengatakan "Masih tidak ada jaminan argumen yang disebutkan pertama dalam nama fungsi benar-benar adalah parameter pertama."
@cbojar mengatakan "Gunakan tipe bukan argumen yang mendua"
Masalahnya adalah bahwa bahasa pemrograman tidak mengerti nama: mereka hanya diperlakukan sebagai simbol atom yang buram. Oleh karena itu, seperti halnya dengan komentar kode, tidak perlu ada korelasi antara apa fungsi dinamai dan bagaimana fungsinya sebenarnya.
Bandingkan
assertExpectedEqualsActual(foo, bar)
dengan beberapa alternatif (dari halaman ini dan di tempat lain), seperti:Ini semua memiliki struktur lebih dari nama verbose, yang memberikan bahasa sesuatu yang tidak buram untuk dilihat. Definisi dan penggunaan fungsi juga tergantung pada struktur ini, sehingga tidak bisa tidak sinkron dengan apa yang dilakukan oleh implementasi (seperti nama atau komentar yang bisa).
Ketika saya menemukan atau melihat masalah seperti ini, sebelum saya berteriak pada komputer saya dengan frustrasi, saya pertama-tama meluangkan waktu untuk bertanya apakah itu adil untuk menyalahkan mesin. Dengan kata lain, apakah mesin itu memberikan informasi yang cukup untuk membedakan apa yang saya inginkan dari yang saya minta?
Panggilan seperti
assertEqual(expected, actual)
sangat masuk akalassertEqual(actual, expected)
, jadi mudah bagi kita untuk membuat mereka tercampur dan alat berat maju dan melakukan hal yang salah. Jika kita menggunakanassertExpectedEqualsActual
sebagai gantinya, itu mungkin membuat kita cenderung untuk membuat kesalahan, tetapi itu tidak memberikan informasi lebih lanjut ke mesin (tidak bisa mengerti bahasa Inggris, dan pilihan nama tidak boleh mempengaruhi semantik).Apa yang membuat pendekatan "terstruktur" lebih disukai, seperti argumen kata kunci, bidang berlabel, jenis berbeda, dll. Adalah bahwa informasi tambahan juga dapat dibaca oleh mesin , sehingga kami dapat membuat mesin mendeteksi kesalahan penggunaan dan membantu kami melakukan hal-hal dengan benar. The
assertEqual
kasus ini tidak terlalu buruk, karena satu-satunya masalah akan pesan akurat. Contoh yang lebih seram mungkinString replace(String old, String new, String content)
, yang mudah membingungkan denganString replace(String content, String old, String new)
yang memiliki arti yang sangat berbeda. Obat sederhana adalah mengambil pasangan[old, new]
, yang akan membuat kesalahan memicu kesalahan segera (bahkan tanpa tipe).Perhatikan bahwa walaupun dengan tipe, kita mungkin tidak 'memberi tahu mesin apa yang kita inginkan'. Misalnya anti-pola yang disebut "pemrograman mengetik ketat" memperlakukan semua data sebagai string, yang membuatnya mudah untuk membuat argumen bercampur (seperti kasus ini), untuk lupa melakukan beberapa langkah (misalnya melarikan diri), untuk secara tidak sengaja memecahkan invarian (mis. membuat JSON yang tidak dapat dihapus), dll.
Ini juga terkait dengan "kebutaan boolean", di mana kami menghitung sekelompok boolean (atau angka, dll.) Di satu bagian kode, tetapi ketika mencoba menggunakannya di bagian lain, tidak jelas apa yang sebenarnya mereka wakili, apakah kita telah mencampuradukkannya, dll. Bandingkan ini dengan misalnya enum yang berbeda yang memiliki nama deskriptif (misalnya
LOGGING_DISABLED
bukanfalse
) dan yang menyebabkan pesan kesalahan jika kita membuatnya tercampur.sumber
Benarkah itu? Masih tidak ada jaminan argumen yang disebutkan pertama dalam nama fungsi benar-benar adalah parameter pertama. Jadi lebih baik mencarinya (atau biarkan IDE Anda melakukan itu) dan tetap menggunakan nama yang masuk akal daripada mengandalkan secara membabi buta pada nama yang cukup konyol.
Jika Anda membaca kode, Anda harus dengan mudah melihat apa yang terjadi ketika parameter dinamai sebagaimana mestinya.
copy(source, destination)
jauh lebih mudah dipahami daripada apa puncopyFromTheFirstLocationToTheSecondLocation(placeA, placeB)
.Karena ada sudut pandang berbeda pada gaya yang berbeda dan Anda dapat menemukan x penulis artikel lain yang menyatakan sebaliknya. Anda akan menjadi gila mencoba mengikuti semua yang ditulis seseorang di suatu tempat ;-)
sumber
Saya setuju bahwa pengkodean nama parameter menjadi nama fungsi membuat penulisan dan penggunaan fungsi menjadi lebih intuitif.
Sangat mudah untuk melupakan urutan argumen dalam fungsi dan perintah shell dan banyak programmer bergantung pada fitur IDE atau referensi fungsi untuk alasan ini. Memiliki argumen yang dijelaskan dalam nama akan menjadi solusi yang fasih untuk ketergantungan ini.
Namun begitu ditulis, deskripsi argumen menjadi redunant ke programmer berikutnya yang harus membaca pernyataan, karena dalam banyak kasus variabel bernama akan digunakan.
Kesederhanaan ini akan memenangkan sebagian besar programmer dan saya pribadi merasa lebih mudah untuk membaca.
EDIT: Seperti yang ditunjukkan oleh @Blrfl, parameter pengkodean tidak begitu 'intuitif' karena Anda harus mengingat nama fungsi di tempat pertama. Ini membutuhkan mencari referensi fungsi atau mendapatkan bantuan dari IDE yang kemungkinan akan menyediakan informasi pemesanan parameter.
sumber
copyFromSourceToDestination
atau tidakcopyToDestinationFromSource
, pilihan Anda menemukannya dengan coba-coba atau membaca bahan referensi. IDE yang dapat melengkapi sebagian nama hanyalah versi otomatis dari yang terakhir.copyFromSourceToDestination
adalah bahwa jika Anda pikir itucopyToDestinationFromSource
, kompiler akan menemukan bug Anda, tetapi jika dipanggilcopy
, ia tidak akan melakukannya. Mendapatkan salinan-rutin params dengan cara yang salah itu mudah, karena strcpy, strcat dll menetapkan preseden. Dan apakah yang paling mudah dibaca? Apakah mergeLists (listA, listB, listC) membuat listA dari listB & listC, atau membaca listA & listB dan menulis listC?dir1.copy(dir2)
bekerja? Tidak ada ide. Bagaimana dengandir1.copyTo(dir2)
?