2 poin oleh GN⁺ 2025-01-26 | 1 komentar | Bagikan ke WhatsApp
  • Budaya pengembangan yang menjadikan instalasi paket sebagai default memperbesar dependency churn yang berujung pada pembaruan, patch, audit, dan pengelolaan dependensi transitif, sehingga menciptakan biaya tersembunyi bagi produktivitas
  • Dampaknya makin besar pada ekosistem dengan sistem packaging yang matang seperti JavaScript dan Rust; contohnya proyek Tokio baru membawa 28 crate, Rocket 172 crate, dan MiniJinja CLI 142 dependensi
  • Bahkan fitur yang sudah lama stabil seperti terminal_size menimbulkan paradoks karena perubahan pada library abstraksi platform memicu crate tambahan dan rilis berulang
  • Untuk fitur kecil, membuat implementasi tanpa dependensi dengan ChatGPT atau Cursor bisa lebih cepat daripada menelusuri dependensi dan terus-menerus meng-upgrade, sekaligus mengurangi beban kompilasi kode eksternal
  • Library untuk area sulit seperti HTTP, QUIC, grafis, atau tokio memang diperlukan, tetapi keputusan menerima graph dependensi besar demi satu fungsi seharusnya dipertanyakan lebih kritis

Treadmill pemeliharaan yang diciptakan oleh pertumbuhan dependensi

  • Pengembang mudah memasang paket demi produktivitas, tetapi akibatnya mereka masuk ke dalam dependency churn berupa pembaruan, patch, audit, dan pengelolaan dependensi transitif yang tak ada habisnya
  • Masalah ini semakin menonjol pada ekosistem yang solusi packaging-nya sangat matang, dan JavaScript serta Rust sangat terdampak
  • Contoh di Rust:
    • Proyek Tokio baru membawa 28 crate
    • Proyek Rocket baru bisa membengkak hingga 172 crate
    • MiniJinja sendiri bisa ada dengan satu dependensi, tetapi varian CLI-nya membawa 142 dependensi
  • Masalah intinya adalah demi satu fitur kecil, kita akhirnya membangun dan mengelola jauh lebih banyak kode eksternal daripada kode yang benar-benar dibutuhkan

Paradoks kode stabil yang ditunjukkan oleh terminal_size

  • terminal_size adalah crate yang, sesuai namanya, mengetahui ukuran terminal
  • API dasar yang dipakai fitur ini pada praktiknya sudah stabil sejak awal terminal komputasi, tetapi tergantung OS tetap menarik 3~4 crate tambahan
  • Situasi pun muncul di mana kita mengompilasi ribuan fungsi lain hanya untuk memeriksa apakah terminal berukuran 80x25 atau 120x40
  • Crate tersebut telah dirilis 26 kali, tetapi implementasi buatan sendiri yang dimasukkan ke proyek 10 tahun lalu untuk fungsi yang sama masih tetap bekerja tanpa pembaruan
  • Alasan banyaknya rilis bukan karena fungsinya berubah, melainkan karena library abstraksi platform yang menjadi fondasinya terus berubah
  • Di UNIX ada pengecualian bahwa dependensi libc memang diperlukan
    • Ini karena Rust tidak mengekspos konstanta libc milik platform, dan konstanta tersebut juga tidak terstandardisasi
    • Namun libc adalah dependensi yang umum dan ringan, sehingga sulit dihindari

Keamanan dan budaya penggunaan ulang kode memperkuat dependensi

  • Pendekatan ala “big supply chain” mewaspadai tindakan menyalin-tempel fungsi atau memakai unsafe secara langsung, dan mendorong agar semuanya diserahkan ke lapisan abstraksi platform
  • Ada juga perusahaan yang memasok alat untuk menangani masalah dependensi, dan atas nama keamanan mereka mendorong dependensi agar terus dipelihara dan diperbarui ke versi terbaru
  • Namun, banyak dependensi justru bisa menjadi sumber utama masalah keamanan
  • Tujuan penulisan kode seharusnya adalah mencapai kondisi stabil pada suatu titik sehingga tidak lagi memerlukan pembaruan
  • Di ekosistem Rust, bahkan dependensi yang berjalan stabil pun bisa mendapat penilaian rendah di RUSTSEC hanya karena bug tracker-nya agak tidak aktif
  • Budaya code review ala perusahaan juga memengaruhi open source
    • Engineer yang membawa library baru dan mengilap cenderung lebih mungkin dihargai daripada ditegur
    • Akibatnya, muncullah alat seperti Dependabot, dan proyek terus menerima PR pembaruan dependensi
    • Di dalam perusahaan, hanya dengan vendoring, audit internal, dan upgrade skala organisasi pun tim engineering bisa dibuat terus sibuk

