2 poin oleh GN⁺ 2025-03-12 | 1 komentar | Bagikan ke WhatsApp
  • 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, goto yang 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
  • 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 if dan return memiliki operator dan operand lebih sedikit
    • 4 operator unik, 7 operator total
    • 5 operand unik, 6 operand total
    • Volume 33,30, Difficulty 2,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
    • Volume 71,35, Difficulty 3,75
    • Implementasi pertama terlihat lebih sederhana secara visual, dan nilai Volume serta Difficulty Halstead 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
  • 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 bentuk if (a != null) { myObj = a.myObj; }
    • Namun kedua kode itu mungkin sebenarnya tidak sepenuhnya sama
    • Yang pertama membuat myObj menjadi a.myObj atau null
    • Yang kedua membuat myObj menjadi a.myObj atau undefined
    • 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
  • 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
    • switch dihitung sebagai satu grup, tetapi chain else-if dianggap makin sulit untuk setiap tambahan else-if
    • Alasannya, else-if dapat melakukan dua atau lebih perbandingan di tiap cabang
    • Namun fall-through pada switch dan break yang 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 || consoleMode adalah ekspresi kondisional sederhana
    • debug || (verbose && consoleMode) lebih sulit dibaca karena mencampur operator
    • debug || !(verbose && consoleMode) menjadi lebih kompleks karena juga memuat negasi
  • Penanganan exception dan goto

    • try/catch menaikkan tingkat kesulitan dalam Cognitive Complexity, tetapi beberapa blok catch tidak dianggap lebih sulit daripada satu catch, sedangkan try dan finally diabaikan
    • 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
    • goto biasanya dihitung sebagai elemen yang menaikkan tingkat kesulitan
    • Namun bentuk seperti goto out atau goto done yang melepas resource dan keluar dari fungsi pada kondisi error dianggap bisa berguna oleh sebagian pakar
    • Sebaliknya, goto yang melompati batas loop dengan cara yang tidak dapat diungkapkan lewat continue atau break memberi 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 i dan j, atau item dan items, 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 goto kecuali 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

 
