2 poin oleh GN⁺ 2023-11-02 | 1 komentar | Bagikan ke WhatsApp
  • Artikel tentang 5 aturan pemrograman Rob Pike pada tahun 1989
  • Aturan 1: Jangan berasumsi di mana program akan menghabiskan sebagian besar waktunya; bottleneck bisa muncul secara tak terduga. Hindari hack demi kecepatan sampai bottleneck itu terbukti.
  • Aturan 2: Selalu ukur sebelum melakukan tuning demi kecepatan. Optimalkan hanya jika sebagian kode benar-benar memberi dampak signifikan pada sisanya.
  • Aturan 3: Algoritma yang rumit itu lambat saat n kecil. Dan biasanya memang begitu. Gunakan algoritma rumit hanya jika n sering besar, dan bahkan saat itu pun terapkan dulu Aturan 2.
  • Aturan 4: Algoritma dan struktur data yang sederhana lebih diinginkan. Keduanya lebih kecil kemungkinannya mengandung bug dibanding yang rumit dan lebih mudah diimplementasikan.
  • Aturan 5: Struktur data yang tepat sangat menentukan dalam pemrograman. Jika data tersusun dengan baik, algoritmanya akan menjadi jelas dengan sendirinya.
  • Aturan 1 dan 2 Pike mencerminkan pepatah Tony Hoare, "premature optimization is the root of all evil".
  • Ken Thompson mengungkapkan kembali Aturan 3 dan 4 Pike sebagai "when in doubt, use brute force".
  • Aturan 3 dan 4 mewujudkan filosofi desain KISS (Keep It Simple, Stupid).
  • Aturan 5 sejalan dengan pernyataan Fred Brooks dalam 'The Mythical Man-Month', yang sering diringkas sebagai "Show me your flowchart and conceal your tables, and I shall continue to be mystified. Show me your tables, and I won't usually need your flowchart; it'll be obvious."

