Jul 26, 2025

1 Bug = 1 Guardrail (Biar Gak Balik Lagi)

Bug yang paling mahal itu bukan yang bikin error sekali. Yang mahal itu bug yang balik lagi minggu depan, lalu semua orang pura-pura kaget seperti baru pertama kali hidup di bumi.

Maarif Alawi

Full-Stack Web App Developer

Hero fix itu seru, tapi guardrail itu yang bikin product survive. Dalam 3 tahun dan ~13 project shipped, pola paling konsisten yang bikin tim tenang adalah ini: setiap bug signifikan harus ditutup dengan 1 guardrail. Fix sekarang menyelamatkan hari ini, guardrail menyelamatkan 3 bulan ke depan.

Cerita singkat: double submit yang balik lagi (dan bikin data kotor)

Pernah ada bug double submit di flow form: user klik sekali, request kepanggil dua kali. Kita fix cepat: disable button pas loading, kelar.

Seminggu kemudian, bug balik lagi. Ternyata bukan cuma dari klik user, tapi dari network retry + refresh + “back-forward cache” yang bikin request keulang dalam kondisi tertentu.

Sentry nunjukin pattern: endpoint yang sama, payload mirip, timestamp mepet. Pelajarannya: UI fix itu bagus, tapi bukan guardrail utama.

Akhirnya guardrail-nya dipindah ke backend: idempotency key. Sejak itu, request dobel jadi “no-op” yang aman, bukan data duplikat yang bikin audit jadi horor.

Fix vs Anti-Regression

Fix itu jawab pertanyaan: “gimana caranya ini berhenti error sekarang?”. Anti-regression jawab pertanyaan yang lebih penting: “gimana caranya ini gak balik lagi dengan cara yang beda?”.

Fix biasanya lokal dan cepat. Anti-regression biasanya nambah satu lapisan proteksi: validasi, test, monitoring, atau constraint.

Contoh gampang: “null edge case” di UI bisa di-fix dengan optional chaining. Anti-regression-nya: validasi di API supaya payload null gak pernah masuk database.

Fix bikin lampu merah mati, guardrail bikin jalanan gak tabrakan lagi.

RCA 4 Baris (Copy Format)

  • Trigger:

  • Root cause:

  • Blast radius:

  • Guardrail:

Format ini cukup karena memaksa kamu mikir inti masalah tanpa bikin dokumen yang cuma dibaca pas onboarding lalu dilupakan. Empat baris itu udah mencakup “apa yang nyalain”, “kenapa bisa”, “seberapa parah”, dan “apa pagar pengamannya”.

Kalau kamu gak bisa jelasin bug dalam 4 baris, biasanya kamu belum ngerti bug-nya, cuma capek aja.

Guardrail yang paling sering kepake

  • Validation backend: reject payload aneh sebelum jadi data “abadi” di database.

  • Idempotency: cegah double submit dan retry jadi duplikasi (server-side, bukan berharap UI selalu sempurna).

  • Test kecil yang tepat: satu unit/integration test buat kasus bug-nya, bukan test 200 baris buat ego.

  • Sentry alert: tangkep spike error sebelum user yang jadi QA gratisan.

  • Feature flag / rollback: rilis aman, kalau aneh tinggal off tanpa drama.

  • Smoke steps: 3 langkah wajib setelah deploy biar gak “feels good ship it”.

  • Permission checks: enforce di API/RLS Supabase, bukan cuma hide button di UI.

Anti-patterns yang bikin bug balik

  • Fix pakai delay (setTimeout) biar “kelihatan stabil”.
    Solusinya: cari race condition-nya dan bikin guardrail di layer yang bener.

  • Catch error lalu diem (return 200 tapi sebenarnya gagal).
    Solusinya: fail loudly, capture ke Sentry, dan kasih response yang jujur.

  • Hotfix di prod tanpa catatan, terus lupa ada perubahan.
    Solusinya: minimal PR + release note singkat, biar knowledge gak hilang.

  • Hardcode biar lewat dulu, nanti “dibenerin”.
    Solusinya: bikin solusi yang bisa dijaga, bukan yang bikin utang makin kreatif.

Kode: Next.js API route + Idempotency Key + Supabase

import { NextResponse } from "next/server";
import { createClient } from "@supabase/supabase-js";

const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!
);

export async function POST(req: Request) {
  const idem = req.headers.get("Idempotency-Key")?.trim();
  if (!idem || idem.length < 10) {
    return NextResponse.json({ ok: false, error: "Missing Idempotency-Key" }, { status: 422 });
  }

  const body = await req.json().catch(() => null);
  if (!body?.user_id || !body?.amount) {
    return NextResponse.json({ ok: false, error: "Invalid payload" }, { status: 422 });
  }

  // Guardrail: insert with unique idempotency_key (DB should enforce UNIQUE)
  const payload = {
    user_id: String(body.user_id),
    amount: Number(body.amount),
    idempotency_key: idem,
    created_at: new Date().toISOString(),
  };

  const { data, error } = await supabase
    .from("payments")
    .insert(payload)
    .select("id")
    .single();

  if (error && String(error.message).toLowerCase().includes("duplicate")) {
    return NextResponse.json({ ok: true, deduped: true });
  }
  if (error) return NextResponse.json({ ok: false }, { status: 500 });

  return NextResponse.json({ ok: true, id: data.id });
}

COPY THIS

  • Bug signifikan selalu punya 1 guardrail setelah fix

  • RCA ditulis 4 baris (Trigger/Root cause/Blast radius/Guardrail)

  • Guardrail dipilih sesuai layer: UI, API, DB, monitoring

  • Payload invalid ditolak di backend (bukan “dibenerin diam-diam”)

  • Endpoint rawan retry punya idempotency key

  • Permission enforced di API/RLS, bukan sekadar UI hide

  • Minimal 1 test ditambah untuk kasus bug tersebut

  • Sentry capture + tag untuk error penting ditambah/ditinjau

  • Ada smoke steps 3 langkah setelah deploy

  • Catatan perubahan singkat dibuat biar gak lupa konteks

Kebiasaan “1 bug = 1 guardrail” bikin maintenance jauh lebih tenang, karena kamu berhenti ngulang hal yang sama dengan gaya berbeda. Release juga jadi makin safe karena sistem punya pagar, bukan cuma harapan.

Kalau tiap bug kamu tutup dengan guardrail, kamu bukan sekadar fixing. Kamu lagi membangun product yang bisa dipegang.

  • ALAWI

    CONTACT

( 00-09 )

CONTACT

Let’s talk about what you’re building
and how I can help.

  • Want to chat fast?

+62 817-7577-7778

  • Prefer email?

maarifalaawi@gmail.com

  • LinkedIn

maarifalawi

Start with a brief.

  • ALAWI

    CONTACT

( 00-09 )

CONTACT

Let’s talk about what you’re building
and how I can help.

  • Want to chat fast?

+62 817-7577-7778

  • Prefer email?

maarifalaawi@gmail.com

  • LinkedIn

maarifalawi

Start with a brief.

  • ALAWI

    CONTACT

( 00-09 )

CONTACT

Let’s talk about what you’re building
and how I can help.

  • Want to chat fast?

+62 817-7577-7778

  • Prefer email?

maarifalaawi@gmail.com

  • LinkedIn

maarifalawi

Start with a brief.

I build and maintain production-ready web apps. Clean UI, stable backend, calm releases.

I build and maintain production-ready web apps. Clean UI, stable backend, calm releases.

I build and maintain production-ready web apps. Clean UI, stable backend, calm releases.

Create a free website with Framer, the website builder loved by startups, designers and agencies.