Dev Notes
Kenapa Senior Dev Selalu Baca Error Message Dulu, Bukan Langsung Tanya ChatGPT
Bedah tuntas kebiasaan debugging senior engineer: cara membaca stack trace, root cause analysis, hingga Debug Decision Tree praktis.

Pernahkah Anda melihat developer yang begitu terminalnya memunculkan tulisan merah, tanpa membaca satu kata pun langsung menekan Ctrl+A, Ctrl+C, lalu melempar seluruh log setebal 200 baris ke ChatGPT dengan prompt: "Kenapa ini error dan tolong perbaiki"?
Jujur saja, sebagian besar dari kita pernah berada di fase itu. AI generatif memberi ilusi jalan pintas: lempar masalahnya ke mesin, tunggu solusi instan, salin kodenya kembali, lalu berdoa agar aplikasi berjalan normal.
Namun, perhatikan bagaimana engineer level senior atau staff menangani situasi yang sama. Saat error meledak di terminal atau monitoring alert berbunyi di Slack pada jam 2 pagi, hal pertama yang mereka lakukan bukanlah membuka tab AI. Mereka mendekatkan mata ke layar, membaca baris demi baris teks merah tersebut, mengabaikan tumpukan framework internals, dan langsung menunjuk satu file serta baris spesifik.
Perbedaan ini bukan soal senior "kolot" atau anti-teknologi. Ini adalah perbedaan fundamental antara debugging deterministik berbasis investigasi dengan debugging stokastik berbasis tebakan.
Membaca Anatomi Stack Trace: Bukan Tembok Teks, tapi Peta Koordinat

