1 poin oleh GN⁺ 2023-11-16 | 1 komentar | Bagikan ke WhatsApp
  • Intinya adalah dua aturan penataan kode: tarik percabangan kondisi ke sisi pemanggil, dan dorong pemrosesan berulang ke sisi operasi batch
  • Jika if dinaikkan, prasyarat menjadi lebih mudah dipaksakan lewat tipe atau assert, dan alur kontrol yang kompleks dapat dikumpulkan di satu tempat sehingga kondisi duplikat dan cabang mati lebih mudah ditemukan
  • Struktur yang membuat enum lalu segera match lagi mengulang kondisi yang sama dalam bentuk percabangan, struktur data, dan percabangan ulang, sehingga ada ruang untuk disederhanakan menjadi pemanggilan foo(x) dan bar(y)
  • Jika for diturunkan, operasi batch menjadi unit dasar alih-alih iterasi per objek, sehingga biaya awal bisa diamortisasi, urutan pemrosesan bisa diubah, dan vektorisasi serta optimasi struct-of-array menjadi mungkin
  • Jika kondisi dikeluarkan dari loop, percabangan di hot loop bisa dikurangi, dan ini juga terhubung ke struktur seperti TigerBeetle yang mengamortisasi biaya pengambilan keputusan di control plane ke pemrosesan batch di data plane

Menaikkan if

  • Kondisi if di dalam fungsi pertama-tama layak diperiksa apakah bisa dipindahkan ke pemanggil
    • Contohnya, fungsi yang menerima Walrus secara langsung dianggap lebih baik daripada fungsi yang menerima Option<Walrus> lalu mengembalikan tanpa melakukan apa pun saat nilainya None
    • Daripada memeriksa prasyarat di dalam fungsi lalu “tidak melakukan apa-apa”, lebih jelas bila pemanggil yang memeriksanya dan memaksakan kebenarannya lewat tipe atau assert
  • Refactoring yang memindahkan pemeriksaan prasyarat ke atas dapat merambat sepanjang jalur pemanggilan, dan pada akhirnya bisa mengurangi jumlah pemeriksaan secara keseluruhan
  • Alur kontrol dan if meningkatkan kompleksitas dan mudah menjadi sumber bug
    • Jika if dinaikkan, logika percabangan yang kompleks terkumpul dalam satu fungsi, sedangkan pekerjaan aktual turun ke subrutin yang linear
    • Alur kontrol yang kompleks lebih mudah dipakai untuk menemukan duplikasi dan kondisi mati saat berada dalam satu fungsi di satu layar dibanding tersebar di seluruh berkas
  • Refactoring “dissolving enum” adalah cara menghilangkan pola yang mengubah percabangan menjadi struktur data lalu bercabang lagi
    • Jika f() membuat E::Foo(x) atau E::Bar(y) berdasarkan kondisi, lalu g(e) kembali memanggil foo(x) atau bar(y) lewat match, maka kondisi yang sama diulang dalam beberapa bentuk
    • Jika kondisi itu ditarik ke main(), bentuknya menjadi lebih sederhana: if condition { foo(x) } else { bar(y) }

