Lompat ke konten utama
vourdev
Kembali ke Blog

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.

Gambar Cover Kenapa Senior Dev Selalu Baca Error Message Dulu, Bukan Langsung Tanya ChatGPT
Ditulis olehvourdev11 menit baca

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.

Perbandingan Pendekatan Debugging: Refleks AI vs Investigasi DeterministikRefleks AI (Tebakan)Copy-paste error mentah tanpafilterAI menebak tanpa tahu environment& stateMenerapkan solusi trial-and-errorMenambah technical debt baruInvestigasi DeterministikIsolasi userland frame dari stacktraceBentuk hipotesis berdasarkan stateaplikasiReproduksi bug secara lokalSolusi tepat sasaran ke root causeInvestigasi deterministik menghemat waktu eliminasi bug kompleks.
Perbandingan Pendekatan Debugging: Refleks AI vs Investigasi Deterministik

Membaca Anatomi Stack Trace: Bukan Tembok Teks, tapi Peta Koordinat

Tampilan debugging console dan stack trace pada terminal
Stack trace memberikan koordinat presisi masalah sebelum Anda berspekulasi. · Screenshot of Chrome DevTools console tab — Novem Linguae (CC BY-SA 4.0)

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:

typescript
// 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:

bash
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:

  1. Error Name & Message (`TypeError: Cannot read properties of null (reading 'user')`): Ini memberi tahu kita bahwa runtime mencoba membaca properti .user dari sebuah variabel bernilai null.
  2. Topmost Userland Frame (`/app/src/services/userService.ts:27:37`): Ini adalah koordinat absolut. Jangan buang waktu membaca baris di dalam node_modules/express atau node:internal. Masalah terjadi tepat di baris 27 file userService.ts, karakter ke-37.
  3. Analisis Variabel: Di baris 27, ada ekspresi membership.user.role. Jika .user dibaca dari null, artinya variabel membership bernilai null.
  4. Root Cause: prisma.membership.findFirst tidak menemukan baris yang cocok dan mengembalikan null, 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:

  1. Aplikasi memunculkan Connection pool timeout error.
  2. Developer menanyakan ke AI: "Gimana cara benerin pool timeout di PostgreSQL?"
  3. AI memberikan konfigurasi: "Naikkan `max_connections` dan `pool_timeout` di config Anda."
  4. Developer menaikkan angka pool dari 10 ke 100.
  5. 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.

typescript
// 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:

  1. Symptom Identification (Apa yang rusak di permukaan?)
  2. 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.

typescript
// 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:

typescript
// 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

Developer menganalisis error log di IDE bersama AI assistant
Gunakan AI setelah Anda memiliki hipotesis konkret dan konteks yang terisolasi. · Rubaitul Azad (Unsplash)

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.

Alur Interaksi Debugging yang Efektif dengan AIDeveloperLocal Runtime / LogsAI Assistant (LLM)1. 1. Jalankan kode & baca stack trace2. 2. Temukan file, baris, & variable st…3. 4. Kirim potongan kode terisolasi + hipotesis4. 5. Dapatkan alternatif implementasi / syntax5. 6. Verifikasi & tulis automated test
Alur Interaksi Debugging yang Efektif dengan AI

Kapan Waktu yang Tepat Menggunakan AI:

  1. RegEx Parsing & Optimization: Menemukan kenapa regular expression mengalami catastrophic backtracking.
  2. Esoteric System / Compiler Errors: Menerjemahkan error cryptic dari linker C++, rustc borrow checker, atau konfigurasi Webpack/Vite yang kompleks.
  3. 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:

markdown
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:

Debug Decision Tree Framework1. Baca Error & Stack TraceCari topmost userland frame2. Reproduksi MasalahJalankan secara lokal / isolated test3. Cek State & AsumsiGunakan debugger / loggerBisa Diisolasi Sendiri?Evaluasi kompleksitasFix & Tambah Unit TestVerifikasi solusiGunakan AI TerarahPrompt dengan konteks & hipotesisKoordinat ketemuBisa direproduksiAnalisis dataYa, akar masalah jelasTidak, butuh referensi syntax/libraryTerapkan rekomendasi
Debug Decision Tree Framework

Panduan Eksekusi 5 Langkah:

  1. 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.

  1. 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.

  1. Langkah 3: Rumuskan Hipotesis Tunggal

Tuliskan satu kalimat pernyataan: "Fungsi ini crash karena variabel X bernilai undefined ketika user belum memverifikasi email."

  1. 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).

  1. 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