Anda sedang membaca petikan daripada buku saya tentang kod bersih, "Mencuci kod anda." Tersedia sebagai PDF, EPUB dan sebagai edisi paperback dan Kindle. Dapatkan salinan anda sekarang.
Kod pintar ialah sesuatu yang mungkin kita lihat dalam soalan temu duga kerja atau kuiz bahasa, apabila mereka mengharapkan kita mengetahui cara ciri bahasa, yang mungkin tidak pernah kita lihat sebelum ini, berfungsi. Jawapan saya kepada semua soalan ini ialah: "ia tidak akan lulus semakan kod".
Sesetengah orang mengelirukan ringkas dengan kejelasan. Kod pendek (ringkas) tidak selalunya kod yang paling jelas (kejelasan), selalunya ia adalah sebaliknya. Berusaha untuk menjadikan kod anda lebih pendek ialah matlamat yang mulia, tetapi ia tidak sepatutnya mengorbankan kebolehbacaan.
Terdapat banyak cara untuk menyatakan idea yang sama dalam kod, dan ada yang lebih mudah difahami daripada yang lain. Kami harus sentiasa menyasarkan untuk mengurangkan beban kognitif pembangun seterusnya yang membaca kod kami. Setiap kali kita terjumpa sesuatu yang tidak jelas, kita membazirkan sumber otak kita.
Maklumat: Saya "mencuri" nama bab ini daripada buku Steve Krug tentang kebolehgunaan web dengan nama yang sama.
Mari kita lihat beberapa contoh. Cuba tutup jawapan dan teka apa yang coretan kod ini lakukan. Kemudian, hitung jumlah yang anda teka dengan betul.
Contoh 1:
const percent = 5; const percentString = percent.toString().concat('%');
Kod ini hanya menambah tanda % pada nombor dan harus ditulis semula sebagai:
const percent = 5; const percentString = `${percent}%`; // → '5%'
Contoh 2:
const url = 'index.html?id=5'; if (~url.indexOf('id')) { // Something fishy here… }
Simbol ~ dipanggil pengendali bitwise NOT. Kesan bergunanya di sini ialah ia mengembalikan nilai palsu hanya apabila indexOf() mengembalikan -1. Kod ini hendaklah ditulis semula sebagai:
const url = 'index.html?id=5'; if (url.includes('id')) { // Something fishy here… }
Contoh 3:
const value = ~~3.14;
Satu lagi penggunaan operator NOT yang tidak jelas adalah untuk membuang bahagian pecahan nombor. Gunakan Math.floor() sebaliknya:
const value = Math.floor(3.14); // → 3
Contoh 4:
if (dogs.length + cats.length > 0) { // Something fishy here… }
Yang ini boleh difahami selepas seketika: ia menyemak sama ada salah satu daripada dua tatasusunan mempunyai sebarang elemen. Walau bagaimanapun, adalah lebih baik untuk menjadikannya lebih jelas:
if (dogs.length > 0 && cats.length > 0) { // Something fishy here… }
Contoh 5:
const header = 'filename="pizza.rar"'; const filename = header.split('filename=')[1].slice(1, -1);
Yang ini mengambil sedikit masa untuk saya faham. Bayangkan kita mempunyai sebahagian daripada URL, seperti nama fail="pizza". Mula-mula, kita bahagikan rentetan dengan = dan ambil bahagian kedua, "pizza". Kemudian, kita potong watak pertama dan terakhir untuk mendapatkan piza.
Saya mungkin akan menggunakan ungkapan biasa di sini:
const header = 'filename="pizza.rar"'; const filename = header.match(/filename="(.*?)"/)[1]; // → 'pizza'
Atau, lebih baik lagi, URLSearchParams API:
const header = 'filename="pizza.rar"'; const filename = new URLSearchParams(header) .get('filename') .replaceAll(/^"|"$/g, ''); // → 'pizza'
Petikan ini pelik. Biasanya kami tidak memerlukan petikan mengenai parameter URL, jadi bercakap dengan pembangun bahagian belakang boleh menjadi idea yang baik.
Contoh 6:
const percent = 5; const percentString = percent.toString().concat('%');
Dalam kod di atas, kami menambah harta pada objek apabila syarat itu benar, jika tidak, kami tidak melakukan apa-apa. Niatnya lebih jelas apabila kita mentakrifkan secara eksplisit objek untuk dimusnahkan daripada bergantung pada pemusnahan nilai palsu:
const percent = 5; const percentString = `${percent}%`; // → '5%'
Saya biasanya lebih suka apabila objek tidak berubah bentuk, jadi saya akan mengalihkan keadaan di dalam medan nilai:
const url = 'index.html?id=5'; if (~url.indexOf('id')) { // Something fishy here… }
Contoh 7:
const url = 'index.html?id=5'; if (url.includes('id')) { // Something fishy here… }
Satu pelapik yang indah ini mencipta tatasusunan yang diisi dengan nombor dari 0 hingga 9. Tatasusunan(10) mencipta tatasusunan dengan 10 elemen kosong, kemudian kaedah kekunci() mengembalikan kekunci (nombor dari 0 hingga 9) sebagai lelaran, yang kemudiannya kita tukar menjadi tatasusunan biasa menggunakan sintaks penyebaran. Emoji kepala meletup…
Kita boleh menulis semula menggunakan gelung for:
const value = ~~3.14;
Walaupun saya suka mengelakkan gelung dalam kod saya, versi gelung lebih mudah dibaca untuk saya.
Suatu tempat di tengah akan menggunakan kaedah Array.from():
const value = Math.floor(3.14); // → 3
Array.from({length: 10}) mencipta tatasusunan dengan 10 elemen undefined, kemudian menggunakan kaedah map(), kami mengisi tatasusunan dengan nombor dari 0 hingga 9.
Kita boleh menulisnya dengan lebih pendek dengan menggunakan panggilan balik peta Array.from():
if (dogs.length + cats.length > 0) { // Something fishy here… }
Peta eksplisit() lebih mudah dibaca dan kita tidak perlu mengingati perkara yang dilakukan oleh hujah kedua Array.from(). Selain itu, Array.from({length: 10}) lebih mudah dibaca daripada Array(10). Walaupun hanya sedikit.
Jadi, apakah markah anda? Saya rasa saya kira-kira 3/7.
Sesetengah corak berada di antara kepandaian dan kebolehbacaan.
Contohnya, menggunakan Boolean untuk menapis elemen tatasusunan palsu (null dan 0 dalam contoh ini):
if (dogs.length > 0 && cats.length > 0) { // Something fishy here… }
Saya dapati corak ini boleh diterima; walaupun ia memerlukan pembelajaran, ia lebih baik daripada alternatif:
const header = 'filename="pizza.rar"'; const filename = header.split('filename=')[1].slice(1, -1);
Walau bagaimanapun, perlu diingat bahawa kedua-dua variasi menapis nilai palsu, jadi jika sifar atau rentetan kosong adalah penting, kita perlu menapis secara eksplisit untuk undefined atau null:
const header = 'filename="pizza.rar"'; const filename = header.match(/filename="(.*?)"/)[1]; // → 'pizza'
Apabila saya melihat dua baris kod rumit yang kelihatan sama, saya menganggap ia berbeza dalam beberapa cara, tetapi saya belum nampak perbezaannya lagi. Jika tidak, pengaturcara mungkin akan mencipta pembolehubah atau fungsi untuk kod berulang dan bukannya menyalinnya.
Sebagai contoh, kami mempunyai kod yang menjana ID ujian untuk dua alatan berbeza yang kami gunakan pada projek, Enzim dan Codeception:
const header = 'filename="pizza.rar"'; const filename = new URLSearchParams(header) .get('filename') .replaceAll(/^"|"$/g, ''); // → 'pizza'
Sukar untuk segera mengesan sebarang perbezaan antara dua baris kod ini. Ingat pasangan gambar di mana anda perlu mencari sepuluh perbezaan? Itulah yang dilakukan oleh kod ini kepada pembaca.
Walaupun saya biasanya ragu-ragu tentang PENGERINGAN kod yang melampau, ini adalah kes yang baik untuknya.
Maklumat: Kami bercakap lebih lanjut tentang prinsip Jangan ulangi diri anda dalam bab Bahagikan dan takluk, atau gabung dan berehat.
const percent = 5; const percentString = percent.toString().concat('%');
Kini, tidak syak lagi bahawa kod untuk kedua-dua ID ujian adalah sama.
Mari kita lihat contoh yang lebih rumit. Katakan kita menggunakan konvensyen penamaan yang berbeza untuk setiap alat ujian:
const percent = 5; const percentString = `${percent}%`; // → '5%'
Perbezaan antara dua baris kod ini sukar untuk diperhatikan dan kami tidak dapat memastikan bahawa pemisah nama (- atau _) adalah satu-satunya perbezaan di sini.
Dalam projek dengan keperluan sedemikian, corak ini mungkin akan muncul di banyak tempat. Satu cara untuk memperbaikinya ialah dengan mencipta fungsi yang menjana ID ujian untuk setiap alat:
const url = 'index.html?id=5'; if (~url.indexOf('id')) { // Something fishy here… }
Ini sudah jauh lebih baik, tetapi ia masih belum sempurna — kod berulang masih terlalu besar. Mari kita betulkan ini juga:
const url = 'index.html?id=5'; if (url.includes('id')) { // Something fishy here… }
Ini adalah kes ekstrem menggunakan fungsi kecil, dan saya biasanya cuba mengelakkan pemisahan kod sebanyak ini. Walau bagaimanapun, dalam kes ini, ia berfungsi dengan baik, terutamanya jika terdapat banyak tempat dalam projek di mana kita boleh menggunakan fungsi getTestIdProps() baharu.
Kadangkala, kod yang kelihatan hampir sama mempunyai perbezaan yang ketara:
const value = ~~3.14;
Satu-satunya perbezaan di sini ialah parameter yang kami hantar ke fungsi dengan nama yang sangat panjang. Kita boleh mengalihkan keadaan di dalam panggilan fungsi:
const value = Math.floor(3.14); // → 3
Ini menghapuskan kod yang serupa, menjadikan keseluruhan coretan lebih pendek dan lebih mudah difahami.
Setiap kali kita menghadapi keadaan yang menjadikan kod berbeza sedikit, kita harus bertanya kepada diri sendiri: adakah syarat ini benar-benar perlu? Jika jawapannya "ya", kita harus bertanya kepada diri sendiri semula. Selalunya, tiada keperluan sebenar untuk keadaan tertentu. Sebagai contoh, mengapakah kita perlu menambah ID ujian untuk alatan yang berbeza secara berasingan? Tidak bolehkah kita mengkonfigurasi salah satu alat untuk menggunakan ID ujian yang lain? Jika kita menggali cukup dalam, kita mungkin mendapati tiada siapa yang tahu jawapannya, atau sebab asal tidak lagi relevan.
Pertimbangkan contoh ini:
if (dogs.length + cats.length > 0) { // Something fishy here… }
Kod ini mengendalikan dua kes tepi: apabila assetsDir tidak wujud dan apabila assetsDir bukan tatasusunan. Juga, kod penjanaan objek diduakan. (Dan jangan bercakap tentang ternaries bersarang…) Kita boleh menyingkirkan pendua dan sekurang-kurangnya satu syarat:
if (dogs.length > 0 && cats.length > 0) { // Something fishy here… }
Saya tidak suka kaedah castArray() Lodash yang tidak ditentukan dalam tatasusunan, yang bukan seperti yang saya jangkakan, tetapi hasilnya lebih mudah.
CSS mempunyai sifat trengkas dan pembangun sering menggunakannya secara berlebihan. Ideanya ialah satu sifat boleh menentukan berbilang sifat pada masa yang sama. Berikut ialah contoh yang baik:
const header = 'filename="pizza.rar"'; const filename = header.split('filename=')[1].slice(1, -1);
Yang sama dengan:
const header = 'filename="pizza.rar"'; const filename = header.match(/filename="(.*?)"/)[1]; // → 'pizza'
Satu baris kod dan bukannya empat, dan perkara yang berlaku masih jelas: kami menetapkan jidar yang sama pada keempat-empat sisi elemen.
Sekarang, lihat contoh ini:
const percent = 5; const percentString = percent.toString().concat('%');
Untuk memahami perkara yang mereka lakukan, kita perlu tahu bahawa:
Ini menghasilkan beban kognitif yang tidak perlu dan menjadikan kod lebih sukar untuk dibaca, diedit dan disemak. Saya mengelakkan trengkas seperti itu.
Isu lain dengan sifat trengkas ialah mereka boleh menetapkan nilai untuk sifat yang kami tidak mahu ubah. Pertimbangkan contoh ini:
const percent = 5; const percentString = `${percent}%`; // → '5%'
Pengisytiharan ini menetapkan keluarga fon Helvetica, saiz fon 2rem dan menjadikan teks condong dan tebal. Apa yang kami tidak nampak di sini ialah ia turut menukar ketinggian garisan kepada nilai lalai biasa.
Peraturan saya ialah menggunakan sifat trengkas hanya apabila menetapkan nilai tunggal; jika tidak, saya lebih suka hartanah longhand.
Berikut ialah beberapa contoh yang baik:
const url = 'index.html?id=5'; if (~url.indexOf('id')) { // Something fishy here… }
Dan berikut ialah beberapa contoh yang perlu dielakkan:
const url = 'index.html?id=5'; if (url.includes('id')) { // Something fishy here… }
Walaupun sifat trengkas memang menjadikan kod lebih pendek, ia sering menjadikannya lebih sukar untuk dibaca, jadi gunakannya dengan berhati-hati.
Menghapuskan keadaan tidak selalu mungkin. Walau bagaimanapun, terdapat cara untuk membuat perbezaan dalam cawangan kod lebih mudah dikesan. Salah satu pendekatan kegemaran saya ialah apa yang saya panggil pengekodan selari.
Pertimbangkan contoh ini:
const value = ~~3.14;
Ia mungkin kesal peribadi, tetapi saya tidak suka apabila penyata pemulangan berada pada tahap yang berbeza, menjadikannya lebih sukar untuk dibandingkan. Mari tambahkan pernyataan lain untuk membetulkan perkara ini:
const value = Math.floor(3.14); // → 3
Kini, kedua-dua nilai pulangan berada pada tahap lekukan yang sama, menjadikannya lebih mudah untuk dibandingkan. Corak ini berfungsi apabila tiada satu pun cabang keadaan mengendalikan ralat, dalam hal ini pemulangan awal akan menjadi pendekatan yang lebih baik.
Maklumat: Kami bercakap tentang pulangan awal dalam bab Elakkan syarat.
Ini satu lagi contoh:
if (dogs.length + cats.length > 0) { // Something fishy here… }
Dalam contoh ini, kami mempunyai butang yang berkelakuan seperti pautan dalam penyemak imbas dan menunjukkan modal pengesahan dalam apl. Keadaan terbalik untuk prop onPress menjadikan logik ini sukar dilihat.
Mari jadikan kedua-dua keadaan positif:
if (dogs.length > 0 && cats.length > 0) { // Something fishy here… }
Kini, jelas bahawa kami sama ada menetapkan onPress atau prop pautan bergantung pada platform.
Kita boleh berhenti di sini atau melangkah lebih jauh, bergantung pada bilangan Platform.OS === keadaan 'web' dalam komponen atau berapa banyak prop yang perlu kita tetapkan secara bersyarat
Kita boleh mengekstrak prop bersyarat ke dalam pembolehubah berasingan:
const header = 'filename="pizza.rar"'; const filename = header.split('filename=')[1].slice(1, -1);
Kemudian, gunakannya dan bukannya pengekodan keras keseluruhan keadaan setiap kali:
const header = 'filename="pizza.rar"'; const filename = header.match(/filename="(.*?)"/)[1]; // → 'pizza'
Saya juga mengalihkan prop sasaran ke cawangan web kerana ia tidak digunakan oleh apl lagi.
Semasa saya berumur dua puluhan, mengingati sesuatu tidak menjadi masalah bagi saya. Saya dapat mengingati buku yang pernah saya baca dan semua fungsi dalam projek yang sedang saya kerjakan. Sekarang saya berumur empat puluhan, itu tidak lagi berlaku. Saya kini menghargai kod mudah yang tidak menggunakan sebarang helah; Saya menghargai enjin carian, akses pantas kepada dokumentasi dan alatan yang membantu saya membuat alasan tentang kod dan menavigasi projek tanpa menyimpan segala-galanya dalam kepala saya.
Kita tidak sepatutnya menulis kod untuk diri kita sekarang tetapi untuk siapa kita akan menjadi beberapa tahun dari sekarang. Berfikir adalah sukar dan pengaturcaraan memerlukan banyak perkara, walaupun tanpa perlu mentafsir kod yang rumit atau tidak jelas.
Mula berfikir tentang:
Jika anda mempunyai sebarang maklum balas, mastodon saya, tweet saya, buka isu di GitHub, atau e-mel saya di artem@sapegin.ru. Dapatkan salinan anda.
Atas ialah kandungan terperinci Mencuci kod anda: jangan buat saya berfikir. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!