Menurunkan for

  • Dari sudut pandang data-oriented, program dan hot path biasanya menangani banyak objek, jadi batch objek diperkenalkan dan operasi batch dijadikan kasus dasar
    • Versi skalar untuk objek tunggal menjadi kasus khusus dari operasi batch
    • for walrus in walruses { frobnicate(walrus) } dianggap bentuk yang kurang baik dibanding frobnicate_batch(walruses)
  • Keuntungan utamanya adalah performa
    • Jika seluruh batch ditangani sekaligus, biaya awal bisa diamortisasi
    • Urutan pemrosesan bisa diubah dengan lebih leluasa, dan pendekatan seperti vektorisasi atau struct-of-array yang memproses satu field dari semua entitas lebih dulu juga menjadi mungkin
    • Sebagai contoh ekstrem, artikel ini menautkan Vectorized Interpreters Talk
  • Contoh menarik lainnya adalah perkalian polinomial berbasis FFT
    • Pendekatan yang mengevaluasi polinomial di banyak titik secara bersamaan bisa lebih cepat daripada melakukan evaluasi titik per titik berulang kali
  • Aturan if dan for bisa diterapkan bersama
    • Bentuk yang baik adalah menjalankan for walrus in walruses per cabang di luar if condition
    • Bentuk yang buruk adalah mengevaluasi if condition setiap kali di dalam loop for
    • Ini bisa menghindari evaluasi ulang kondisi, menghilangkan percabangan dari hot loop, dan memungkinkan vektorisasi
  • Pola ini berlaku baik di level mikro maupun makro
    • Arsitektur TigerBeetle memproses batch objek secara bersamaan di data plane untuk mengamortisasi biaya pengambilan keputusan di control plane
    • Motivasi utama nasihat tentang for adalah performa, tetapi cara kerja berbasis koleksi seperti pada jQuery juga bisa membantu dari sisi daya ekspresi

