3 poin oleh GN⁺ 2023-09-17 | 1 komentar | Bagikan ke WhatsApp
  • Alih-alih memisahkan tingkat abstraksi dengan membaginya menjadi fungsi-fungsi kecil, ada pandangan bahwa kode linear yang mengalir dari atas ke bawah lebih mudah diikuti untuk memahami keseluruhan alurnya
  • Jika struktur top-down dibentuk lewat ekstraksi fungsi, kita mungkin harus bolak-balik memeriksa antar fungsi dengan nama mirip seperti bake dan bakePizza
  • Hal-hal seperti lokasi pemanasan awal oven atau hasil saat pizza yang sama dimasukkan dua kali bisa membuat fungsi kecil, alih-alih memperjelas niat, justru menyembunyikan perilaku nyata
  • Jika kode linear diberi komentar per langkah, niat pekerjaan bisa dijelaskan tanpa menambah referensi tidak langsung, sehingga bisa lebih mudah dibaca daripada menambah abstraksi
  • Mengekstrak fungsi kecil yang hanya dipakai sekali menyebabkan hilangnya linearitas, dan seperti pada contoh cara pembuatan oven, di kode nyata hal ini bahkan bisa memperlihatkan masalah performa

Saat linearitas lebih penting daripada ekstraksi fungsi

  • Contoh dari Google Testing Blog membandingkan dua implementasi createPizza, dan menilai implementasi di kanan lebih mudah dibaca serta top-down karena tidak mencampur tingkat abstraksi
  • Dari sudut pandang sebaliknya, yang lebih penting adalah bahwa implementasi di kiri merupakan kode yang bisa dibaca secara linear dari atas ke bawah layar
    • Untuk memahami keseluruhan perilaku pada implementasi kanan, pembaca harus berpindah ke beberapa definisi fungsi kecil
    • Dari cara penyajiannya juga, sebagian kode di kanan dihilangkan sehingga ukuran kedua implementasi tampak mirip, padahal sebenarnya yang kanan lebih panjang
  • Ekstraksi fungsi bisa membuat perilaku sulit dipahami hanya dari namanya
    • Saat ada bake dan bakePizza sekaligus, sulit langsung tahu fungsi mana yang memanaskan oven
    • Untuk memastikan apakah memasukkan pizza yang sama dua kali bersifat idempoten atau justru merusak hasil, kita harus melihat implementasi internalnya

Kode linear dengan komentar dan contoh oven

  • Versi kode linear di kiri yang diberi komentar memakai nama fungsi dari versi kanan dinilai sebagai bentuk yang paling mudah dibaca
    • Komentar seperti Prepare pizza, Add toppings, Heat oven, Bake pizza, Box and slice mengungkapkan niat di setiap tahap
    • Keterbacaan tidak datang dari lapisan abstraksi tambahan dan referensi tidak langsung, melainkan dari penjelasan yang tepat tentang apa yang sedang dilakukan saat itu
  • Kesimpulannya cenderung pada gagasan agar fungsi kecil yang hanya dipakai sekali tidak diekstrak dari kode linear
    • Manfaat ekstraksi fungsi kecil dianggap tidak mampu menutupi hilangnya linearitas
  • Penanganan oven dalam contoh tersebut juga terasa canggung secara struktural
    • Pemanasan awal oven adalah perilaku yang lengkap dengan sendirinya, sehingga lebih tepat jika menjadi metode milik oven
    • Alur membuat oven baru dan memanaskannya setiap kali membuat satu pizza tidak sesuai dengan cara penggunaan yang realistis
    • Struktur seperti ini juga muncul di kode nyata dan kadang bisa menimbulkan masalah performa
  • Kemungkinan besar oven seharusnya diterima sebagai parameter alih-alih dibuat baru di dalam createPizza
    • Penyediaan oven lebih dekat dengan tanggung jawab pemanggil
    • Jika alurnya adalah memasukkan pizza ke dalam kotak, antarmuka yang mengembalikan kotak alih-alih pizza mungkin terasa lebih alami