GN⁺ 2025-03-12
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 map dan filter

    • Ungkapannya terasa tidak perlu tajam, tetapi contoh yang dimaksud kalimat yang dikutip bukanlah rantai pendek seperti itu
      Saya 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
    • Contoh itu hanya filter yang secara konseptual sederhana pada satu daftar
      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;
    • Contohnya baik-baik saja, tetapi menurut saya keluhan awal lebih mendekati rantai yang sangat panjang
      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()
    • Solusi SQL yang diposting orang-orang juga bagus, tetapi dengan Prolog bisa ditulis seperti ini
      ?- 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

    • Setuju 100%. Salah satu pujian terbaik yang pernah saya terima terkait pemrograman adalah “kodenya terbaca seperti cerita
      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
    • Jika butuh lebih dari 5 detik untuk membaca dan memahami tujuan tingkat tinggi sebuah fungsi, menurut saya itu kode buruk
      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
    • Banyak pola sintaks yang dianggap elegan sebenarnya terasa kurang jelas bahkan dibanding matematika
      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 manusia
      Seorang matematikawan manusia mungkin akan menulisnya sebagai fungsi per bagian, seperti jika n mod 2 = 0 maka 'Even', selain itu 'Odd', dan itu jauh lebih jelas
    • Karena itu code review penting
      Ia membantu anggota tim baru mempelajari gaya yang konsisten, dan menjaga gaya tim tetap cukup konsisten
      Komentar tentang .editorconfig juga layak dilihat: https://news.ycombinator.com/item?id=43333011
      Ini mengurangi perdebatan soal detail gaya dalam pull request
    • Mungkin itu juga sebabnya Literate Programming tidak pernah benar-benar meluas
  • 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

    • Tidak setuju. Ada ruang informasi abstrak yang dimodelkan oleh kode, dan instruction pointer di kepala harus bergerak di dalam ruang itu
      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
    • Kompleksitas keseluruhan bukan hanya soal memindahkan instruction pointer dari titik awal yang diketahui
      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 if dan loop for juga seharusnya punya masalah yang sama
      Saya 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 if dan membiarkan sisanya menjadi jalur default implisit
    if (n % 2 === 0) return "Even"; return "Odd"; memang lebih pendek, tetapi saya jauh lebih suka if ... return "Even"; else return "Odd";
    Alasannya, yang pertama memberi kesan asimetris. "Even" dan "Odd" adalah pilihan yang simetris, jadi versi dengan else terasa lebih intuitif

    • Menulisnya seperti return (n % 2 === 0) ? "Even" : "Odd"; adalah yang paling mudah dibaca karena boilerplate-nya paling sedikit
      Jika bahasanya punya operator ternary, seharusnya mudah dikenali
    • Guard clause/early return cenderung mengalihkan fokus developer dari jalur normal semula ke arah mempersempit perilaku fungsi
      Berdasarkan pengalaman, else menciptakan nesting tambahan dan mudah membuat kita terus mengevaluasi kondisi batas atau variabel di luar cakupan langsung jalur normal. Semua konteks itu harus terus dibawa
    • Secara pribadi saya lebih suka gaya yang pertama
      Tepat 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
    • Saya akan menambahkan baris kosong agar return "Odd"; terlihat terpisah dari if, dan jika bahasanya mengizinkan, saya juga akan menambahkan kurung kurawal pada badan if
      Ada situasi ketika else bisa diterima, tetapi biasanya ketika ada efek samping; umumnya, jika direfaktor sampai hilang, hasilnya menjadi lebih jelas
      Kode 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
    • Asimetri menjadi jelas jika kode direfaktor ke gaya continuation/callback atau ke cara mutasi struktur data yang lebih kompleks
      Cara pertama mengalir ke bawah dan menjalankan kumpulan instruksi kedua. return adalah operator khusus yang memutus control flow, sehingga control flow normal pada cara pertama tidak benar-benar memuat kelengkapan kedua kasus
      Dalam Rust yang idiomatis, return tidak digunakan kecuali untuk kasus luar biasa yang memutus control flow method, dan contoh kedua biasanya lebih sering terlihat tanpa pernyataan return
      Python 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-else lengkap dipecah, return yang terindentasi terlihat seperti situasi luar biasa. Jika mengikuti konvensi ini, pernyataan return secara alami tampak redundan kecuali saat memutus control flow, dan konvensi Rust menjadi masuk akal. Di semua bahasa, return adalah pernyataan yang setara dengan break
  • Mungkin 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 frustrasi

    • Menurut saya fungsi mungkin harus mendeklarasikan tipe output
      Alasan 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 biarkan checkedDoggos mewarisi tipe fungsi
      Saya sedang menangani codebase yang linternya memaksa const checkedDoggos: DogBreedAndSize[] = checkDogs([]), dan itu cukup konyol serta tidak banyak nilainya
    • Di TypeScript, meskipun sebagian return type diinferensikan, saya tetap lebih suka ada sedikit informasi tipe daripada JavaScript yang sama sekali tidak memiliki informasi tipe
      Di JavaScript, kita tidak bisa yakin sehingga harus terus bolak-balik naik turun stack dan menyimpannya di kepala
    • Prinsip saya adalah hanya menambahkan tipe ketika kompiler TypeScript berteriak
  • 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

    • Terutama ketika beberapa file saling terkait, jelas ada titik tengah
      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/filter juga, jika digunakan dengan baik, dapat menggantikan operator lain dan mengurangi “volume”, jadi arahnya bisa ke mana saja
    Contoh ?. tampaknya merupakan diagnosis yang sangat spesifik bahasa terhadap desain JavaScript yang sulit dibaca. Di banyak bahasa, null dan undefined tidak 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 //TODO menjadi kuning
    Saya 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 belakang
    Masih 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

    • Setuju. Setelah dikompilasi, kemungkinan besar compiler tidak peduli bahwa return value diberi nama
      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

    • Salah satu kesulitan utamanya adalah bahwa kita belajar cara membaca kode
      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
    • Menurut saya nilainya tidak banyak
      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
    • Pendekatan itu terlalu satu dimensi
      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