[{"data":1,"prerenderedAt":190},["ShallowReactive",2],{"site-header":3,"site-footer-license":42,"article-jawaban-kumpulkan-semua-masalah-dulu-baru-lapor-with-latest":46},{"navigations":4},[5,20,37],{"collection":6,"item":7},"series",{"__typename":6,"title":8,"slug":9,"children_series":10},"Artikel","all",[11,14,17],{"title":12,"slug":13},"Algoritma dan Struktur Data","algoritma-struktur-data",{"title":15,"slug":16},"Kecerdasan Artifisial","kecerdasan-artifisial",{"title":18,"slug":19},"Konsep Dasar Pemrograman","konsep-dasar-pemrograman",{"collection":6,"item":21},{"__typename":6,"title":22,"slug":23,"children_series":24},"Tutorial","tutorial",[25,28,31,34],{"title":26,"slug":27},"Belajar Java","belajar-java",{"title":29,"slug":30},"Belajar Pascal","belajar-pascal",{"title":32,"slug":33},"Belajar Golang","belajar-golang",{"title":35,"slug":36},"Belajar Python","belajar-python",{"collection":6,"item":38},{"__typename":6,"title":39,"slug":40,"children_series":41},"Lab","lab",[],{"name":43,"url":44,"show":45},"CC BY-SA","https://creativecommons.org/licenses/by-sa/4.0/",false,{"article":47,"renderedBody":110,"renderedTakeaway":111,"latestArticles":112,"toc":163},{"prerequisite":48,"id":52,"title":53,"slug":54,"excerpt":55,"published_at":56,"thumbnail_image":57,"tags":58,"reading_time":62,"takeway":63,"body":64,"authors":65,"series":71},{"id":49,"title":50,"slug":51},"122","Script yang Nggak Bisa Bilang di Mana Salahnya","script-yang-tidak-bilang-di-mana-salahnya","123","Jawabannya: Kumpulkan Semua Masalah Dulu, Baru Lapor","jawaban-kumpulkan-semua-masalah-dulu-baru-lapor","Pesan error yang berguna menjawab di mana, apa, dan harusnya bagaimana — sekaligus, tanpa berhenti di masalah pertama.","2026-10-11T23:00:15.000Z",null,[59,60,61,40],"python","error-handling","validasi",4,"- **Satu baris jelek nggak boleh menghentikan seluruh proses.** Laporkan, lalu lanjut ke baris berikutnya.\n- **Nomor baris di file dimulai dari `enumerate(pembaca, start=2)`** — baris 1 adalah judulnya, bukan data.\n- **`IndexError` itu artinya belum memvalidasi.** Periksa jumlah kolom dulu sebelum menyentuh isi baris.\n- **Kode keluar itu kontrak:** `0` beres, `1` ada baris bermasalah, `2` cara pakainya salah.","**Ini artikel jawabannya.** Kalau kamu belum mengerjakan lab 0005 dan masih mau mencoba sendiri, mendingan tutup dulu halaman ini. Sisanya di bawah adalah jawabannya, lengkap dengan angka pengukurannya.\n\n## Angka lengkapnya\n\nDua keadaan, diukur dengan `scripts/check.py` yang sama, di mesin yang sama, tanpa mengubah `data/` atau `scripts/check.py` sedikit pun:\n\n| varian | hijau | catatan |\n|---|---|---|\n| kondisi awal (branch `main`) | **1 dari 7** | cuma A hijau: `periksa()` melempar `int(baris[2])`, dan siapa pun yang memanggilnya berhenti di situ |\n| semua masalah dikumpulkan, nomor baris `start=2`, kode keluar 0/1/2 | **7 dari 7** | — |\n\nAngka konkretnya, kalau kamu mau mencocokkan dengan yang kamu lihat di laptopmu. Di kondisi awal, `beberapa.csv` mencuatkan `ValueError: invalid literal for int() with base 10: 'dua puluh'` dan cuma **0 dari 4** masalah yang dilaporkan. `kosong.csv` malah tanpa keluhan sama sekali, lalu tetap bilang `3 baris diimpor` dengan kode keluar `0`, padahal ada baris tanpa nama di dalamnya. `kolom.csv` meledak dengan `IndexError: list index out of range`, **0 dari 2** masalah. Dan `tidak-ada.csv` memberi `FileNotFoundError` dengan kode keluar `1`, bukan `2`.\n\nDi branch `solusi`, `make demo` menghasilkan satu baris laporan untuk tiap masalah — empat baris untuk `beberapa.csv`, dua untuk `kolom.csv` — dengan kode keluar `0`/`1`/`2` sesuai keadaan.\n\n## Kenapa kondisinya begini\n\n### `periksa()` mencetak, bukan mengembalikan — dan melempar di tengah jalan\n\n```python\numur = int(baris[2])   # ValueError kalau isinya bukan angka\nif umur \u003C 1 or umur > 120:\n    print(\"umur tidak masuk akal, dilewati\")\n```\n\nAda dua cacat di dua baris itu. Pertama, `int()` melempar, dan `main()` nggak menangkapnya: satu baris jelek menghentikan seluruh proses, jadi masalah sesudahnya nggak pernah keburu dilaporkan. Kedua, satu-satunya diagnostik cuma `print` yang nggak menyebut baris maupun kolomnya.\n\nYang bikin jebakan ini licin: komentar di atasnya justru membenarkan dua-duanya — \"Python sudah memberi pesan yang jelas\", \"yang bikin sistem rusak cuma umur\". Kodenya **kelihatan** hati-hati. Padahal aturan yang kamu butuhkan kebalikannya: **satu baris jelek nggak boleh menghentikan seluruh proses.** Baris itu dilaporkan, lalu perulangan jalan terus ke baris berikutnya.\n\n### Nomor baris itu urusan yang memanggil, bukan yang memvalidasi\n\n`periksa()` sebaiknya **mengembalikan daftar masalah** dan nggak tahu apa-apa soal nomor baris. Yang menempelkan nomornya adalah `main()`, lewat `enumerate(pembaca, start=2)`. Angka `2` itu bukan detail sepele: baris 1 di file adalah judulnya, jadi baris data pertama ada di baris 2.\n\nKalau kamu mulai dari `start=1`, angka yang kamu laporkan salah persis satu dari yang dilihat pengguna di editornya. Inilah *off-by-one* yang paling gampang lolos dari mata sendiri, karena layarnya \"kelihatannya benar\". Yang memisahkan keduanya cuma satu hal: nomor baris **di file** itu urusan yang memanggil, sedangkan index data itu urusan yang memvalidasi.\n\n### `IndexError` sama saja belum memvalidasi\n\n`kolom.csv` punya baris dengan 2 kolom dan ada juga yang 4 kolom, padahal judulnya cuma punya 3. Menyentuh `baris[2]` sebelum memastikan jumlah kolomnya cukup cuma memindahkan lokasi crash-nya. **`IndexError` di sini artinya belum memvalidasi** — bukan tanda data yang rumit.\n\nUrutan yang benar: periksa jumlah kolom lebih dulu. Kalau `len(baris)` nggak sama dengan jumlah kolom yang diharapkan, laporkan sebagai satu masalah di kolom `jumlah kolom`, lalu jangan lanjut memeriksa isi baris itu. Kolom per kolom belum bisa dipercaya sebelum jumlahnya cocok.\n\n### Kode keluar adalah bagian dari kontrak\n\nScript seperti ini dipanggil dari `make`, `cron`, atau pipa shell. Pemanggilnya nggak membaca layar; dia membaca *exit code*. \"File tidak ada\" dan \"file ada tapi isinya ada yang salah\" itu dua hal yang beda bagi pemanggil — yang pertama artinya salah pakai, yang kedua artinya datanya perlu diperbaiki.\n\nKarena itu kode keluarnya dibedakan: `0` untuk beres, `1` untuk ada baris bermasalah, `2` untuk cara pakainya yang salah (file nggak ada, argumen kurang). Kondisi awal cuma punya satu kode untuk hampir semua kegagalan, jadi pemanggil otomatis nggak bisa memutuskan harus mengulang, berhenti, atau melanjutkan.\n\n## Bentuk solusinya\n\nValidasi jadi fungsi yang **mengumpulkan**, bukan yang melempar:\n\n```python\ndef periksa(baris):\n    masalah = []\n    if len(baris) != len(KOLOM):\n        masalah.append((\"jumlah kolom\", \"... seharusnya ...\"))\n        return masalah\n    nama, email, umur = (sel.strip() for sel in baris)\n    if not nama:\n        masalah.append((\"nama\", \"kolom nama kosong, padahal harus diisi\"))\n    if \"@\" not in email:\n        masalah.append((\"email\", f\"'{email}' bukan alamat email karena tidak ada tanda '@'\"))\n    try:\n        umur_angka = int(umur)\n    except ValueError:\n        masalah.append((\"umur\", f\"'{umur}' bukan angka — isi dengan bilangan bulat, misalnya 25\"))\n    return masalah\n```\n\nlalu di `main()` nomor barisnya ditempel dari baris file:\n\n```python\nfor nomor, baris in enumerate(pembaca, start=2):   # baris 1 file = judul\n    for kolom, pesan in periksa(baris):\n        sys.stderr.write(f\"baris {nomor}: {kolom} — {pesan}\\n\")\n```\n\nSatu baris yang benar mengembalikan daftar kosong, jadi nggak ada yang dilaporkan. Satu baris yang salah bisa mengembalikan **lebih dari satu** masalah sekaligus — di `beberapa.csv` ada satu baris yang nama kosong **dan** emailnya tanpa `@`. Perhatikan juga bahwa `try/except`-nya kecil dan spesifik, cuma membungkus `int(umur)`. Jangan bungkus semuanya dengan satu `try/except` besar di `main()`: itu memang menghilangkan *stack trace*, tapi sekalian menghentikan program di baris jelek pertama, dan pekerjaanmu balik ke titik awal.\n\n## Yang perlu kamu bawa dari sini\n\nPesan error yang berguna menjawab tiga hal — **di mana**, **apa yang salah**, dan **harusnya bagaimana** — dan jawabannya untuk semuanya sekaligus, bukan cuma yang pertama kebetulan ditemui. Program yang berhenti di masalah pertama cuma menjawab sepertiganya. Sisa waktu yang kamu hemat di situ, kamu bayar mahal di telepon yang berdering.\n\nSatu kebiasaan saja yang berubah: sebelum menulis `raise`, tanya dulu siapa yang akan membaca pesannya.\n",[66],{"authors_id":67},{"name":68,"slug":69,"role":70,"profile_picture":57},"Daffa Izzuddin","izzudd","Writer",{"id":72,"title":39,"slug":40,"articles":73,"parent_series":57},"29",[74,83,92,99,107],{"id":75,"title":76,"slug":77,"excerpt":78,"thumbnail_image":57,"tags":79},"104","Query-nya Tinggal 2, Tapi Response-nya Masih 1 Detik","query-tinggal-2-tapi-response-masih-1-detik","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.",[80,81,82],"database","performance","backend",{"id":84,"title":85,"slug":86,"excerpt":87,"thumbnail_image":57,"tags":88},"114","Saldo Naik Empat Kali untuk Satu Pembayaran","saldo-naik-empat-kali-untuk-satu-pembayaran","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.",[89,90,91,80,40],"idempotency","webhook","postgres",{"id":93,"title":94,"slug":95,"excerpt":96,"thumbnail_image":57,"tags":97},"116","Webhook-nya Telat, Job Rekonsiliasinya Keburu Jalan","webhook-telat-job-rekonsiliasi-keburu-jalan","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.",[89,98,90,91,40],"reconciliation",{"id":100,"title":101,"slug":102,"excerpt":103,"thumbnail_image":57,"tags":104},"120","Tiap Nota di Kafe Ini Isinya Pesanan yang Sama","tiap-nota-isinya-pesanan-yang-sama","Nota tiap meja isinya sama semua, dan totalnya Rp0 — padahal tiap meja cuma pesan satu barang. Coba tebak kenapa.",[59,105,106,40],"scope","mutasi",{"id":49,"title":50,"slug":51,"excerpt":108,"thumbnail_image":57,"tags":109},"Script yang berhenti di baris jelek pertama cuma melaporkan sebagian masalah yang ada. Ini yang muncul di layar sekarang.",[59,60,61,40],"\u003Cp>\u003Cstrong>Ini artikel jawabannya.\u003C/strong> Kalau kamu belum mengerjakan lab 0005 dan masih mau mencoba sendiri, mendingan tutup dulu halaman ini. Sisanya di bawah adalah jawabannya, lengkap dengan angka pengukurannya.\u003C/p>\n\u003Ch2 id=\"angka-lengkapnya\">Angka lengkapnya\u003C/h2>\n\u003Cp>Dua keadaan, diukur dengan \u003Ccode>scripts/check.py\u003C/code> yang sama, di mesin yang sama, tanpa mengubah \u003Ccode>data/\u003C/code> atau \u003Ccode>scripts/check.py\u003C/code> sedikit pun:\u003C/p>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>varian\u003C/th>\n\u003Cth>hijau\u003C/th>\n\u003Cth>catatan\u003C/th>\n\u003C/tr>\n\u003C/thead>\n\u003Ctbody>\u003Ctr>\n\u003Ctd>kondisi awal (branch \u003Ccode>main\u003C/code>)\u003C/td>\n\u003Ctd>\u003Cstrong>1 dari 7\u003C/strong>\u003C/td>\n\u003Ctd>cuma A hijau: \u003Ccode>periksa()\u003C/code> melempar \u003Ccode>int(baris[2])\u003C/code>, dan siapa pun yang memanggilnya berhenti di situ\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>semua masalah dikumpulkan, nomor baris \u003Ccode>start=2\u003C/code>, kode keluar 0/1/2\u003C/td>\n\u003Ctd>\u003Cstrong>7 dari 7\u003C/strong>\u003C/td>\n\u003Ctd>—\u003C/td>\n\u003C/tr>\n\u003C/tbody>\u003C/table>\n\u003Cp>Angka konkretnya, kalau kamu mau mencocokkan dengan yang kamu lihat di laptopmu. Di kondisi awal, \u003Ccode>beberapa.csv\u003C/code> mencuatkan \u003Ccode>ValueError: invalid literal for int() with base 10: &#39;dua puluh&#39;\u003C/code> dan cuma \u003Cstrong>0 dari 4\u003C/strong> masalah yang dilaporkan. \u003Ccode>kosong.csv\u003C/code> malah tanpa keluhan sama sekali, lalu tetap bilang \u003Ccode>3 baris diimpor\u003C/code> dengan kode keluar \u003Ccode>0\u003C/code>, padahal ada baris tanpa nama di dalamnya. \u003Ccode>kolom.csv\u003C/code> meledak dengan \u003Ccode>IndexError: list index out of range\u003C/code>, \u003Cstrong>0 dari 2\u003C/strong> masalah. Dan \u003Ccode>tidak-ada.csv\u003C/code> memberi \u003Ccode>FileNotFoundError\u003C/code> dengan kode keluar \u003Ccode>1\u003C/code>, bukan \u003Ccode>2\u003C/code>.\u003C/p>\n\u003Cp>Di branch \u003Ccode>solusi\u003C/code>, \u003Ccode>make demo\u003C/code> menghasilkan satu baris laporan untuk tiap masalah — empat baris untuk \u003Ccode>beberapa.csv\u003C/code>, dua untuk \u003Ccode>kolom.csv\u003C/code> — dengan kode keluar \u003Ccode>0\u003C/code>/\u003Ccode>1\u003C/code>/\u003Ccode>2\u003C/code> sesuai keadaan.\u003C/p>\n\u003Ch2 id=\"kenapa-kondisinya-begini\">Kenapa kondisinya begini\u003C/h2>\n\u003Ch3 id=\"periksa-mencetak-bukan-mengembalikan-dan-melempar-di-tengah-jalan\">\u003Ccode>periksa()\u003C/code> mencetak, bukan mengembalikan — dan melempar di tengah jalan\u003C/h3>\n\n\u003Cdiv class=\"code-block-wrapper my-6 not-prose rounded-xl border-2 border-black dark:border-gray-600 shadow-neo overflow-hidden bg-[#24292e] text-[#e1e4e8] transition-all\">\n  \u003Cdiv class=\"code-block-header flex items-center justify-between px-4 py-2.5 bg-[#1f2428] border-b-2 border-black dark:border-gray-600 font-mono text-xs font-bold text-gray-300 select-none\">\n    \u003Cdiv class=\"flex items-center gap-2\">\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#ff5f56] border border-black/40 inline-block\">\u003C/span>\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#ffbd2e] border border-black/40 inline-block\">\u003C/span>\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#27c93f] border border-black/40 inline-block\">\u003C/span>\n      \u003Cspan class=\"ml-2 font-mono font-bold text-xs uppercase tracking-wider text-gray-300\">PYTHON\u003C/span>\n    \u003C/div>\n    \u003Cbutton type=\"button\" class=\"copy-code-btn flex items-center gap-1.5 px-3 py-1 text-xs font-bold bg-[#2f363d] text-gray-200 border-2 border-black dark:border-gray-500 rounded-lg shadow-neo-sm hover:translate-x-0.5 hover:translate-y-0.5 hover:shadow-none hover:bg-gray-700 transition-all cursor-pointer\" data-code=\"umur%20%3D%20int(baris%5B2%5D)%20%20%20%23%20ValueError%20kalau%20isinya%20bukan%20angka%0Aif%20umur%20%3C%201%20or%20umur%20%3E%20120%3A%0A%20%20%20%20print(%22umur%20tidak%20masuk%20akal%2C%20dilewati%22)\" title=\"Salin kode\">\n      \u003Csvg class=\"w-3.5 h-3.5 copy-icon-svg\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n        \u003Cpath stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z\">\u003C/path>\n      \u003C/svg>\n      \u003Cspan class=\"copy-text\">Copy\u003C/span>\n    \u003C/button>\n  \u003C/div>\n  \u003Cdiv class=\"code-block-content p-4 overflow-x-auto text-sm font-mono leading-relaxed bg-[#24292e]\">\n    \u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">umur \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#79B8FF\"> int\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(baris[\u003C/span>\u003Cspan style=\"color:#79B8FF\">2\u003C/span>\u003Cspan style=\"color:#E1E4E8\">])   \u003C/span>\u003Cspan style=\"color:#6A737D\"># ValueError kalau isinya bukan angka\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">if\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> umur \u003C/span>\u003Cspan style=\"color:#F97583\">&#x3C;\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 1\u003C/span>\u003Cspan style=\"color:#F97583\"> or\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> umur \u003C/span>\u003Cspan style=\"color:#F97583\">>\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 120\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">    print\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"umur tidak masuk akal, dilewati\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n  \u003C/div>\n\u003C/div>\u003Cp>Ada dua cacat di dua baris itu. Pertama, \u003Ccode>int()\u003C/code> melempar, dan \u003Ccode>main()\u003C/code> nggak menangkapnya: satu baris jelek menghentikan seluruh proses, jadi masalah sesudahnya nggak pernah keburu dilaporkan. Kedua, satu-satunya diagnostik cuma \u003Ccode>print\u003C/code> yang nggak menyebut baris maupun kolomnya.\u003C/p>\n\u003Cp>Yang bikin jebakan ini licin: komentar di atasnya justru membenarkan dua-duanya — &quot;Python sudah memberi pesan yang jelas&quot;, &quot;yang bikin sistem rusak cuma umur&quot;. Kodenya \u003Cstrong>kelihatan\u003C/strong> hati-hati. Padahal aturan yang kamu butuhkan kebalikannya: \u003Cstrong>satu baris jelek nggak boleh menghentikan seluruh proses.\u003C/strong> Baris itu dilaporkan, lalu perulangan jalan terus ke baris berikutnya.\u003C/p>\n\u003Ch3 id=\"nomor-baris-itu-urusan-yang-memanggil-bukan-yang-memvalidasi\">Nomor baris itu urusan yang memanggil, bukan yang memvalidasi\u003C/h3>\n\u003Cp>\u003Ccode>periksa()\u003C/code> sebaiknya \u003Cstrong>mengembalikan daftar masalah\u003C/strong> dan nggak tahu apa-apa soal nomor baris. Yang menempelkan nomornya adalah \u003Ccode>main()\u003C/code>, lewat \u003Ccode>enumerate(pembaca, start=2)\u003C/code>. Angka \u003Ccode>2\u003C/code> itu bukan detail sepele: baris 1 di file adalah judulnya, jadi baris data pertama ada di baris 2.\u003C/p>\n\u003Cp>Kalau kamu mulai dari \u003Ccode>start=1\u003C/code>, angka yang kamu laporkan salah persis satu dari yang dilihat pengguna di editornya. Inilah \u003Cem>off-by-one\u003C/em> yang paling gampang lolos dari mata sendiri, karena layarnya &quot;kelihatannya benar&quot;. Yang memisahkan keduanya cuma satu hal: nomor baris \u003Cstrong>di file\u003C/strong> itu urusan yang memanggil, sedangkan index data itu urusan yang memvalidasi.\u003C/p>\n\u003Ch3 id=\"indexerror-sama-saja-belum-memvalidasi\">\u003Ccode>IndexError\u003C/code> sama saja belum memvalidasi\u003C/h3>\n\u003Cp>\u003Ccode>kolom.csv\u003C/code> punya baris dengan 2 kolom dan ada juga yang 4 kolom, padahal judulnya cuma punya 3. Menyentuh \u003Ccode>baris[2]\u003C/code> sebelum memastikan jumlah kolomnya cukup cuma memindahkan lokasi crash-nya. \u003Cstrong>\u003Ccode>IndexError\u003C/code> di sini artinya belum memvalidasi\u003C/strong> — bukan tanda data yang rumit.\u003C/p>\n\u003Cp>Urutan yang benar: periksa jumlah kolom lebih dulu. Kalau \u003Ccode>len(baris)\u003C/code> nggak sama dengan jumlah kolom yang diharapkan, laporkan sebagai satu masalah di kolom \u003Ccode>jumlah kolom\u003C/code>, lalu jangan lanjut memeriksa isi baris itu. Kolom per kolom belum bisa dipercaya sebelum jumlahnya cocok.\u003C/p>\n\u003Ch3 id=\"kode-keluar-adalah-bagian-dari-kontrak\">Kode keluar adalah bagian dari kontrak\u003C/h3>\n\u003Cp>Script seperti ini dipanggil dari \u003Ccode>make\u003C/code>, \u003Ccode>cron\u003C/code>, atau pipa shell. Pemanggilnya nggak membaca layar; dia membaca \u003Cem>exit code\u003C/em>. &quot;File tidak ada&quot; dan &quot;file ada tapi isinya ada yang salah&quot; itu dua hal yang beda bagi pemanggil — yang pertama artinya salah pakai, yang kedua artinya datanya perlu diperbaiki.\u003C/p>\n\u003Cp>Karena itu kode keluarnya dibedakan: \u003Ccode>0\u003C/code> untuk beres, \u003Ccode>1\u003C/code> untuk ada baris bermasalah, \u003Ccode>2\u003C/code> untuk cara pakainya yang salah (file nggak ada, argumen kurang). Kondisi awal cuma punya satu kode untuk hampir semua kegagalan, jadi pemanggil otomatis nggak bisa memutuskan harus mengulang, berhenti, atau melanjutkan.\u003C/p>\n\u003Ch2 id=\"bentuk-solusinya\">Bentuk solusinya\u003C/h2>\n\u003Cp>Validasi jadi fungsi yang \u003Cstrong>mengumpulkan\u003C/strong>, bukan yang melempar:\u003C/p>\n\n\u003Cdiv class=\"code-block-wrapper my-6 not-prose rounded-xl border-2 border-black dark:border-gray-600 shadow-neo overflow-hidden bg-[#24292e] text-[#e1e4e8] transition-all\">\n  \u003Cdiv class=\"code-block-header flex items-center justify-between px-4 py-2.5 bg-[#1f2428] border-b-2 border-black dark:border-gray-600 font-mono text-xs font-bold text-gray-300 select-none\">\n    \u003Cdiv class=\"flex items-center gap-2\">\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#ff5f56] border border-black/40 inline-block\">\u003C/span>\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#ffbd2e] border border-black/40 inline-block\">\u003C/span>\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#27c93f] border border-black/40 inline-block\">\u003C/span>\n      \u003Cspan class=\"ml-2 font-mono font-bold text-xs uppercase tracking-wider text-gray-300\">PYTHON\u003C/span>\n    \u003C/div>\n    \u003Cbutton type=\"button\" class=\"copy-code-btn flex items-center gap-1.5 px-3 py-1 text-xs font-bold bg-[#2f363d] text-gray-200 border-2 border-black dark:border-gray-500 rounded-lg shadow-neo-sm hover:translate-x-0.5 hover:translate-y-0.5 hover:shadow-none hover:bg-gray-700 transition-all cursor-pointer\" data-code=\"def%20periksa(baris)%3A%0A%20%20%20%20masalah%20%3D%20%5B%5D%0A%20%20%20%20if%20len(baris)%20!%3D%20len(KOLOM)%3A%0A%20%20%20%20%20%20%20%20masalah.append((%22jumlah%20kolom%22%2C%20%22...%20seharusnya%20...%22))%0A%20%20%20%20%20%20%20%20return%20masalah%0A%20%20%20%20nama%2C%20email%2C%20umur%20%3D%20(sel.strip()%20for%20sel%20in%20baris)%0A%20%20%20%20if%20not%20nama%3A%0A%20%20%20%20%20%20%20%20masalah.append((%22nama%22%2C%20%22kolom%20nama%20kosong%2C%20padahal%20harus%20diisi%22))%0A%20%20%20%20if%20%22%40%22%20not%20in%20email%3A%0A%20%20%20%20%20%20%20%20masalah.append((%22email%22%2C%20f%22'%7Bemail%7D'%20bukan%20alamat%20email%20karena%20tidak%20ada%20tanda%20'%40'%22))%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20umur_angka%20%3D%20int(umur)%0A%20%20%20%20except%20ValueError%3A%0A%20%20%20%20%20%20%20%20masalah.append((%22umur%22%2C%20f%22'%7Bumur%7D'%20bukan%20angka%20%E2%80%94%20isi%20dengan%20bilangan%20bulat%2C%20misalnya%2025%22))%0A%20%20%20%20return%20masalah\" title=\"Salin kode\">\n      \u003Csvg class=\"w-3.5 h-3.5 copy-icon-svg\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n        \u003Cpath stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z\">\u003C/path>\n      \u003C/svg>\n      \u003Cspan class=\"copy-text\">Copy\u003C/span>\n    \u003C/button>\n  \u003C/div>\n  \u003Cdiv class=\"code-block-content p-4 overflow-x-auto text-sm font-mono leading-relaxed bg-[#24292e]\">\n    \u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">def\u003C/span>\u003Cspan style=\"color:#B392F0\"> periksa\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(baris):\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    masalah \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> []\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    if\u003C/span>\u003Cspan style=\"color:#79B8FF\"> len\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(baris) \u003C/span>\u003Cspan style=\"color:#F97583\">!=\u003C/span>\u003Cspan style=\"color:#79B8FF\"> len\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#79B8FF\">KOLOM\u003C/span>\u003Cspan style=\"color:#E1E4E8\">):\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        masalah.append((\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"jumlah kolom\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"... seharusnya ...\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">))\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        return\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> masalah\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    nama, email, umur \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> (sel.strip() \u003C/span>\u003Cspan style=\"color:#F97583\">for\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> sel \u003C/span>\u003Cspan style=\"color:#F97583\">in\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> baris)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    if\u003C/span>\u003Cspan style=\"color:#F97583\"> not\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> nama:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        masalah.append((\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"nama\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"kolom nama kosong, padahal harus diisi\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">))\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    if\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"@\"\u003C/span>\u003Cspan style=\"color:#F97583\"> not\u003C/span>\u003Cspan style=\"color:#F97583\"> in\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> email:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        masalah.append((\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"email\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#F97583\">f\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"'\u003C/span>\u003Cspan style=\"color:#79B8FF\">{\u003C/span>\u003Cspan style=\"color:#E1E4E8\">email\u003C/span>\u003Cspan style=\"color:#79B8FF\">}\u003C/span>\u003Cspan style=\"color:#9ECBFF\">' bukan alamat email karena tidak ada tanda '@'\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">))\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    try\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        umur_angka \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#79B8FF\"> int\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(umur)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    except\u003C/span>\u003Cspan style=\"color:#79B8FF\"> ValueError\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        masalah.append((\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"umur\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#F97583\">f\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"'\u003C/span>\u003Cspan style=\"color:#79B8FF\">{\u003C/span>\u003Cspan style=\"color:#E1E4E8\">umur\u003C/span>\u003Cspan style=\"color:#79B8FF\">}\u003C/span>\u003Cspan style=\"color:#9ECBFF\">' bukan angka — isi dengan bilangan bulat, misalnya 25\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">))\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    return\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> masalah\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n  \u003C/div>\n\u003C/div>\u003Cp>lalu di \u003Ccode>main()\u003C/code> nomor barisnya ditempel dari baris file:\u003C/p>\n\n\u003Cdiv class=\"code-block-wrapper my-6 not-prose rounded-xl border-2 border-black dark:border-gray-600 shadow-neo overflow-hidden bg-[#24292e] text-[#e1e4e8] transition-all\">\n  \u003Cdiv class=\"code-block-header flex items-center justify-between px-4 py-2.5 bg-[#1f2428] border-b-2 border-black dark:border-gray-600 font-mono text-xs font-bold text-gray-300 select-none\">\n    \u003Cdiv class=\"flex items-center gap-2\">\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#ff5f56] border border-black/40 inline-block\">\u003C/span>\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#ffbd2e] border border-black/40 inline-block\">\u003C/span>\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#27c93f] border border-black/40 inline-block\">\u003C/span>\n      \u003Cspan class=\"ml-2 font-mono font-bold text-xs uppercase tracking-wider text-gray-300\">PYTHON\u003C/span>\n    \u003C/div>\n    \u003Cbutton type=\"button\" class=\"copy-code-btn flex items-center gap-1.5 px-3 py-1 text-xs font-bold bg-[#2f363d] text-gray-200 border-2 border-black dark:border-gray-500 rounded-lg shadow-neo-sm hover:translate-x-0.5 hover:translate-y-0.5 hover:shadow-none hover:bg-gray-700 transition-all cursor-pointer\" data-code=\"for%20nomor%2C%20baris%20in%20enumerate(pembaca%2C%20start%3D2)%3A%20%20%20%23%20baris%201%20file%20%3D%20judul%0A%20%20%20%20for%20kolom%2C%20pesan%20in%20periksa(baris)%3A%0A%20%20%20%20%20%20%20%20sys.stderr.write(f%22baris%20%7Bnomor%7D%3A%20%7Bkolom%7D%20%E2%80%94%20%7Bpesan%7D%5Cn%22)\" title=\"Salin kode\">\n      \u003Csvg class=\"w-3.5 h-3.5 copy-icon-svg\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n        \u003Cpath stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z\">\u003C/path>\n      \u003C/svg>\n      \u003Cspan class=\"copy-text\">Copy\u003C/span>\n    \u003C/button>\n  \u003C/div>\n  \u003Cdiv class=\"code-block-content p-4 overflow-x-auto text-sm font-mono leading-relaxed bg-[#24292e]\">\n    \u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">for\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> nomor, baris \u003C/span>\u003Cspan style=\"color:#F97583\">in\u003C/span>\u003Cspan style=\"color:#79B8FF\"> enumerate\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(pembaca, \u003C/span>\u003Cspan style=\"color:#FFAB70\">start\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#79B8FF\">2\u003C/span>\u003Cspan style=\"color:#E1E4E8\">):   \u003C/span>\u003Cspan style=\"color:#6A737D\"># baris 1 file = judul\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    for\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> kolom, pesan \u003C/span>\u003Cspan style=\"color:#F97583\">in\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> periksa(baris):\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        sys.stderr.write(\u003C/span>\u003Cspan style=\"color:#F97583\">f\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"baris \u003C/span>\u003Cspan style=\"color:#79B8FF\">{\u003C/span>\u003Cspan style=\"color:#E1E4E8\">nomor\u003C/span>\u003Cspan style=\"color:#79B8FF\">}\u003C/span>\u003Cspan style=\"color:#9ECBFF\">: \u003C/span>\u003Cspan style=\"color:#79B8FF\">{\u003C/span>\u003Cspan style=\"color:#E1E4E8\">kolom\u003C/span>\u003Cspan style=\"color:#79B8FF\">}\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> — \u003C/span>\u003Cspan style=\"color:#79B8FF\">{\u003C/span>\u003Cspan style=\"color:#E1E4E8\">pesan\u003C/span>\u003Cspan style=\"color:#79B8FF\">}\\n\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n  \u003C/div>\n\u003C/div>\u003Cp>Satu baris yang benar mengembalikan daftar kosong, jadi nggak ada yang dilaporkan. Satu baris yang salah bisa mengembalikan \u003Cstrong>lebih dari satu\u003C/strong> masalah sekaligus — di \u003Ccode>beberapa.csv\u003C/code> ada satu baris yang nama kosong \u003Cstrong>dan\u003C/strong> emailnya tanpa \u003Ccode>@\u003C/code>. Perhatikan juga bahwa \u003Ccode>try/except\u003C/code>-nya kecil dan spesifik, cuma membungkus \u003Ccode>int(umur)\u003C/code>. Jangan bungkus semuanya dengan satu \u003Ccode>try/except\u003C/code> besar di \u003Ccode>main()\u003C/code>: itu memang menghilangkan \u003Cem>stack trace\u003C/em>, tapi sekalian menghentikan program di baris jelek pertama, dan pekerjaanmu balik ke titik awal.\u003C/p>\n\u003Ch2 id=\"yang-perlu-kamu-bawa-dari-sini\">Yang perlu kamu bawa dari sini\u003C/h2>\n\u003Cp>Pesan error yang berguna menjawab tiga hal — \u003Cstrong>di mana\u003C/strong>, \u003Cstrong>apa yang salah\u003C/strong>, dan \u003Cstrong>harusnya bagaimana\u003C/strong> — dan jawabannya untuk semuanya sekaligus, bukan cuma yang pertama kebetulan ditemui. Program yang berhenti di masalah pertama cuma menjawab sepertiganya. Sisa waktu yang kamu hemat di situ, kamu bayar mahal di telepon yang berdering.\u003C/p>\n\u003Cp>Satu kebiasaan saja yang berubah: sebelum menulis \u003Ccode>raise\u003C/code>, tanya dulu siapa yang akan membaca pesannya.\u003C/p>\n","\u003Cul>\n\u003Cli>\u003Cstrong>Satu baris jelek nggak boleh menghentikan seluruh proses.\u003C/strong> Laporkan, lalu lanjut ke baris berikutnya.\u003C/li>\n\u003Cli>\u003Cstrong>Nomor baris di file dimulai dari \u003Ccode>enumerate(pembaca, start=2)\u003C/code>\u003C/strong> — baris 1 adalah judulnya, bukan data.\u003C/li>\n\u003Cli>\u003Cstrong>\u003Ccode>IndexError\u003C/code> itu artinya belum memvalidasi.\u003C/strong> Periksa jumlah kolom dulu sebelum menyentuh isi baris.\u003C/li>\n\u003Cli>\u003Cstrong>Kode keluar itu kontrak:\u003C/strong> \u003Ccode>0\u003C/code> beres, \u003Ccode>1\u003C/code> ada baris bermasalah, \u003Ccode>2\u003C/code> cara pakainya salah.\u003C/li>\n\u003C/ul>\n",[113,124,134,143,152,161],{"id":114,"title":115,"slug":116,"excerpt":117,"thumbnail_image":57,"tags":118},"111","Rust Masuk Kernel Linux: Revolusi Nyata atau Cuma Euforia RIIR?","rust-di-kernel-linux-revolusi-atau-cuma-hype","Selama lebih dari 30 tahun kernel Linux cuma mau bahasa C, lalu tiba-tiba Rust resmi masuk di Linux 6.1. Itu revolusi beneran atau cuma kebawa euforia meme Rewrite It In Rust? Yuk lihat datanya, bukan cuma opininya.",[119,120,121,122,123],"rust","linux","kernel","system-programming","memory-safety",{"id":125,"title":126,"slug":127,"excerpt":128,"thumbnail_image":57,"tags":129},"118","Belajar Idempotency dari Stok yang Keburu Kebeli","pub-sub-sendiri-lalu-belajar-idempotency","Tugas pertama saya di kerjaan pertama: sistem yang kewalahan menerima webhook. Saya putuskan membangun pub/sub sendiri dari nol, dan pelajaran yang akhirnya datang bukan dari situ — tapi dari stok gudang yang keburu kebeli orang.",[89,130,131,132,133],"event-driven","celery","rabbitmq","post-mortem",{"id":135,"title":136,"slug":137,"excerpt":138,"thumbnail_image":57,"tags":139},"112","Aplikasimu lambat, dan kamu udah kepikiran ganti bahasa. Tunggu dulu","aplikasimu-lambat-dan-kamu-udah-kepikiran-ganti-bahasa-tunggu-dulu","Ada endpoint yang grafiknya spike tiap beberapa menit, dan kepikiran buat pindah dari Go ke Rust? Discord dan Cloudflare beneran melakukannya — tapi cuma setelah kehabisan cara lain dan punya datanya. Yuk, bedah gejala mana yang beneran butuh rewrite, dan mana yang cuma butuh profiling. 🤔",[82,81,140,119,141,142],"golang","system-design","profiling",{"id":144,"title":145,"slug":146,"excerpt":147,"thumbnail_image":57,"tags":148},"113","Garbage Collector dituduh bikin aplikasi lemot. Seberapa adil tuduhan itu?","garbage-collector-dituduh-bikin-aplikasi-lemot","GC sering jadi tersangka utama tiap aplikasi melambat, dan banyak yang percaya dia jalan pakai timer tiap beberapa detik. Dua-duanya nggak sepenuhnya benar. Yuk bedah cara kerja GC dari dalam, plus satu pengecualian yang bikin Discord kesakitan tiap 2 menit. 🤔",[82,81,140,149,150,151],"jvm","garbage-collector","memory",{"id":153,"title":154,"slug":155,"excerpt":156,"thumbnail_image":57,"tags":157},"119","Aturan Sintaks: Satu Titik Koma Bikin Program Nggak Jalan","aturan-sintaks-pemrograman","Baru nulis beberapa baris, programnya udah nolak jalan? Seringnya bukan logikanya yang salah, cuma satu karakter yang kurang atau salah tempat.",[158,159,160,59],"syntax","dasar","konsep",{"id":100,"title":101,"slug":102,"excerpt":103,"thumbnail_image":57,"tags":162},[59,105,106,40],[164,168,171,175,178,181,184,187],{"text":165,"depth":166,"id":167},"Angka lengkapnya",2,"angka-lengkapnya",{"text":169,"depth":166,"id":170},"Kenapa kondisinya begini","kenapa-kondisinya-begini",{"text":172,"depth":173,"id":174},"periksa() mencetak, bukan mengembalikan — dan melempar di tengah jalan",3,"periksa-mencetak-bukan-mengembalikan-dan-melempar-di-tengah-jalan",{"text":176,"depth":173,"id":177},"Nomor baris itu urusan yang memanggil, bukan yang memvalidasi","nomor-baris-itu-urusan-yang-memanggil-bukan-yang-memvalidasi",{"text":179,"depth":173,"id":180},"IndexError sama saja belum memvalidasi","indexerror-sama-saja-belum-memvalidasi",{"text":182,"depth":173,"id":183},"Kode keluar adalah bagian dari kontrak","kode-keluar-adalah-bagian-dari-kontrak",{"text":185,"depth":166,"id":186},"Bentuk solusinya","bentuk-solusinya",{"text":188,"depth":166,"id":189},"Yang perlu kamu bawa dari sini","yang-perlu-kamu-bawa-dari-sini",1791759826828]