1 komentar

 
GN⁺ 2023-09-17
Komentar Hacker News
  • Ini soal gaya, dan seperti memasak, terlalu banyak atau terlalu sedikit garam sama-sama bisa merusak masakan.
    Saya harap tidak ada yang di sini mengusulkan satu fungsi dewa 1000 baris, dan batas maksimal 5 baris per fungsi juga tidak enak dibaca. Menentukan di mana harus memecahnya butuh penilaian, intuisi yang baik, dan iterasi. Hanya karena abstraksi pertama yang dicoba kurang bagus, bukan berarti abstraksi harus ditinggalkan; setelah beberapa kali refactoring, bisa saja muncul class dan API yang cocok dengan domain bisnis.
    Pada saat yang sama, jangan terlalu tergesa-gesa membuat abstraksi atau bersikap seolah beberapa baris duplikasi adalah luka fatal. Abstraksi prematur mudah mengikat kode yang sebenarnya tidak perlu berevolusi bersama. Mengekstrak fungsi yang hanya dipanggil dari satu tempat untuk menyembunyikan satu unit kerja bisa membuat algoritme lebih rapi, dan sangat berguna terutama saat menyembunyikan boilerplate atau campuran concern infrastruktur seperti logika bisnis dan penanganan koneksi DB. Namun harus dipakai dengan hati-hati, dan sebaiknya hindari memecah langkah-langkah yang seharusnya berada pada tingkat abstraksi yang sama.

    • Intinya ada di bagian ini. Developer pemula cenderung menulis fungsi raksasa, sementara developer antusias yang baru pertama kali membaca buku seperti Clean Code mencoba memecah semuanya menjadi sejuta fungsi yang masing-masing hanya beberapa baris.
      Seseorang yang pernah bekerja bersama saya mengekstrak semua kondisi boolean menjadi fungsi dengan alasan “lebih mudah dibaca”, dan sama sekali tidak menulis komentar karena “komentar itu buruk”. Saya tidak suka buku itu karena menghasilkan fanatik yang mengikuti saran buruk seperti ini secara membabi buta.
    • Mengapa fungsi dewa 1000 baris tidak boleh? Siapa yang bilang itu lebih buruk, dan penelitian apa yang menyimpulkannya?
      Kadang-kadang domain bisa saja membutuhkan fungsi dewa 1000 baris, dan karena logika serta pekerjaan terkumpul di satu tempat, itu bisa jauh lebih mudah dibaca daripada 20 fungsi masing-masing 50 baris. Toh untuk memahami keseluruhannya, Anda tetap harus membaca semua 20 fungsi itu, dan bisa saja ada orang yang mencoba memakai ulang sebagian di antaranya, lalu menyesuaikannya untuk 2–3 kebutuhan yang tidak ada dalam pekerjaan aslinya, dan akhirnya mengikat logika tertentu dengan use case yang tidak terkait.
      Kalau fungsi itu pure function, menurut saya mau 1000 baris atau 10000 baris tetap tidak masalah dan masih baik-baik saja.
    • Kalau memakai analogi memasak, saat menjelaskan cara membuat hidangan kepada seseorang dan pada titik tertentu perlu menambahkan fond, masuk akal untuk menjelaskan cara membuat fond di bagian terpisah. Fond adalah hal yang berdiri sendiri dan hanya bersentuhan dengan hidangan di satu titik, jadi mengeluarkannya ke luar bisa baik, bahkan bermanfaat.
      Resep masakan pun sudah sangat diabstraksikan. Saat tertulis “tumis bawang bombai sebentar”, diasumsikan Anda sudah tahu cara memotong bawang bombai dan algoritme menumis sebentar. Kalau semuanya ditulis inline, resepnya tidak akan terbaca.
      Kode juga serupa. Jika abstraksi disingkirkan secara ketat, kita akan turun sampai ke level terendah yang diizinkan bahasa, dan itu jelas bukan kode yang mudah dibaca. Misalnya, jika alih-alih memakai metode decode di Python Anda mencoba melakukan decoding Unicode sendiri, akan sangat sulit memahami apa yang sebenarnya dilakukan program. Orang tidak melakukan itu hanya karena bahasa menyediakan abstraksi yang sederhana dan sudah teruji dengan baik; lalu apa bedanya dengan membuat abstraksi sederhana dan teruji dengan baik sendiri untuk dipakai di seluruh logika bisnis?
      Bagian yang sulit adalah membuat abstraksi yang dipilih dengan begitu baik sampai tidak ada orang yang perlu menyentuhnya lagi.
    • Masukan yang sering saya berikan kepada tim di perusahaan adalah mundur selangkah, melihat domain masalah yang lebih besar, lalu memikirkan apakah hal-hal ini secara niscaya memang sama atau hanya kebetulan sama.
      Hanya karena baris kode saat ini terlihat mirip, bukan berarti ke depannya harus tetap sama atau dipertahankan agar sama. Jika dua use case berbeda dipaksa digabung hanya karena “kodenya hampir berulang”, seiring waktu abstraksi itu mudah berubah menjadi abstraksi yang tidak mengabstraksikan apa pun.
      Jika use case-nya terlalu menyimpang, implementasinya akan mendorong banyak logika ke sisi caller, atau mengekspos perbedaan sebagai flag sehingga di dalamnya ada dua implementasi berbeda berdampingan. Yang pertama adalah abstraksi dangkal sehingga nilainya kecil, dan yang kedua kurang jelas dibanding dua implementasi independen.
    • Kalau itu fungsi 1000 baris yang terstruktur dengan baik, kapan pun saya akan lebih memilihnya daripada spaghetti buruk yang terdiri dari ratusan fungsi kecil.
  • Kode contoh terlalu sederhana, jadi wajar kode linear lebih enak dibaca, tetapi idenya tidak mudah diskalakan
    Reusability atau kemudahan unit test juga perlu dipertimbangkan, dan jika semua kode dimasukkan ke dalam satu fungsi, semua variabel lokal—yang bisa saja terkait atau tidak terkait dengan blok kode yang sedang dibaca—akan berada dalam scope, sehingga penalaran bisa jadi lebih sulit
    Namun kalau mengingat masa ketika pengalaman saya masih sedikit, saya sering membuat kode linear yang sebenarnya baik-baik saja menjadi terlalu termodularisasi, sehingga harus melompat ke sana-sini dan justru kurang maintainable. Bentuk awal yang ditulis punya kelebihan karena lebih dekat dengan alur berpikir di kepala saat itu, dan pembaca mungkin juga lebih mungkin menafsirkannya begitu. Refactoring berlebihan bisa menghilangkan hal itu
    Pada akhirnya, pemrograman lebih dekat dengan kerajinan; pengalaman membantu memilih yang tepat sesuai situasi

    • Salah satu fungsi yang mendapat review terbaik di tempat kerja adalah monster 2000 baris, dengan gaya linear yang menempatkan 9 scope variabel terpisah seperti tahapan
      Tujuannya hanya satu. Pekerjaannya adalah mengubah halaman-halaman HTML individual yang dipakai di satu sudut aplikasi, pada satu platform, menjadi carousel yang meniru nuansa native platform lain, dan sangat terspesialisasi untuk platform itu serta area aplikasi tersebut
      Ke-9 scope itu bisa saja dijadikan fungsi masing-masing, tetapi itu akan membuat para developer ingin me-reuse-nya. Tiap tahap punya asumsi halus tentang apa yang terjadi di tahap sebelumnya, dan untuk menjadikannya fungsi terpisah, asumsi-asumsi itu harus ditinjau ulang, digeneralisasi, dan diverifikasi bahwa tiap method bisa berjalan secara mandiri. Tidak ada alasan mengeluarkan biaya seperti itu untuk kode yang hampir tidak akan dibutuhkan di tempat lain
      Debugging juga tidak lebih sulit, ada test end-to-end, dan state tahap perantara tidak bocor keluar dari fungsi. Faktanya, 2 developer lain ikut berkontribusi pada perubahan seiring waktu, semuanya berjalan baik, dan penulisannya juga cepat
      Kode linear bisa diskalakan dan menyelesaikan masalah. Tidak selalu bentuk yang diinginkan, tetapi dalam lebih banyak situasi daripada yang dikira, ia membuat hidup jauh lebih mudah
      Reaksi saat pertama melihat monster 2000 baris itu memang tidak bagus, tetapi setelah melihatnya 5 menit saja, sulit menemukan cacat nyata; dengan beberapa test, yang tersisa hanya ketakutan yang tidak terwujud
    • Di mana buktinya bahwa kode yang dipecah menjadi fungsi itu akan diskalakan? Ketika kompleksitas kode keseluruhan membesar, kecenderungan menjadi tidak terbaca karena terpotong menjadi puluhan fungsi juga ikut membesar
      Pada suatu titik, orang akan sadar bahwa puluhan fungsi itu harus dipanggil dalam urutan tertentu, dan masing-masing hanya dipakai sekali. Pada akhirnya, ini sama saja memaksa siapa pun yang ingin memakai fungsi-fungsi itu secara berguna untuk mengetahui urutan kombinasi yang seperti sihir
    • Pernyataan “idenya tidak diskalakan” itu salah, sedangkan “pemrograman adalah kerajinan, sehingga pengalaman membantu penilaian situasional” itu benar
      Alasan inti mengapa fungsi linear yang besar sering kali lebih mudah dibaca dan lebih diinginkan adalah karena beberapa konsep dan relasinya bisa dipertahankan sekaligus dalam satu bongkahan tanpa context switching, sehingga membantu pemahaman. Pendukung ekstremnya adalah Arthur Whitney, penemu bahasa K, yang menulis kode sangat ringkas—hampir tidak bisa dipahami orang lain—agar sebanyak mungkin muat dalam satu layar
      Sebagai contoh pribadi, membaca, memahami, dan men-debug fungsi pemroses pesan Windows raksasa, yaitu WndProc, yang berisi business logic dalam statement switch besar, jauh lebih mudah dibanding versi Visual C++ yang memecah message handler menjadi fungsi terpisah
      Ada juga kode sampel mikrokontroler: versi contoh penggunaan ADC yang semuanya ada dalam satu file, dan versi yang dibagi ke beberapa file seperti main.c, config.c, interrupts.c, timer.c, dan sebagainya. Meski kurang dari 200 baris, versi kedua sulit dipahami karena context switching
    • Saya sering melihat orang-orang secara kebiasaan mengeluarkan kode linear yang tidak akan di-reuse menjadi banyak fungsi terpisah
      Potongan kode seperti ini biasanya menjadi fungsi private dalam class, dan memiliki state. Karena fungsi private, praktiknya juga sulit dites
      Akibatnya muncul banyak fungsi private yang hanya dipanggil sekali dan biasanya memodifikasi state lewat side effect. Jika masih tepat menempel pada pemanggilnya, dalam kasus sederhana masih cukup terbaca, tetapi seiring waktu seseorang menambahkan fungsi lain di antara fungsi pemanggil dan fungsi yang diekstrak itu
      Maka potongan-potongan kode yang tidak jelas dipanggil dari mana kecuali melihat call graph atau mencari di file class akan memodifikasi state side effect yang berbeda-beda
      Jika hendak membuat kode menjadi non-linear, bila bahasanya mendukung, setidaknya saya berharap fungsi private yang diekstrak dipertimbangkan untuk dijadikan inner function dari fungsi pemanggil. Dengan begitu jelas bahwa ia tidak dipanggil dari tempat lain
      Dalam codebase nyata, ini pun bukan pilihan biner, melainkan lebih mirip seni mengombinasikan keduanya agar bentuknya mudah dibaca dan maintainable
    • Jika sebuah fungsi benar-benar linear, fungsi panjang pun tidak terlalu buruk. Namun contoh nyatanya tidak linear dan berisi banyak percabangan
      Apakah orang-orang akan menguji semua cabang itu? Atau hanya menulis test yang memasukkan satu pizza lalu sekadar melihat apakah kira-kira berjalan? Menguji banyak cabang dari luar biasanya merepotkan, dan lebih menyebalkan dibanding menguji fungsi kecil yang terspesialisasi, jadi kemungkinan besar yang terjadi adalah opsi kedua
  • Pernyataan bahwa “kode linear tidak dapat diskalakan” justru kebalikannya. Dalam codebase besar, yang benar-benar menjadi mimpi buruk adalah fungsi-fungsi kecil dan ringkas dengan call stack yang bertumpuk dalam
    Tidak jelas di mana kode baru harus ditambahkan, dan karena harus melacak semua jalur tempat kode bisa dipanggil, tingkat kesulitan memahami dampak perubahan meningkat secara eksponensial, serta muncul subrutin yang duplikatif
    Dalam 99% kasus, itu bukan abstraksi yang baik, jadi lebih baik gunakan saja kode linear. Saya lebih memilih salin/tempel daripada semantik fungsi yang meragukan

    • Bahaya lainnya adalah jika print_table() ditambahkan, seseorang akan menemukannya lalu memakainya di kodenya sendiri, kemudian menambahkan flag kecil untuk menyesuaikan output dengan use case-nya
      Setelah 12 bulan, bentuknya menjadi seperti ini:
      print_table(
      rows,
      headers = None,
      is_unicode = False,
      left_align = False,
      align = [],
      remove_emoji = None,
      max_width = 80,
      potato_mode = 7,
      _debug_frontend = not FLAGS.dont_debug,
      ellipsis_for = 0,
      no_print = False,
      )
    • Ini menjelaskan masalah keterbacaan, dan pada dasarnya mengatakan bahwa keterbacaan merugikan skalabilitas
      Jika kedua konsep ini dilihat secara ortogonal, selain fakta bahwa keterbacaan dapat memengaruhi skalabilitas, kode linear tidak diskalakan sebaik kode modular. Dikotomi ini layak diketahui dan layak dipertimbangkan bergantung pada situasinya
      Meski begitu, saya tetap tidak setuju. Jika fungsi kecil itu adalah fungsi murni, ia tidak menimbulkan masalah keterbacaan. Artinya ia tidak menyentuh state, tidak menyuntikkan logika ke dalam kode, dan dependency injection serta meneruskan fungsi ke fungsi lain harus diminimalkan secara eksplisit
      Jika membuat pipeline fungsi-fungsi murni yang hanya meneruskan data, kode menjadi mudah dibaca dan dapat diskalakan. Kasus ketika logika harus ditulis ulang karena cacat desain jauh berkurang, dan ketika fungsi murni dikomposisikan, kode menjadi seperti Lego. Refactoring juga lebih menyerupai menyusun ulang dan mengombinasikan kembali elemen primitif yang sudah ada
  • Contoh kodenya setidaknya akan kurang mengganggu jika berusaha mempertahankan metafora pizza secara bermakna, atau jika bukan kode Go berkualitas rendah
    prepare adalah nama fungsi yang buruk sekali. Gopher berpengalaman mungkin akan menamainya seperti NewPizzaFromOrder
    Saya tidak melihat alasan untuk menjadikan addToppings sebagai fungsi terpisah. Jika memang perlu, secara pribadi saya akan menjadikannya method milik Pizza, seperti func (p *Pizza) WithToppings(topping ...Topping) *Pizza { /* ... */ }. Pizza sungguhan bersifat mutable, jadi method-nya mengubah receiver
    Saya juga tidak paham mengapa harus menginstansiasi oven baru setiap kali memanggang pizza. Seharusnya mulai dengan oven yang sudah ada, lakukan oven.Preheat(), lalu panggil oven.Bake(pizza). Lebih jauh lagi, oven.Preheat() bisa dibuat mengembalikan tipe baru dari Oven yang mengekspos .Bake(), sehingga kesalahan memanggang tanpa pemanasan awal dapat dicegah pada tahap kompilasi. Di tempat lain mungkin ada interface Baker, dan mungkin juga ada implementasi ToasterOven yang tidak memerlukannya karena pemanasan awal tidak terlalu penting
    Bahkan tanpa mengubah kodenya, saya akan menata ulang urutan deklarasi agar sesuai dengan alur yang dapat diprediksi. Dengan begitu, saat menelusuri fungsi-fungsi yang saling memanggil, kita tidak perlu melompat naik turun halaman
    Saya tidak punya waktu, jadi berhenti di sini, tetapi kode ini sudah merupakan contoh yang terlalu buruk bahkan untuk memulai perdebatan “mana yang lebih mudah dibaca”

  • John Carmack juga mengatakan hal yang hampir sama, dan sejak itu saya terus mengikutinya. Kode linear secara alami mudah dibaca karena mengikuti urutan eksekusi, dan meminimalkan lompatan pandangan
    Sebagian kode harus non-linear demi reuse, dan saat itu eksekusinya menjadi graf. Jika kode tidak memanfaatkan reuse berstruktur graf, tidak perlu memperkenalkan simpul di tempat yang cukup dengan satu sisi
    http://number-none.com/blow/blog/programming/2014/09/26/carm...

    • Bagian yang dikatakan Carmack tetapi tidak ada dalam teks asli adalah bahwa jika logika tanpa side effect bisa dipisahkan ke fungsi tersendiri, biasanya itu ide yang baik
      Dalam kasus ini, akan lebih baik jika kode di kiri dibuat seperti pizza.Toppings = get_pizza_toppings(order.kind), sehingga perubahan pada pizza tetap menjadi pusat di fungsi utama
  • Saya cukup setuju bahwa kode linear lebih mudah dibaca, tetapi itu saja tidak otomatis menjadikannya praktik kode yang baik
    Saya menganggap kode linear yang baik memang lebih mudah dibaca, tetapi maintainability dan kemudahan pengujiannya jauh lebih rendah. Saya punya pengalaman puluhan tahun dan juga menjadi penilai eksternal untuk mahasiswa CS, dan selama bertahun-tahun satu-satunya praktik baik yang benar-benar jelas saya lihat di dunia nyata hanyalah menjaga fungsi tetap kecil
    Saya juga tidak terlalu menyukai abstraksi, dan tidak berpikir duplikasi kode harus selalu dihindari, tetapi jika Anda membuat fungsi sedekat mungkin dengan satu tujuan, diri Anda di masa depan akan berterima kasih
    Jika kode seperti contoh berjalan di produksi selama 10 tahun, tiap bagiannya akan berubah. Kalau beruntung, komentarnya juga akan diperbarui, tetapi kebanyakan tidak akan begitu. Unit test juga akan menjadi besar dan sulit ditangani, makin lama makin asal, dan seseorang bisa lupa memperbaiki bagian test yang tidak terlihat jelas terkait dengan perubahan. Kodenya sendiri juga kemungkinan besar akan makin kurang mudah dibaca seiring waktu. Bukan karena niat buruk atau ketidakmampuan, melainkan karena alasan manusiawi seperti tekanan waktu
    Di dunia yang sempurna kita tidak perlu memisahkan concern, tetapi kita hidup di dunia yang tidak sempurna, dan semakin kecil fungsi serta semakin sedikit tanggung jawabnya, semakin mudah menangani ketidaksempurnaan itu seiring waktu

    • Benar, memang kurang mudah dites, tetapi kasus saat ini adalah perubahan state yang harus dilakukan dalam urutan tertentu
      Jika sebuah objek sedang dilewatkan melalui alur state tertentu, menurut saya lebih baik memecahnya dan menandai transisi dengan tipe, atau menuliskannya sebagai satu fungsi besar. Misalnya, jika bakePizza menerima RawPizza dan mengembalikan BakedPizza, urutan pemanggilan bisa dipaksakan pada compile time
      Karena keterbacaan, kebenaran, dan kemudahan pengujian, saya lebih menyukai yang pertama, tetapi di sebagian besar bahasa pemrograman, untuk mengubah tipe objek Anda harus membuat objek baru dan ada biaya runtime. Jika itu hot code path, perubahan in-place masuk akal, dan dalam kasus itu lebih baik menaruhnya dalam satu fungsi linear
    • Belakangan ini saya mulai membaca Software Design for Flexibility dari Sussman, dan ini langsung berkaitan dengan topik ini
      https://mitpress.mit.edu/9780262045490/
  • Email terkait dari John Carmack: http://number-none.com/blow/blog/programming/2014/09/26/carm...
    Diskusi: https://news.ycombinator.com/item?id=12120752

  • Sangat setuju. Dulu saya berada di kubu sebaliknya
    Ketegangan dasar di sini adalah antara locality of behaviour di satu sisi dan keinginan untuk menampilkan tampilan “daftar isi” tingkat tinggi dengan jelas di sisi lain. Untuk kode yang enak dibaca, locality lebih penting. Seperti yang dikatakan tulisan itu, perspektif daftar isi bisa dibuat cukup jelas dengan komentar bagian
    Ada alasan yang lebih penting untuk lebih menyukai kode linear. Saat menelusuri seluruh codebase, jauh lebih mudah jika “potongan” besar—yaitu fungsi, class, atau unit yang dipaksakan bahasa—secara kasar berpadanan dengan use case bisnis. Jika tidak, ruang pencarian menjadi terlalu besar, dan Anda harus merekonstruksi sendiri keseluruhan dari potongan-potongannya. Struktur kode seharusnya melakukan pekerjaan itu untuk Anda
    Jika beberapa “hal” semuanya terkait dengan satu pekerjaan, misalnya pendaftaran atau pembelian, sebaiknya semuanya juga ditempatkan sebagai satu hal di kode. Jauh lebih mudah ditemukan dan diubah. Pecah menjadi subfungsi hanya ketika reuse diperlukan, jangan memecah hanya demi organisasi
    [0] https://htmx.org/essays/locality-of-behaviour/

    • Saya bergerak ke arah sebaliknya. Dulu saya di kubu kode linear, tetapi sekarang condong ke lebih banyak fungsi
      Alasan terbesar adalah state. Semakin panjang fungsi, semakin luas scope variabel lokal. Variabel apa pun bisa diubah dari mana saja dalam fungsi, dan aliran data tidak langsung jelas. Dengan lebih banyak fungsi, scope tetap kecil dan aliran data menjadi lebih eksplisit
      Efek sampingnya, indentasi juga berkurang
      Pada saat yang sama, saya tidak suka fungsi yang terlalu kecil. Karena jadi sulit menemukan di mana pekerjaan sebenarnya terjadi
    • Soal “pecah menjadi subfungsi hanya ketika reuse diperlukan, jangan memecah hanya demi organisasi”, lalu bagaimana dengan testing? Bagaimana dengan mengurangi state yang harus dipertahankan di kepala? Pembebasan resource? Memahami dampak perubahan?
      Bayangkan proses tutup buku harian dengan 10 langkah yang tidak bisa di-reuse dan harus dijalankan berurutan, masing-masing 100 baris. Tiap langkah memakai data yang mirip dengan langkah sebelumnya, tetapi tidak sama. Apakah Anda benar-benar akan memilih satu fungsi tunggal 1000 baris?
  • Keduanya terbaca secara linear. Versi yang mengekstrak fungsi-fungsi kecil punya daftar isi di bagian atas halaman dan merangkum aliran data antar langkah. Jika akan membaca semuanya, itu tampak seperti urutan baca yang menarik
    Namun untuk mempertahankan keterbacaan ini, ketika urutan langkah berubah, posisi fungsi juga harus dipindahkan. Jika itu fungsi private dan hanya dipanggil dari daftar isi, tidak masalah. Tetapi tidak ada yang memaksa urutannya tetap terjaga, dan juga tidak ada yang memaksa orang memikirkan alur baca keseluruhan
    Begitu fungsi mulai di-reuse, sering kali ia tidak lagi bisa dilinearkan. Kadang orang menyerah lalu mengurutkannya secara alfabetis, atau akhirnya menjadi acak begitu saja

  • Berdasarkan pengalaman, semakin seseorang terbiasa dengan kode, semakin ia menganggap mendorong kode ke fungsi-fungsi kecil adalah jalan yang benar
    Karena ia sudah membangun model mental untuk kode tersebut, implementasi yang paling rapi baginya adalah implementasi dengan jumlah baris yang sangat sedikit
    Namun ketika orang berikutnya datang, ia harus bolak-balik ke sana kemari sambil melakukan push/pop stack di kepalanya untuk membangun model mental yang sama tanpa konteks awal, dan ini jauh lebih sulit

    • Kalau kodenya masuk akal, tidak begitu. Jika kodenya ditulis dengan baik, dengan abstraksi yang elegan, antarmuka yang tipis, dan dokumentasi yang tepat, kita tidak perlu terlalu sering bolak-balik
      Misalnya, seberapa sering Anda membaca kode sumber standard library dari bahasa yang Anda gunakan? Hampir tidak pernah; biasanya Anda melihat signature metode, lalu membaca dokumentasi jika agak rumit atau baru
      Inti dari antarmuka adalah membuat kita hanya perlu peduli apa yang dilakukan sebuah metode, bukan bagaimana metode itu diimplementasikan. Itu dijelaskan oleh kombinasi konteks, nama, dan dokumentasi. Namun banyak developer tidak memahami atau tidak memedulikan hal ini, sehingga menulis kode yang tidak masuk akal, entah linear maupun modular
      Misalnya, jika dalam sebuah kelas service Anda harus memanggil satu metode untuk mendapatkan suatu data, metode lain untuk mendapatkan data lain, lalu metode ketiga untuk mendapatkan data yang harus digabungkan dengan dua data sebelumnya, apa sebenarnya makna service itu? Itu sama saja mengekspos seluruh kompleksitas internal ke luar
      Bukan berarti kita harus memaksakan metode kecil. Dua puluh fungsi 5 baris yang hanya dipanggil sekali, mengerjakan hal yang sangat spesifik, dan harus dipanggil dalam urutan yang benar itu tidak ada artinya. Itu bukan clean code, melainkan lebih dekat ke cargo cult programming
      Yang penting adalah melakukan abstraksi dengan tepat agar masuk akal bagi anggota tim baru maupun yang berpengalaman, mudah dinalar, dan kompleksitas disembunyikan di tempat yang sesuai. Tidak mudah, tetapi mungkin
    • Saya tidak setuju, tetapi mungkin ada perbedaan antara orang yang membaca dan berpikir secara bottom-up dan orang yang berpikir secara top-down
      Anak saya cukup cerdas, tetapi kesulitan di sekolah, dan salah satu dari beberapa ahli menjelaskan bahwa sekolah umumnya mengajar secara bottom-up, sementara anak saya adalah pembelajar yang sangat top-down. Ia perlu gambaran umum terlebih dahulu sebelum masuk ke detail, sedangkan orang lain perlu memahami detail dulu lalu menyusun gambaran besarnya. Sekolah biasanya mengajar sesuai kelompok kedua
      Mungkin ada perbedaan serupa di antara para programmer
    • “Orang berikutnya harus bolak-balik ke sana kemari” hanya berlaku ketika orang itu tidak bisa membaca kode. Kode setidaknya pada awalnya harus dibaca sebagaimana ditulis, bukan dengan mencoba membacanya sesuai urutan eksekusi; itu cara yang keliru
      Jika developer sebelumnya menulis fungsi BakePizza, Anda cukup mengasumsikan pizza akan dipanggang dengan benar lalu lanjut ke baris berikutnya. Jika saat mencoba memahami cara kerja restoran Anda malah tenggelam dalam detail seperti suhu oven, Anda tidak akan memahami bagaimana restoran berjalan dan juga akan lupa suhu oven yang tepat
    • Karena itu kita membutuhkan alat yang lebih baik seperti projectional code editor
      Editor harus punya toggle untuk melakukan inline sementara pada fungsi. Tidak perlu lagi bolak-balik