Error Handling yang Baik — Clean Code

Error handling yang baik membuat aplikasi robust tanpa menyembunyikan bug. Prinsipnya: fail fast, fail loud — lebih baik error yang jelas daripada bug silent ya

Error handling yang baik membuat aplikasi robust tanpa menyembunyikan bug. Prinsipnya: fail fast, fail loud — lebih baik error yang jelas daripada bug silent yang muncul 3 hari kemudian.

Sebelum — error handling yang buruk:

// BURUK 1: catch kosong — menyembunyikan bug!
try {
  const data = JSON.parse(response);
  processData(data);
} catch (e) {
  // tidak melakukan apa-apa...
  // bug tersembunyi, data tidak diproses, user tidak tahu
}

// BURUK 2: catch terlalu luas
try {
  const user = await fetchUser(id);
  const orders = await fetchOrders(user.id);
  const report = generateReport(orders);
  await sendEmail(user.email, report);
} catch (e) {
  console.log("Something went wrong"); // apa yang salah? di mana?
}

// BURUK 3: return null tanpa alasan jelas
function findUser(id) {
  const user = db.users.find(u => u.id === id);
  return user || null; // caller tidak tahu kenapa null
}

Sesudah — error handling yang baik:

// BERSIH 1: tangani error secara spesifik
try {
  const data = JSON.parse(response);
  processData(data);
} catch (error) {
  if (error instanceof SyntaxError) {
    console.error("Response bukan JSON valid:", response);
    showUserError("Data tidak bisa diproses");
  } else {
    throw error; // re-throw error yang tidak dikenal
  }
}

// BERSIH 2: error di level yang tepat
async function loadDashboard(userId) {
  const user = await fetchUser(userId);    // biarkan throw jika gagal
  const orders = await fetchOrders(user.id); // biarkan throw jika gagal

  try {
    await sendWelcomeEmail(user.email);
  } catch (emailError) {
    // Email gagal tidak fatal — log dan lanjutkan
    console.warn("Welcome email gagal:", emailError.message);
  }

  return { user, orders };
}

Custom Error classes:

class ValidationError extends Error {
  constructor(field, message) {
    super(message);
    this.name = "ValidationError";
    this.field = field;
  }
}

class NotFoundError extends Error {
  constructor(resource, id) {
    super(`${resource} with id ${id} not found`);
    this.name = "NotFoundError";
    this.resource = resource;
    this.id = id;
  }
}

function getUser(id) {
  const user = db.users.find(u => u.id === id);
  if (!user) throw new NotFoundError("User", id);
  return user;
}

// Caller bisa handle berdasarkan tipe
try {
  const user = getUser(123);
} catch (error) {
  if (error instanceof NotFoundError) {
    showNotFound(error.message);
  } else if (error instanceof ValidationError) {
    showValidationError(error.field, error.message);
  } else {
    throw error; // unexpected error — biarkan bubble up
  }
}

Guard clauses — fail fast di awal fungsi:

// BURUK: nested validation
function createOrder(user, items, address) {
  if (user) {
    if (items && items.length > 0) {
      if (address) {
        // ... actual logic buried deep inside
      } else { throw new Error("No address"); }
    } else { throw new Error("No items"); }
  } else { throw new Error("No user"); }
}

// BERSIH: guard clauses
function createOrder(user, items, address) {
  if (!user) throw new ValidationError("user", "User wajib diisi");
  if (!items?.length) throw new ValidationError("items", "Items wajib diisi");
  if (!address) throw new ValidationError("address", "Alamat wajib diisi");

  // Happy path — kode utama tidak ter-nest
  const order = { user, items, address, total: calculateTotal(items) };
  return db.orders.insert(order);
}

Aturan error handling:

  1. Jangan catch error yang tidak kamu tahu cara handle-nya — biarkan bubble up
  2. Jangan catch kosong — minimal log error-nya
  3. Gunakan custom Error untuk error yang spesifik ke domain kamu
  4. Fail fast — validasi di awal, jangan di tengah-tengah logic
  5. Error message harus actionable — "Email format invalid" bukan "Error occurred"

🎭 Analogi sehari-hari

Error handling itu kayak alarm asap di rumah. Alarm yang baik = bunyi keras pas ada api, jelas lokasinya, action-able ("Asap di dapur, cek kompor"). Alarm jelek = bunyi pas masak biasa (false positive), atau gak bunyi pas kebakaran beneran (silent failure). Silent catch (try {} catch (e) {}) = matiin alarm asap, "biar gak ribut" — pas beneran kebakaran kamu gak tau, semuanya keburu hangus. Generic error ("Something went wrong") = alarm yang cuma bunyi tanpa info — kamu tau ada masalah tapi gak tau di mana, mau action apa. Custom error ("Validasi email gagal di field user.email pada step register") = alarm yang nunjuk tepat — fix dalam 30 detik.

⚠️ Jebakan yang sering ditemui

Tipe Error yang Harus Beda Handling

class ValidationError extends Error {
  constructor(field, message) {
    super(message)
    this.field = field
    this.name = "ValidationError"
  }
}

class NotFoundError extends Error {
  constructor(resource, id) {
    super(`${resource} dengan ID ${id} tidak ditemukan`)
    this.name = "NotFoundError"
  }
}

class AuthError extends Error {
  constructor(message = "Tidak terotorisasi") {
    super(message)
    this.name = "AuthError"
  }
}

🎯 Catch error atau biarin bubble up?

  • Bisa recover dengan action konkret (retry, fallback, default value) → catch + handle
  • User butuh tahu pesan ramah (form error, network down) → catch + tampilkan
  • Error developer (bug code) → biarin bubble up ke ErrorBoundary global
  • Programmatic error (TypeError, ReferenceError) → biarin bubble up
  • Unknown error type → log + re-throw (jangan silent swallow)
  • Async function tanpa await → wrap dalam Promise.catch atau wajib await di try/catch

Aturan: catch harus PRODUKTIF (handle, log, transform). Catch yang cuma ngumpet error = pasti bug nanti.

TL;DR: Error handling = fail fast + fail loud. Pakai try/catch cuma kalau bisa handle konkret (retry, fallback, message). Custom Error class per domain (ValidationError, NotFoundError) bedain handling. JANGAN silent catch (catch (e) {}) — minimum log. Guard clauses (validate di top fungsi) > nested if. Error message actionable (sebut field/lokasi/cara fix). Async error WAJIB await di try/catch atau .catch().