1 komentar

 
GN⁺ 2023-11-02
Komentar Hacker News
  • Saya sepenuhnya setuju dengan ungkapan “data yang berkuasa
    Karena itu wawancara LeetCode selalu terasa aneh. Biasanya fokusnya pada algoritma, padahal dalam praktiknya sejak awal sering kali tidak boleh didekati seperti itu, dan struktur data seharusnya lebih menjadi pusatnya
    Tentu, kalau sama sekali tidak tahu algoritma, kita bisa saja tidak menyadari situasi pengecualian atau saat tertentu ketika perlu bergantung pada algoritma tertentu karena alasan tertentu. Meski begitu, algoritma bisa diajarkan relatif singkat, sementara orang tampaknya lebih sulit mendapatkan intuisi tentang struktur data apa yang harus dipakai

    • Dari pengalaman saya juga setuju. Dalam wawancara, ketika pembicaraan langsung melampaui pemeriksaan algoritma seperti FizzBuzz dan mulai membahas struktur data, arsitektur, serta pemetaan ke domain, saya melihat pewawancara jauh lebih menghormati kandidat
      Pada saat itu suasananya berubah menjadi “oh, benar-benar ada engineer senior yang masuk,” masalah teknis dibicarakan dengan lebih terbuka, dan sikap untuk membuktikan “apakah bisa coding” juga berkurang
      Sebaliknya, tim yang paling sulit dalam membuat perubahan yang baik, mencapai milestone, dan berkolaborasi adalah tim yang tidak punya orang yang benar-benar menata struktur data dan arsitektur kode dengan baik. Tampaknya makin banyak orang terbiasa berpikir bahwa framework akan menangani semuanya, dan kalau tidak berhasil, plugin atau middleware buatan orang yang lebih pintar akan menyelesaikannya
      Engineer yang menghindari struktur data ibarat menembak kakinya sendiri, menyerahkan salah satu alat paling berguna, sehingga batasannya terlihat dalam pekerjaan sehari-hari
    • Saat membantu keponakan yang mempersiapkan lomba competitive programming, saya melihat bahwa pada sebagian besar soal, mengubah data menjadi struktur data yang tepat adalah bagian besar dari solusi
      Misalnya, kode bisa banyak dipakai untuk mencari jalur terpanjang pada graf berarah asiklik (DAG) berbobot, tetapi intinya adalah menyadari bahwa masalah tersebut bisa direpresentasikan sebagai DAG berbobot. Kalau tidak melihat itu, masih bisa diselesaikan, tetapi solusinya menjadi jauh lebih lambat dan rumit
    • Soal LeetCode yang umum sebenarnya juga berfokus pada struktur data. Sebab kandidat harus punya daftar struktur data di kepalanya untuk dikeluarkan saat melakukan pattern matching antara soal dan solusi
      Pewawancara tidak akan lebih dulu memberi tahu bahwa harus memakai priority queue, adjacency matrix, atau trie. Jika mentok, mereka bisa memberi petunjuk, tetapi pengarahan yang berlebihan sulit dilihat sebagai sinyal perekrutan yang kuat
    • “Tunjukkan flowchart tetapi sembunyikan tabelnya, maka saya akan terus bingung. Tunjukkan tabelnya, maka saya tidak perlu melihat flowchart. Karena semuanya akan menjadi jelas dengan sendirinya”
    • Kalau harus memilih salah satu dari keduanya, rasanya tetap perlu gambaran kasar tentang yang lain. Kalau sama sekali tidak tahu bagaimana data akan diakses, sulit mengetahui struktur data apa yang harus dipakai
  • Terkait ungkapan “algoritma keren itu lambat saat n kecil, dan n biasanya kecil,” yang saya rasakan dalam proyek baru-baru ini adalah n besar bisa jauh lebih besar daripada yang dibayangkan
    Mudah untuk berpikir “harus melakukan 100 ribu operasi, jadi ini pasti perlu dioptimalkan,” tetapi komputer itu cepat, dan 100 ribu perkalian biasanya bisa terlalu cepat sehingga tidak perlu dipikirkan terlalu dalam
    Bukan berarti jangan berpikir sama sekali, tetapi sering kali mengejutkan betapa gilanya kecepatan hardware modern

    • Saya sulit sangat setuju dengan pernyataan ini. Algoritma waktu kuadratik adalah jenis yang bisa menggigit pada saat yang tidak terduga
      Saya pernah melihat gangguan produksi akibat kode yang tidak sengaja menjadi kuadratik, dan meskipun 99% pengguna selalu memakai n kecil, sebagian pengguna sering menemui n besar dan mengalami aplikasi yang sangat lambat
      Dalam kebanyakan kasus, meski sedikit lebih lambat pada kasus umum dan implementasinya sedikit lebih rumit, saya ingin memilih algoritma yang lebih baik daripada waktu kuadratik. Jalur lambat yang umum akan dioptimalkan, tetapi jalur lambat yang jarang terjadi bisa tidak pernah disentuh langsung oleh developer lalu meledak di produksi
      Tentu, jika algoritmanya terlalu rumit, bisa saja memilih implementasi waktu kuadratik yang sederhana, tetapi default-nya saya berusaha menetapkan di bawah kuadratik jika memungkinkan. Saya juga pernah menulis tentang ini: https://kevincox.ca/2023/05/09/less-than-quadratic/
    • Hierarki memori juga memengaruhi hal ini. Banyak algoritma keren memiliki locality of reference yang buruk dan menambah branch
      Jadi ini mungkin lebih cocok 40 tahun lalu, ketika CPU tidak secepat itu dibanding memori, dan branch misprediction belum terlalu dipedulikan di hardware konsumen
    • Dalam soal wawancara LeetCode, saya terus melihat daftar berisi 100 ribu item dilintasi beberapa kali. Mungkin bukan yang optimal, tetapi dari sisi waktu operasi nyata, traversal 100 ribu item itu bukan apa-apa dibanding panggilan jaringan yang dilakukan tepat setelahnya
      Setiap kali wawancara, hiring manager menginginkannya, tetapi pemula LeetCode yang belum pernah mengalami luka produksi menolak keputusan itu
    • Referensi utama untuk topik ini adalah Scalability! But at what COST?
      https://www.frankmcsherry.org/assets/COST.pdf
    • Saat memulai pekerjaan programming sungguhan pertama saya di perusahaan game pada awal 2000-an, technical director memberi saran, “kalau jumlah item yang ditangani sekitar 10 ribu, jangan dioptimalkan”
      Melihat peningkatan performa komputer selama 20 tahun terakhir, menaikkan patokan itu menjadi 100 ribu terasa cukup masuk akal
  • Aforisme terkenal “optimisasi prematur adalah akar dari segala kejahatan” sebenarnya berasal dari Donald Knuth, bukan Tony Hoare, dan sering dipakai tanpa konteks seolah-olah menentang optimisasi secara umum
    Kalimat lengkapnya adalah: “Kita sebaiknya melupakan efisiensi kecil, katakanlah dalam 97% kasus. Optimisasi prematur adalah akar dari segala kejahatan. Namun kita tidak boleh melewatkan peluang pada 3% yang penting”
    Intinya adalah luangkan waktu untuk mengoptimalkan bagian yang memang berdampak

    • Knuth menyebut itu perkataan Hoare, dan Hoare menyebut itu perkataan Knuth, jadi ini soal siapa yang dipercaya. Mungkin paling baik mengatribusikannya kepada keduanya
      Tampaknya mungkin Tony yang mengatakannya lebih dulu dan Knuth yang memoles lalu menerbitkannya. Selalu baik menyertakan kutipan panjang yang memberi konteks yang diperlukan
    • Sering juga dilupakan bahwa kutipan itu berasal dari akhir 1970-an. Hampir 50 tahun lalu
      Pemrograman saat itu sangat berbeda dari sekarang. “Optimisasi prematur” pada masa itu bukan “pakai saja pustaka populer yang skalabel”, melainkan lebih dekat ke “pakai algoritma manipulasi bit yang tidak bisa dipahami dan hanya berjalan di perangkat keras ini”
    • Saya tidak melihat kutipan panjang itu memberikan konteks tambahan yang berarti. Jika 3% yang penting sudah diukur dan ditemukan, kondisi itu tidak lagi prematur
      Makna itu sudah terkandung dalam ungkapan “optimisasi prematur adalah akar dari segala kejahatan”; aforismenya bukan “optimisasi adalah akar dari segala kejahatan”
    • Terlalu banyak orang menerima perkataan ini seperti doktrin sampai tidak mempelajari cara yang efisien itu sendiri
      Dalam wawancara struktur data dan algoritma di perusahaan, saya tak terhitung sering melihat developer frontend mengatakan bahwa bubble sort adalah pilihan terbaik. Tidak perlu sampai menurunkannya di tempat; cukup mengetahui beberapa opsi dan bisa menyebutkan pilihan yang baik untuk masalahnya
      Jika seseorang hidup terlalu ekstrem dengan “jangan melakukan optimisasi prematur” sampai bahkan tidak tahu cara yang efisien, bagaimana ia bisa tahu bagian mana yang penting?
    • Dalam konteks ini, sepertinya itu tidak dipakai untuk menentang optimisasi secara umum
  • Pernyataan “struktur data adalah inti” menjadi dua kali lebih penting dalam database
    Orang yang memakai DB sekadar sebagai tempat penyimpanan bit bodoh atau pantulan 1:1 dari definisi objek sering terkejut ketika DB menganggapnya personal dan merusak performa
    Kalau saya harus melihat lagi skema DB yang dihasilkan ORM, rasanya pertemuan kembali itu terlalu cepat

    • Menurut saya kebanyakan ORM membuat skema sesuai yang diminta. Menggunakan ORM tidak otomatis menghasilkan desain database yang lebih buruk daripada membuatnya dengan tangan
      Masalahnya adalah sebagian, atau banyak, developer tidak tahu SQL, dan juga tidak punya pengetahuan DB yang diperlukan untuk memakai ORM
      ORM adalah abstraksi yang cukup bocor, sehingga kita perlu tahu apa yang ada di bawahnya. Jika itu dipahami, dengan kebanyakan ORM pun kita bisa membuat skema yang layak
    • Hukum Conway juga bisa ditambahkan di sini. Maksudnya, “organisasi yang merancang sistem akan menghasilkan desain yang menyalin struktur komunikasi organisasi tersebut”
      Untuk mengorganisasi struktur data dengan baik dan mempertahankannya ketika desain berubah, data dan kode harus dipisahkan pada tingkat organisasi
      Desain skema DB, use case, dan pemetaan di antaranya harus dipisahkan dari implementasi lainnya, dan kelompok ini juga harus menulis pemeriksaan integritas, dan sebagainya. Jika struktur organisasi tidak memisahkan data dan kode, sulit memisahkan kode dan data
    • Stored procedure yang menang
  • Aturan tambahan saya adalah bahwa pemborosan performa kecil, jika menumpuk, pada akhirnya membuat program lambat meskipun masing-masing tampak sepele
    Jika tidak berdampak pada kompleksitas, keterbacaan, kemudahan pemeliharaan, atau biaya implementasi, performa jangan dibiarkan begitu saja. Jika kondisi lain hampir sama, memilih opsi yang lebih lambat dari dua pilihan bukanlah hal yang baik
    Selain itu, jika kita berasumsi n kecil, hampir apa pun bisa berjalan. Namun jika memakai kode yang berjalan baik untuk n di bawah 100 tetapi rusak di atas 10000, misalnya O(n²), maka sebaiknya tetapkan saja batasnya. Jika asumsi n kecil terpatahkan, lebih baik gagal dengan error besar daripada tagihan AWS meledak atau program macet

    • Aturan 1 dan 2 berlaku di sini
  • Banyak dari pedoman ini pada akhirnya bermuara pada strategi untuk mencegah overengineering
    Menurut pengalaman saya, optimisasi prematur adalah salah satu jebakan paling mahal. Jika kita menghindari masalah potensial terlalu dini, asumsi itu tidak tervalidasi, dan tim berikutnya harus membuat solusi mahal untuk mengatasi kompleksitas yang tidak perlu
    Pendekatan yang saya pelajari adalah ini: optimisasi bergantung pada estimasi, dan estimasi di tahap awal sering salah
    Saya juga menyadari bahwa agar orang tidak membuat kode yang terlalu rumit, mengelola ego dan memahami psikologi ternyata cukup penting

    • Saya sering mengatakannya seperti ini: “Selesaikan masalah yang Anda punya. Jangan selesaikan masalah yang Anda pikir Anda punya”
    • Konsep ini juga beririsan dengan identifikasi pemborosan dalam Lean dan Six Sigma
      Overproduction biasanya dianggap sebagai pemborosan terburuk, karena bukan hanya membuat sesuatu yang tidak dibutuhkan, tetapi juga menghabiskan upaya yang sebenarnya bisa dipakai untuk hal yang benar-benar dibutuhkan. Overengineering juga serupa
    • Jika masuk satu tingkat lebih dalam, overengineering muncul karena kita berpikir kompleksitas mungkin akan dibutuhkan nanti, dan saat itu memperluas sistem akan lebih sulit atau berisiko
      Misalnya, meski pengguna baru 100 orang, kita memulai dengan arsitektur microservices karena suatu hari jika menjadi 1 juta pengguna, mendesain ulang monolith akan sulit
      Jadi yang perlu ditangani lebih dulu adalah mengapa seiring waktu kode menjadi kurang lentur
    • Untuk penanganan error, jangan sok canggih; sebaiknya gagal lebih awal dan sederhana
  • Secara umum ini aturan yang bagus, tetapi dalam praktiknya aturan nomor 1 tidak berlaku begitu saja
    Saat memulai, kita perlu hipotesis tentang apa yang akan menjadi bottleneck. Tidak selalu mungkin sekadar mengimplementasikan XYZ lalu mengukur apa yang lambat dan memperbaikinya. Karena X, Y, dan Z saling terhubung, kadang X dan Z harus dibuat dengan cara tertentu agar Y bisa cepat, dan kadang kita sudah tahu bahwa Y akan menjadi bottleneck
    Bahkan setelah nanti mengukur dan mengetahui apa yang lambat, kita tetap harus bertaruh pada pendekatan untuk membuatnya lebih cepat. Semakin terdidik taruhannya, semakin baik
    Programmer yang baik memang mengukur, tetapi mereka juga bisa memprediksi apa yang akan lambat, banyak bug, dan boros memori, sehingga iterasinya lebih sedikit. Mengatakan seolah-olah perilaku performa tidak bisa diprediksi sebagai sebuah aturan berarti mengabaikan pengalaman dan keterampilan yang telah dibangun programmer yang baik

    • Aturan nomor 1 adalah hukum mutlak bagi yang tidak memercayainya, dan pedoman longgar bagi yang memercayainya
      Sebab proses menaati aturan nomor 1 justru merupakan cara terbaik untuk memperoleh pengalaman dan latar empiris yang dibutuhkan bagi intuisi yang baik dalam memperkirakan bottleneck
    • Algoritma yang tampaknya akan lambat bisa diperiksa dengan spike implementation. Biasanya algoritma yang lambat mudah diimplementasikan dan diuji
      Jika prediksi kecepatan salah, proyek akan membawa kode yang rumit tanpa perlu sepanjang masa hidupnya
      Orang sering salah menilai kecepatan algoritma. Jika komputer menghabiskan 99% waktunya untuk mengambil n dari server DB, O(n) dan O(n²) sering tampak sama dalam waktu nyata
      Algoritma yang ditulis dalam C kadang lebih lambat daripada kode Python yang setara, mungkin karena bytecode compiler melakukan sesuatu yang cerdas
      Saya sudah sering membuat kode legacy menjadi lebih cepat, dan biasanya jauh lebih mudah daripada yang dibayangkan, serta lambat karena alasan yang tidak jelas bagi penulis aslinya. Dalam praktiknya, sering kali kode itu lambat karena codebase sudah menjadi terlalu kompleks sehingga penulis aslinya tidak lagi bisa menalarinya. Bagi saya ada contoh konkret “terlalu lambat”, jadi mudah menjalankannya sambil mengamati titik lambat dan melakukan debugging
    • Sepertinya ini bukan respons terhadap keseluruhan teks asli. Di sana dikatakan jangan memasukkan speed hack sebelum mengetahui bottleneck. Itu berbeda dari situasi yang dijelaskan
      Jika Anda membuat video game dengan banyak objek fisik dan, dari pengalaman, yakin bahwa deteksi tabrakan akan menjadi masalah besar, merancang game dan sistem berpusat pada hal itu bukanlah speed hack
      Jika Anda tahu performa akan menjadi perhatian besar dalam pekerjaan tersebut, tentu saja harus diukur. Bukan untuk memastikan apakah itu perhatian, melainkan untuk memastikan seberapa baik Anda menangani perhatian tersebut
    • Saya penasaran dengan contoh konkretnya. Dalam sebagian besar kasus, saya ragu apakah perbedaannya benar-benar signifikan
      Jika membuat sistem baru untuk kebutuhan baru, sering kali rasanya tidak masalah untuk mulai saja. Buat, uji dan ukur, buang atau refactor, lalu ulangi
      Ambil Rust sebagai contoh: dimulai dari bahasa rancangan awal dan compiler yang dibuat dengan OCaml, lalu diiterasi. Meski sejak awal diketahui suatu hari bisa pindah dari OCaml ke self-hosting, saya tidak yakin itu akan membuat perbedaan besar
    • Jika developer bisa memprediksi apa yang akan lambat dengan begitu baik, bukankah tingkat keberhasilan startup yang dipimpin developer seharusnya 100%?
      Jika tidak ada pengguna, fungsi yang butuh beberapa jam pun masih cukup cepat dibanding fungsi yang setelah dioptimalkan menjadi milidetik. Saya tidak tahu apakah ada orang yang sudah membuktikan mampu membuat prediksi semacam itu secara akurat
  • Sebagai bantahan terhadap nomor 5, algoritma kompleks di atas data sederhana bisa memberi peningkatan performa besar, menghilangkan hambatan, dan justru menyederhanakan
    Misalnya, jika memakai binary search pada array terurut alih-alih objek BinaryTree, merge menjadi sederhana karena cukup concat lalu sort, tidak ada pointer sehingga serialisasi mudah, dan dalam beberapa kasus serialisasi itu sendiri tidak diperlukan. Array bisa berada di disk, di memori, atau keduanya lewat mmap; bisa menangani data yang lebih besar dari RAM; dan memungkinkan cold start dengan hanya menunjuk ke file atau mapping lalu langsung berjalan. Ada juga sifat cache-oblivious
    Huffman coding juga contohnya. Di universitas biasanya kita belajar algoritma berbasis tree dengan kompleksitas O(n log n), tetapi saya tidak tahu bahwa ada cara membangun Huffman tree berbasis array in-place dalam waktu linear
    Tentu saja 99% waktu kita membuat backend microservice dan memakai struktur data koleksi standar. Namun jika di kantor mengerjakan big data, saya jauh lebih memilih memprosesnya di satu mesin lokal dengan disk besar daripada mengadopsi keluarga MapReduce yang sedang tren saat itu

    • Saya tidak menganggap binary search sebagai algoritma yang keren. Fungsi sort modernlah yang keren, dan karena bisa punya bug yang halus, developer umum sebaiknya tidak membuatnya sendiri. Quicksort pun punya jebakan
      Rob Pike mungkin akan berkata untuk terlebih dahulu melakukan profiling pada kode, lalu melihat apakah kode keren atau struktur data alternatif itu benar-benar lebih cepat
    • Ini tidak terlihat seperti bantahan. Dari sudut pandang nasihat Pike, “binary search pada array terurut” dan “objek BinaryTree” hanyalah implementasi berbeda dari struktur data yang sama
    • Jangan lupa bahwa dalam 99% waktu, developer adalah sumber daya paling mahal. Maintainability dan kecepatan masuk pasar biasanya jauh lebih penting
  • Saya pertama kali membaca tulisan ini di cat-v lebih dari 10 tahun lalu, dan itu meninggalkan pengaruh yang tidak terhapuskan pada cara saya mendekati dan memikirkan desain serta kompleksitas
    http://doc.cat-v.org/bell_labs/pikestyle

  • Saya tidak paham bagaimana aturan asli “struktur data adalah kuncinya” bisa dipangkas menjadi “tulislah kode bodoh yang memakai objek pintar
    Ungkapan “smart objects” sangat buruk, dan aturan aslinya jauh lebih baik meski lebih panjang

    • Rob Pike juga sepertinya akan setuju bahwa “smart objects” adalah cara berpikir yang keliru: https://commandcenter.blogspot.com/2012/06/less-is-exponenti...
    • Menaikkan logika “pintar” ke level yang lebih tinggi lebih mudah dipahami, diuji, dan diubah. Menurut saya smart objects jauh lebih sulit dibuat koheren satu sama lain
    • Anggap saja maksudnya adalah menulis kode yang secara alami mengikuti dari objek yang terstruktur dengan baik