Banyak developer pemula menganggap error message dan stack trace sebagai "dinding teks yang menakutkan". Padahal, runtime (baik itu V8 di Node.js, runtime Go, JVM, maupun Python VM) sudah bersusah payah mendokumentasikan detik-detik terakhir sebelum program Anda mengalami crash.
Mari kita bedah contoh nyata error di backend TypeScript yang menggunakan Prisma ORM dan Express/NestJS:
// Contoh kode backend yang mengandung subtle bug
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
interface UpdateUserPayload {
organizationId: string;
userId: string;
metadata: {
roles: string[];
settings?: Record<string, unknown>;
};
}
export async function updateUserRole(payload: UpdateUserPayload) {
const { organizationId, userId, metadata } = payload;
// Query database untuk memastikan user ada di organisasi tersebut
const membership = await prisma.membership.findFirst({
where: {
userId: userId,
organizationId: organizationId,
},
include: {
user: true,
},
});
// BUG: Mengakses nested property tanpa validasi null-check pada membership
const targetUserRole = membership.user.role;
if (targetUserRole === 'SUPERADMIN') {
throw new Error('Cannot downgrade SUPERADMIN role directly');
}
return await prisma.membership.update({
where: { id: membership.id },
data: {
role: metadata.roles[0],
},
});
}Saat endpoint ini dipanggil dengan payload di mana kombinasi userId dan organizationId tidak cocok, terminal Anda akan memuntahkan stack trace seperti ini:
TypeError: Cannot read properties of null (reading 'user')
at updateUserRole (/app/src/services/userService.ts:27:37)
at processTicksAndRejections (node:internal/process/task_queues:95:5)
at async handleUserUpdate (/app/src/controllers/userController.ts:54:20)
at async /app/node_modules/express/lib/router/layer.js:95:5
at async trim_prefix (/app/node_modules/express/lib/router/index.js:328:13)Jika Anda membaca log tersebut dengan teknik triage yang benar:
- Error Name & Message (`TypeError: Cannot read properties of null (reading 'user')`): Ini memberi tahu kita bahwa runtime mencoba membaca properti
.userdari sebuah variabel bernilainull. - Topmost Userland Frame (`/app/src/services/userService.ts:27:37`): Ini adalah koordinat absolut. Jangan buang waktu membaca baris di dalam
node_modules/expressataunode:internal. Masalah terjadi tepat di baris 27 fileuserService.ts, karakter ke-37. - Analisis Variabel: Di baris 27, ada ekspresi
membership.user.role. Jika.userdibaca darinull, artinya variabelmembershipbernilainull. - Root Cause:
prisma.membership.findFirsttidak menemukan baris yang cocok dan mengembalikannull, namun kode langsung mengasumsikan datanya selalu ada (happy-path assumption).
Senior engineer membaca pola ini dalam waktu 5 detik. Jika Anda langsung melempar stack trace tersebut ke AI tanpa menyertakan skema Prisma dan relasinya, AI kemungkinan besar akan menyarankan solusi superfisial seperti membership?.user?.role tanpa mengatasi fakta bahwa membership.id di baris update juga akan meledak karena membership bernilai null.
Mengapa Langsung Lempar ke AI Sering Bikin Nyasar
Kelemahan utama LLM (Large Language Model) adalah ketiadaan runtime context. AI tidak tahu:
- State memori saat aplikasi sedang berjalan.
- Data aktual yang ada di database staging atau production Anda.
- Konfigurasi environment variables, network latency, atau concurrency race conditions.
Ketika Anda memberikan log error mentah tanpa konteks arsitektural, AI bekerja dengan probabilitas token kata berikutnya. AI akan menebak skenario paling umum di internet yang menghasilkan error serupa, bukan skenario spesifik yang sedang terjadi di sistem Anda.
Anti-Pattern: The Blind "Fix-and-Pray" Cycle
Mari lihat skenario yang sering terjadi ketika developer bergantung 100% pada AI untuk debugging:
- Aplikasi memunculkan
Connection pool timeout error. - Developer menanyakan ke AI: "Gimana cara benerin pool timeout di PostgreSQL?"
- AI memberikan konfigurasi: "Naikkan `max_connections` dan `pool_timeout` di config Anda."
- Developer menaikkan angka pool dari 10 ke 100.
- Error hilang sementara di lokal, tetapi ketika deploy ke server, database mengalami out-of-memory (OOM) dan crash total.
Masalah aslinya bukan ukuran connection pool yang kurang, melainkan adanya connection leak akibat transaksi database yang tidak pernah di-commit atau di-rollback pada blok catch.
// Anti-pattern connection leak yang tidak bisa dideteksi AI tanpa full codebase
import { Pool } from 'pg';
const pool = new Pool({ max: 20 });
export async function processPayment(orderId: string) {
const client = await pool.connect();
try {
await client.query('BEGIN');
await client.query('UPDATE orders SET status = $1 WHERE id = $2', ['PAID', orderId]);
// External API call yang berpotensi timeout
const paymentGatewayResponse = await callExternalGateway(orderId);
if (!paymentGatewayResponse.success) {
throw new Error('Payment gateway rejected transaction');
}
await client.query('COMMIT');
} catch (error) {
// GOTCHA: Lupa client.query('ROLLBACK') dan lupa client.release() di catch!
// Akibatnya koneksi menggantung selamanya sampai pool habis.
throw error;
} finally {
// FIX YANG BENAR: Pastikan koneksi selalu dilepas kembali ke pool
client.release();
}
}
async function callExternalGateway(orderId: string) {
// Simulasi network call
return { success: false };
}Tips: AI tidak bisa melihat koneksi database yang bocor di balik pemanggilan API pihak ketiga hanya dari teks log error. Anda sendiri yang harus menyusuri lifecycle resource tersebut.
Root Cause Analysis: Membedakan Gejala dari Penyakit Utama
Senior engineer membagi proses pemecahan masalah ke dalam dua tahap tegas:
- Symptom Identification (Apa yang rusak di permukaan?)
- Root Cause Analysis (Kenapa kondisi tersebut bisa terjadi?)
Mari kita telaah kasus yang lebih rumit: sebuah race condition pada sistem e-commerce berbasis concurrent inventory.
// Implementasi rentan race condition
import { db } from './database';
export async function deductInventory(productId: string, quantityToDeduct: number) {
// 1. Ambil stok saat ini
const product = await db.product.findUnique({
where: { id: productId },
});
if (!product) {
throw new Error('Product not found');
}
// 2. Validasi ketersediaan stok
if (product.stock < quantityToDeduct) {
throw new Error(`Insufficient stock. Current: ${product.stock}, requested: ${quantityToDeduct}`);
}
// 3. Simulasikan latency network/processing
await new Promise((resolve) => setTimeout(resolve, 50));
// 4. Update stok baru
const updatedProduct = await db.product.update({
where: { id: productId },
data: {
stock: product.stock - quantityToDeduct,
},
});
return updatedProduct;
}Ketika dua request masuk secara simultan untuk produk dengan stok tersisa 1, kedua request akan melewati validasi product.stock < quantityToDeduct secara bersamaan. Akibatnya, stok akhir menjadi -1 atau database melempar constraint violation error.
Jika Anda hanya membaca pesan error permukaan: Database constraint violation on column 'stock', solusi instan yang mungkin dipikirkan adalah menghapus constraint database tersebut. Itu menyelesaikan gejalanya, tetapi memperparah penyakitnya (stok minus di dunia nyata).
Solusi level senior adalah menerapkan Atomic Updates atau Pessimistic / Optimistic Locking:
// Solusi deterministik: Atomic update dengan conditional where clause
export async function deductInventoryAtomic(productId: string, quantityToDeduct: number) {
try {
const result = await db.product.update({
where: {
id: productId,
// Atomic condition: Hanya update jika stok di database masih mencukupi
stock: {
gte: quantityToDeduct,
},
},
data: {
stock: {
decrement: quantityToDeduct,
},
},
});
return result;
} catch (error: any) {
// Tangani error spesifik jika record tidak cocok dengan kondisi 'gte'
if (error.code === 'P2025') {
throw new Error('Stok barang sudah habis atau berubah saat transaksi diproses.');
}
throw error;
}
}Penjelasan teknis:
- Kita memindahkan logika pengecekan stok langsung ke level query database (
stock: { gte: quantityToDeduct }). - Database engine (seperti PostgreSQL) menjamin operasi row-level locking secara internal saat mengeksekusi update atomik.
- Dengan cara ini, tidak ada celah waktu (time-of-check to time-of-use) yang bisa disusupi oleh request paralel lain.
Kapan dan Bagaimana Memanfaatkan AI Secara Benar

