หลังจากเขียน Code ในการทำงานมาหลายปี พอดีเจอบทความนึงที่น่าสนใจ ผมเลยทำความเข้าใจ และสรุป เกี่ยวกับ Exception ดังนี้ครับ
Exception คือ อะไร ?
- คือ การแจ้งข้อมูลข้อผิดพลาดที่เกิดขึ้น จากการทำงานของระบบครับ มันมีกลุ่มที่เราสามารถจัดการเองได้ ใช้ Try Catch เข้าช่วย หรือจัดการไม่ได้เลยพวกกลุ่ม Error
Guidelines for Exception Handling
- Catch only the exceptions that you can handle.
- เวลาเขียน Code ใน Method หรือ Class ให้จัดการ Exception ที่น่าจะเกิดขึ้นได้กับชิ้นงานที่ทำอยู่ และพยายามแจ้งกับ User ดูภาษาที่เข้าใจง่าย ส่วน Exception นอกเหนือจากนี้ ปล่อยให้ Caller เป็นคนจัดการ เข่น ถ้าทำ Component ในการอ่านไฟล์ สิ่งที่เราสนใจ
- ทำ Component อ่านไฟล์ กลุ่ม Exception ควรเป็นกลุ่ม IO
- ถ้าเกิด Expception จริง เช่น ไฟล์มันถูกอ่านอยู่ หลังจาก Exception เกิดขึ้น สิ่งที่เราควรทำ คือ แจ้ง Message ที่ User สามารถเข้าใจง่าย และเลือกที่ทำอะไรต่อไป
- ไม่จำเป็นต้องดัก Exception แปลก เช่น StackOverflowException ถ้าเกิดขึ้นจริง สิ่งที่ทำได้ คือ Throw(โยน) ไปให้คนที่มีหน้าที่จริงๆจัดการ
- Don’t hide (bury) exceptions you don’t fully handle.
- ใช้ Catch เท่าที่จำเป็น บางเรื่องเราไม่จำเป็นต้องรับมาหมด โยน(Throw) ให้คนที่เกี่ยวข้องจัดการดีกว่า อย่าซ่อน Exception เพราะ คิดว่า User จะตกใจ เช่น
- ทำ Process B เกี่ยวกับคำนวณอยู่ Exception ที่เราต้องใช้น่าจะมี ArithmeticException, DividebyZeroException เป็นต้น
- เมื่อตอน Runtime มันเกิด Expception OutofMemory ขึ้นมา
- สิ่งที่เราควรทำ คือ โยนให้ Process A ที่เรียก B มาทำงาน จัดการ Exception อย่าซ่อนมันไว้ (ถ้าซ่อนไว้ เราอาจจะเจอเคสที่อยู่ App ปิดตัวเองแบบเงียบๆ ไม่แจ้งอะไร)
- Use System.Exception and general catch blocks rarely.
- พยายามอย่าดักจับ Class Exception ตรงๆ ใช้ให้น้อยที่สุด อาจจะใช้ในกรณีที่ Exception ที่ถูกโยนเข้ามามันไม่มีทางไปแล้ว เพื่อให้ Exception แสดงออกมา
- Catch Exception จากเล็กไปใหญ่เสมอ ตาม Code ดังนี้
try { //Do some business, logic here !!! } catch (DivideByZeroException ex) { Console.WriteLine("Exception Divide by Zero occur:" + ex); } catch (ArithmeticException ex) { Console.WriteLine("Exception Arithmeti cException occur:" + ex); } catch (Exception) { //I Cannot handle. someone help please. throw; }
- จาก Code นี้ ผมเรียก Exception ดังนี้
- ด่านแรก DividebyZeroException เพราะถูก Extend มาจาก ArithmeticException
- ด่านสอง ArithmeticException เพราะถูก Extend มาจาก SystemException
- ด่านสุดท้าย Exception - Mother of Exception เป็น Class แม่ของเหล่า Exception เลย - ใช้เท่าที่จำเป็น เพราะ ปกติแล้ว เวลาที่เราเขียน Code ตัว IDE มันจะ Hint Exception มาให้ หรือ ถ้าถึกหน่อยไปดูใน MSDN
- Use throw; rather than throw <exception object> inside a catch block.
- มารู้จักกับ throw และ throw <exception object> กันก่อน
- throw <exception object> มาดู Code กันเลย
try { //Do some business, logic here !!! } catch (Exception ex) { //I Cannot handle. someone help please. throw ex; }
- อันนี้ คือการ Throw Exception เปลี่ยน call stack ของโปรแกรม โดย call stack จะมาสุด ณ ตำแหน่งที่เรา throw exception ออกมา
- ซึ่งเมื่อเกิด exception ขึ้นจะทำให้เรา trace ปัญหาที่เกิดได้ยากขึ้น เพราะไม่รู้ว่าเกิดขึ้นที่ตรงไหน
- throw หรือ Rethrow มาดู Code กันเลย
try { //Do some business, logic here !!! } catch (Exception) { //I Cannot handle. someone help please. throw; }
- การ Rethrow exception ออกมาโดยที่ไม่ได้เปลี่ยน call stack ดังนั้นถ้าเราต้องการที่จะ throw exception เดิมออกมา ให้ใช้ throw
- ตัวอย่าง Code ของ Throw vs Rethrow ได้ที่ Git ครับ
- ใช้คำสั่ง throw; สำหรับ Exception ในกลุ่มเดียวกัน เพื่อรักษา call stack เดิมไว้ ส่วนถ้า Exception คนละชนิดให้ดูที่ข้อสุดท้าย
- Avoid exception reporting or logging lower in the call stack.
- ต้องย้อนกลับไปข้อที่แล้วก่อน ระหว่าง throw(Rethrow) กับ throw ex
- throw ex - Call Stack เปลี่ยน
- rethrow - Call Stack ไม่เปลี่ยน
- หลังจากเข้าใจ throw กับ rethrow แล้วมาดูการเขียน Log หรือ Report ให้ User ทราบกันดีกว่า ว่าควรทำอย่างไร
- แบบ rethrow - การเขียน Log หรือ Report ควรจะเขียนใน High Stack (ไปเขียนเอาตอนสุดท้ายสุดเลย ไม่ต้อง Log ตอน rethrow) เพื่อป้องกันการเขียน Log ซ้ำซ้อน
- แบบ throw - ต้องดูลำดับของ Call Stack ด้วย ถ้าเขียน Log ไม่ดีข้อมูลหายไปนะครับ
- System.Console.WriteLine() ไม่ต้องพ่นออกมานะ เพราะใน WebApp กับ WinApp พ่นไป ก็ไม่มีประโยชน์
- Avoid exception conditionals that might change over time.
- Avoid exception conditionals that might change over time.
- อย่าเอา Exception Message มาเป็นเงื่อนไข หรือ ใช้ exception conditionals ของ C#6 ถ้าไม่มั่นใจว่า Business Logic มันจะนิ่ง หรือ Message นิ่ง ให้ระวังเรื่องภาษาด้วย เพราะ Lib บางตัว อาจจะมีการทำ custom exception แยกตาม แต่ละภาษาทำให้ดักมากกว่าเดิมอีก
- *สำหรับ exception conditionals - ส่วนตัวมองว่า มันควรใช้ในตอนท้ายสุด ใช้ในการดักจับ Error Code หรือ Message เพื่อแจ้งคำอธิบาย หรือ การแก้ไขเบื้องต้นให้ User มากกว่า
- Avoid throwing exceptions from exception conditionals.
- ต่อจากข้อที่แล้วครับ ถ้าเราใช้ exception conditionals มากรอง Exception แล้วโยนต่อไป ปัญหา คือ ฝั่งที่รับ Exception มาจัดการ จะไม่รู้ว่ามันมีสาเหตุมาอย่างไร และควรจัดการอย่างไรครับ
- Use caution when rethrowing different exceptions.
- ระมัดระวังในการ rethrowing สำหรับ Exception ที่แตกต่างกัน การไปปรับเปลี่ยนชนิดของ Exception มันทำให้ Call Stack หายไปครับ ใน .Net มีวิธีการแก้ปัญหามาแล้ว คือ การใช้งาน Inner Exception เอาไว้เก็บที่มาของ Exception ล่าสุดที่เกิดขึ้นครับ
- ตัวอย่างของ Inner Exception ลองดูตามตัวอย่างผมนะครับ อันแรก
- เรากำลังเขียนไฟล์อยู่ พบว่าไฟล์ไม่มี เลย Throw FileNotFoundException
- สิ่งที่ทำให้เกิด FileNotFoundException มาจาก Argument ที่ส่งเข้ามาผิด (จาก Code ไม่ส่งมาเลย และไฟล์ไม่มีอยู่จริงด้วย)
- สิ่งที่ระบบสร้าง FileNotFoundException โดยมี ArgumentException เป็น Inner Exception เพื่อมาบอกสาเหตุที่แท้จริงครับ
- ลองดู Code กันเลยย
static void Main(string[] args) { try { ReadSomething(); } catch (Exception e) { Console.WriteLine(String.Concat(e.StackTrace, e.Message)); if (e.InnerException != null) { Console.WriteLine("Inner Exception"); Console.WriteLine(String.Concat(e.InnerException.StackTrace, e.InnerException.Message)); } } Console.ReadLine(); } static void ReadSomething() { try { throw new ArgumentException(); } catch (ArgumentException e) { //make sure this path does not exist if (File.Exists("test.txt") == false) { throw new FileNotFoundException("File Not found when trying to write argument exception to the file", e); } } }
- มาตัวอย่างที่สอง
- A ทำการ Process คิดเลขสักอย่าง A มีแต่การคำนวณ ดัก Exception arithmeticexception, DividebyZeroException
- A เรียก B มาประมวลผล B ไปอ่าน TextFile อ้าวคนอื่นใช้อยู่ Throw Exception มา IOException
- ใช้ InnerException สิ โดยเป็น A - arithmeticexception และมี ฺB - IOException เป็น inner exception
** สำหรับสถานการณ์อื่นๆ ลองอ่าน Guideline แล้วลองไปปรับดูครับ
- Changing the exception type clarifies the problem.
- ปรับชนิดของ Exception ให้เหมาะสม เพื่อให้สามารถวิเคราะห์ปัญหาได้ง่ายขึ้นครับ
- Private data is part of the original exception.
- ข้อมูล Exception บางอย่าง อาจจะมีข้อมูลที่สำคัญกับระบบ เราควรห่อหุ่มมันให้เรียบร้อย
- The exception type is too specific for the caller to handle appropriately.
- บาง Exception มันเหมาะสมที่จะโยนขึ้นไปอยู่แล้ว เช่น Error Code ของ Database มันมีความชัดเจนอยู่ในตัว อย่าทำให้มันหายไปครับ เก็บไว้ใน inner exception ครับ
สำหรับผมแล้วบทความนี้สามารถนำไปปรับใช้ได้กับหลายๆภาษาครับ แต่อาจจะต้องเข้าใจ พื้นฐานของแต่ละภาษาก่อนครับ
Reference
Discover more from naiwaen@DebuggingSoft
Subscribe to get the latest posts to your email.