Pola Kompleksitas Visual yang Menurunkan Keterbacaan Kode (2023)
(seeinglogic.com)- Kode bisa saja sangat baik secara logis, tetapi tetap melelahkan untuk dibaca lama; tulisan ini menelusuri kelelahan itu pada kompleksitas yang terlihat oleh mata
- Halstead Complexity Metrics menghitung jumlah operator dan operand untuk menghitung Volume dan Difficulty, dan menilai implementasi dengan lebih banyak variabel serta operator sebagai lebih sulit meskipun perilakunya sama
- Cognitive Complexity dari SonarSource menilai beban dari pernyataan kondisional, loop, penanganan exception, kombinasi operator logika, rekursi, dan
goto, dengan fokus pada sintaks ringkas, interupsi alur linear, dan alur kontrol bersarang - Dari sisi variabel, variable shadowing, nama yang mirip, rentang hidup variabel yang panjang, dan pola penggunaan yang tidak familier meningkatkan biaya pembaca dalam melacak aliran data
- Fungsi kecil, pola yang familier, pengelompokan chain panjang, ekspresi kondisional sederhana,
gotoyang terbatas, nesting dangkal, nama variabel yang mudah dibedakan, dan rentang hidup variabel yang pendek menjadi kriteria keterbacaan yang dapat diterapkan lintas bahasa dan format
Batasan dan tujuan metrik keterbacaan kode
- Untuk keterbacaan kode, tidak ada satu metrik yang digunakan luas dan disepakati bersama
- Referensi yang bisa dijadikan acuan cenderung berupa makalah akademik yang tidak banyak dipakai di dunia nyata atau opini, sehingga dibutuhkan alat diskusi yang lebih konkret dan bisa langsung dipakai dalam code review
- Tujuannya bukan menciptakan metrik baru, melainkan mengumpulkan pola visual yang bisa digunakan siapa pun saat membicarakan apakah kode mudah dibaca
- Yang ditinjau adalah pengukuran atau gagasan yang memenuhi syarat berikut
- Dapat diterapkan pada potongan source code atau satu fungsi
- Tidak hanya berfokus pada kompleksitas inti yang sulit dipisahkan dari algoritme implementasinya sendiri, seperti Cyclomatic Complexity
- Tidak berhenti pada elemen gaya permukaan seperti panjang nama variabel, spasi, indentasi, atau penempatan tanda kurung
Halstead Complexity Metrics
- Maurice Halstead mengusulkan Halstead Complexity Metrics pada akhir 1970-an untuk membuat ukuran empiris bagi source code
- Metrik ini dapat diterapkan lintas bahasa dan platform, serta berfokus pada bentuk penulisan kode, bukan algoritme yang diimplementasikan itu sendiri
- Ukuran intinya adalah empat jumlah berdasarkan operator dan operand
- Jumlah operator unik
n1 - Jumlah operand unik
n2 - Jumlah total operator
N1 - Jumlah total operand
N2
- Jumlah operator unik
- Berdasarkan itu, Halstead membuat metrik terkait seperti length, volume, dan difficulty, bahkan mencoba menurunkan angka untuk memperkirakan jumlah bug dalam implementasi
- Secara intuitif, makin banyak operator, makin banyak interaksi potensial yang perlu dipertimbangkan; makin banyak operand, makin sulit memahami kemungkinan aliran data
-
Contoh JavaScript
- Untuk fungsi penentu ganjil-genap yang sama, implementasi sederhana yang memakai
ifdanreturnmemiliki operator dan operand lebih sedikit - 4 operator unik, 7 operator total
- 5 operand unik, 6 operand total
Volume33,30,Difficulty2,50- Implementasi yang memakai array,
Number, ekspresi perbandingan, dan indeks memiliki lebih banyak operator dan operand - 7 operator unik, 10 operator total
- 9 operand unik, 12 operand total
Volume71,35,Difficulty3,75- Implementasi pertama terlihat lebih sederhana secara visual, dan nilai
VolumesertaDifficultyHalstead juga mendukung hal itu - Kekurangannya adalah tidak selalu jelas apa yang harus dianggap sebagai operator dan operand di semua bahasa; untuk pengukuran, lebih baik menentukan alat atau implementasi tertentu dan menggunakannya secara konsisten
- Untuk fungsi penentu ganjil-genap yang sama, implementasi sederhana yang memakai
-
Pola praktis dari Halstead
- Fungsi yang lebih kecil dan variabel yang lebih sedikit umumnya lebih mudah dibaca
- Operator khusus bahasa atau syntax sugar memberi beban tambahan bagi pembaca, jadi sebaiknya tidak disalahgunakan
- Menghubungkan komponen fungsional seperti
map,reduce,filter, lambda, iterator, dan comprehension dalam chain panjang dapat menurunkan keterbacaan meskipun tampak ringkas - Chain panjang seperti ini bisa lebih sering terlihat di JavaScript dan Rust, atau pada kode Python yang terlalu dalam memakai
itertools
Tingkat kesulitan baca menurut Cognitive Complexity
- Cognitive Complexity yang dibuat SonarSource adalah metrik untuk menangkap kesulitan membaca dengan lebih akurat
- Gagasan inti metrik ini ada tiga
- Sintaks ringkas yang menggabungkan pernyataan menurunkan tingkat kesulitan
- Setiap kali alur keluar dari alur linear, tingkat kesulitan naik
- Alur kontrol bersarang menaikkan tingkat kesulitan
- Ada kritik bahwa namanya terdengar seperti metrik yang ilmiah atau objektif, tetapi secara praktis ini bisa dilihat sebagai heuristik yang efektif
-
Masalah kepadatan sintaks ringkas
- Sintaks ringkas seperti
MyObj myObj = a?.myObj;lebih pendek dan membutuhkan lebih sedikit waktu baca dibanding bentukif (a != null) { myObj = a.myObj; } - Namun kedua kode itu mungkin sebenarnya tidak sepenuhnya sama
- Yang pertama membuat
myObjmenjadia.myObjataunull - Yang kedua membuat
myObjmenjadia.myObjatauundefined - Bahasa dengan pemeriksaan tipe kuat seperti TypeScript atau Rust pun hanya mengurangi kemungkinan kasus yang terlewat, bukan menjamin semua kasus ditangani dengan benar
- Jika dukungan pemeriksaan tipe lemah seperti JavaScript biasa, kemungkinan corner case seperti ini tidak tertangani menjadi lebih tinggi
- Sintaks ringkas bisa mudah ditulis dan mudah dibaca, tetapi ada trade-off antara keringkasan dan kepadatan
- Sintaks ringkas seperti
-
Elemen yang memecah alur linear
- Kode linear tanpa pernyataan kondisional lebih mudah dipindai dibanding kode dengan pernyataan kondisional
- Cognitive Complexity memandang bukan hanya pernyataan kondisional, loop, dan
goto, tetapi juga makro kondisional,try/except, urutan operator logika, dan rekursi sebagai elemen yang meningkatkan tingkat kesulitan switchdihitung sebagai satu grup, tetapi chainelse-ifdianggap makin sulit untuk setiap tambahanelse-if- Alasannya,
else-ifdapat melakukan dua atau lebih perbandingan di tiap cabang - Namun fall-through pada
switchdanbreakyang hilang juga dapat menaikkan tingkat kesulitan baca - Kasus ketika operator logika yang sama disambung dalam ekspresi kondisional dan kasus ketika
&&,||,!dicampur memiliki tingkat kesulitan yang berbeda debug || verbose || consoleModeadalah ekspresi kondisional sederhanadebug || (verbose && consoleMode)lebih sulit dibaca karena mencampur operatordebug || !(verbose && consoleMode)menjadi lebih kompleks karena juga memuat negasi
-
Penanganan exception dan goto
try/catchmenaikkan tingkat kesulitan dalam Cognitive Complexity, tetapi beberapa blokcatchtidak dianggap lebih sulit daripada satucatch, sedangkantrydanfinallydiabaikan- Tindakan melempar exception itu sendiri juga dapat menimbulkan biaya baca
- Jika penanganan exception melewati batas fungsi, kompleksitas fungsi-fungsi terkait menjadi saling terjalin
- Pembaca harus mencari di mana exception tersebut ditangkap
gotobiasanya dihitung sebagai elemen yang menaikkan tingkat kesulitan- Namun bentuk seperti
goto outataugoto doneyang melepas resource dan keluar dari fungsi pada kondisi error dianggap bisa berguna oleh sebagian pakar - Sebaliknya,
gotoyang melompati batas loop dengan cara yang tidak dapat diungkapkan lewatcontinueataubreakmemberi beban baca besar karena pembaca harus menyusun ulang alur kontrol baru
Nesting dan bentuk fungsi
- Jika pernyataan kondisional itu sendiri sulit dibaca, maka pernyataan kondisional bersarang lebih sulit lagi
- Cognitive Complexity memberi tambahan tingkat kesulitan untuk setiap level nesting, selain skor dari pernyataan kondisional atau loop itu sendiri
- Gagasan ini juga disebut dengan nama seperti “Level of Indentation” atau “Bumpy Road”
- Jika nesting melewati 2 tingkat, kode menjadi sangat sulit dibaca; kode yang mengurangi nesting dengan early return terbaca lebih datar
- Metrik ini tidak secara langsung mencerminkan panjang fungsi, tetapi dengan kondisi lain yang sama, fungsi panjang membutuhkan lebih banyak usaha baca daripada fungsi pendek
Nama variabel, rentang hidup, dan pola familier
-
Nama yang mudah dibedakan dan deskriptif
- Nama deskriptif penting untuk memahami apa yang ingin dilakukan kode, sementara nama yang duplikatif atau seperti sandi memberi efek sebaliknya
- Variable shadowing berbahaya
- Situasi yang memaksa pembaca menimbang aturan scope untuk membedakan variabel mana yang dipakai perlu dihindari
- Identifier yang tampak mirip secara visual juga perlu dihindari
- Nama yang mudah tertukar secara visual seperti
idanj, atauitemdanitems, dapat memicu kesalahan - Kode yang memakai beberapa variasi dari nama variabel yang sama dalam satu fungsi, seperti
node,_node,thisNode, adalah bentuk yang mudah menimbulkan bug
-
Rentang hidup variabel yang pendek
- Live variable analysis melihat rentang dari titik pertama variabel dipakai hingga titik terakhir variabel mungkin dipakai
- Jika rentang hidup variabel panjang, pembaca harus menyimpan lebih banyak variabel dan kemungkinan nilainya di kepala
- Dibanding mendeklarasikan semua variabel di bagian paling atas fungsi, mendeklarasikannya tepat sebelum digunakan dapat mengurangi rentang hidup
- Kasus terburuk adalah situasi ketika variabel hidup melintasi beberapa fungsi dan dipakai di banyak tempat
- Jika rentang hidup beberapa variabel pada dasarnya sama, objek mungkin lebih tepat
- Jika objek tidak cocok, sebaiknya minimalkan jumlah fungsi dan baris yang harus dibaca pembaca untuk memahami nilai tersebut
-
Chain panjang dan variabel perantara
- Gaya functional programming membuat rentang hidup variabel lebih pendek, tetapi chain yang terlalu panjang atau nesting callback dapat menimbulkan beban baca
- Chain fungsi panjang dapat dibagi menjadi grup kecil, dan variabel perantara atau helper function yang diberi nama baik dapat mengurangi beban kognitif pembaca
- Versi yang memakai variabel perantara mungkin sedikit kurang efisien
- Selama alat performa tidak menunjukkan bahwa baris tersebut benar-benar bottleneck, perbedaan efisiensi mikro seperti ini tidak penting
-
Penggunaan ulang pola kode yang familier
- Menggunakan ulang bentuk kode dan variabel yang familier membuat pembaca mengenali pola yang sudah mereka ketahui, sehingga dapat membaca dengan lebih ringan
- Ini berkaitan dengan Principle of Least Surprise
- Dalam codebase, sebaiknya bentuk berulang seperti cara menulis pernyataan kondisional dijaga tetap konsisten
- Saat harus menyimpang dari pola, perbedaannya dapat ditunjukkan lewat nama variabel atau komentar
- Jika gagasan ini didorong sampai akhir, arahnya adalah memakai fungsi template atau fungsi generik agar pembaca tidak perlu mengenali ulang pola yang berulang
8 pola visual untuk meningkatkan keterbacaan
- Line/Operator/Operand count: Fungsi yang lebih kecil serta variabel dan operator yang lebih sedikit lebih mudah dibaca
- Novelty: Hindari kebaruan dalam bentuk fungsi, operator, dan syntax sugar; gunakan ulang pola umum dalam codebase
- Grouping: Chain fungsi panjang, iterator, dan comprehension dibagi menjadi grup logis dengan helper function atau variabel perantara
- Conditional simplicity: Pertahankan ekspresi kondisional sesingkat mungkin, dan dalam satu kondisi lebih pilih urutan operator yang sama daripada mencampur operator logika yang berbeda
- Gotos: Jangan memakai
gotokecuali mengikuti pola penanganan error tertentu dan alternatifnya lebih buruk - Nesting: Minimalkan logika bersarang dan perubahan indentasi besar; jika nesting dalam memang diperlukan, pisahkan ke fungsi tersendiri alih-alih menguburnya dalam fungsi besar
- Variable distinction: Gunakan nama variabel yang deskriptif dan mudah dibedakan secara visual, serta hindari variable shadowing
- Variable liveness: Pertahankan rentang hidup variabel tetap pendek, dan berhati-hatilah khususnya pada rentang hidup panjang yang melintasi batas fungsi
Masalah yang terlihat dalam codebase nyata
- Codebase yang menimbulkan kelelahan mental besar memiliki beberapa antipattern sekaligus
- Secara konkret, ada fungsi panjang, campuran berbagai konstruksi bahasa, dan banyak chain fungsi yang seharusnya dipisahkan menjadi helper function
- Akibatnya, kompleksitas nesting dan rentang hidup variabel yang panjang meningkat bersamaan di dalam fungsi besar
- Meski kualitas kode dan penulisnya tinggi, satu atau lebih bug fatal ditemukan
- Salah satunya adalah bug yang mudah dilihat, tetapi kemungkinan terlewat karena berada di tengah fungsi yang panjang dan kompleks sehingga sulit ditalar
- Orang yang paling mungkin paling banyak membaca kode Anda sebulan kemudian adalah Anda sendiri, jadi menulis dalam bentuk yang mudah dibaca berkaitan langsung dengan biaya praktik kerja nyata
1 komentar
Opini Hacker News
Pernyataan artikel bahwa rantai map/reduce/filter yang panjang atau berjumlah banyak merusak keterbacaan sama sekali tidak didukung oleh bagian lain
Rasanya seperti menyelipkan keluhan umum bahwa itu buruk karena belum terbiasa, padahal setelah sedikit terbiasa, dalam banyak kasus ia jauh lebih mudah dibaca dan ditulis daripada alternatifnya
Misalnya, menurut saya sulit menyajikan kode yang lebih mudah dibaca daripada
books.filter(book => book.pageCount > 1000).map(book => book.author).distinct()Dilihat dari metrik kompleksitas pun, kode seperti ini lebih baik daripada hampir cara lain apa pun, dan orang seharusnya mempelajari setidaknya dasar-dasar pemrograman fungsional. Tidak perlu sampai menjelaskan monad, tetapi harus cukup familier agar tidak asal merendahkan
mapdanfilterSaya melihatnya mulai sulit dibaca kira-kira sejak rantai dengan 5 pemanggilan berurutan, dan contoh di artikel juga sepanjang itu
“multiple” mengacu pada kasus adanya rantai bersarang atau tipe yang sedang dimanipulasi berubah; dalam kasus seperti ini, kecepatan membaca melambat
Komponen fungsional bisa elegan, tetapi juga bisa dipakai berlebihan
Jika rantainya menjadi terlalu panjang, kondisinya makin kompleks, dan daftar/variabelnya banyak, ia menjadi sulit dipahami sekaligus
Dalam loop prosedural, kita bisa memberi nama pada hasil antara, dan berkat nama itu kita bisa melupakan pemrosesan sebelumnya lalu fokus pada tahap berikutnya
SELECT DISTINCT author FROM books WHERE pageCount > 1000;Secara pribadi, saya cenderung memecah rantai untuk memberi nama pada hasil antara, semacam memakai nama variabel seperti komentar
var longBooks = books.filter(book => book.pageCount > 1000)var authorsOfLongBooks = longBooks.map(book => book.author).distinct()?- setof(Author, Book^Pages^(book_author(Book, Author), book_pages(Book, Pages), Pages > 1000), Authors).Tergantung struktur database Prolog, bisa dibuat lebih pendek lagi
?- setof(Author, Pages^(book(_, Author, Pages), Pages > 1000), Authors).Menurut saya kode yang baik pada dasarnya punya sisi kualitatif dan sastrawi yang cukup besar
Programmer atau kalangan akademis yang terbiasa dengan pemikiran matematis dan menginginkan jawaban kuantitatif sering merasa tidak nyaman dengan hal ini
Saya menyukai Dostoyevsky dan Wodehouse sekaligus; keduanya hebat, tetapi caranya sangat berbeda
Meski coding bukan bidang yang seterbuka itu, saya pernah menangani codebase yang baik namun terasa sangat berbeda secara kualitatif, dan seperti membiasakan diri dengan gaya penulis baru, sering kali butuh waktu sampai “merasakan” gaya sebuah codebase
Maksudnya, urutan fungsi disusun agar ketika file dibaca dari atas ke bawah, narasinya mengalir alami, dan implementasinya deklaratif seolah berbicara kepada pembaca
Saya mengikuti paradigma pemrograman fungsional murni, dan menurut saya cara ini lebih cocok dengan gaya yang naratif
Karena dependensi/input fungsi dibatasi pada argumen atau fungsi murni lain, dan output hanya terkandung dalam tipe kembalian, lebih mudah memandu kompleksitas langkah demi langkah
Berbeda dari paradigma lain yang punya kompleksitas seperti state tersembunyi, ironisnya menurut saya paradigma yang paling presisi secara matematis juga paling cocok untuk gaya yang lebih naratif
Bentuknya tidak penting; jika tanpa pengalaman pengembangan yang panjang seseorang tidak bisa mengetahui apa yang dilakukan fungsi itu dalam waktu yang wajar, itu memang kode buruk
Misalnya operator ternary di artikel,
return n % 2 === 0 ? 'Even' : 'Odd';, terasa seperti dibaca terbalik oleh otak manusia, dan lebih cocok untuk compiler memproses pohon sintaks daripada untuk manusiaSeorang matematikawan manusia mungkin akan menulisnya sebagai fungsi per bagian, seperti jika
n mod 2 = 0maka'Even', selain itu'Odd', dan itu jauh lebih jelasIa membantu anggota tim baru mempelajari gaya yang konsisten, dan menjaga gaya tim tetap cukup konsisten
Komentar tentang
.editorconfigjuga layak dilihat: https://news.ycombinator.com/item?id=43333011Ini mengurangi perdebatan soal detail gaya dalam pull request
Artikelnya bagus, tetapi menurut saya melewatkan faktor yang paling melelahkan secara mental saat membaca kode: mutabilitas
Saat membaca sebuah method, bisa “mengunci” makna sebuah variabel sekali saja lalu mempertahankannya apa adanya selama menalar sisanya itu adalah hadiah besar
Pemahaman terhadap sebuah method seharusnya meningkat secara monoton dari 0% ke 100%, bukan sampai harus memulai ulang method itu di kepala karena bingung bagaimana badan loop mengubah accumulator pada iterasi tertentu
Alasan sebenarnya GOTO berbahaya juga ada di sini. Bukan karena memindahkan instruction pointer di kepala dalam sebuah method itu sulit, melainkan karena saat ada GOTO, sulit mengetahui status variabel yang mutable
Baik variabel mutable maupun immutable bisa membantu atau menghambat pergerakan itu, tergantung seberapa rapi kode tersebut memetakan dirinya ke ruang itu
Variabel immutable memang punya sedikit keunggulan taktis karena tidak perlu khawatir nilainya berubah atau berubah dengan cara yang menyesatkan, tetapi berdasarkan pengalaman, keunggulan itu tidak cukup besar untuk dijadikan aturan “selalu gunakan immutability”
Kadang mutabilitas justru membuat ruang informasi itu dapat diekspresikan jauh lebih rapi
Jika dilihat dari sudut pandang pihak yang dipanggil, bukan dari titik pemanggilan, ketika seseorang bisa melompat ke baris tertentu, kita tidak bisa menelusuri balik apa yang terjadi sebelumnya. Karena bisa datang dari mana saja, yang dibutuhkan bukan analisis lokal, melainkan analisis program global
Jika mutabilitas adalah sumber sebenarnya dari kompleksitas GOTO, maka pernyataan
ifdan loopforjuga seharusnya punya masalah yang samaSaya setuju mutabilitas dan state langsung menciptakan kompleksitas, tetapi menurut saya GOTO berada dalam kategori yang sepenuhnya berbeda dan jauh lebih merusak
Pola yang secara pribadi tidak saya sukai adalah langsung return di
ifdan membiarkan sisanya menjadi jalur default implisitif (n % 2 === 0) return "Even"; return "Odd";memang lebih pendek, tetapi saya jauh lebih sukaif ... return "Even"; else return "Odd";Alasannya, yang pertama memberi kesan asimetris.
"Even"dan"Odd"adalah pilihan yang simetris, jadi versi denganelseterasa lebih intuitifreturn (n % 2 === 0) ? "Even" : "Odd";adalah yang paling mudah dibaca karena boilerplate-nya paling sedikitJika bahasanya punya operator ternary, seharusnya mudah dikenali
Berdasarkan pengalaman,
elsemenciptakan nesting tambahan dan mudah membuat kita terus mengevaluasi kondisi batas atau variabel di luar cakupan langsung jalur normal. Semua konteks itu harus terus dibawaTepat di bawah nama fungsi, pada satu tingkat indentasi, return-nya terlihat secara visual, dan jika tidak ada terminasi dini, terasa ada hasil yang terjamin
Kalau return terkubur lebih jauh di bawah, rasanya agak janggal
return "Odd";terlihat terpisah dariif, dan jika bahasanya mengizinkan, saya juga akan menambahkan kurung kurawal pada badanifAda situasi ketika
elsebisa diterima, tetapi biasanya ketika ada efek samping; umumnya, jika direfaktor sampai hilang, hasilnya menjadi lebih jelasKode yang kompleks sering berubah menjadi urutan guard yang keluar berdasarkan urutan kepentingan atau biaya eksekusi, dan itu memisahkan logika fungsi/method yang sebenarnya dari kondisi terminasi
Cara pertama mengalir ke bawah dan menjalankan kumpulan instruksi kedua.
returnadalah operator khusus yang memutus control flow, sehingga control flow normal pada cara pertama tidak benar-benar memuat kelengkapan kedua kasusDalam Rust yang idiomatis,
returntidak digunakan kecuali untuk kasus luar biasa yang memutus control flow method, dan contoh kedua biasanya lebih sering terlihat tanpa pernyataan returnPython juga biasanya melakukan early return di awal untuk argumen atau state yang salah, lalu return di posisi akhir menjadi nilai kembalian sebenarnya
Karena konvensi seperti ini, jika struktur
if-elselengkap dipecah,returnyang terindentasi terlihat seperti situasi luar biasa. Jika mengikuti konvensi ini, pernyataanreturnsecara alami tampak redundan kecuali saat memutus control flow, dan konvensi Rust menjadi masuk akal. Di semua bahasa,returnadalah pernyataan yang setara denganbreakMungkin hanya saya, tetapi TypeScript kadang membuat kode sulit dibaca
Tidak masalah jika model data dijaga cukup “atomik” dan developer rajin benar-benar mendeklarasikan serta mendokumentasikan tipe
Namun ketika tipe mulai diturunkan dari tipe lain dengan utility type, dan kita mengandalkan type inference sambil menghilangkan tipe eksplisit, semuanya cepat mulai terurai
Menjadi sangat sulit melacak asal sebuah field dalam stack yang dalam, seperti 4–5 tingkat indireksi tipe. Sebagiannya hasil inferensi, sebagiannya eksplisit, sebagiannya derived type, dan alias field juga bercampur
Pada model data besar dan call stack yang dalam, bentuk yang menghilangkan return type seperti
function checkDogs(dogs: Dog[]) { ... }benar-benar tidak bisa dipakai dan sangat membuat frustrasiAlasan utamanya adalah untuk memaksa semua jalur yang dikembalikan dari fungsi itu mematuhi tipe tersebut
Saya sering melihat regresi muncul ketika menambahkan kondisi baru lalu mengembalikan tipe yang sedikit berbeda dari cabang lain
Namun, saya tidak melihat banyak nilai dalam menempelkan tipe pada deklarasi variabel
Pada contoh,
const checkedDoggos = checkDogs([])sudah bagus, dan biarkancheckedDoggosmewarisi tipe fungsiSaya sedang menangani codebase yang linternya memaksa
const checkedDoggos: DogBreedAndSize[] = checkDogs([]), dan itu cukup konyol serta tidak banyak nilainyaDi JavaScript, kita tidak bisa yakin sehingga harus terus bolak-balik naik turun stack dan menyimpannya di kepala
Pernyataan bahwa “fungsi kecil dengan sedikit variabel biasanya lebih mudah dibaca” perlu disikapi hati-hati
Saya tidak suka ketika diskusi keterbacaan hanya berfokus pada keterbacaan mikro. Jika begitu, kode mudah sekali dipecah terlalu kecil-kecil di bawah asumsi keliru bahwa keterbacaan mikro lebih penting daripada keterbacaan makro
Dogmatisme seperti ini membuat programmer hanya melihat pohon, bukan hutan, dan menghasilkan kode yang terlalu tidak efisien atau sulit di-debug
Bahasa-bahasa keluarga APL berada di ekstrem sebaliknya, tetapi titik optimal sebenarnya sepertinya ada di suatu tempat di tengah, dan sangat berbeda tergantung orangnya
Pada kode yang belum familier, berpindah ke definisi 3–4 kali saja sudah terasa berat; mungkin ini masalah saya, tetapi sulit membayangkan kebanyakan orang jauh lebih baik daripada saya
Dalam budaya .NET, terutama “clean architecture”, masalah ini terasa mengejutkan. Saat mencoba mengubah fitur atau melacak masalah, semuanya tersebar di 4 lapisan dan 15 file, dan sebagian file lebih dari 60% isinya hanya keyword
Saya tidak tahu di mana harus menarik garis, tetapi secara umum saya lebih suka satu fungsi panjang yang bisa dibaca berurutan sambil tetap mengikuti rekomendasi lain, daripada kode yang terlalu terfragmentasi sehingga harus scroll naik-turun setiap 5 baris
Hal yang sama berlaku untuk type/class; enum berisi 4 nilai yang hanya dipakai di DTO ini tidak perlu dipisah ke file lain
Artikelnya menarik, tetapi belum memuaskan
Kesimpulannya melompat terlalu cepat dan kembali lagi ke selera pribadi. Saya setuju dengan beberapa preferensi itu, tetapi artikel ini sendiri secara eksplisit mencoba keluar dari ranah selera
Pernyataan bahwa “operator atau syntactic sugar khusus bahasa membebani pembaca sehingga harus dihindari” tidak mengikuti dari metriknya. Jika sebuah fungsi memiliki 3 operator berbeda, dan satu operator khusus bahasa menggantikan ketiganya sekaligus, maka “effort” fungsi tersebut berkurang
Komponen seperti
map/reduce/filterjuga, jika digunakan dengan baik, dapat menggantikan operator lain dan mengurangi “volume”, jadi arahnya bisa ke mana sajaContoh
?.tampaknya merupakan diagnosis yang sangat spesifik bahasa terhadap desain JavaScript yang sulit dibaca. Di banyak bahasa,nulldanundefinedtidak dipisahkan, sehingga ini lazim disebut null-safe operator“Variable shadowing itu buruk sekali” dan “masa hidup yang panjang membuat lebih banyak variabel harus disimpan di kepala” bisa saling bertentangan
Dalam beberapa konteks saya sangat menyukai variable shadowing, karena ia menghapus instance sebelumnya dari scope alih-alih membiarkannya tetap bisa diakses
Ada plugin keren untuk VS Code bernama Highlight
Plugin ini bisa menerapkan warna berbeda pada kode dengan regex kustom, dan penggunaan yang umum mungkin membuat
//TODOmenjadi kuningSaya memakainya untuk meredupkan log, karena setelah log disisipkan di sana-sini, noise visualnya besar
Library yang saya pelihara memakai log seperti
this.logger?.info('Some logs here');, dan saya menerapkan opacity 0.4 agar mundur ke latar belakangMasih tetap terlihat, tetapi sekilas logika bisnis sebenarnya lebih menonjol
Konfigurasinya bisa diubah seperti ini:
"highlight.regexes": { "((?:this\\.)?(?:_)?logger(?:\\?)?.(debug|error|info|warn)[^\\)]*\\)\\;)": { "regexFlags": "gmi", "decorations": [{ "opacity": "0.4" }] } }https://marketplace.visualstudio.com/items?itemName=fabiospa...
Saya tidak setuju dengan pernyataan bahwa memecah chain fungsi atau callback yang panjang menjadi grup kecil dan memakai variabel bernama baik itu sedikit kurang efisien
Kedua versi bisa sama-sama efisien
Dalam kedua kasus, objek yang sama dialokasikan, disimpan di heap, dan menjadi target garbage collection. Perbedaan efisiensi bergantung pada compiler
Pada versi kedua, compiler semestinya bisa melihat bahwa tiap variabel hanya dipakai tepat setelah dideklarasikan, lalu memperlakukan objek-objek itu seolah keluar dari scope seperti pada pemanggilan berantai
Tentu ini dengan asumsi tipe variabel dibiarkan di-infer
Biaya yang benar-benar terlihat adalah ketika intermediate value secara eksplisit dimaterialisasi agar bisa dilihat di debugger. Misalnya, jika dibuat menjadi list, akan muncul alokasi yang sebenarnya bisa dihindari sehingga ada biaya
Upaya menguantifikasi “keterbacaan” itu bagus. Kita butuh jauh lebih banyak pendekatan seperti ini
Definisi keterbacaan yang paling umum sekarang terasa lebih dekat ke “yang mudah saya baca”
Mungkin dimensi nyata dari keterbacaan bisa ditemukan dengan menunjukkan kode kepada sangat banyak orang, meminta mereka memilih kalimat yang menjelaskan apa yang dilakukan kode itu, lalu mengukur waktunya
Soal yang dijawab benar oleh paling banyak orang dalam rata-rata waktu tersingkat akan menjadi contoh kode yang mudah dibaca di dunia nyata, dan yang lebih penting, dapat membantu mengidentifikasi praktik yang benar-benar sulit dibaca
Responden kemungkinan akan terklaster menurut sumbu seperti “pengalaman pemrograman” atau “apakah memahami paradigma X”, dan seiring tren berubah, hasilnya juga bisa bergeser dari waktu ke waktu
Apa yang kita pelajari untuk dibaca dan ditulis membentuk apa yang kita rasa mudah dibaca
Banyak faktor berpengaruh: apa yang ingin dilakukan, dengan siapa bekerja, apa yang sudah bisa dilakukan sebelum coding, bahasa lain apa yang diketahui, dan sebagainya
Setelah memetik buah yang tergantung rendah—misalnya melampaui level sekadar jangan memberi nama variabel yang arbitrer, tidak relevan, atau menyesatkan—banyak masalah “keterbacaan” pada akhirnya mungkin soal membangun konsensus
Mungkin tidak ada jawaban benar yang melampaui kelompok programmer tertentu yang ingin bekerja bersama
Keterbacaan kode mirip dengan keterbacaan bahasa: umumnya menjadi masalah bagi orang yang belum terlalu memahami bahasa itu, dan bisa diselesaikan dengan waktu
Masalah sebenarnya dalam pemrograman adalah kompleksitas kode, dan ini tidak bisa dinilai hanya dengan metrik potongan kode individual
Masalahnya ada pada hubungan antar fungsi, bukan pada pilihan implementasi di dalam body fungsi
Mengetahui apa yang dilakukan kode biasanya mudah; yang sulit adalah mengubah kode itu atau menambahkan fitur
Kesulitan ini muncul karena berbagai lapisan abstraksi menyembunyikan bagaimana semuanya saling terhubung