🛑 1. The Problem First: "แอปพังแบบไม่มีร่องรอย"
ลองนึกถึงแอปที่จู่ๆ ก็ดับไปดื้อๆ หรือเวลา Error แล้ว User เห็น Stack Trace ยาวเหยียดที่บอกรหัสผ่าน Database:
// ❌ Naive Approach: เขียน Error Handling แบบเดาสุ่ม
app.get("/user", async (req, res) => {
try {
const user = await db.user.find(req.query.id);
res.json(user);
} catch (e) {
res.status(500).send(e.message); // 🌋 รั่วไหล! ถ้ามีบั๊ก User จะเห็นชื่อ Table หรือ Column ของเราทันที
}
});
ปัญหา: การจัดการ Error แบบกระจายตัว (Ad-hoc) ทำให้คุณควบคุมพฤติกรรมของระบบไม่ได้ และความปลอดภัยต่ำมาก เมื่อระบบต้องเจอ User จริงที่ส่ง "ค่าประหลาด" เข้ามา แอปของคุณจะ Error จนเดี้ยงเพราะไม่ได้ทำการตรวจสอบ (Validation) ข้อมูลอย่างถูกต้อง นี่คือจุดต่างระหว่างโปรเจกต์นักศึกษากับซอฟต์แวร์ที่ใช้งานจริงครับ
💡 2. Real-Life Analogy: การทำงานในโรงพยาบาล
- Validation (ด่านคัดกรอง): เหมือน "พยาบาลหน้าห้องฉุกเฉิน". ต้องเช็คความดันและอาการเบื้องต้น (Input Validation) ถ้าอาการปกติถึงจะส่งต่อให้หมอ ถ้าไม่ปกติก็ให้กลับบ้านไปก่อนเพื่อไม่ให้หมอ (Controller) ทำงานหนักเกินไป
- Global Error Handler (ผู้ดูแลตึก): เหมือน "ศูนย์สั่งการโรงพยาบาล". เมื่อเกิดเคสฉุกเฉิน (Error) หมอจะกดกริ่งเรียก (next(err)) แล้วศูนย์นี้จะตัดสินใจเองว่าจะส่งทีมไหนไปช่วย ไม่ให้หมอต้องมานั่งคุมทีมกู้ภัยเอง
- Security Headers (Helmet): เหมือนการ "ล็อกประตูและติดกล้องวงจรปิด". ป้องกันคนแปลกหน้าที่ชอบแอบเข้ามาดูข้อมูลในห้องยาของเรา
🚀 3. Execution Journey: เส้นทางข้อมูลที่ "สะอาด" และ "ปลอดภัย"
เราจะใช้ Zod สำหรับการคัดกรองข้อมูล และ Middleware สำหรับการจัดการ Error กลาง
🛠 Step-by-step:
- Schema Definition: นิยามรูปร่างข้อมูลที่ถูกต้องด้วย Zod
- Validation Middleware: สกัดกั้น Input ที่ "เน่า" ตั้งแต่วันแรกที่ขอบประตู API
- Execution: ส่งต่อข้อมูลที่สะอาดแล้วให้ Controller ทำงาน
- Centralized Catch: ถ้ามีพังตรงไหน ให้โยน Error ไปที่ "จุดรวมพล" (Global Handler) เพียงที่เดียว
// ✅ Best Practice: การคัดกรองข้อมูลระดับพรีเมียม
const userSchema = z.object({
email: z.string().email(),
age: z.number().min(18),
});
// 🛠 Middleware ที่จัดการ Error ครั้งเดียวจบ
app.use((err, req, res, next) => {
const status = err.status || 500;
res.status(status).json({
status: "error",
message: status === 500 ? "Internal Server Error" : err.message, // 🛠 ซ่อนรายละเอียดลึกๆ ไม่ให้ User เห็น
});
});
🪤 4. The Junior Trap: โรค "Try-Catch ท่วมแผ่นดิน"
จูเนียร์มักจะกลัว Error จนเขียน try-catch ครอบทุกบรรทัด:
// ❌ Junior Trap: โค้ดที่รกรุงรังและแก้ไขยาก
async function controller(req, res) {
try {
/* logic */
} catch (e) {
res.status(500).send("Help!");
} // 🌋 เขียนแบบเดิมซ้ำๆ 100 จุด
}
ระวัง: การเขียน try-catch ซ้ำซ้อนทำให้โค้ดอ่านยากมาก
✅ การแก้ไข: ใช้ Async Wrapper หรือส่ง next(err) ทุกครั้งที่เกิด Error เพื่อให้ระบบจัดการกลาง (Global Handler) ทำหน้าที่ของมันแทนครับ
⚖️ 5. The Why Matrix: Development vs Production Setup
| หัวข้อ | ช่วง Dev (เน้นเร็ว) | ช่วง Production (เน้นทน) |
|---|---|---|
| Error Handling | แสดง Stack Trace ไว้แก้บั๊ก | ซ่อนความลับ, Log ลง Cloud เท่านั้น |
| Validation | อาจจะข้ามไปก่อนได้ | ⚡⚡⚡ ต้องเป๊ะ 100% (ห้ามหลุด) |
| Logging | console.log รัวๆ | ใช้ Winston หรือ Pino (Structured Log) |
| Security | ไม่ค่อยห่วง | 🚀 ติดตั้ง Helmet และ Rate Limit เสมอ |
🎓 6. Senior Mindset Summary
การเป็น Senior คือการมองว่า "ความล้มเหลวคือสิ่งที่หลีกเลี่ยงไม่ได้ แต่การจัดการความล้มเหลวอย่างเป็นระบบคือสิ่งที่ฝึกฝนได้". อย่าพยายามทำให้โค้ดไม่มี Error แต่จงสร้างระบบที่ "รับมือกับ Error ได้อย่างมืออาชีพ" ครับ!
Mission Accomplished
You've reached the end of this module. Time to apply these senior mindsets to your real-world projects!