Fitur kecil bisa dibuat sendiri

  • Jalan yang lebih sederhana adalah menulis sendiri kode yang dibutuhkan
  • Pekerjaan awal mungkin lebih besar, tetapi setelah selesai, kodenya tidak memerlukan crate baru dan tidak perlu menunggu penulis upstream memperbaiki edge case
  • Jika kode rusak di tempat pemakaiannya sendiri, kita bisa langsung memperbaikinya, dan kode yang berfungsi tidak harus selalu naik ke treadmill pemeliharaan
  • Per 2025, ChatGPT atau Cursor bisa dengan cepat membuat implementasi tanpa dependensi untuk fitur-fitur kecil yang umum
  • Banyak fungsi kecil memiliki overhead pemeliharaan yang rendah dan bisa lebih ringan daripada beban upgrade dependensi terus-menerus
  • Jika hanya beberapa baris kode, tidak perlu mengompilasi ribuan baris kode orang lain demi satu fungsi tunggal

Memberi nilai lebih tinggi pada dependensi yang rendah

  • Tidak semua dependensi itu buruk
    • Library grafis yang mengabstraksi driver kompleks
    • Implementasi protokol seperti HTTP dan QUIC
    • Library penting seperti tokio yang tidak bisa dan memang tidak ingin dihapus
  • Namun, jika hanya memakai satu fungsi tetapi harus mengompilasi ratusan fungsi, itu patut dianggap sebagai sinyal peringatan
  • Kita perlu lebih menghargai pilihan menulis fungsi kecil sendiri demi menghindari graph dependensi transitif
  • Graph crate yang besar seharusnya dipandang dengan lebih curiga, dan kode sederhana serta stabil yang bisa dibiarkan tanpa disentuh selama bertahun-tahun perlu dinilai positif
  • sha1-smol awalnya menjadi crate standar untuk menghitung hash SHA1 dengan nama sha1, tetapi kemudian nama itu diserahkan ke rust-crypto dan mendapat tekanan untuk menyesuaikan diri dengan ekosistem crypto yang lebih besar
    • Jika memakai crate sha1 yang baru, akan ikut terbawa 10 dependensi
    • Hal ini sulit dihindari karena nama di registry itu penting dan ada tuntutan kompatibilitas trait
  • MiniJinja menekankan rendahnya dependensi di README-nya
$ cargo tree
minimal v0.1.0 (examples/minimal)
└── minijinja v2.6.0 (minijinja)
    └── serde v1.0.144
  • MiniJinja memiliki PR untuk menghapus dependensi terakhir
  • Dalam situasi yang tepat, kita perlu merayakan pendekatan membuatnya sendiri, dan memberi lebih banyak pengakuan kepada penulis yang membuat library open source dengan dependensi rendah atau tanpa dependensi sama sekali

