อย่ากลืนข้อผิดพลาดเงียบ ๆ
catch (e) {} สองบรรทัดนี้เขียนง่ายมาก และมันทำให้เทสต์ผ่านทันที แต่มันก็ทำให้อีกหกเดือนถัดมามีคนนั่งไล่หาสาเหตุที่ยอดเงินของลูกค้าหายไปเฉย ๆ โดยไม่มีร่องรอยใน log
ความต่างระหว่างการจัดการกับการซ่อน
การจัดการข้อผิดพลาด แปลว่าคุณรู้ว่ามันพลาดได้ และตัดสินไว้แล้วว่าจะทำอะไรต่อ ส่วนการซ่อนคือการทำเหมือนว่าไม่มีอะไรเกิดขึ้น สองอย่างนี้หน้าตาคล้ายกันมาก
// ซ่อน — ไม่มีใครรู้ว่ามันพัง
try {
await sendReceiptEmail(order);
} catch (e) {}
// จัดการ — รู้ว่ามันพังได้ และตัดสินว่าไม่ควรล้มทั้งคำสั่ง
try {
await sendReceiptEmail(order);
} catch (err) {
logger.warn('ส่งอีเมลใบเสร็จไม่สำเร็จ', { orderId: order.id, err });
await emailQueue.retryLater(order.id);
}แยกข้อผิดพลาดเป็นสองพวก
สิ่งที่ช่วยให้โค้ดสะอาดขึ้นมากคือการแยกว่า ข้อผิดพลาดนั้นคาดไว้แล้ว หรือเหนือความคาดหมาย
| ประเภท | ตัวอย่าง | ควรทำอย่างไร |
|---|---|---|
| คาดไว้แล้ว | ผู้ใช้กรอกอีเมลผิด รหัสส่วนลดหมดอายุ | จัดการตรงนั้น บอกผู้ใช้ด้วยภาษาคน |
| ชั่วคราว | ต่อฐานข้อมูลหลุด API ปลายทาง timeout | retry แบบ backoff แล้วค่อยยอมแพ้ |
| ไม่คาดคิด | null ในที่ที่ไม่ควร null ได้ | ปล่อยให้พังดัง ๆ แล้วแก้ที่ต้นเหตุ |
ข้อความ error ที่ดีต้องบอกสามอย่าง
- อะไรที่พยายามทำแล้วไม่สำเร็จ
- ทำไมถึงไม่สำเร็จ
- ผู้ใช้ควรทำอะไรต่อ
"เกิดข้อผิดพลาด" ตกทั้งสามข้อ ส่วน "อัปโหลดไฟล์ไม่สำเร็จ เพราะไฟล์ใหญ่เกิน 10 MB กรุณาย่อไฟล์แล้วลองใหม่" ครบทั้งสาม
แยก error ของระบบกับ error ของผู้ใช้
ผู้ใช้ไม่ควรเห็น stack trace และทีมดูแลไม่ควรเห็นแค่คำว่า "เกิดข้อผิดพลาด" แยกสองชั้นนี้ออกจากกัน แล้วผูกด้วย correlation id เดียวกัน เวลาผู้ใช้แจ้งมาพร้อมรหัส คุณจะหา log เจอทันที
สรุปสั้น ๆ คือ ข้อผิดพลาดไม่ใช่สิ่งที่ต้องซ่อน มันคือข้อมูลที่ระบบพยายามบอกอะไรบางอย่างกับเรา

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