เนื้อหาตอนต่อไปนี้ เราจะมาดูการจัดการเกี่ยวกับเงื่อนไขข้อผิดพลาด
กรณีพิเศษที่เราสามารถกำหนดขึ้นเอง เพื่อใช้ในการตรวจสอบและกำหนด
เงื่อขไขในการทำงานเฉพาะ ให้กับข้อผิดพลาดนั้นๆ หรือที่เรียกว่า Exception
สามารถทดสอบการเขียนโปรแกรมผ่านเว็บไซต์ DartPad
Exceptions คืออะไร
Exceptions คือ เงื่อนไขข้อผิดพลาด หรือข้อยกเว้น ในกรณีที่เราต้องการกำหนดการทำงานเพิ่มเติม ให้กับเงื่อนไขนั้นๆ โดยใน
ภาษา Dart จะมีชนิดข้อมูลข้อผิดพลาดมาให้ คือ Exception และ Error โดยเราสามารถกำหนด Exception ขึ้นมาใช้งานเองโดย
โดยทำการ implement หรือใช้งาน Exception Class ตัวอย่างเช่น
// กำหนด Exception เอง โดยใช้ Exception class class MyException implements Exception { final String msg; const MyException([this.msg]); @override String toString() => msg ?? 'MyException'; }
ทำความเข้าใจระหว่าง Errors และ Exceptions จะได้ว่า Exceptions เป็นการพิจารณาเงื่อนไขที่เราสามารถวางแผนการจัดการ
ให้กับสิ่งที่จะเกิดขึ้นได้ ส่วน Errors เป็นเงื่อนไข ข้อผิดพลาดที่เราคาดเดาไม่ได้ หรือไม่ได้วางแผนจัดการเอาไว้
อย่างไรก็ตามในภาษา Dart นอกจาก Exception และ Error object แล้ว ยังสามารถเกิด Exception ที่เป้นชนิดข้อมูลอื่นๆ ที่ไม่ใช่
NULL object ได้อีกด้วย ตัวอย่างเช่น เป็นข้อความ String
// สร้าง Exceptions ที่เป้น String ข้อความด้วย "throw" keyword throw 'ยอดเงินไม่เพียงพอ';
ตอนนี้เราเข้าใจเกี่ยวกับ Exceptions คร่าวๆ แล้ว ต่อไปเราจะมาดูวิธีการกำหนด และใช้งาน Exceptions รวมถึงการใช้งาน throw, on,
catch, rethrow และ finally กันต่อตามลำดับดังนี้
การกำหนด Exceptions
การใช้งาน Throw
อย่างที่เราทราบมาบ้างแล้วว่า การตรวจจับ Exception สามารถจัดการโดยการใช้งาน try...catch ดูตัวอย่างประกอบ
void main() { try { double deposit = 200; // เงินเกิบ double withdraw = 300; // จะถอนมาใช้ if(withdraw > deposit){ // ถอนเงินเกินยอดที่มี throw 'ยอดเงินไม่เพียงพอ'; // สร้าง เงื่อนไขข้อผิพลาด } } catch (e) { // ได้ Exceptions เป็นชนิดข้อมูล String print('Exceptions ที่มีชนิดข้อมูลเป็น ${e.runtimeType}.'); // output: Exceptions ที่มีชนิดข้อมูลเป็น String. } }
ตัวอย่างการใช้งานกับ Exceptions ที่กำหนดเอง และใช้งาน exception class
void main() { try { double deposit = 200; // เงินเกิบ double withdraw = 300; // จะถอนมาใช้ if(withdraw > deposit){ // ถอนเงินเกินยอดที่มี throw WithdrawException('ยอดเงินไม่เพียงพอ'); // สร้าง เงื่อนไขข้อผิพลาด } } catch (e) { print(e); // output: ยอดเงินไม่เพียงพอ print('Exceptions ที่มีชนิดข้อมูลเป็น ${e.runtimeType}.'); // output: Exceptions ที่มีชนิดข้อมูลเป็น WithdrawException. } } // กำหนด Exception เอง โดยใช้ Exception class class WithdrawException implements Exception { final String msg; const WithdrawException([this.msg]); @override String toString() => msg ?? 'ยอดเงินไม่เพียงพอ'; }
จะเห็นว่าเราใช้ "throw" ในการกำหนด Exceptions ซึ่งในการใช้งาน ถึงแม้เราจะสามารถกำหนดชนิดข้อมูลอื่นๆ อย่าง String ให้กับ
Exceptions แต่เพื่อใช้งานที่มีประสิทธิภาพ การกำหนด Exceptions โดยใช้งาน Exception หรือ Error class จะเป็นวิธีที่ดีกว่า เหมือนใน
กรณีการใช้งาน WithdrawException ในตัวอย่างที่สองข้างต้น ที่มีการ implement มาจาก Exception class
ตัวอย่างการใช้งานกับ Exception class โดยตรง
void main() { try { double deposit = 200; // เงินเกิบ double withdraw = 300; // จะถอนมาใช้ if(withdraw > deposit){ // ถอนเงินเกินยอดที่มี throw Exception('ยอดเงินไม่เพียงพอ'); // สร้าง เงื่อนไขข้อผิพลาด } } catch (e) { print(e); // output: ยอดเงินไม่เพียงพอ print('Exceptions ที่มีชนิดข้อมูลเป็น ${e.runtimeType}.'); // output: Exceptions ที่มีชนิดข้อมูลเป็น _Exception. } }
ในกรณีใช้ Error class โดยตรง สามารถใช้เป็น throw Error();
ผลลัพธ์ที่ได้ จะเป็น Exceptions ที่มีชนิดข้อมูลเป็น Error.
การใช้งาน Catch และ On
การตรวจจับเงื่อนไขพิเศษ หรือ Exception ที่ถูกส่งออกมาโดยการใช้งาน "throw" keyword ก็เพื่อช่วยให้เราสามารถจัดการกับเงื่อนไขนั้น
หรือกำหนดการทำงาน ให้กับเงื่อนไขข้อผิดพลาดที่เกิดขึ้น ในตัวอย่างด้านบน เราจะเห็นว่า มีการใช้ "catch" ในการตรวจจับ exception ซึ่งจะ
ไม่มีการระบุชนิดของ Exception หรือก็คือ เมื่อเราใช้ "catch" ก็จะเป็นการทุกๆ exception ที่เกิดขึ้นทั้งหมด
อย่างไรก็ตาม เราสามารถใช้ "on" keyword แทน หรือใช้งานร่วมกับ "catch" keyword ก็ได้ ในกรณีที่เราต้องการตรวจสอบเงื่อนไข โดย
มีการพิจารณาชนิดของ Exceptions ด้วย ดูตัวอย่างโค้ดด้านล่างประกอบ
void main() { try { double deposit = 200; // เงินเกิบ double withdraw = 300; // จะถอนมาใช้ if(withdraw > deposit){ // ถอนเงินเกินยอดที่มี throw WithdrawException('ยอดเงินไม่เพียงพอ'); } } on WithdrawException catch (e) { print(e); // output: ยอดเงินไม่เพียงพอ print('Exceptions ที่มีชนิดข้อมูลเป็น ${e.runtimeType}.'); } } // กำหนด Exception เอง โดยใช้ Exception class class WithdrawException implements Exception { final String msg; const WithdrawException([this.msg]); @override String toString() => msg ?? 'ยอดเงินไม่เพียงพอ'; }
จากตัวอย่างข้างต้น เราตรวจสอบ Exceptions ที่เป้น WithdrawException ซึ่งมีการคืนค่า error ข้อความกลับมา เราต้องใช้ catch (e) หาก
ต้องการนำค่าไปใช้งานต่อ
เรามาลองเพิ่ม การกำหนด Exceptions หลายๆ เงื่อนไข เป็นดังนี้
void main() { try { double deposit = 200; // เงินเกิบ double withdraw = 0; // จะถอนมาใช้ if(withdraw > deposit){ // ถอนเงินเกินยอดที่มี throw WithdrawException('ยอดเงินไม่เพียงพอ'); }else if(withdraw == 0){ throw Exception("ยอดเงินที่ต้องการถอน ต้องมากกว่า 0"); }else if(withdraw < 0){ throw "ยอดเงินที่ต้องการถอน ไม่ถูกต้อง"; } } on WithdrawException {// ถ้า withdraw มากกว่า 200 เช่น 300 // กรณีเราไม่ต้องการใช้งานค่า erro ก็ไม่ต้องกำหนด catch (e) print('Exceptions เป็นชนิด WithdrawException'); } on Exception catch (e) { // ถ้า withdraw เท่ากับ 0 print(e); // output: ยอดเงินที่ต้องการถอน ต้องมากกว่า 0 print('Exceptions ที่มีชนิดข้อมูลเป็น ${e.runtimeType}.'); // Exceptions ที่มีชนิดข้อมูลเป็น _Exception. } catch (e) { // ถ้า withdraw เป็นตัวเลขติดลบ เช่น -200 print(e); // output: ยอดเงินที่ต้องการถอน ไม่ถูกต้อง print('Exceptions ที่มีชนิดข้อมูลเป็น ${e.runtimeType}.'); // Exceptions ที่มีชนิดข้อมูลเป็น String. } } // กำหนด Exception เอง โดยใช้ Exception class class WithdrawException implements Exception { final String msg; const WithdrawException([this.msg]); @override String toString() => msg ?? 'ยอดเงินไม่เพียงพอ'; }
จากตัวอย่าง จะเห็นว่า เรากำหนด รูปแบบของ Exceptions ที่หลากหลาย ไม่ว่าจะเป็น WithdrawException , Exception และ String โดย
ในการตรวจจับ Exceptions เราใช้ "on" กับกรณีที่ต้องการระบุว่าเป็นข้อมูล ชนิดใด เป็น พิเศษ และใช้ "on" กับ "catch (e)" กรณีที่ต้องการระบุ
ชนิดข้อมูล และใช้งานค่าที่ถูกส่งกลับมา และสุดท้าย เราใช้งาน "catch" สำหรับ Exceptions กรณีอื่นๆ ที่ไม่เข้าเงื่อนไขก่อนหน้า
ในการใช้งาน "catch" เราสามารถกำหนด parameter 1 ค่า เช่น catch (e) หรือ สองค่า เช่น catch (e, s) โดยที่ e คือ exception object
ที่ถูก throw ออกมา ส่วน s คือ stack trace object เป็นลำดับข้อมูลการทำงานการเกิด exception
ตัวอย่างโค้ดการใช้งานบางส่วน
} catch (e, s) { // ถ้า withdraw เป็นตัวเลขติดลบ เช่น -200 print(e); // output: ยอดเงินที่ต้องการถอน ไม่ถูกต้อง print('Exceptions ที่มีชนิดข้อมูลเป็น ${e.runtimeType}.'); // Exceptions ที่มีชนิดข้อมูลเป็น String. print('Stack trace:\n $s'); print('StackTrace ที่มีชนิดข้อมูลเป็น ${s.runtimeType}.'); }
การใช้งาน Rethrow
ในกรณีที่เราต้องการตรวจสอบ Exception เป็นส่วนๆ หรือบางส่วน เช่น สมมติเราสร้างฟังก์ชั่นในการตรวจสอบข้อมูลขึ้นมา และทำการเรียกใช้
งานฟังก์ชั้นนั้น พร้อมกับตรวจจับ Exceptions ด้วย try...catch อีกที เราสามารถส่งต่อ exception object ไปยังการจัดการหลัก โดยใช้ "rethrow"
keyword เพื่อส่งต่อ Exceptions ได้ ดูตัวอย่างโค้ดด้านล่างประกอบ
void main() { double deposit = 200; // เงินเกิบ double withdraw = 0; // จะถอนมาใช้ try { // ใช้งานฟังก์ชั่นตรวจสอบข้อมูล validWithdraw(deposit, withdraw); } catch (e){ // Exception ส่วนนี้มาจาก การ rethrow มาอีกที print(e); // output: ยอดเงินที่ต้องการถอน ไม่ถูกต้อง print('Exception ใน main() ฟังก์ชั่น ${e.runtimeType}.'); } } // สร้างฟังก์ชั่น ตรวจสอบข้อมูล void validWithdraw(double deposit ,double withdraw){ try { if(withdraw > deposit){ // ถอนเงินเกินยอดที่มี throw WithdrawException('ยอดเงินไม่เพียงพอ'); }else if(withdraw == 0){ throw Exception("ยอดเงินที่ต้องการถอน ต้องมากกว่า 0"); }else if(withdraw < 0){ throw "ยอดเงินที่ต้องการถอน ไม่ถูกต้อง"; } } on WithdrawException {// ถ้า withdraw มากกว่า 200 เช่น 300 // กรณีเราไม่ต้องการใช้งานค่า erro ก็ไม่ต้องกำหนด catch (e) print('Exceptions เป็นชนิด WithdrawException'); } on Exception catch (e) { // ถ้า withdraw เท่ากับ 0 print(e); // output: ยอดเงินที่ต้องการถอน ต้องมากกว่า 0 print('Exception ใน validWithdraw() ฟังก์ชั่น ${e.runtimeType}.'); // Exceptions ที่มีชนิดข้อมูลเป็น _Exception. rethrow; // ส่งต่อ Exception object } catch (e) { // ถ้า withdraw เป็นตัวเลขติดลบ เช่น -200 print(e); // output: ยอดเงินที่ต้องการถอน ไม่ถูกต้อง print('Exception ใน validWithdraw() ฟังก์ชั่น ${e.runtimeType}.'); rethrow; // ส่งต่อ Exception object } } // กำหนด Exception เอง โดยใช้ Exception class class WithdrawException implements Exception { final String msg; const WithdrawException([this.msg]); @override String toString() => msg ?? 'ยอดเงินไม่เพียงพอ'; }
ในตัวอย่าง เรากำหนด rethrow ให้กับกรณีที่ ยอดเงินที่ถอนเท่ากับ 0 และ กรณีที่เป็นเลขติดลบ ทำให้ เมื่อมีการปล่อย Exception ออกมาจาก
ฟังก์ชั่น validWithdraw() ก็จะถูกส่งไปใช้ต่อไปอีกที และถูกตรวจจับในฟังก์ชั่น main() ผลลัพธ์ที่ได้ กรณี กำหนดค่า withdraw เท่ากับ 0 จะได้เป็น
Exception: ยอดเงินที่ต้องการถอน ต้องมากกว่า 0 Exception ใน validWithdraw() ฟังก์ชั่น _Exception. Exception: ยอดเงินที่ต้องการถอน ต้องมากกว่า 0 Exception ใน main() ฟังก์ชั่น _Exception.
การใช้งาน Finally
ในกรณีที่เราต้องการทำคำสั่งบางอย่าง โดยไม่สนใจว่าจะเกิด Exceptions ขึ้นหรือไม่ เราสามารถใช้งาน "finally keyword ร่วมกับ try หรือก็คือ
try....finally โดย finally ก็เหมือนเงื่อนไขสุดท้ายที่อยากให้มี หรืออยากให้ทำงานเสมอ โดยจะมี Exceptions หรือไม่ก็ตาม เราจะลองใช้โค้ด
จากด้านบนมาอธิบาย บางส่วนดังนี้
void main() { double deposit = 200; // เงินเกิบ double withdraw = 200; // จะถอนมาใช้ try { // ใช้งานฟังก์ชั่นตรวจสอบข้อมูล validWithdraw(deposit, withdraw); } finally { print('Finally'); } }
ในตัวอย่างนี้ เราถอนเงิน 200 บาท จากเงินที่มีทั้งหมด 200 ซึ่งกือสามารถทำได้ปกติ และไม่เกิด Exceptions ขึ้น และเราต้องการให้พิมพ์คำว่า
"Finally" ทุกๆ ครั้งที่มีการถอนเงิน เราจึงใช้งาน "finally" keyword ตามตัวอย่าง ผลลัพธ์ที่ได้ ก็คือ จะแสดงข้อความว่า "Finally" ออกมา
ทีนี้ดูต่อว่า เราสนใจหากเกิด Exceptions ขึ้น ต้องการรับค่า Exception ที่ถูกส่งต่อมาถ้ามี โดยใช้รูปแบบ try...catch..finally คือ จะมีหรือไม่มี
Exceptions ก็ต้องทำการพิมพ์ข้อความ "Finally" เสมอ ก็จะได้เป็น
void main() { double deposit = 200; // เงินเกิบ double withdraw = 0; // จะถอนมาใช้ try { // ใช้งานฟังก์ชั่นตรวจสอบข้อมูล validWithdraw(deposit, withdraw); } catch (e){ // Exception ส่วนนี้มาจาก การ rethrow มาอีกที print(e); // output: ยอดเงินที่ต้องการถอน ไม่ถูกต้อง print('Exception ใน main() ฟังก์ชั่น ${e.runtimeType}.'); } finally { print('Finally'); } }
ในตัวอย่าง เราต้องการถอนเงิน 0 บาท ซึ่งเป็นจำนวนเงินที่ทำให้เกิด Exceptions ของเงื่อนไขยอดเงินต้องมากกว่า 0 ซึ่งเป็น Exceptions ที่
เกิดในฟังก์ชั่น validWithdraw() และถูกส่งต่อด้วย "rethrow" keyword เมื่อ main() ฟังก์ชั่นเรียกใช้งาน ก็จะแสดงผลลัพธ์นั้นซ้ำอีกที และจบด้วย
finally ทำงานคำสั่งสุดท้ายที่ต้องการ คือพิมพ์ข้อความ "Finally" ออกมา
ลำดับผลลัพธ์การทำงานที่เกิดขึ้น จะเห็นว่า การใช้งาน "finally" keyword ก็เพื่อให้สามารถใช้งานหรือทำคำสั่งที่ต้องการเสมอ แม้จะมีหรือไม่
มี Exceptions เกิดขึ้นก็ตาม ปกติ มักประยุกต์ใช้ในการล้างค่า หรือ รีเซ็ตค่าที่ไม่ได้ใช้งานแล้ว
Exception: ยอดเงินที่ต้องการถอน ต้องมากกว่า 0 Exception ใน validWithdraw() ฟังก์ชั่น _Exception. Exception: ยอดเงินที่ต้องการถอน ต้องมากกว่า 0 Exception ใน main() ฟังก์ชั่น _Exception. Finally
หวังว่าเนื้อหานี้ จะเป็นประโยชน์และแนวทาง ในการนำไปใช้งานและทำความเข้าใจต่อไป