1 komentar

 
GN⁺ 2025-01-26
Komentar Hacker News
  • Saya suka bahasa Rust itu sendiri, tetapi tidak suka ekosistem dependensi Rust. Banyak keluhan bahwa C++ sulit menambahkan dependensi, tetapi justru itu terasa seperti sebuah fitur. Karena hal itu membuat kita berpikir apakah benar-benar diperlukan
    Di C++, saya mengendalikan dependensi, tetapi di Rust jumlahnya cepat melampaui 100 dan akhirnya saya menyerah. Dari sudut pandang keamanan, terus terang saya tidak bisa tahu apa yang sebenarnya saya distribusikan
    Selain itu Rust juga tidak punya kompatibilitas ABI dan tidak punya budaya pustaka bersama. Jadi rasanya merusak model distribusi paket sistem operasi. Saat memilih distro Linux, kita mempercayai orang-orang yang membangun distro itu, tetapi Rust tampak lebih dekat ke Python, di mana siapa pun bisa mengunggah sesuatu ke PyPI

    • Debian/Ubuntu bisa dianggap aman karena maintainer menandatangani paket dan para maintainer itu diverifikasi. Pada akhirnya strukturnya adalah percaya bahwa Debian/Ubuntu hanya mengizinkan orang yang tepercaya untuk menandatangani paket
      Bagaimana dengan Docker/Python/Rust? Saya sama sekali tidak mengenal orang-orang yang membuat image Docker, paket PyPI, atau crate Rust saya
      Pada dasarnya kita kembali ke masa ketika EXE dan DLL dikirim-kirim lewat ZIP. Hanya saja sekarang kita menyebutnya container, dan dengan bangga menjalankannya sebagai root
      Seperti kata penulis, terkadang yang terbaik adalah sekadar menggabungkan kode sumber dependensi. Dulu ini disebut vendoring, dan cukup penting di Rails/Ruby. Keuntungan besarnya adalah kelak tidak terdampak oleh pengambilalihan paket yang berbahaya, dan jika mau, patch keamanan upstream bisa digabungkan sendiri
    • Saya tidak tahu orang-orang dengan sudut pandang seperti ini bekerja di bidang apa sampai-sampai mereka bisa membuat ulang roda di setiap proyek hanya karena takut pada risiko hipotetis. Sebagian besar risiko itu tetap ada pada implementasi ulang internal, begitu pula bug
      Saya tidak bisa memikirkan proyek di mana sindrom NIH menghasilkan efek bersih yang positif. Bahkan dependensi yang tidak esensial pun menghemat banyak waktu
      Jika sebagian dependensi opsional dibuat ulang di setiap proyek, saya penasaran dari mana datangnya waktu untuk memperbaiki bug tambahan, kondisi batas, berbagai versi dependensi turunan, dan dukungan berbagai platform
    • Jika sejak awal ada package manager yang sangat menurunkan gesekan untuk menambahkan dan mendistribusikan paket, ekosistem bahasa kehilangan sesuatu yang sangat berguna: tekanan yang baik dari seleksi alam
      Jika ini digabungkan dengan repositori yang bisa diunggah siapa saja dan kurasinya lemah seperti PyPI, npm, Cargo, serta standard library yang kecil, bersiaplah untuk menderita
    • Saya bertanya-tanya apakah fitur “kumpulan library yang mudah diambil” menjadi anti-fitur hanya karena membuat library pendukung mudah diambil. Ini tampaknya lebih dekat ke masalah pilihan developer daripada Rust atau JS
      Developerlah yang memilih dependensi mana yang akan diambil. Dependensi yang tidak dipakai siapa pun akan hilang, dan alasan sebagian besar library punya banyak dependensi adalah karena banyak developer lebih suka membangun sesuatu di atas dependensi
      Kalau yang dibahas adalah build system, Cargo tidak memaksa penggunaan Crates.io. Ia hanya mempermudahnya. Dependensi berbasis path seperti CMake/Vcpkg, Conan juga bisa dipakai, atau library bisa dibuat sendiri
      Bahkan jika memakai Crates.io, kalau tidak suka perubahan, cukup kunci versinya. Ia hanya memudahkan mendapatkan versi terbaru
      Di Rust, mudah membangun software di atas software yang sudah ada. Jika tidak suka software yang ada atau laju perubahannya, jangan salahkan Cargo; lakukan dengan cara yang diinginkan
    • Masalah sebenarnya dari sulitnya menambahkan dependensi di C++ adalah sulit menambahkannya dengan “cara yang bisa dibuild semua orang tanpa masalah”. Meski berjalan di komputer saya, bisa saja tidak berjalan di lingkungan calon pengguna atau kontributor, dan itu menjadi masalah
      Distro masih membangun paket Rust dari sumber dan melakukan vendoring dependensi crate ke repositori. Karena dependensinya lebih banyak dan pembaruannya juga sering, ini lebih menyakitkan, tetapi ini terpisah dari shared library
  • Pernyataan bahwa API untuk mengetahui ukuran terminal sudah stabil selama 50 tahun tidak akurat. Setahu saya, ioctl TIOCGWINSZ tidak pernah distandardisasi, dan namanya pun beragam di berbagai Unix dan BSD
    Fungsi tcgetwinsize() baru masuk POSIX pada 2024, dan keseluruhan topik ini punya sejarah yang cukup menyedihkan https://news.ycombinator.com/item?id=42039401. Maksudnya, bahkan sebelum masuk ke sisi Windows pun sudah begitu

    • Kesan yang saya dapat dari tulisan itu lebih ke bahwa implementasi pengganti bukan sederhana karena lebih baik, melainkan sederhana karena hanya menangani use case miliknya sendiri
      Kadang itu tidak masalah, tetapi bagi orang dengan use case lain atau orang yang menjalankannya di sistem yang tidak dipahami dan tidak dipakai penulis, software itu bisa menjadi lebih buruk
      Nilai sebenarnya dari sebuah library adalah menangani fakta bahwa bahkan masalah yang tampak sederhana pun sebenarnya punya banyak kompleksitas. Saya juga tidak menganggap 3–4 dependensi untuk library yang berjalan di Windows/Linux itu terlalu banyak. Dalam kasus yang baik, orang yang memakai library itu lebih berpengalaman di area tersebut dan bisa menangani bagian-bagian tak diketahui yang tidak saya ketahui
    • Meski tidak distandardisasi, panggilan-panggilan seperti itu tidak berubah. Terutama panggilan Windows, karena sudah terkompilasi ke begitu banyak binary, sehingga stabilitas ABI terjamin
      ioctl memang punya masalah, tetapi perubahan yang membuat terminal-size atau dependensinya merilis beberapa versi tidak berkaitan dengan konstanta ioctl/TIOCGWINSZ atau struct winsize. Kode itu tidak berubah
    • Dalam kasus ini, crate terminal-size hanya memanggil tcgetwinsize milik Rustix, lalu Rustix kembali memanggil tcgetwinsize milik libc. Jadi jika melakukan hal yang sama secara langsung, dependensi bisa cukup dikurangi, dengan biaya kira-kira dukungan Windows
      Apakah API ini stabil selama 50 tahun atau 25 tahun adalah detail. Dependensi tersebut bahkan tidak pura-pura menangani kompleksitas itu, dan kecil kemungkinan fungsi itu akan berubah atau dihapus dalam waktu dekat
    • Terminal adalah contoh bagus dari sesuatu yang tampak sangat sederhana, tetapi menjadi masalah besar karena pada masa awal ada terlalu banyak vendor dan standar industri tidak pernah benar-benar mapan. Bahkan yang paling dekat itu VT100 atau VT102 pun masih agak ambigu
      Kebanyakan orang menulis langsung ke sana, tetapi fitur seperti ukuran terminal atau raw mode berantakan dan membutuhkan sesuatu seperti ioctl. Terus terang, itu tidak bagus
      Namun library lebih tidak bagus lagi. Kalau tidak ingin link ke ncurses, semoga Tuhan menolong Anda
  • Baru-baru ini saya menghidupkan kembali web app startup pertama yang saya buat pada 2006. Itu adalah situs media sosial yang berfokus pada berbagi media, dan menurut standar saat itu merupakan LAMP stack yang cukup sederhana. PHP 5, MySQL 3.2, tetapi secara umum sudah punya fitur media sosial pada masanya
    Karena ingin mencoba sendiri teknologi CI/CD baru, saya sedang membuat proses deployment dengan merekayasa app ini secara berlebihan untuk belajar. Bisa saja memakai WordPress atau app Hello World, tetapi ini jauh lebih menarik
    Hampir semua PHP saya tulis sendiri. Saya juga membuat sendiri library untuk autentikasi/otorisasi, template, pemrosesan form, dan sebagainya, dan hanya memakai satu library PEAR untuk pengiriman email. Frontend-nya HTML murni, hampir tidak ada JavaScript, dan Flash dipakai untuk pemutaran media. Pada 2006, kebanyakan orang membuatnya seperti ini
    Hanya butuh sekitar satu jam untuk menjalankan kembali app berusia 19 tahun itu. Saya mengganti driver PHP mysql lama dengan mysqli, lalu menyesuaikan skema dan sebagian query untuk MySQL 8. Utamanya hanya membungkus kata-kata yang kini menjadi reserved word dengan backtick dan memperbaiki default value kolom yang sekarang lebih ketat. Satu-satunya yang tidak jalan hanya Flash
    Sebaliknya, di tempat kerja sekarang kami mengoperasikan puluhan app Spring Boot yang ditulis dengan Java 8, dan daftar vulnerability yang berasal dari puluhan dependency menumpuk sampai berhalaman-halaman. Kalau satu di-update, library lain juga harus ikut naik berantai, dan karena transitive dependency ini menjadi mimpi buruk. Jadi kami hanya menangani seminimal mungkin vulnerability yang paling kritis, dan tidak ada rencana realistis untuk menaikkan semuanya
    Yang lucu, apa yang dilakukan app PHP tahun 2006 itu tidak jauh berbeda dari apa yang dilakukan app-app Spring Boot saat ini. Pada akhirnya semuanya CRUD, hanya saja kini di sekelilingnya jauh lebih banyak hiasan dan tooling enterprise

    • Melihat dunia Linux bergaya Go dan keluarga C membuat saya yakin pada filosofi library tebal. Caranya adalah mengambil satu library yang menyelesaikan masalah di satu domain, lalu menambahkan bagian yang dibutuhkan di atasnya
      Tidak menarik satu dependency untuk setiap item di checklist. Mungkin ada pekerjaan yang duplikatif, tetapi jalur upgrade menjadi jauh lebih mudah
    • Sebagian besar teknologi CI/CD baru menjadi standar karena kompleksitas yang muncul dari menjaga dependency pihak ketiga dan runtime environment yang terus berubah. Pada aplikasi LAMP lama yang dideploy dengan scp, ini umumnya bukan masalah
    • Saya setuju sebagian, tetapi saya tidak ingin menghabiskan waktu menciptakan ulang roda lalu memasukkan bug ke dalamnya
      Misalnya, untuk format pesan komunikasi standar seperti FHIR atau HL7, orang tentu tidak ingin mengimplementasikan sendiri seluruh definisi standar yang sudah kompleks itu
      Menulis sendiri fungsi kriptografi juga biasanya sama saja menembak kaki sendiri, dan berbagai masalah keamanan serius yang ditemukan selama bertahun-tahun sudah membuktikannya
      Sekarang ini eranya ingin fokus menyelesaikan masalah bisnis, bukan bagaimana sebuah solusi dibuat dengan benar. Dengan hadirnya AI, ketika semua kode terasa seperti disambung-sambung sambil mata tertutup, hal ini menjadi makin penting
      Menghabiskan waktu membuat semuanya sendiri mungkin menguntungkan dalam jangka panjang, tetapi pertama-tama kita harus bertahan dalam persaingan. Kompetitor mungkin sudah merebut pasar sejak awal dengan kode yang cepat dibuat dan bisa dibuang
    • Di tempat kerja ada kebijakan analisis statis yang cukup ketat dan akan makin ketat mulai April. Saya penasaran apakah Anda sudah melihat https://docs.openrewrite.org/ untuk meng-upgrade dependency secara otomatis
      Baru-baru ini kami pindah dari Java 8, Spring Boot 2, Swagger ke Java 17, Spring Boot 3.3, OpenAPI 3, dan prosesnya cukup painless
      Masih ada beberapa dependency langsung dan transitive dependency yang perlu dinaikkan lagi, tetapi hambatan terbesar sudah tertangani lewat migrasi
    • Poin penting di sini adalah bahwa di Rust praktis tidak ada masalah transitive dependency. Kalau Anda menaikkan sebuah crate lalu public dependency-nya juga naik, dan dependency itu terekspos di API sehingga Anda harus berinteraksi dengannya, tentu saja Anda harus ikut menaikkannya
      Namun selama tidak menautkan ke library C, private transitive dependency sama sekali tidak menjadi masalah. Dalam dependency tree yang sama Anda bisa menaruh sebanyak mungkin versi crate yang tidak kompatibel secara SemVer, dan jika perlu Anda juga bisa bergantung langsung pada beberapa versi
      Tidak perlu upgrade massal ala Java; cukup naikkan satu yang punya vulnerability. Setahu saya C# juga punya kemampuan serupa, tetapi sedikit lebih verbose
  • Saya 100% setuju. NodeJS punya pengaruh besar pada karier saya, tetapi NPM memberi trauma yang lebih besar daripada masa kecil yang kacau
    Bayangkan seorang programmer baru sedang membuat app baru untuk ditunjukkan kepada teman-temannya. Ia ingin menambahkan dependency baru, tetapi tidak cocok dengan dependency lain, jadi ia memutuskan meng-update semuanya. Sesaat kemudian tidak ada yang berjalan dan Babel berteriak
    Mungkin ia berpikir bagaimanapun nanti akan bisa diselesaikan, tetapi tak lama kemudian ia melihat issue Git terbuka yang menunjukkan bahkan hal mendasar secara harfiah tidak berjalan. Misalnya, di Expo ada issue terbuka bahwa proyek React Native baru default tidak bisa di-build di Android
    Separuhnya terasa seperti tidak ada yang peduli, dan dalam kasus itu solusinya pun bukan berada di ekosistem Node, melainkan di suatu tempat di ekosistem Android. Dari atas sampai bawah semuanya duct tape. Meski begitu, kalau proyek bernilai miliaran dolar saja bisa merilis template yang tidak berfungsi, itu juga memberi keyakinan bahwa saya tidak perlu merasa imposter syndrome hanya karena side project saya separuhnya tidak jalan

    • Ini masalah Node, bukan masalah Rust. Dependency tidak perlu saling “menyukai”. Semua versi bisa ada bersamaan, dan tidak ada yang rusak
    • Node.js dan npm yang menjadi tak terkendali sekitar 15 tahun lalu menjadi peringatan bagi saya
      Saya mulai melihat momen memasukkan dependency pertama sebagai kegagalan pribadi. Karena pada saat itu, alih-alih sekadar memuat JS biasa dengan tag script, seluruh urusan packaging dan organisasi menjadi diperlukan hanya demi memakai satu package.json
  • Ini adalah hal yang mengejutkan saat beralih dari Go ke ekosistem Rust. Proyek Go yang matang, misalnya backend web produksi sebuah perusahaan, bisa saja hanya memiliki 10–20 dependensi, termasuk dependensi transitif
    Seperti yang disebutkan dalam tulisan itu, proyek Rust kecil pun kemungkinan besar akan memiliki jauh lebih banyak dari itu, dan jika melakukan pekerjaan asinkron, hampir pasti demikian
    Saya tidak yakin apakah ini karena budaya atau karena fitur bahasa. Misalnya, di Go, interface dipenuhi secara implisit, jadi tidak perlu mengambil sesuatu hanya untuk menyatakan bahwa kita mengimplementasikannya

    • Salah satu alasan besarnya adalah Go memiliki standard library yang hebat dan matang, sementara Rust sebenarnya tidak demikian
      Hal-hal yang ada di bahasa atau standard library Go tetapi membutuhkan dependensi di Rust antara lain green thread, channel, regex, HTTP client, HTTP server, waktu, flag command-line, logger, membaca/menulis GIF animasi, dan sebagainya
      Saya tidak terlalu menyukai bahasa Go itu sendiri, tetapi dalam hal tooling dan standard library, Go berada di depan, dan jelas yang terbaik dari yang pernah saya pakai
    • Perbedaan Rust dan Go terutama ada pada standard library. Standard library Rust sengaja dibuat kecil
    • Standard library Go yang sangat luas sangat membantu mengurangi jumlah dependensi
  • Saya sangat setuju dengan argumennya. Membiarkan abstraksi dari library lain terekspos ke luar library kita sendiri memiliki banyak biaya tersembunyi. Bukan berarti itu sama sekali tidak boleh dilakukan, tetapi saat mengambil keputusan, biaya di masa depan harus dipertimbangkan secara seimbang
    Jika paket pembungkus sering mengubah desain atau mengubah tujuannya, akan muncul ketidakstabilan. Fitur yang semula merupakan bentuk jaminan paling sederhana dalam rekayasa perangkat lunak bisa hilang atau terpecah
    Selain itu, ini membuat pemilik yang bukan kontributor open source karier tetapi memahami bidang tertentu menjadi sulit berpartisipasi dalam ekosistem. Jika “saya membuat cara yang rapi untuk melakukan X” berubah menjadi diskusi, negosiasi, dan politik selama berminggu-minggu tentang “karena orang mungkin menyukai Z, X harus cocok di bawah Y”, itu membuang waktu semua orang
    Satu hal yang saya pelajari dalam hidup adalah bahwa hal yang paling sederhana bertahan paling lama. Minilith dan monolith seharusnya lebih sering dipuji. Ini juga bukan masalah khusus Rust; saya sudah melihatnya di berbagai bahasa. Komunitas open source sering sangat mendorong paket berukuran atomik, dan saya sering bertanya-tanya apakah tujuannya lebih untuk menancapkan bendera atau memindahkan kepemilikan daripada untuk kepentingan komunitas pengguna yang sebenarnya

  • Pada 2025, saya sedikit banyak secara kebetulan sampai pada pandangan bahwa lebih cepat meminta ChatGPT atau Cursor membuat implementasi tanpa dependensi untuk fungsi-fungsi umum, dan saya makin setuju dengannya. Terutama setelah mengalami dependency hell di aplikasi React besar
    Dependensi pada SaaS atau layanan pihak ketiga juga perlu dipikirkan. Banyak di antaranya adalah pola umum dan masalah yang sudah terselesaikan, sehingga LLM dapat menirunya dengan cepat

    • Dibanding sebelumnya, saya jauh lebih mungkin memulai dengan fungsi utilitas internal kecil yang diimplementasikan AI sebelum menambahkan library
      Untuk masalah dengan cakupan terbatas, ini cukup efektif, dan saya bisa meminta AI membuat implementasi yang lebih lengkap dan kokoh daripada jika saya menulisnya sendiri
      Kalaupun nanti memutuskan menambahkan library, akan ada enkapsulasi yang alami. Cukup ubah implementasi fungsi saya agar memakai library, tanpa harus menyentuh semua tempat yang menggunakannya. Saat waktunya tiba, mencoba beberapa library juga jadi mudah
  • Ironisnya, ini cukup menarik. Armin adalah penulis asli framework web Python Flask. Pada periode yang mirip, ada juga library yang sangat serupa bernama Bottle
    Fungsinya hampir sama, tetapi Flask menjadi sangat populer, sementara saya selalu lebih menyukai Bottle. Alasannya, Bottle berupa satu file dan tanpa dependensi, sehingga sangat mudah disalin begitu saja ke proyek
    Mudah juga untuk diutak-atik, dan akhirnya saya sampai pada titik bisa memahami keseluruhannya. Untuk server dan WebSocket saya memasangkan Gevent, tetapi dengan cara itu saya bisa membuat proyek yang cukup berat
    Sampai sekarang pun saya masih punya dorongan kuat untuk memakai Bottle pada proyek web kecil. Sayangnya, Bottle tidak banyak mengikuti praktik modern yang diperkenalkan Python selama bertahun-tahun, jadi kini terlihat agak usang

  • Untuk membuat sendiri, diperlukan kemampuan engineering yang mumpuni. Jika yang ada hanya engineer yang selama ini selalu mengambil library dari ekosistem seperti NPM atau PyPI, mereka akan kesulitan mengembangkan sendiri solusi untuk banyak masalah. Terutama jika solusinya harus tahan lama dan memiliki fleksibilitas yang dibutuhkan
    “Tidak memprogram diri sendiri ke jalan buntu” membutuhkan banyak latihan
    Saya juga sering melihat kasus di mana kita bisa dengan mudah membuat sesuatu yang lebih baik daripada library yang ada. Dalam satu proyek, saya mengimplementasikan parser untuk varian Markdown yang memiliki metadata di bagian atas file. Sintaksnya kecil, dan parser-nya jadi dengan kode kurang dari satu layar
    Namun library frontend yang mem-parse file yang sama ternyata jauh lebih buruk dari perkiraan. Jika identifier metadata memiliki tanda hubung, ia rusak, dan ternyata identifier metadata itu dipakai langsung sebagai nama anggota objek. Alih-alih memakai objek JSON sederhana, mereka membatasi pilihan nama secara artifisial dan membuatnya rusak jika ada tanda hubung seperti something-something
    Pada akhirnya parser saya dibuang, dengan alasan orang-orang harus “memeliharanya”. Padahal parser itu berjalan begitu saja, dan mudah beradaptasi terhadap perubahan sintaks. Jika tahu sedikit saja tentang parser, tidak ada yang sulit dipahami
    Sulit dipercaya, tetapi sepertinya tidak ada orang lain di tim selain saya yang pernah menulis parser dengan library parser generator. Ada banyak contoh lain seperti ini

  • Jika sampai harus mengompilasi ratusan hal hanya untuk memakai satu fungsi, seharusnya itu menjadi lampu peringatan. Sekitar setahun lalu, saya mengerjakan proyek untuk memperbarui dependensi pihak ketiga.
    Salah satunya adalah pustaka matematika yang kaya, berisi berbagai macam fungsi matematika. Setelah ditelusuri sedikit, ternyata yang kami pakai hanya satu metode untuk mencari median dari sebuah list.
    Saya mengarahkan engineer yang bertanggung jawab ke halaman Wikipedia, menghapus dependensi itu, lalu memintanya menulis satu metode saja untuk melakukan operasi matematika tersebut.
    Namun masalah sebenarnya bukanlah penggunaan dependensi pihak ketiga itu sendiri, melainkan perlunya konsep untuk hanya mengambil bagian sempit dari sebuah pustaka. Jika yang dibutuhkan hanya sebagian kecil dari pustaka raksasa, mengapa harus membawa semuanya? Saya pernah mendengar usulan pendekatan “microframework” untuk hal ini.

    • Di Rust, pustaka dapat mendefinisikan features yang bisa diaktifkan atau dinonaktifkan secara kondisional, sehingga ada cara bawaan untuk mengatur cakupan yang benar-benar disertakan. Tokio adalah contoh yang bagus; mungkin mengejutkan bahwa dependensi langsung yang wajib dibutuhkan Tokio totalnya hanya dua. Sisanya semuanya opsional https://github.com/tokio-rs/tokio/blob/ee19b0ed7371b069112b9...
      Sayangnya, tampaknya orang-orang tidak benar-benar meninjau dengan saksama kumpulan fitur default yang digunakan dependensi dan secara aktif memangkasnya. Sintaks untuk menambahkan fitur tambahan memang sederhana, tetapi untuk menghapus fitur opsional yang aktif secara default, Anda harus menentukan no-default-features lalu tetap menambahkan kembali satu per satu fitur default yang masih diinginkan, sehingga lebih merepotkan.
      Yang lebih buruk, agar sebuah pustaka bisa membuat pengguna memangkas fitur yang tidak perlu dari dependensinya sendiri, pustaka itu harus membuat fiturnya sendiri dan memetakannya ke fitur tiap dependensi. Misalnya ada 5 dependensi dan masing-masing punya 1 dependensi wajib serta 4 dependensi opsional; agar pengguna bisa mengontrol penuh fitur transitif, pustaka Anda harus membuat 20 fitur pemetaan. Itu pun belum termasuk fitur yang akan Anda buat demi mempertimbangkan pengguna hilir untuk kode Anda sendiri.
      Saya makin melihat bahwa usability di sekitar fitur, terutama untuk mengurangi pembengkakan yang tidak perlu, sangat buruk sehingga menjadi pemicu masalah waktu kompilasi Rust. Sepertinya topik seperti ini pun tidak banyak dibahas, jadi mungkin saya perlu menulis pemikiran kuat saya ini sebagai artikel blog agar bisa dirujuk jika kondisi saat ini terus berlanjut.
    • Kalau begitu, akan terjadi hal seperti leftpad di ekosistem JS.
      Di satu sisi, menurut saya itu bukan masalah jika sistem kepercayaannya bagus.
      Saya juga cukup menyukai pendekatan seperti Shadcn, yaitu gagasan bahwa jika Anda menyukai sebuah komponen, Anda menyalinnya ke pustaka Anda sendiri. Namun jika muncul kerentanan, saya tidak bisa tahu apakah saya terdampak.
      Untuk sebagian kode itu tidak masalah, tetapi untuk kode lain kita benar-benar harus bergantung pada sebanyak mungkin orang yang meninjaunya.
    • Untuk yang belum tahu, dalam pseudocode kira-kira seperti ini.
      Median(list) { let len = length(list) if len % 2 == 0 { let x = floor(len/2) return (list[x] + list[x+1]) / 2 } return list[len/2] }
      Dengan asumsi list sudah diurutkan. Saya menahan godaan untuk memanggil is_odd di sini.