1 komentar

 
GN⁺ 2023-11-16
Pendapat Hacker News
  • Awalnya saya terkejut tulisan ini mendapat penolakan sebesar ini, tetapi setelah teringat bahwa ini adalah saran desain berorientasi data, saya jadi paham
    Seperti banyak orang di forum ini, saya juga kebanyakan membuat aplikasi web untuk pekerjaan, dan dalam konteks seperti itu saran ini bisa terlihat seperti omong kosong
    Jika dalam pekerjaan sehari-hari Anda tidak perlu memikirkan instruction cache, heuristik bahwa saran ini secara umum boleh diabaikan tampaknya tepat
    Kalau ingin mendapat gambaran kapan saran ini menjadi penting, ada baiknya mencari “Typical C++ Bullshit” dari Mike Acton; tulisan ini terasa seperti merangkum isinya dengan cara yang lebih mudah dipahami
    Saya cukup bersimpati dengan kegelisahan Casey Muratori, tetapi sebagian besar software untuk bisnis perlu mengoptimalkan kemudahan perubahan dan kebenaran dibanding performa, yaitu “pemrograman seiring waktu”

    • Menurut saya saran ini masih cukup bisa diterapkan juga pada pemrograman berorientasi objek
      Developer sering mencoba membuat logic bisnis yang kompleks menjadi DRY dengan memecahnya ke metode-metode private kecil di dalam class, dan “naikkan if ke atas” membantu agar logic percabangan tidak tersebar di banyak metode
      “Turunkan for ke bawah” juga penting. Banyak alur pemanggilan pada akhirnya berujung pada query database yang mahal, dan sering kali loop di level atas membuat pemanggilan DB berkali-kali di level bawah
      Iterasi itu sering bisa digantikan dengan klausa where atau join di SQL, dan untuk agregasi atau filtering pun lebih baik didorong lebih dekat ke DAO daripada mengambil banyak objek lalu mengiterasinya, karena lebih mudah dioptimalkan di dekat DB
      Namun, seperti semua prinsip desain, jangan menerapkannya sebagai dogma; nilailah secara sadar
    • Desain berorientasi data sepertinya selalu memancing reaksi orang. Mungkin karena ia menyiratkan bahwa banyak bagian dari pendekatan berorientasi objek yang selama ini dominan itu keliru
      Sayang sekali jika kemudahan perubahan dan performa dipandang sebagai hal yang saling bertentangan. Sejauh ini saya belum melihat bukti meyakinkan bahwa keduanya benar-benar berbenturan
    • Kemudahan perubahan, kebenaran, dan performa tidak saling eksklusif; justru sering bergerak bersama
      Salah satu aspek terpenting dalam performa adalah membuatnya kecil. Kode kecil, struktur data kecil, dan jumlah instruksi eksekusi yang sedikit adalah intinya; “memikirkan instruction cache” pun pada dasarnya berarti menulis kode kecil
      Semakin kecil kode, semakin sedikit ruang bagi bug untuk masuk, semakin mudah dipahami sekaligus, dan semakin mudah memastikan cakupan pengujian, sehingga menguntungkan kebenaran. Kemudahan perubahan juga diuntungkan karena semakin kecil kode, semakin kecil pula perubahannya
      Tentu ada optimasi yang membuat kode menjadi rumit seperti paralelisasi, caching, dan optimasi level rendah, tetapi itu hanya sebagian dari optimasi performa, dan programmer yang benar-benar peduli pada performa tidak akan melakukan hal seperti itu tanpa profiling dan analisis
      if/for dan for/if mungkin tidak jauh berbeda secara fungsional, tetapi salah satunya bisa lebih cepat; jadi jika bisa memakai yang benar tanpa biaya, tidak ada alasan sengaja memilih yang lebih lambat
    • Jika mendengarkan John Ousterhout, dalam praktik kemudahan perubahan dan kebenaran tidak sebanyak itu berbenturan dengan performa seperti yang kita kira pada awalnya
      Program yang sederhana memiliki call stack pendek dan menghindari abstraksi yang rumit sekaligus mahal, sehingga cenderung memakai lebih sedikit memori dan berjalan lebih cepat
      Mengejar performa tingkat tertinggi bisa membuat program menjadi rumit, tetapi kesederhanaan sejati biasanya menghasilkan performa yang masuk akal
      “Menurunkan for ke bawah” terutama adalah saran berorientasi data, tetapi “menaikkan if ke atas” lebih dekat ke arah membuat program menjadi lebih sederhana. Tepatnya, ini soal meningkatkan lokalitas source code, dan intinya adalah mengumpulkan logic percabangan di satu tempat: https://loup-vaillant.fr/articles/source-of-readability
    • Menurut saya kedua saran ini juga cukup berlaku untuk aplikasi bisnis
      Terutama “turunkan loop ke bawah” sangat kuat di aplikasi CRUD. Jika pembuatan dan pembaruan dilakukan secara batch sebisa mungkin, sering kali waktu yang dihemat jauh lebih besar daripada bottleneck CPU
      Perbedaan antara items.map(insertToDb/postToServer) dan insertToDb/postToServer(items) hampir selalu berbeda beberapa order of magnitude
      Saya pernah melihat optimasi seperti ini memangkas waktu kerja dari hitungan detik atau menit menjadi milidetik, dan sering kali API juga menjadi lebih rapi serta log lebih mudah dibaca
  • Seiring bertambahnya pengalaman, saya merasa terlalu banyak programmer memperhatikan “kode cantik” pada unit kecil, tetapi tidak cukup memperhatikan desain codebase secara keseluruhan
    Fungsi yang ringkas dan dipoles dengan baik memang bagus, tetapi tulisan seperti ini bisa berubah menjadi perdebatan bike-shedding yang tidak produktif di PR atau diskusi
    Apakah sebuah fungsi agak berantakan, di mana menaruh if dan for, apakah memakai map dan filter, tidak terlalu saya persoalkan selama nama fungsinya tepat, antarmuka dan tipenya ekspresif, tujuannya jelas, terdokumentasi, dan tidak memakai efek samping secara berlebihan

    • “Menaikkan if ke atas” bukanlah perdebatan bike-shedding; justru, seperti yang dikatakan, lebih dekat ke pilihan arsitektural yang harus diputuskan
      Jika memikirkan di mana memvalidasi input saat ada nilai masukan, menurut heuristik ini validasi harus dilakukan di bagian paling atas tempat input diterima
      Ini membantu memahami kode, dan dari sudut pandang pembuktian juga diperlukan secara teknis karena prasyarat harus dipropagasikan ke atas
      Nasihat pertama jelas bukan perdebatan sepele, sedangkan yang kedua agak lebih ambigu
    • Saat bekerja di bidang keamanan, saya banyak berinteraksi dengan technical lead dan arsitek kira-kira level L7–L9 menurut standar FAANG, dan orang yang paling terobsesi pada “desain yang benar” biasanya memiliki pengalaman 5–10 tahun
      Mereka berusaha mencegah junior menembak kakinya sendiri, tetapi sering kali belum punya cukup kepekaan terhadap biaya kompleksitas dan bagaimana kode berubah dalam jangka panjang
      Sebaliknya, setelah melakukannya lebih dari 20 tahun, kebanyakan orang menilai kesederhanaan lebih tinggi daripada sebagian besar unsur teknis
      Pertanyaan seperti “seberapa awal harus bercabang” dan “apa yang sebenarnya harus dilakukan kode ini” cenderung memberikan jawaban paling bernilai dalam jangka panjang
      Pertanyaan tentang abstraksi dan enkapsulasi justru mudah mengarah ke jenis perdebatan yang disebutkan tadi, dan alasan masalah keamanan paling sering muncul di kode orang-orang yang hanya melihat “gambaran besar” adalah karena mereka tidak benar-benar memahami apa yang dilakukan codebase yang katanya dirancang dengan baik itu
    • Jika prinsip-prinsip yang tampak sepele seperti ini sudah ditetapkan sebelumnya, perdebatan melelahkan dengan pola yang sama di PR dan diskusi bisa berkurang
      Dalam software sensitif performa yang cocok dengan desain berorientasi data, hal-hal unit kecil seperti ini perlu lebih diperhatikan. Sebab optimisasi compiler bekerja dengan cara seperti itu
      Maka aturan mainnya berubah. Makna pernyataan menjadi penting, kode yang menjelaskan dirinya sendiri menjadi lebih bermakna, komentar kode tersisa hanya untuk penalaran, dan “dokumentasi” lebih dekat ke spesifikasi dan panduan pengguna
    • Saya juga sering melihat kebalikannya. Ada banyak orang yang suka merancang katedral yang over-engineered, tetapi tidak memikirkan efisiensi algoritmik tingkat rendah
    • Salah satu alasannya adalah bahasa pemrograman kekurangan cara untuk mengekspresikan secara bermakna komponen yang jauh lebih besar daripada fungsi, atau komponen yang terhubung dengan cara berbeda dari fungsi
      Paling-paling kita bisa mengelompokkan fungsi-fungsi, lalu setelah itu harus mengurus sendiri
  • Kekurangan dari “menaikkan if ke atas” adalah prasyarat dan pascakondisi tidak terlihat langsung di dalam definisi fungsi, sehingga harus diperiksa di setiap titik pemanggilan
    Dalam proyek besar yang melibatkan banyak orang, fungsi seperti ini bisa digunakan kembali di luar konteks yang dimaksud dan berujung pada bug
    Ini bisa diselesaikan dengan framework kontrak, tetapi kondisi harus ditulis dua kali, di kontrak dan di kode; tipe dependen pun sama
    Cara menandai area kode yang termasuk konteks tertentu, lalu mendefinisikan fungsi yang hanya bisa dipanggil di dalam konteks itu, menarik untuk dipikirkan
    Di Python, misalnya, decorator seperti @requires_context("VALIDATED_XY") dan context manager validated_xy dapat membuat fungsi hanya bisa dipanggil di area yang sudah divalidasi
    Runtime tidak mengetahui makna konteks itu, tetapi dengan tooling dan pengujian, kita bisa merancang agar konteks yang diinginkan hanya dibangun saat kondisi tertentu terpenuhi
    Dalam bahasa seperti Haskell, ini bisa dipaksakan di level tipe dengan sesuatu seperti identity monad, dan meski bukan pemaksaan level tipe, ini bisa menjadi cara menarik untuk melindungi area kode “unsafe”

    • Sepertinya Anda melewatkan bagian kedua dari argumen penulis. Maksudnya adalah “pemeriksaan prasyarat didorong ke pemanggil, dan bisa dipaksakan lewat tipe
      Jadi prasyarat tetap terlihat langsung pada definisi fungsi, hanya saja bukan sebagai pernyataan if, melainkan sebagai bagian dari type signature
      Ini adalah pola yang umum di Rust seperti yang digunakan dalam tulisan itu, dan berbeda dari pemeriksaan if, prasyarat yang ketat ini diperiksa pada waktu kompilasi, bukan runtime, sehingga jika tidak dipenuhi program tidak akan terkompilasi
    • Di Python pun ini bisa diekspresikan dengan tipe
      Misalnya menerima do_something(position: ValidatedPosition), lalu memvalidasi Position biasa dan mengubahnya menjadi ValidatedPosition sebelum meneruskannya
      Dalam praktiknya validasi mungkin akan dienkapsulasi di dalam konstruktor ValidatedPosition, tetapi intinya adalah jika mencoba meneruskan Position apa adanya, mypy akan memberi tahu bahwa tipe yang diberikan salah
      Pemeriksaan tipe Python tidak sekomprehensif Rust, tetapi semakin berguna ketika ingin memastikan data yang diteruskan sudah diproses dengan semestinya
    • Nasihat “menaikkan if ke atas” bukan tentang pemeriksaan prasyarat, melainkan tentang memilih jalur kode yang benar
      Jika sebuah fungsi memiliki prasyarat, tentu saja boleh melakukan assertion di awal fungsi. Misalnya sistem tipe Java mengizinkan null, jadi fungsi yang membutuhkan objek harus melempar exception jika menerima null
      Setiap titik pemanggilan bertanggung jawab memanggil fungsi hanya ketika prasyarat benar. Ini wajar menurut definisi prasyarat
      Memanggil fungsi dengan melanggar prasyarat adalah bug di sisi pemanggil. Di dalam fungsi mungkin diperlukan kode untuk memeriksanya agar mencegah perilaku tak terdefinisi, tetapi assertion seperti ini harus dibedakan dari alur kontrol program yang sebenarnya, dan tulisan itu membahas yang terakhir
    • Dalam contoh fn frobnicate(walrus: Walrus) ini, jika Anda meneruskan sesuatu yang bukan Walrus yang dimiliki, program tidak akan terkompilasi
      Sekalipun generik, argumen harus memenuhi trait bound, dan bergantung pada bagaimana argumen digunakan di dalam fungsi, compiler akan menuntut bound yang diperlukan pada definisi fungsi
    • Saya merasa maksud public dan private sampai taraf tertentu adalah untuk menandai kode agar sesuai dengan konteks tertentu
      Atau mungkin diperlukan semantik yang lebih spesifik yang melintasi area yang ditangani oleh public, private, dan protected di ekosistem .NET
  • Contoh pertama buruk bukan karena if dan for, melainkan karena alasan lain
    Secara umum, jika ada “kontainer” untuk sesuatu, lebih baik menulis fungsi pada “objek” level domain di dalamnya, bukan pada kontainernya
    Saat memakai agent di Clojure, kita tidak menulis fungsi khusus agent, melainkan fungsi untuk objek yang bisa ditampung oleh agent
    Di Elixir juga, fungsi domain inti bekerja pada struktur data domain internal, bukan pada PID, dan bila perlu pemanggilan GenServer didelegasikan ke sana
    Dengan begitu kode menjadi lebih fleksibel, dan kita bisa memisahkan dengan lebih rapi domain inti “melakukan frobnicate pada Walrus” dari kepentingan aplikasi “Walrus mungkin ada atau mungkin tidak ada; jika ada, lakukan frobnicate”

    • Saran yang diberikan tampaknya berisiko mengangkat logika validasi terlalu jauh ke atas
      Validasi sejak awal memang baik, tetapi penting juga agar fungsi mengeluarkan error validasi yang jelas, alih-alih tiba-tiba meledak dengan error aneh
      Haskell menyelesaikan masalah ini dengan newtype, yaitu menyediakan “kontainer transparan yang menyertifikasi bahwa validasi yang semestinya sudah dilakukan”
      Saran yang benar-benar ingin saya tekankan kepada orang-orang adalah memilih “sad if”. Hampir selalu lebih mudah dibaca dan dipelihara bila kode memeriksa kondisi-kondisi yang salah satu per satu di bagian atas lalu langsung memperbaiki atau menghentikannya, daripada menumpuk jalur normal dalam beberapa lapis nesting
      Manusia secara alami ingin berfokus pada kasus yang diharapkan, tetapi kode justru sebaiknya lebih dulu berfokus pada kasus-kasus pengecualian. Setiap if menambah beban mental, dan jika kita harus mengambil informasi dari sistem eksternal atau keluar lebih awal karena error, beban itu bisa segera dilepas ketika deteksi dan penanganannya berada berdekatan
    • Jika maksudnya contoh pertama akan lebih baik bila menjadi walrus.frobnicate(), rasanya itu lebih dekat ke soal selera sintaksis daripada inti yang ingin disampaikan penulis
  • Saya tidak sampai mengatakan ini saran buruk, tetapi juga belum tentu saran yang baik
    Fakta bahwa bahasa yang dipilih adalah Rust cukup banyak menjelaskan. Sistem tipe yang kuat mencegah banyak defensive programming yang dibutuhkan di bahasa lain
    Jika seorang programmer C tidak memeriksa validitas pointer yang diteruskan ke fungsi lalu menyebabkan dereference NULL, saya tidak ingin satu tim dengan orang itu
    Jadi setidaknya sebagian if jelas harus berada di bagian bawah, dan sebaiknya error bisa dipropagasikan ke atas dengan baik
    Untuk for saya tidak sekuat itu pendapatnya, tetapi di C, karena argumen array mengalami decay menjadi pointer, saya pikir iterasi juga seharusnya berada di atas, bukan di bawah. Di fungsi tempat array dibuat, panjangnya bisa dipercaya, tetapi di fungsi yang menerimanya sebagai argumen, tidak demikian

    • Antarmuka C harus mendokumentasikan ekspektasi dengan hati-hati, dan hanya memeriksa sebatas itu
      Dokumentasilah yang harus menggantikan sistem tipe yang kuat, bukan pemeriksaan runtime
      Kode yang penuh pemeriksaan NULL dan langkah defensif lain jauh lebih sulit dibaca
      Bisa dibilang pemeriksaan yang lebih defensif diperlukan di batas library, dan tulisan ini tepat mengatakan agar pemeriksaan seperti itu didorong ke atas
      Kode yang kritis untuk keamanan bisa berbeda, tetapi dalam kebanyakan kasus, dereference NULL yang tidak disengaja cukup ditangkap lewat testing, sanitizer, dan fuzzing
    • Di C, NULL sering kali merupakan nilai pointer yang valid tetapi tidak bisa di-dereference
      Untuk memeriksa nilai pointer yang tidak valid, kita harus memeriksa keseluruhan begitu banyak kemungkinan nilai yang salah, dan jika hanya memeriksa NULL, pada dasarnya kita tidak benar-benar memeriksa nilai yang tidak valid
      Jika prasyarat fungsi adalah “parameter p tidak boleh NULL”, boleh saja diperiksa. Tetapi jika prasyaratnya adalah “p harus berupa pointer yang valid”, semoga beruntung menemukan kondisi assertion yang tepat
    • Heuristik saya adalah: jika sistem tipe tidak bisa mencegah nilai yang salah, maka runtime punya tanggung jawab untuk mencegahnya
      Belakangan saya banyak memakai T-SQL, dan parameter atau variabel tidak bisa dideklarasikan sebagai NOT NULL
      Jadi sebaiknya NULL diperiksa sedini mungkin secara masuk akal, biasanya di bagian paling atas stored procedure
      Jika tidak, NULL bisa merambat secara tak terduga jauh ke dalam lapisan pemanggilan dan menimbulkan masalah yang kurang jelas
      Untungnya data tabel bisa dideklarasikan sebagai NOT NULL, jadi bug seperti ini biasanya tidak merusak data, tetapi jauh lebih mudah jika ditangkap lebih awal
      Namun jika ada logika yang menulis ke DB bergantung pada suatu nilai parameter dan nilai itu ternyata NULL secara tak terduga, nilai yang salah bisa tertulis atau nilai yang diperlukan malah tidak tertulis sama sekali, yang pada praktiknya bisa merusak data
      Jadi defensive programming adalah jawabannya
  • Tanpa konteks yang tepat, saran ini cukup aneh, dan mungkin bahkan buruk
    Loop dan pernyataan if sama-sama operasi control flow, jadi sebagian argumen dalam tulisan ini tidak terlalu masuk akal
    Argumen terkuat tampaknya adalah performa, tetapi performa biasanya seharusnya menjadi hal yang dipikirkan paling akhir, apalagi jika ini berupa saran heuristik
    Sayangnya penulis sudah menjadikannya slogan, dan saya berharap ini tidak menyebar

    • Performa bisa menjadi argumen, tetapi bukan argumen yang kuat
      Jika kita bisa menulis kode yang diperbaiki seperti contoh penulis, kondisinya adalah konstanta selama loop berjalan. Selama bukan kondisi mahal yang dievaluasi setiap kali, branch prediction akan menangani sebagian besarnya
      Jika kondisinya berupa ekspresi boolean dari nilai const, compiler juga mungkin bisa mengetahuinya
  • Saya kira seluruh tulisan akan berlanjut ke contoh seperti ini: membagi walrus berdasarkan kondisi lalu memanggil frobnicate_batch(fwalrus) dan transmogrify_batch(twalrus)
    Namun kenyataannya contoh yang dipakai adalah membungkus seluruh loop menjadi dua cabang dengan satu kondisi

  • Mengejutkan bahwa programmer dengan latar belakang software engineering sering salah soal ini
    Saya mulai memrogram dari bidang sains, dan di sana memikirkan hal-hal seperti ini benar-benar mutlak diperlukan
    Salah menentukan urutan loop saja bisa membuat simulasi memakan waktu 1 minggu alih-alih 1 jam
    Berkat latar belakang itu, saya secara naluriah melakukan optimasi kecil dengan mengatur urutan for dan if secara tepat, dan kode yang tidak melakukannya terasa seperti salah

  • Saya tidak yakin aturan umum seperti ini benar-benar bisa diterapkan pada kode nyata
    Aturan semacam ini sering terlihat seperti dogma yang salah tempat, dan meskipun artikel blog memulainya sebagai heuristik, programmer muda tidak selalu menerimanya seperti itu
    Beberapa minggu lalu YouTube terus merekomendasikan video berjudul “I'm a never-nester”, yang tampaknya berargumen agar jangan pernah menumpuk if, dan itu terasa cukup konyol
    Seperti contoh dalam tulisan itu, kode dengan if condition di dalam for diberi label “buruk”, tetapi pada sebagian besar kode nyata yang ditulis seperti itu, condition bergantung pada walrus sehingga if tidak bisa dinaikkan
    Jika memang bisa dinaikkan, fakta bahwa kondisi yang sama dievaluasi ulang di setiap iterasi loop akan terlalu jelas, sehingga programmer cenderung menghindarinya secara alami
    Namun ketika junior atau mahasiswa membaca saran yang terdengar seperti dogma, mereka bisa saja berusaha mengikutinya secara kaku dan justru menghasilkan kode yang lebih buruk

    • Saya tidak terlalu dogmatis soal “jangan pernah menumpuk”, tetapi dengan asumsi sintaksnya memungkinkan, secara empiris saya belum pernah melihat kasus di mana memisahkan empat kemungkinan dengan match (condition_a, condition_b) tidak lebih baik daripada if bertumpuk
    • Saya justru melihat ini sebagai contoh bagus bahwa if sebaiknya “dinaikkan”
      Tujuan kode itu adalah melakukan operasi tertentu pada walrus bergantung pada kondisi, tetapi sebenarnya if sedang dipakai sebagai pengganti polimorfisme dan sistem tipe
      Mengapa walrus harus memiliki dua fungsi yang perlu dipanggil dalam skenario berbeda? Mengapa tidak satu fungsi dan dua tipe saja, lalu meneruskan tipe yang tepat?
      Bahkan dengan struktur saat ini, di bagian atas kita bisa memilih fungsi penangan berdasarkan kondisi, lalu di loop di bawah cukup memanggil fungsi yang sudah dipilih setiap kali
      Keputusan sebaiknya dibuat sedini mungkin, supaya tidak perlu disebar ke seluruh kode. Bagian dalam kode melakukan pekerjaan yang sama setiap kali tanpa percabangan, dan keluarannya hanya berbeda lewat grafik komposisi
      Tentu ini bukan gagasan baru, dan 15 tahun lalu pun sudah merupakan ide lama: https://www.youtube.com/watch?v=4F72VULWFvc
    • Refaktorisasi “GOOD” hanya bekerja ketika kondisi tidak bergantung pada walrus, dan membantu membuat fakta itu terlihat eksplisit
      Jika “turunkan for” diterapkan lagi di sini, bentuknya menjadi memanggil frobnicate_batch(walruses) atau transmogrify_batch(walruses) di dalam if condition
    • Saya rasa bagus bahwa tulisan-tulisan seperti itu ada
      Ada orang yang merumuskan dengan kata-kata masalah yang sudah beberapa kali saya temui tetapi belum bisa saya ungkapkan dengan baik, dan itu tampak seperti model yang bagus untuk disimpan di kepala
      Pada saat yang sama, kritik seperti ini juga bernilai. Semoga developer junior yang dogmatis membacanya juga dan menilai dengan sedikit lebih bernuansa
    • Hal utama yang perlu dipelajari dari semua “heuristik” dan “praktik terbaik” adalah alasan di baliknya
      Programmer terutama tidak boleh menyalin hal semacam ini lalu menerapkannya secara mekanis
      Menerapkan heuristik dan praktik terbaik secara buta bisa jadi bukan ide bagus, dan pada akhirnya semuanya “tergantung situasi”
  • Heuristik seperti ini biasanya mengandung secuil kebijaksanaan, tetapi umumnya juga menciptakan sesuatu yang kemudian harus dilepaskan dari dogma lagi pada programmer pemula
    Selalu ada banyak kasus yang menjadi lebih buruk ketika diikuti terlalu kaku, dan “ketahui kapan harus tidak mengikuti saran ini” pada dasarnya adalah bagian sulit utamanya

    • Pelepasan dari dogma pada akhirnya memang tetap diperlukan
      Menurut saya aturan-aturan seperti ini berguna untuk dimainkan. Cobalah menerimanya, dorong sampai batasnya, lalu balikkan selama sehari atau setahun, dan lihat sejauh mana ia berjalan
      Mempelajari batasannya juga merupakan satu bahan lagi yang menumpuk di atas perkamen yang terus ditulisi ulang
    • Tulisan ini bisa berguna sebagai koan dalam kumpulan yang lebih besar
      Sebagian koan itu memang seharusnya saling bertentangan