Missing Error Handling
ในการพัฒนา service ในปัจจุบัน เชื่อว่าหลายคนก็จะต้องมีการเชื่อมต่อไปยัง Service อื่นๆ เช่น อาจจะมีการ call ไปยัง payment gateway, line api เป็นต้น
โดยในการเชื่อมต่อนั้น ผู้ให้บริการ Service นั้นๆ ก็จะให้ข้อมูลรายละเอียดของ API ที่ใช้ในการเชื่อมต่อไว้ใน API Document ซึ่งใน API เหล่านั้นก็มักจะ มีรายละเอียดบอกชัดเจนว่า API นี้ ถ้ายิงมาสำเร็จจะได้ผลอย่างไร ถ้ายิงมาผิด จะส่งผลอย่างไร ซึ่งตรงนี้ทางผู้ให้บริการ API จะต้องอธิบายข้อมูลมาอย่างชัดเจน
ตัวอย่างเช่น API ของธนาคารไทยพาณิชย์ ก็จะให้ข้อมูล ว่า Status Code, Business Code แต่ละตัว มีความหมายอย่างไร
อย่างไรก็ตามในการเขียน เชื่อมต่อจริงๆนั้น เราจะจบ เพียงแต่ตอบเฉพาะ API ที่เขียนมาไม่ได้ เพราะ ในกระบวนการเชื่อมต่อจริงๆ แล้ว มีรายละเอียด ที่ต้องจัดการอยู่ ซึ่งโดยส่วนใหญ่ API Document จะมีสมมติฐานว่า เราสามารถเชื่อมต่อกับ Service ปลายทางได้อยู่แล้ว จึงไม่บอก Error เกี่ยวกับการเชื่อมต่อระบบไว้
การ Handle ปัญหา Connection ทั่วไป
เชื่อว่าหลายคนที่ Dev มาจนถึงจุดนี้จะคุ้นเคยกับการทำ Basic Troubleshooting เบื้องต้น เช่น การ Ping หรือ การเช็ค Port ว่าเปิดหรือไม่ หรือติด Firewall
นั่นคือ สิ่งที่เราจำเป็นต้องเตรียมไว้ครับ ซึ่งถ้าเรายกกรณีของ REST API เข้ามา เราก็จะพบว่า มีส่วนที่เราสามารถดูได้ ตาม Layer ต่างๆ แบบรวดรัด ดังนี้
ในปัจจุบันเรามักจะข้ามเรื่อง Physical กันไป เนื่องจากส่วนใหญ่ก็ไปอยู่ใน VM , Container หรือ Cloud กัน
ในส่วนของ ระดับของ TCP /IP จะมีส่วนที่สำคัญที่ควรจะ handle ดังนี้ครับ
- hostname ของปลายทางผิด มีบางครั้งที่เราอาจจะพิมพ์ชื่อเครื่องผิด ทำให้ระบบเวลาจะเชื่อมต่อหาเครื่องไม่เจอ เครื่องก็จะส่ง Error ออกมาว่า
ENOTFOUND
แล้วเราก็ไป stack overflow กันว่าENOTFOUND
คืออะไร ก็ handle ดีๆ ไปเลยครับ จะได้ไม่ต้องเสียเวลา google กันหลายรอบ - Connection Refused เมื่อเราต่อไปที่ปลายทาง แล้ว Port ไม่ได้เปิด ส่วนใหญ่จะเป็นเพราะ Service ยังไม่เปิด หรือ ไปผิด port และส่วนน้อยจะเป็นเรื่องของ Firewall อันนี้ Error Code เรามักจะเจอ
ECONNREFUSED
- Connection Timed out เมื่อเราทำการต่อไปที่ปลายทาง แต่ปลายทางไม่ตอบ ภายในระยะเวลาหนึ่ง เช่น 30 วินาที เราก็จะได้รับ
ETIMEOUT
กลับมา เวลาดูต้องไล่ connection กันว่า ใคร block ใคร - Connection Reset เป็นสถานการณ์ที่ เราต่อใช้งานระบบได้แล้ว ระหว่างทำๆ อยู่ โดยตัดสาย… เหตุการณ์นี้มักจะเกิดกับ endpoint ที่มีการทำงานนานๆ หรือพวกที่เป็น Persistent Connection
กลุ่มด้านบนถือเป็นกลุ่มหลัก ที่ควรจะทำ message handler ไว้ดีๆ เพราะจะช่วยให้ตีความปัญหาได้รวดเร็ว
อย่างไรก็ตามอาจจะมี Message ตัวอื่นๆ ที่สามารถเตรียมเป็น Standard Message ไว้ได้ เช่น
- Abort Operation (
ECONNABORTED
) - Network Unreachable (
ENETUNREACH
) - Network Down (
ENETDOWN
) - Host Down (
EHOSTDOWN
)
Handle ที่ SSL
ในปัจจุบันแทบจะเรียกว่าหลายที่ก็บีบคอให้เราใช้ SSL อยู่แล้ว และหลายครั้งที่เมื่อเราปล่อยผ่านเรื่องนี้ เวลาเจอ error ข้อมูลจะไม่เพียงพอต่อการพิจารณา ว่าเกิดอะไรขึ้น ดังนั้น Client SSL Error ควรจะ handle ด้วย
ซึ่งในกรณีของ NodeJS ก็จะมีรายการ Error ตาม Link
Handle ระดับ HTTP Level
แม้หลายคนจะมองว่า REST กับ HTTP มันก็อันเดียวกัน อย่างไรก็ตาม มันจะมีพฤติกรรมบางอย่างที่ตัว HTTP Provider หรือ Application Server จัดการให้เราเองโดยอัติโนมัติ และบางอันก็จะมีความสับสนกับ REST Definition ของ เราเองด้วย
- Path not found (404) — กรณีนี้น่าจะเป็นตัวที่น่าปวดหัวที่สุด เพราะหลายครั้งมันจะสับสนกับ REST Response 404 (Resource not found) ซึ่งถ้าผู้ให้บริการจัดการมาดี ให้ข้อมูลมาครบว่า เคสนี้ ข้อมูลไม่มี เคสนี้ path ผิด ก็จะช่วยได้เยอะ
- Payload too Large (413) — ปกติแล้ว Server จะมีการป้องกันตัวเองในการรับ input จาก client อย่างกรณีนี้ถ้าเรารับ input ใหญ่เกิน ที่กำหนด มันจะคืน code นี้ออกมา และหลายครั้งที่ เราก็เขียน Code ไม่ค่อยดี ส่ง code นี้ไปให้ลูกค้าตรงๆ โดยไม่ทำอะไร
- Internal Server Error (50x) — แน่นอนว่า ถ้าเราทำ service ให้บริการ คนอื่นด้วย การแสดงผล 500 น่าจะเป็นความอับอายในระดับหนึ่ง แต่ เราจำเป็นต้อง classify error ตัวนี้ให้ดี และพิจารณาว่า จะแสดงให้ user เห็นแค่ไหน จะแสดงใน log แค่ไหน เพื่อ balance ไม่ได้ user ตกใจ ขณะที่เรายังสามารถได้ข้อมูลมาแก้ปัญหาได้
สิ่งที่มักพบบ่อยในการจัดการ error
ปัญหาที่สำคัญที่สุดที่มักจะเจอคือ การลืม handle error ในระดับ connection level รวมถึง ssl
หลายคนอาจจะสงสัยว่า ทำไมมันถึงเป็นปัญหา เพราะ ปกติก็น่าจะใช้ handling แบบ one size fit all ได้
อย่างไรก็ตามจุดสำคัญคือ ในบรรดา error ที่เกิดขึ้นนั้น จะมีเพราะ ระดับ HTTP และ REST เท่านั้น ที่จะมี status code
ซึ่งเมื่อพิจารณาจาก API Doc และ หลายคนจะลืมไปว่า status code ไม่ได้มี ใน ทุกเคส หรือ บางครั้ง Response Error ก็อาจจะไม่ใช่ตัวที่คาดหวัง เช่น
เมื่อเราใช้ Axios เราจะมี AxiosError เมื่อมี error ในการเชื่อมต่อ ซึ่งจะมี element เช่น
console.error(err.response.status);
console.error(err.response.statusText);
console.error(err.response.data);
แต่เมื่อใดก็ตามที่ เจอ connection error หรือ ssl ค่า Error จะเป็นเพียง Error Object ธรรมดา ซึ่งไม่มี response ทำให้ เกิด uncaught error เกิดขึ้น
แม้เราจะใช้ Safe Navigation ให้รอดจาก uncaught error ได้ เราก็ไม่มีข้อมูลให้ดูอยู่ดี
วิธีหนึ่งที่สามารถทำได้ คือ classify Error กับ AxiosError ออก แล้วแยกจัดการไป เพื่อที่จะได้ข้อมูลที่ครบถ้วน
Business Error กับ Technical Error
ท้ายที่สุดแม้ว่า ปกติ เราจะสามารถพิมพ์ error message ได้ถูกต้อง แต่บางครั้ง error message นั้น ก็ลึกเกินกว่าที่ ผู้ใช้งานทั่วไปจะเข้าใจ เช่น ถ้าในการเชื่อมต่อมีการคืน error message กลับไปให้ กับ user ว่า
Certificate revoked
ผู้ใช้บางคนจะเกิดคำถามขึ้นมาทันทีว่า อันนี้คืออะไร แล้วเขาต้องแก้อะไรยังไงไหม บางครั้งก็ไปเปิดคู่มือดู จนกระทั่งไปถาม Call Center , Call Center ก็ไม่เจอ เรื่องนี้ในคู่มือ ก็ส่งไปหา ทีม Product ถึงได้รู้ว่า นี่เป็นปัญหาภายใน ที่ทางทีมต้องเปลี่ยน Certificate ใหม่
ณ จุดนี้จะเห็นว่า บางครั้ง การแสดง Error ให้ผู้ใช้งานหมดทุกอย่างก็ไม่ใช่สิ่งที่ดีสักเท่าไหร่ ทางหนึ่งที่ทีมงานสามารถนำไปใช้แก้ปัญหาได้ คือการกำหนดการแสดงผลออกมา 2 ชุด ได้แก่
- Business Error เป็น Error ที่อธิบายในระดับการใช้งานของ End User อะไรที่ไม่เกี่ยวกับการทำงานจริงๆ จะลดรายละเอียดเชิงเทคนิคลง เช่น เมื่อสักครู่ก็อาจจะแสดงเป็น หัวข้อความ service problem แล้วก็มี รหัส error ตาม เพื่อไม่ให้ไปพารานอยด์มากเกินไป แต่อย่างน้อยพอจะทราบคร่าวๆ ว่า เป็นปัญหาของทางทีมงาน
- Technical Error เป็น Error รายละเอียด ซึ่งแสดงอยู่ใน Log ของทีม ตรงนี้ จำเป็นต้องแสดงข้อมูลให้ชัดเจนและเพียงพอ
ซึ่งตรงนี้ ทีมงาน สามารถกำหนดได้ตามความเหมาะสมของการออกแบบระบบของตัวเองได้