อย่ากลืนข้อผิดพลาดเงียบ ๆ

catch (e) {} สองบรรทัดนี้เขียนง่ายมาก และมันทำให้เทสต์ผ่านทันที แต่มันก็ทำให้อีกหกเดือนถัดมามีคนนั่งไล่หาสาเหตุที่ยอดเงินของลูกค้าหายไปเฉย ๆ โดยไม่มีร่องรอยใน log

ความต่างระหว่างการจัดการกับการซ่อน

การจัดการข้อผิดพลาด แปลว่าคุณรู้ว่ามันพลาดได้ และตัดสินไว้แล้วว่าจะทำอะไรต่อ ส่วนการซ่อนคือการทำเหมือนว่าไม่มีอะไรเกิดขึ้น สองอย่างนี้หน้าตาคล้ายกันมาก

typescript
// ซ่อน — ไม่มีใครรู้ว่ามันพัง
try {
  await sendReceiptEmail(order);
} catch (e) {}

// จัดการ — รู้ว่ามันพังได้ และตัดสินว่าไม่ควรล้มทั้งคำสั่ง
try {
  await sendReceiptEmail(order);
} catch (err) {
  logger.warn('ส่งอีเมลใบเสร็จไม่สำเร็จ', { orderId: order.id, err });
  await emailQueue.retryLater(order.id);
}
ต่างกันที่เจตนา

แยกข้อผิดพลาดเป็นสองพวก

สิ่งที่ช่วยให้โค้ดสะอาดขึ้นมากคือการแยกว่า ข้อผิดพลาดนั้นคาดไว้แล้ว หรือเหนือความคาดหมาย

ประเภทตัวอย่างควรทำอย่างไร
คาดไว้แล้วผู้ใช้กรอกอีเมลผิด รหัสส่วนลดหมดอายุจัดการตรงนั้น บอกผู้ใช้ด้วยภาษาคน
ชั่วคราวต่อฐานข้อมูลหลุด API ปลายทาง timeoutretry แบบ backoff แล้วค่อยยอมแพ้
ไม่คาดคิดnull ในที่ที่ไม่ควร null ได้ปล่อยให้พังดัง ๆ แล้วแก้ที่ต้นเหตุ

ข้อความ error ที่ดีต้องบอกสามอย่าง

  1. อะไรที่พยายามทำแล้วไม่สำเร็จ
  2. ทำไมถึงไม่สำเร็จ
  3. ผู้ใช้ควรทำอะไรต่อ
"เกิดข้อผิดพลาด" ตกทั้งสามข้อ ส่วน "อัปโหลดไฟล์ไม่สำเร็จ เพราะไฟล์ใหญ่เกิน 10 MB กรุณาย่อไฟล์แล้วลองใหม่" ครบทั้งสาม

แยก error ของระบบกับ error ของผู้ใช้

ผู้ใช้ไม่ควรเห็น stack trace และทีมดูแลไม่ควรเห็นแค่คำว่า "เกิดข้อผิดพลาด" แยกสองชั้นนี้ออกจากกัน แล้วผูกด้วย correlation id เดียวกัน เวลาผู้ใช้แจ้งมาพร้อมรหัส คุณจะหา log เจอทันที

สรุปสั้น ๆ คือ ข้อผิดพลาดไม่ใช่สิ่งที่ต้องซ่อน มันคือข้อมูลที่ระบบพยายามบอกอะไรบางอย่างกับเรา

ความคิดเห็น

กำลังโหลดความคิดเห็น