Apakah ini berarti Anda sama sekali tidak boleh memakai AI saat debugging? Tentu saja tidak. AI adalah akselerator yang luar biasa jika Anda sudah tahu apa yang sedang Anda cari.
Gunakan AI sebagai rubber duck debugger berkecepatan tinggi, bukan sebagai pengganti nalar investigasi Anda.
Kapan Waktu yang Tepat Menggunakan AI:
- RegEx Parsing & Optimization: Menemukan kenapa regular expression mengalami catastrophic backtracking.
- Esoteric System / Compiler Errors: Menerjemahkan error cryptic dari linker C++, rustc borrow checker, atau konfigurasi Webpack/Vite yang kompleks.
- Brainstorming Edge Cases: Menguji fungsi yang sudah kita buat dengan meminta: "Berikan 5 kemungkinan input ekstrem yang bisa menggagalkan fungsi TypeScript ini."
Template Prompt Debugging yang Efektif
Jangan kirim prompt malas: Error ini kenapa ya? [paste log 100 baris].
Gunakan struktur prompt presisi tinggi:
Saya sedang men-debug aplikasi backend Node.js (TypeScript) dengan PostgreSQL via Prisma.
Konteks Masalah:
- Fungsi `deductInventory` mengalami `RecordNotFound` (P2025) saat diuji dengan load test 50 concurrent requests.
- File terkait: `src/services/inventoryService.ts` baris 42.
Hipotesis Saya:
Terjadi race condition antara pembacaan stok awal dan query update, sehingga query update kedua gagal memenuhi klausa WHERE.
Berikut potongan kode terisolasi:
[Paste fungsi 20-30 baris yang relevan saja]
Pertanyaan:
1. Apakah ada alternatif implementasi menggunakan Prisma transaction dengan optimistic concurrency control?
2. Bagaimana cara menulis integration test menggunakan Vitest untuk mereproduksi race condition ini secara konsisten?Dengan prompt seperti ini, AI tidak perlu menebak arsitektur Anda. AI langsung bertindak sebagai expert consultant yang memberikan solusi terarah.
Framework: Debug Decision Tree untuk Tim Engineering
Untuk menghentikan kebiasaan asal tebak dan asal tanya AI di tim Anda, terapkan alur pikir sistematis berikut setiap kali menemui bug:
Panduan Eksekusi 5 Langkah:
- Langkah 1: Identifikasi Koordinat (Stack Trace Filtering)
Buka file yang berada di stack trace paling atas milik codebase Anda sendiri. Abaikan semua trace dari internal node_modules atau runtime system kecuali Anda memang sedang menulis custom framework engine.
- Langkah 2: Observasi Nilai Variabel Aktual
Jangan berasumsi variabel berisi data yang Anda harapkan. Pasang breakpoint menggunakan VS Code Debugger atau gunakan console.log({ variable }) terstruktur untuk melihat tipe data sebenarnya saat crash terjadi.
- Langkah 3: Rumuskan Hipotesis Tunggal
Tuliskan satu kalimat pernyataan: "Fungsi ini crash karena variabel X bernilai undefined ketika user belum memverifikasi email."
- Langkah 4: Tulis Test yang Gagal (Failing Test Case)
Sebelum memperbaiki kode, buat satu unit test atau integration test yang mereplikasi skenario error tersebut. Test ini harus merah (gagal).
- Langkah 5: Terapkan Solusi & Pastikan Test Menjadi Hijau
Perbaiki kode Anda. Jika test yang tadi gagal sekarang berubah menjadi hijau (berhasil), Anda baru saja membuktikan bahwa solusi Anda tepat sasaran dan mencegah regresi di masa depan.
Kesimpulan & Action Items
AI adalah alat bantu produktivitas terhebat yang pernah diciptakan untuk software engineer. Tetapi AI adalah kopilot, bukan nakhoda. Ketika Anda menyerahkan seluruh proses berpikir dan penalaran logis kepada AI tanpa memahami akar masalahnya:
- Anda tidak sedang belajar menjadi engineer yang lebih baik.
- Anda sedang menimbun bom waktu di codebase produksi.
- Anda kehilangan kemampuan membaca intuisi sistem saat terjadi incident kritis di mana AI tidak bisa membantu.
Action Items Mulai Hari Ini:
- [ ] Terapkan Aturan 10 Menit: Ketika menemukan bug, luangkan waktu 10 menit untuk membaca stack trace, memeriksa log, dan merumuskan hipotesis sendiri sebelum membuka antarmuka AI.
- [ ] Pelajari Debugger Native: Tinggalkan kebiasaan hanya mengandalkan print log acak. Pelajari cara menggunakan interactive breakpoint di IDE Anda (VS Code, WebStorm, dsb).
- [ ] Strukturkan Prompt AI Anda: Selalu sertakan konteks environment, potongan kode minimal yang terisolasi, dan hipotesis Anda saat meminta bantuan LLM.
- [ ] Kunci Bug dengan Automated Test: Jangan anggap bug sudah selesai diperbaiki sebelum ada unit/integration test yang memvalidasi perbaikan tersebut.
Butuh penyesuaian khusus untuk project Anda?
Jika situasi operasional atau arsitektur sistem bisnis Anda membutuhkan solusi kustom, diskusikan langsung bersama tim engineer kami. Anda juga bisa melihat rincian layanan vour.dev atau menghitung estimasi biaya project lebih dulu.
Mulai Project