[{"data":1,"prerenderedAt":293},["ShallowReactive",2],{"tag-labels":3,"site-header":4,"article-script-yang-tidak-bilang-di-mana-salahnya-id":72},{},{"featuredPost":5,"navigations":29},{"id":6,"title":7,"title_id":8,"slug":9,"excerpt":10,"excerpt_id":11,"has_translation":12,"published_at":13,"reading_time":14,"hidden_from_listing":15,"tags":16,"thumbnail_image":21,"og_image":22,"series":23,"authors":28},110,"When You Pick a Framework Just to Look Different","Ketika Pilih Framework Cuma Biar Kelihatan Beda","ketika-pilih-framework-cuma-biar-kelihatan-beda","The last time you chose a new framework or library, was it because your problem needed that tool, or because it sounded cool and few people around you were using it? That question is uncomfortable. Technology decisions are often made for reasons that never appear in any technical document.","Terakhir kali kamu milih framework atau library baru, itu karena masalahmu butuh alat itu, atau karena alatnya kedengaran keren dan belum banyak yang pakai? Pertanyaan ini nggak enak dijawab. Soalnya keputusan teknologi sering diambil dengan alasan yang nggak pernah ditulis di dokumen teknis mana pun.",true,"2026-10-06T15:25:21.000Z",5,false,[17,18,19,20],"karier","framework","opini","pengalaman",null,"\u002Fog\u002Fketika-pilih-framework-cuma-biar-kelihatan-beda.png",{"id":24,"title":25,"title_id":26,"slug":27},9,"Articles","Artikel","all",[],[30,46,67],{"collection":31,"item":32},"series",{"__typename":31,"title":25,"title_id":26,"slug":27,"children_series":33},[34,38,42],{"title":35,"title_id":36,"slug":37},"Artificial Intelligence","Kecerdasan Artifisial","kecerdasan-artifisial",{"title":39,"title_id":40,"slug":41},"Programming Fundamentals","Konsep Dasar Pemrograman","konsep-dasar-pemrograman",{"title":43,"title_id":44,"slug":45},"Algorithms and Data Structures","Algoritma dan Struktur Data","algoritma-struktur-data",{"collection":31,"item":47},{"__typename":31,"title":48,"title_id":48,"slug":49,"children_series":50},"Tutorial","tutorial",[51,55,59,63],{"title":52,"title_id":53,"slug":54},"Learning Python","Belajar Python","belajar-python",{"title":56,"title_id":57,"slug":58},"Learning Go","Belajar Golang","belajar-golang",{"title":60,"title_id":61,"slug":62},"Learning Java","Belajar Java","belajar-java",{"title":64,"title_id":65,"slug":66},"Learning Pascal","Belajar Pascal","belajar-pascal",{"collection":31,"item":68},{"__typename":31,"title":69,"title_id":69,"slug":70,"children_series":71},"Lab","lab",[],{"article":73,"renderedBody":175,"renderedTakeaway":176,"latestArticles":177,"toc":276},{"id":74,"title":75,"title_id":75,"slug":76,"excerpt":77,"excerpt_id":77,"has_translation":12,"published_at":78,"reading_time":79,"hidden_from_listing":15,"tags":80,"thumbnail_image":21,"og_image":84,"series":85,"authors":167,"body_locale":170,"available_locales":171,"body":173,"takeway":174,"prerequisite":21},122,"Script yang Nggak Bisa Bilang di Mana Salahnya","script-yang-tidak-bilang-di-mana-salahnya","Script yang berhenti di baris jelek pertama cuma melaporkan sebagian masalah yang ada. Ini yang muncul di layar sekarang.","2026-10-11T23:00:15.000Z",4,[81,82,83,70],"python","error-handling","validasi","\u002Fog\u002Fscript-yang-tidak-bilang-di-mana-salahnya.png",{"id":86,"title":69,"title_id":69,"slug":70,"description":87,"description_id":88,"articles":89,"children_series":166,"parent_series":21},29,"Practice labs: work the problem first, then read the answer. Every lab ships with a repo you can clone, production-sized data, and a target you have to hit yourself. The write-up lives in this series too, and it only opens from the problem article.","Lab praktik: kerjakan masalahnya dulu, baru lihat jawabannya. Tiap lab punya repo yang bisa di-clone, data seukuran produksi, dan target yang harus kamu capai sendiri. Artikel jawabannya ada di seri ini juga, dan cuma bisa kamu buka dari artikel masalahnya.",[90,111,128,144,159],{"id":91,"title":92,"title_id":93,"slug":94,"excerpt":95,"excerpt_id":96,"has_translation":12,"published_at":97,"reading_time":14,"hidden_from_listing":15,"tags":98,"thumbnail_image":21,"og_image":102,"series":103,"authors":104},104,"N+1 Was Never the Bottleneck: 41 Queries Down to 2, Still One Second","Query-nya Tinggal 2, Tapi Response-nya Masih 1 Detik","query-tinggal-2-tapi-response-masih-1-detik","A feed endpoint that shows 20 posts takes a full second and hits the database with 41 queries. Apply the standard N+1 advice, get it down to 2 queries, and the latency does not move. This is that problem as it happened, plus the lab.","Endpoint feed yang cuma menampilkan 20 tulisan butuh satu detik, dan server database dihujani 41 query. Kamu kerjakan nasihat N+1 yang standar, query-nya turun jadi 2, dan latency-nya nggak bergerak. Ini versi \"apa adanya\" dari masalah itu, plus lab-nya.","2026-10-04T09:59:21.000Z",[99,100,101],"database","performance","backend","\u002Fog\u002Fquery-tinggal-2-tapi-response-masih-1-detik.png",{"id":86,"title":69,"title_id":69,"slug":70},[105],{"authors_id":106},{"id":107,"name":108,"slug":109,"role":110,"profile_picture":21},1,"Daffa Izzuddin","izzudd","Writer",{"id":112,"title":113,"title_id":114,"slug":115,"excerpt":116,"excerpt_id":117,"has_translation":12,"published_at":118,"reading_time":79,"hidden_from_listing":15,"tags":119,"thumbnail_image":21,"og_image":123,"series":124,"authors":125},114,"One Payment, Four Ledger Rows: An Idempotency Lab","Saldo Naik Empat Kali untuk Satu Pembayaran","saldo-naik-empat-kali-untuk-satu-pembayaran","The code is not wrong and the gateway is not wrong, yet one payment shows up four times in the ledger. Guess why, then make `make check` green in the lab.","Kodenya nggak salah, dan gateway-nya juga nggak salah. Tapi satu pembayaran muncul empat kali di ledger. Coba tebak kenapa — lalu bikin `make check` hijau di lab-nya.","2026-10-11T13:45:00.000Z",[120,121,122,99,70],"idempotency","webhook","postgres","\u002Fog\u002Fsaldo-naik-empat-kali-untuk-satu-pembayaran.png",{"id":86,"title":69,"title_id":69,"slug":70},[126],{"authors_id":127},{"id":107,"name":108,"slug":109,"role":110,"profile_picture":21},{"id":129,"title":130,"title_id":131,"slug":132,"excerpt":133,"excerpt_id":134,"has_translation":12,"published_at":135,"reading_time":136,"hidden_from_listing":15,"tags":137,"thumbnail_image":21,"og_image":139,"series":140,"authors":141},116,"The Webhook Arrived Late. The Reconciliation Job Had Already Run.","Webhook-nya Telat, Job Rekonsiliasinya Keburu Jalan","webhook-telat-job-rekonsiliasi-keburu-jalan","The webhook path is already idempotent. Then a morning reconciliation job appears, and one payment gets recorded twice from two paths that are both right. Guess where the key is wrong.","Jalur webhook sudah idempotent. Lalu ada job rekonsiliasi pagi, dan satu pembayaran tercatat dua kali dari dua jalur yang dua-duanya benar. Coba tebak kuncinya salah di mana.","2026-10-11T13:44:00.000Z",3,[120,138,121,122,70],"reconciliation","\u002Fog\u002Fwebhook-telat-job-rekonsiliasi-keburu-jalan.png",{"id":86,"title":69,"title_id":69,"slug":70},[142],{"authors_id":143},{"id":107,"name":108,"slug":109,"role":110,"profile_picture":21},{"id":145,"title":146,"title_id":147,"slug":148,"excerpt":149,"excerpt_id":150,"has_translation":12,"published_at":78,"reading_time":79,"hidden_from_listing":15,"tags":151,"thumbnail_image":21,"og_image":154,"series":155,"authors":156},120,"Every Receipt in This Café Lists the Same Order","Tiap Nota di Kafe Ini Isinya Pesanan yang Sama","tiap-nota-isinya-pesanan-yang-sama","Every table's receipt holds the same items, and the total is Rp0 — even though each table ordered exactly one thing. Guess why, then make `make check` green. A Python scope and mutation lab.","Nota tiap meja isinya sama semua, dan totalnya Rp0 — padahal tiap meja cuma pesan satu barang. Coba tebak kenapa.",[81,152,153,70],"scope","mutasi","\u002Fog\u002Ftiap-nota-isinya-pesanan-yang-sama.png",{"id":86,"title":69,"title_id":69,"slug":70},[157],{"authors_id":158},{"id":107,"name":108,"slug":109,"role":110,"profile_picture":21},{"id":74,"title":160,"title_id":75,"slug":76,"excerpt":161,"excerpt_id":77,"has_translation":12,"published_at":78,"reading_time":79,"hidden_from_listing":15,"tags":80,"thumbnail_image":21,"og_image":84,"series":162,"authors":163},"A Script That Cannot Say Where It Went Wrong","A script that stops at the first bad line reports only part of the problem. This is what shows up on screen right now.",{"id":86,"title":69,"title_id":69,"slug":70},[164],{"authors_id":165},{"id":107,"name":108,"slug":109,"role":110,"profile_picture":21},[],[168],{"authors_id":169},{"id":107,"name":108,"slug":109,"role":110,"profile_picture":21},"id",[172,170],"en","\nAda satu momen dalam hidup sebuah script kecil yang selalu berulang: hari pertama dia menerima file yang bukan kamu bikin sendiri. Selama yang menyusun `.csv`-nya kamu, semuanya mulus. Begitu file datang dari tim lain, telepon mulai berdering.\n\nScript-nya sederhana, kok. Dia membaca file `.csv` berisi daftar pendaftar dengan tiga kolom — `nama`, `email`, `umur` — lalu menyimpan baris yang valid. Satu file masuk, satu laporan keluar, selesai. Kecuali saat ada satu baris yang isinya jelek.\n\nDi situ yang muncul bukan penjelasan, melainkan laporan setinggi setengah layar. Judulnya `Traceback (most recent call last):`, disusul daftar path file, nomor baris di `impor.py`, nama fungsi, dan di baris paling bawah satu kalimat seperti `ValueError: invalid literal for int() with base 10: 'dua puluh'`. Semuanya benar. Nggak ada satu pun yang berguna buat orang yang mengirim file itu.\n\n## Yang orang keluhkan\n\nDi lab ini, `make demo` memerankan lima file contoh yang semuanya masuk ke script yang sama. Yang keluar dari tiap file belum seragam, dan justru itu masalah pertamanya. Tiga keluhan yang paling sering terdengar:\n\n- **Sebagian file bikin script mati mendadak.** Yang dilaporkan cuma baris yang kebetulan tersandung lebih dulu. Baris bermasalah setelahnya nggak pernah disebut, seolah nggak ada.\n- **File lain malah diam total.** Nggak ada keluhan apa pun, padahal di dalamnya jelas ada baris yang rusak. Script-nya malah mengucapkan terima kasih.\n- **Yang minta tolong mengeluh dengan kalimat yang sama:** \"aku nggak tahu baris berapa yang salah, kolom apa, dan harus ngapain.\" *Stack trace*-nya menyebut nama variabel Python — `baris`, `umur` — bukan baris di dalam file mereka.\n\nKalau kamu pernah menulis validator atau importer kecil, tiga keluhan itu terasa akrab. Yang bikin capek bukan menulis validasinya, tapi menjelaskan hasilnya ke orang lain. Dan itu, ternyata, bagian dari pekerjaannya juga.\n\n## Angka yang sudah diukur\n\nNggak ada tebakan di sini. Semua baris di bawah keluar dari `make demo` dan `make check`, dijalankan pada kondisi awal lab, apa adanya:\n\n| perintah | yang muncul sekarang |\n|---|---|\n| `make impor FILE=data\u002Fcontoh\u002Fbeberapa.csv` | `ValueError: invalid literal for int() ... 'dua puluh'` — **0 dari 4** masalah dilaporkan |\n| `make impor FILE=data\u002Fcontoh\u002Fkosong.csv` | nggak ada keluhan, dan bilang `3 baris diimpor`, kode keluar `0` |\n| `make impor FILE=data\u002Fcontoh\u002Fkolom.csv` | `IndexError: list index out of range` — **0 dari 2** masalah dilaporkan |\n| `make impor FILE=data\u002Fcontoh\u002Ftidak-ada.csv` | `FileNotFoundError`, kode keluar `1` |\n| `make check` | **1 dari 7** pemeriksaan hijau (cuma A) |\n\nBaris pertama itu yang paling menyakitkan. Di `beberapa.csv` ada **empat** masalah yang menunggu dilaporkan, tapi program berhenti di yang pertama dan melupakan sisanya. Baris ketiga sama polanya: `kolom.csv` punya **dua** baris dengan jumlah kolom yang meleset, dan dua-duanya nggak pernah kelihatan.\n\nBaris kedua punya bentuk kegagalan yang berbeda, dan menurutku yang paling licin. `kosong.csv` berisi baris tanpa nama, tapi script-nya santai saja melaporkan `3 baris diimpor` dengan kode keluar `0`. Nggak ada yang merah, nggak ada yang kuning; dan kalau kamu nggak memeriksa file sumbernya baris demi baris, kamu nggak akan pernah tahu.\n\nBandingkan dengan baris terakhir. Di situ `make check` cuma menyalakan satu lampu hijau dari tujuh. Tujuh pemeriksaan itu tersimpan di `scripts\u002Fcheck.py`, dan alat itulah yang jadi juri lab ini.\n\n## Batas lab ini\n\nAda dua hal yang sengaja nggak dibahas di sini, dan keduanya penting biar kamu nggak salah ambil pelajaran.\n\nPertama, semua error yang diurus lab ini adalah **data yang salah**, bukan **program yang salah**. Kamu sedang memvalidasi masukan yang datang dari luar. Kalau yang salah adalah logika di kodemu sendiri (*bug*), itu cerita yang lain lagi. Kedua, ini program satu kali jalan, bukan layanan yang terus hidup. Di sini \"di mana\" artinya nomor baris di file; di sebuah layanan web, \"di mana\" bisa berarti field di body request. Konsepnya sama, tempatnya beda. Ini juga yang bikin kode keluar (*exit code*) penting: script seperti ini sering dipanggil dari `cron`, `make`, atau pipa shell, bukan ditemani orang yang menatap layarnya.\n\nAda juga satu batas yang praktis. Format laporan `baris N: kolom — pesan` cukup untuk file seukuran ini. Begitu filenya ratusan megabyte, melaporkan semua masalah sekaligus itu sendiri punya ongkos. Untuk sekarang, ukurannya masih sopan.\n\n## Misi kamu\n\nLab ini sengaja dibuat level pemula. Buat menjalankannya kamu nggak perlu Docker, nggak perlu database — cukup `python3` yang sudah terpasang. Yang dilatih di sini bukan kecepatan atau concurrency, tapi satu hal yang kepakai di semua bahasa: **membuat program yang salahnya bisa dibaca manusia.**\n\nTargetmu satu: bikin `make check` hijau, tujuh dari tujuh. Kamu bebas mengubah isi `app\u002F` sesukamu. Yang jangan diubah: `scripts\u002Fcheck.py` (itu penilainya), `data\u002F` (fixture-nya), dan `Makefile`.\n\nKalau nyangkut, ada `HINTS.md` dengan tiga tingkat petunjuk. Pakai urut, jangan langsung lompat ke yang ketiga. Dan jangan buka branch `solusi` sebelum kamu benar-benar mentok.\n\n## Satu pertanyaan sebelum kamu mulai\n\nSebelum menyentuh satu baris kode, ada satu hal yang perlu kamu putuskan dulu. Laporan itu sebenarnya untuk siapa, sih — mesin yang membaca kode, atau orang yang sedang panik karena pendaftarannya nggak masuk?\n\nPertanyaan itulah yang menentukan semua keputusan teknis setelahnya. Program yang cuma melaporkan masalah pertama yang dia temui sebenarnya baru menjawab sebagian kecil dari yang ditanyakan penggunanya. Sisa pertanyaannya, dan kenapa jawabannya sesederhana mengubah satu kebiasaan, ada di [artikel jawabannya](\u002Fp\u002Fjawaban-kumpulkan-semua-masalah-dulu-baru-lapor).\n\nKalau script-mu berhenti di baris jelek pertama, apa yang nggak akan pernah kamu lihat?\n","- **Satu pesan error yang berguna menjawab tiga hal:** di mana, apa yang salah, dan harusnya bagaimana.\n- **Script yang berhenti di masalah pertama cuma menjawab sepertiganya.** Empat masalah di satu file, nol yang dilaporkan.\n- **File yang salah kadang malah diam.** `kosong.csv` melaporkan `3 baris diimpor` dengan kode keluar `0`, padahal ada baris tanpa nama.\n- **Targetnya `make check`:** tujuh pemeriksaan, kondisi awal cuma 1 yang hijau.","\u003Cp>Ada satu momen dalam hidup sebuah script kecil yang selalu berulang: hari pertama dia menerima file yang bukan kamu bikin sendiri. Selama yang menyusun \u003Ccode>.csv\u003C\u002Fcode>-nya kamu, semuanya mulus. Begitu file datang dari tim lain, telepon mulai berdering.\u003C\u002Fp>\n\u003Cp>Script-nya sederhana, kok. Dia membaca file \u003Ccode>.csv\u003C\u002Fcode> berisi daftar pendaftar dengan tiga kolom — \u003Ccode>nama\u003C\u002Fcode>, \u003Ccode>email\u003C\u002Fcode>, \u003Ccode>umur\u003C\u002Fcode> — lalu menyimpan baris yang valid. Satu file masuk, satu laporan keluar, selesai. Kecuali saat ada satu baris yang isinya jelek.\u003C\u002Fp>\n\u003Cp>Di situ yang muncul bukan penjelasan, melainkan laporan setinggi setengah layar. Judulnya \u003Ccode>Traceback (most recent call last):\u003C\u002Fcode>, disusul daftar path file, nomor baris di \u003Ccode>impor.py\u003C\u002Fcode>, nama fungsi, dan di baris paling bawah satu kalimat seperti \u003Ccode>ValueError: invalid literal for int() with base 10: &#39;dua puluh&#39;\u003C\u002Fcode>. Semuanya benar. Nggak ada satu pun yang berguna buat orang yang mengirim file itu.\u003C\u002Fp>\n\u003Ch2 id=\"yang-orang-keluhkan\">Yang orang keluhkan\u003C\u002Fh2>\n\u003Cp>Di lab ini, \u003Ccode>make demo\u003C\u002Fcode> memerankan lima file contoh yang semuanya masuk ke script yang sama. Yang keluar dari tiap file belum seragam, dan justru itu masalah pertamanya. Tiga keluhan yang paling sering terdengar:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Sebagian file bikin script mati mendadak.\u003C\u002Fstrong> Yang dilaporkan cuma baris yang kebetulan tersandung lebih dulu. Baris bermasalah setelahnya nggak pernah disebut, seolah nggak ada.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>File lain malah diam total.\u003C\u002Fstrong> Nggak ada keluhan apa pun, padahal di dalamnya jelas ada baris yang rusak. Script-nya malah mengucapkan terima kasih.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Yang minta tolong mengeluh dengan kalimat yang sama:\u003C\u002Fstrong> &quot;aku nggak tahu baris berapa yang salah, kolom apa, dan harus ngapain.&quot; \u003Cem>Stack trace\u003C\u002Fem>-nya menyebut nama variabel Python — \u003Ccode>baris\u003C\u002Fcode>, \u003Ccode>umur\u003C\u002Fcode> — bukan baris di dalam file mereka.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Kalau kamu pernah menulis validator atau importer kecil, tiga keluhan itu terasa akrab. Yang bikin capek bukan menulis validasinya, tapi menjelaskan hasilnya ke orang lain. Dan itu, ternyata, bagian dari pekerjaannya juga.\u003C\u002Fp>\n\u003Ch2 id=\"angka-yang-sudah-diukur\">Angka yang sudah diukur\u003C\u002Fh2>\n\u003Cp>Nggak ada tebakan di sini. Semua baris di bawah keluar dari \u003Ccode>make demo\u003C\u002Fcode> dan \u003Ccode>make check\u003C\u002Fcode>, dijalankan pada kondisi awal lab, apa adanya:\u003C\u002Fp>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>perintah\u003C\u002Fth>\n\u003Cth>yang muncul sekarang\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\u003Ctr>\n\u003Ctd>\u003Ccode>make impor FILE=data\u002Fcontoh\u002Fbeberapa.csv\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>ValueError: invalid literal for int() ... &#39;dua puluh&#39;\u003C\u002Fcode> — \u003Cstrong>0 dari 4\u003C\u002Fstrong> masalah dilaporkan\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>make impor FILE=data\u002Fcontoh\u002Fkosong.csv\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>nggak ada keluhan, dan bilang \u003Ccode>3 baris diimpor\u003C\u002Fcode>, kode keluar \u003Ccode>0\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>make impor FILE=data\u002Fcontoh\u002Fkolom.csv\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>IndexError: list index out of range\u003C\u002Fcode> — \u003Cstrong>0 dari 2\u003C\u002Fstrong> masalah dilaporkan\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>make impor FILE=data\u002Fcontoh\u002Ftidak-ada.csv\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>FileNotFoundError\u003C\u002Fcode>, kode keluar \u003Ccode>1\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>make check\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Cstrong>1 dari 7\u003C\u002Fstrong> pemeriksaan hijau (cuma A)\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\u003C\u002Ftable>\n\u003Cp>Baris pertama itu yang paling menyakitkan. Di \u003Ccode>beberapa.csv\u003C\u002Fcode> ada \u003Cstrong>empat\u003C\u002Fstrong> masalah yang menunggu dilaporkan, tapi program berhenti di yang pertama dan melupakan sisanya. Baris ketiga sama polanya: \u003Ccode>kolom.csv\u003C\u002Fcode> punya \u003Cstrong>dua\u003C\u002Fstrong> baris dengan jumlah kolom yang meleset, dan dua-duanya nggak pernah kelihatan.\u003C\u002Fp>\n\u003Cp>Baris kedua punya bentuk kegagalan yang berbeda, dan menurutku yang paling licin. \u003Ccode>kosong.csv\u003C\u002Fcode> berisi baris tanpa nama, tapi script-nya santai saja melaporkan \u003Ccode>3 baris diimpor\u003C\u002Fcode> dengan kode keluar \u003Ccode>0\u003C\u002Fcode>. Nggak ada yang merah, nggak ada yang kuning; dan kalau kamu nggak memeriksa file sumbernya baris demi baris, kamu nggak akan pernah tahu.\u003C\u002Fp>\n\u003Cp>Bandingkan dengan baris terakhir. Di situ \u003Ccode>make check\u003C\u002Fcode> cuma menyalakan satu lampu hijau dari tujuh. Tujuh pemeriksaan itu tersimpan di \u003Ccode>scripts\u002Fcheck.py\u003C\u002Fcode>, dan alat itulah yang jadi juri lab ini.\u003C\u002Fp>\n\u003Ch2 id=\"batas-lab-ini\">Batas lab ini\u003C\u002Fh2>\n\u003Cp>Ada dua hal yang sengaja nggak dibahas di sini, dan keduanya penting biar kamu nggak salah ambil pelajaran.\u003C\u002Fp>\n\u003Cp>Pertama, semua error yang diurus lab ini adalah \u003Cstrong>data yang salah\u003C\u002Fstrong>, bukan \u003Cstrong>program yang salah\u003C\u002Fstrong>. Kamu sedang memvalidasi masukan yang datang dari luar. Kalau yang salah adalah logika di kodemu sendiri (\u003Cem>bug\u003C\u002Fem>), itu cerita yang lain lagi. Kedua, ini program satu kali jalan, bukan layanan yang terus hidup. Di sini &quot;di mana&quot; artinya nomor baris di file; di sebuah layanan web, &quot;di mana&quot; bisa berarti field di body request. Konsepnya sama, tempatnya beda. Ini juga yang bikin kode keluar (\u003Cem>exit code\u003C\u002Fem>) penting: script seperti ini sering dipanggil dari \u003Ccode>cron\u003C\u002Fcode>, \u003Ccode>make\u003C\u002Fcode>, atau pipa shell, bukan ditemani orang yang menatap layarnya.\u003C\u002Fp>\n\u003Cp>Ada juga satu batas yang praktis. Format laporan \u003Ccode>baris N: kolom — pesan\u003C\u002Fcode> cukup untuk file seukuran ini. Begitu filenya ratusan megabyte, melaporkan semua masalah sekaligus itu sendiri punya ongkos. Untuk sekarang, ukurannya masih sopan.\u003C\u002Fp>\n\u003Ch2 id=\"misi-kamu\">Misi kamu\u003C\u002Fh2>\n\u003Cp>Lab ini sengaja dibuat level pemula. Buat menjalankannya kamu nggak perlu Docker, nggak perlu database — cukup \u003Ccode>python3\u003C\u002Fcode> yang sudah terpasang. Yang dilatih di sini bukan kecepatan atau concurrency, tapi satu hal yang kepakai di semua bahasa: \u003Cstrong>membuat program yang salahnya bisa dibaca manusia.\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>Targetmu satu: bikin \u003Ccode>make check\u003C\u002Fcode> hijau, tujuh dari tujuh. Kamu bebas mengubah isi \u003Ccode>app\u002F\u003C\u002Fcode> sesukamu. Yang jangan diubah: \u003Ccode>scripts\u002Fcheck.py\u003C\u002Fcode> (itu penilainya), \u003Ccode>data\u002F\u003C\u002Fcode> (fixture-nya), dan \u003Ccode>Makefile\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>Kalau nyangkut, ada \u003Ccode>HINTS.md\u003C\u002Fcode> dengan tiga tingkat petunjuk. Pakai urut, jangan langsung lompat ke yang ketiga. Dan jangan buka branch \u003Ccode>solusi\u003C\u002Fcode> sebelum kamu benar-benar mentok.\u003C\u002Fp>\n\u003Ch2 id=\"satu-pertanyaan-sebelum-kamu-mulai\">Satu pertanyaan sebelum kamu mulai\u003C\u002Fh2>\n\u003Cp>Sebelum menyentuh satu baris kode, ada satu hal yang perlu kamu putuskan dulu. Laporan itu sebenarnya untuk siapa, sih — mesin yang membaca kode, atau orang yang sedang panik karena pendaftarannya nggak masuk?\u003C\u002Fp>\n\u003Cp>Pertanyaan itulah yang menentukan semua keputusan teknis setelahnya. Program yang cuma melaporkan masalah pertama yang dia temui sebenarnya baru menjawab sebagian kecil dari yang ditanyakan penggunanya. Sisa pertanyaannya, dan kenapa jawabannya sesederhana mengubah satu kebiasaan, ada di \u003Ca href=\"\u002Fp\u002Fjawaban-kumpulkan-semua-masalah-dulu-baru-lapor\">artikel jawabannya\u003C\u002Fa>.\u003C\u002Fp>\n\u003Cp>Kalau script-mu berhenti di baris jelek pertama, apa yang nggak akan pernah kamu lihat?\u003C\u002Fp>\n","\u003Cul>\n\u003Cli>\u003Cstrong>Satu pesan error yang berguna menjawab tiga hal:\u003C\u002Fstrong> di mana, apa yang salah, dan harusnya bagaimana.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Script yang berhenti di masalah pertama cuma menjawab sepertiganya.\u003C\u002Fstrong> Empat masalah di satu file, nol yang dilaporkan.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>File yang salah kadang malah diam.\u003C\u002Fstrong> \u003Ccode>kosong.csv\u003C\u002Fcode> melaporkan \u003Ccode>3 baris diimpor\u003C\u002Fcode> dengan kode keluar \u003Ccode>0\u003C\u002Fcode>, padahal ada baris tanpa nama.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Targetnya \u003Ccode>make check\u003C\u002Fcode>:\u003C\u002Fstrong> tujuh pemeriksaan, kondisi awal cuma 1 yang hijau.\u003C\u002Fli>\n\u003C\u002Ful>\n",[178,196,209,222,235,256],{"id":179,"title":180,"title_id":21,"slug":181,"excerpt":182,"excerpt_id":21,"has_translation":15,"published_at":183,"reading_time":184,"hidden_from_listing":15,"tags":185,"thumbnail_image":21,"og_image":187,"series":188,"authors":193},133,"Python Strings: the Badge That Only Worked for One Name","python-strings","The badge script printed the right name for the person who wrote it and the wrong name for everybody else. Slicing counts characters, and a name is words.","2026-10-28T13:00:00.000Z",6,[81,186],"string","\u002Fog\u002Fpython-strings.png",{"id":189,"title":190,"title_id":191,"slug":192},30,"Collections and Strings","Koleksi dan String","python-collections",[194],{"authors_id":195},{"id":107,"name":108,"slug":109,"role":110,"profile_picture":21},{"id":197,"title":198,"title_id":21,"slug":199,"excerpt":200,"excerpt_id":21,"has_translation":15,"published_at":201,"reading_time":184,"hidden_from_listing":15,"tags":202,"thumbnail_image":21,"og_image":204,"series":205,"authors":206},132,"Python Tuples: Why All Three Points Are the Same Point","python-tuples","Three points walk into a loop and come out identical. The list was never copied three times; the name was. A tuple makes that bug impossible.","2026-10-28T12:00:00.000Z",[81,203],"tuple","\u002Fog\u002Fpython-tuples.png",{"id":189,"title":190,"title_id":191,"slug":192},[207],{"authors_id":208},{"id":107,"name":108,"slug":109,"role":110,"profile_picture":21},{"id":210,"title":211,"title_id":21,"slug":212,"excerpt":213,"excerpt_id":21,"has_translation":15,"published_at":214,"reading_time":184,"hidden_from_listing":15,"tags":215,"thumbnail_image":21,"og_image":217,"series":218,"authors":219},131,"Python Sets: the Duplicate That Keeps Coming Back","python-sets","A de-duplication loop that works on clean data leaves four tags where there should be two. The bug is case, and the fix is a `set`.","2026-10-28T11:00:00.000Z",[81,216],"set","\u002Fog\u002Fpython-sets.png",{"id":189,"title":190,"title_id":191,"slug":192},[220],{"authors_id":221},{"id":107,"name":108,"slug":109,"role":110,"profile_picture":21},{"id":223,"title":224,"title_id":21,"slug":225,"excerpt":226,"excerpt_id":21,"has_translation":15,"published_at":227,"reading_time":184,"hidden_from_listing":15,"tags":228,"thumbnail_image":21,"og_image":230,"series":231,"authors":232},130,"Python Dictionaries: the Key That Isn't There","python-dictionary","A word counter dies the first time it meets a word twice. The fix is one line; what takes longer is knowing which of a dict's three lookups to reach for.","2026-10-28T10:00:00.000Z",[81,229],"dictionary","\u002Fog\u002Fpython-dictionary.png",{"id":189,"title":190,"title_id":191,"slug":192},[233],{"authors_id":234},{"id":107,"name":108,"slug":109,"role":110,"profile_picture":21},{"id":236,"title":237,"title_id":238,"slug":239,"excerpt":240,"excerpt_id":241,"has_translation":12,"published_at":242,"reading_time":14,"hidden_from_listing":15,"tags":243,"thumbnail_image":21,"og_image":250,"series":251,"authors":253},129,"So Your RAG Stops Hitting 504: Split the Query Path From Ingestion","Biar RAG nggak kena 504: pisahin jalur query dan ingestion","rag-pisah-jalur-query-dan-ingestion","One RAG service that both parses uploads and answers queries will fall over the moment someone uploads a 200-page PDF. The cure is two paths and one queue.","Satu service RAG yang merangkap parsing dan menjawab kueri bakal tumbang pas ada yang upload PDF 200 halaman. Obatnya dua jalur dan satu queue.","2026-10-22T01:30:00.000Z",[244,245,246,247,248,249],"rag","cloud-run","cloud-tasks","gcp","serverless","arsitektur","\u002Fog\u002Frag-pisah-jalur-query-dan-ingestion.png",{"id":252,"title":35,"title_id":36,"slug":37},8,[254],{"authors_id":255},{"id":107,"name":108,"slug":109,"role":110,"profile_picture":21},{"id":257,"title":258,"title_id":259,"slug":260,"excerpt":261,"excerpt_id":262,"has_translation":12,"published_at":263,"reading_time":184,"hidden_from_listing":15,"tags":264,"thumbnail_image":21,"og_image":271,"series":272,"authors":273},128,"Linear or Graph: How to Run Many Agents Without an Eternal Loop","Linear atau graf: cara mengatur banyak agent tanpa loop abadi","multi-agen-linear-atau-grafik","One agent with twenty tools gets messy fast. Once several agents revise each other, you need a graph that allows cycles and a number that makes it stop.","Satu agent dengan dua puluh tool gampang kacau. Begitu beberapa agent saling revisi, kamu butuh graf yang mendukung cycle dan angka yang membuatnya berhenti.","2026-10-21T16:05:00.000Z",[265,266,267,268,269,270],"multi-agent","langgraph","autogen","orkestrasi","checkpoint","llm","\u002Fog\u002Fmulti-agen-linear-atau-grafik.png",{"id":252,"title":35,"title_id":36,"slug":37},[274],{"authors_id":275},{"id":107,"name":108,"slug":109,"role":110,"profile_picture":21},[277,281,284,287,290],{"text":278,"depth":279,"id":280},"Yang orang keluhkan",2,"yang-orang-keluhkan",{"text":282,"depth":279,"id":283},"Angka yang sudah diukur","angka-yang-sudah-diukur",{"text":285,"depth":279,"id":286},"Batas lab ini","batas-lab-ini",{"text":288,"depth":279,"id":289},"Misi kamu","misi-kamu",{"text":291,"depth":279,"id":292},"Satu pertanyaan sebelum kamu mulai","satu-pertanyaan-sebelum-kamu-mulai",1793336439136]