อ่าน 18 นาที
การจัดการข้อผิดพลาด (การเขียนโปรแกรม)
ในการเขียนโปรแกรมคอมพิวเตอร์ มีกลไก ภาษาโปรแกรมหลายอย่างสำหรับการจัดการข้อยกเว้นคำว่าข้อยกเว้นโดยทั่วไปใช้เพื่อหมายถึงโครงสร้างข้อมูลที่เก็บข้อมูลเกี่ยวกับสภาวะผิดปกติ
การจัดการข้อผิดพลาด (การเขียนโปรแกรม)
ในการเขียนโปรแกรมคอมพิวเตอร์ มีกลไก ภาษาโปรแกรมหลายอย่างสำหรับการจัดการข้อยกเว้นคำว่าข้อยกเว้นโดยทั่วไปใช้เพื่อหมายถึงโครงสร้างข้อมูลที่เก็บข้อมูลเกี่ยวกับสภาวะผิดปกติ กลไกหนึ่งในการถ่ายโอนการควบคุมหรือยกข้อยกเว้นขึ้นมาเรียกว่าthrow ; กล่าวคือ ข้อยกเว้นถูกthrownการทำงานจะถูกส่งต่อไปยัง catch
การใช้งาน
ภาษาโปรแกรมแต่ละภาษามีความแตกต่างกันอย่างมากในแนวคิดเกี่ยวกับข้อยกเว้น ข้อยกเว้นสามารถใช้เพื่อแสดงและจัดการสถานการณ์ที่ผิดปกติ คาดเดาไม่ได้ หรือผิดพลาดได้ แต่ยังสามารถใช้เป็นโครงสร้างควบคุมการไหลเพื่อจัดการสถานการณ์ปกติได้อีกด้วย ตัวอย่างเช่นตัววนซ้ำของ Pythonจะโยนข้อยกเว้น StopIteration เพื่อส่งสัญญาณว่าไม่มีรายการเพิ่มเติมที่สร้างขึ้นโดยตัววนซ้ำ[ 1 ]มีความเห็นไม่ตรงกันในหลายภาษาเกี่ยวกับสิ่งที่ถือเป็นการใช้ข้อยกเว้นตามแบบฉบับ ตัวอย่างเช่น Joshua Bloch กล่าวว่าข้อยกเว้นของ Java ควรใช้เฉพาะในสถานการณ์พิเศษเท่านั้น[ 2 ]แต่ Kiniry สังเกตว่าjava.io.FileNotFoundExceptionคลาสของ Java ไม่ใช่เหตุการณ์พิเศษเลย[ 3 ]ในทำนองเดียวกัน Bjarne Stroustrup ผู้เขียน C++ กล่าวว่าข้อยกเว้นของ C++ ควรใช้สำหรับการจัดการข้อผิดพลาดเท่านั้น เนื่องจากนี่คือสิ่งที่ออกแบบมาเพื่อ[ 4 ]แต่ Kiniry สังเกตว่าภาษาที่ทันสมัยหลายภาษา เช่น Ada, C++, Modula-3, ML และ OCaml, Python และ Ruby ใช้ข้อยกเว้นสำหรับการควบคุมการไหล บางภาษา เช่น Eiffel, C#, Common Lisp และModula-2ได้พยายามร่วมกันจำกัดการใช้ข้อยกเว้น แม้ว่าการดำเนินการนี้จะทำในระดับสังคมมากกว่าระดับเทคนิคก็ตาม[ 3 ]
ประวัติศาสตร์
คอมไพเลอร์ Fortranรุ่นแรกๆของ IBM มีคำสั่งสำหรับทดสอบเงื่อนไขที่ผิดปกติ ซึ่งรวมถึง คำสั่ง `true` , `false` และ ` false` เพื่อความเป็นอิสระจากเครื่องจักร คำสั่งเหล่านี้จึงไม่ได้รวมอยู่ใน FORTRAN IV หรือมาตรฐาน Fortran 66 อย่างไรก็ตาม ตั้งแต่ Fortran 2003 เป็นต้นมา สามารถทดสอบปัญหาทางตัวเลขได้โดยการเรียกใช้ฟังก์ชันในโมดูล `format` IF ACCUMULATOR OVERFLOWIF QUOTIENT OVERFLOWIF DIVIDE CHECKIEEE_EXCEPTIONS
การจัดการข้อยกเว้นของซอฟต์แวร์ยังคงได้รับการพัฒนาอย่างต่อเนื่องในช่วงทศวรรษ 1960 และ 1970 LISP 1.5 (1958-1961) [ 5 ]อนุญาตให้มีการยกข้อยกเว้นโดยใช้ERRORฟังก์ชันเสมือน คล้ายกับข้อผิดพลาดที่เกิดขึ้นจากตัวแปลภาษาหรือคอมไพเลอร์ ข้อยกเว้นจะถูกดักจับโดยERRORSETคำหลัก ซึ่งจะส่งคืนค่าNILในกรณีที่เกิดข้อผิดพลาด แทนที่จะยุติโปรแกรมหรือเข้าสู่ดีบักเกอร์ [ 6 ] PL /Iได้นำเสนอรูปแบบการจัดการข้อยกเว้นของตนเองราวปี 1964 ซึ่งอนุญาตให้จัดการการขัดจังหวะด้วยหน่วย ON [ 7 ] MacLispสังเกตว่าERRSETและERRไม่ได้ใช้เฉพาะสำหรับการยกข้อผิดพลาดเท่านั้น แต่ยังใช้สำหรับการควบคุมการไหลที่ไม่ใช่แบบโลคอลด้วย ดังนั้นจึงเพิ่มคำหลักใหม่สองคำ คือCATCHและTHROW(มิถุนายน 1972) [ 8 ]พฤติกรรมการทำความสะอาดที่เรียกกันทั่วไปว่า "finally" ได้รับการแนะนำในNIL (New Implementation of LISP) ในช่วงกลางถึงปลายทศวรรษ 1970 ในUNWIND-PROTECTชื่อ[ 9 ] จากนั้น Common Lispก็ได้นำวิธีการนี้มาใช้ในเวลาเดียวกันdynamic-windScheme ก็ได้จัดการข้อยกเว้นใน closures เอกสารฉบับแรกเกี่ยวกับการจัดการข้อยกเว้นแบบมีโครงสร้างคือGoodenough (1975a)และGoodenough (1975b) [ 10 ] ต่อ มาการจัดการข้อยกเว้นก็ได้รับการนำไปใช้กันอย่างแพร่หลายในภาษาโปรแกรมหลายภาษาตั้งแต่ช่วงปี 1980 เป็นต้นมา
ไวยากรณ์
ภาษาคอมพิวเตอร์หลายภาษามีระบบรองรับทางไวยากรณ์ในตัวสำหรับข้อยกเว้นและการจัดการข้อยกเว้น ซึ่งรวมถึงActionScript , Ada , BlitzMax , C++ , C# , Clojure , COBOL , D , ECMAScript , Eiffel , Java , ML , Object Pascal (เช่นDelphi , Free Pascalและอื่นๆ), PowerBuilder , Objective-C , OCaml , Perl, [ 11 ] PHP (ตั้งแต่เวอร์ชัน 5), PL/I , PL/SQL , Prolog , Python , REALbasic , Ruby , Scala , Smalltalk , Tcl , Visual Prologและภาษา . NET ส่วนใหญ่
นอกเหนือจากความแตกต่างทางไวยากรณ์เล็กน้อยแล้ว มีรูปแบบการจัดการข้อยกเว้นที่ใช้กันอยู่เพียงไม่กี่แบบเท่านั้น ในรูปแบบที่นิยมใช้มากที่สุด ข้อยกเว้นจะเริ่มต้นด้วยคำสั่งพิเศษ ( throwหรือraise) พร้อมด้วยอ็อบเจ็กต์ข้อยกเว้น (เช่น ใน Java หรือ Object Pascal) หรือค่าของประเภทแจงนับ ที่ขยายได้พิเศษ (เช่น ใน Ada หรือ SML) ขอบเขตสำหรับตัวจัดการข้อยกเว้นเริ่มต้นด้วยข้อความบ่งชี้ ( tryหรือตัวเริ่มต้นบล็อกของภาษา เช่นbegin) และสิ้นสุดที่จุดเริ่มต้นของข้อความตัวจัดการแรก ( catch, except, rescue) สามารถมีข้อความตัวจัดการหลายข้อความตามมาได้ และแต่ละข้อความสามารถระบุประเภทของข้อยกเว้นที่จัดการและชื่อที่ใช้สำหรับอ็อบเจ็กต์ข้อยกเว้นได้ ในรูปแบบที่แตกต่างกันเล็กน้อย บางภาษาใช้ข้อความตัวจัดการเดียว ซึ่งจัดการกับคลาสของข้อยกเว้นภายใน
นอกจากนี้ ยังพบข้อความที่เกี่ยวข้อง ( finallyหรือensure) ที่ถูกเรียกใช้งานไม่ว่าจะมีข้อยกเว้นเกิดขึ้นหรือไม่ก็ตาม โดยทั่วไปเพื่อปล่อยทรัพยากรที่ได้รับภายในบล็อกการจัดการข้อยกเว้น ที่น่าสังเกตคือ C++ ไม่มีโครงสร้างนี้ แต่แนะนำให้ใช้ เทคนิค Resource Acquisition Is Initialization (RAII) แทน ซึ่งจะปล่อยทรัพยากรโดยใช้ตัวทำลาย [ 12 ] ตามเอกสารปี 2008 โดย Westley Weimer และGeorge Neculaไวยากรณ์ของ บล็อก try... finallyใน Java เป็นปัจจัยหนึ่งที่ทำให้เกิดข้อบกพร่องของซอฟต์แวร์ เมื่อเมธอดจำเป็นต้องจัดการการได้มาและการปล่อยทรัพยากร 3–5 รายการ โปรแกรมเมอร์ดูเหมือนจะไม่เต็มใจที่จะซ้อนบล็อกมากพอเนื่องจากความกังวลเรื่องความสามารถในการอ่าน แม้ว่านี่จะเป็นวิธีแก้ปัญหาที่ถูกต้องก็ตาม เป็นไปได้ที่จะใช้บล็อกtry... เพียง finallyบล็อกเดียวแม้ว่าจะจัดการกับทรัพยากรหลายรายการ แต่ต้องใช้ค่าตัวบ่งชี้ ที่ถูกต้อง ซึ่งเป็นแหล่งที่มาของข้อบกพร่องทั่วไปอีกประการหนึ่งสำหรับปัญหาประเภทนี้[ 13 ] : 8:6–8:7
Python และ Ruby ยังอนุญาตให้ใช้เงื่อนไข ( else) ซึ่งใช้ในกรณีที่ไม่มีข้อยกเว้นเกิดขึ้นก่อนถึงจุดสิ้นสุดของขอบเขตของตัวจัดการ
โดยรวมแล้ว โค้ดการจัดการข้อผิดพลาดอาจมีลักษณะดังนี้ (ตามไวยากรณ์การจัดการข้อผิดพลาดแบบ Java ):
import java.io.IOException ; import java.util.Scanner ;try { Scanner stdin = new Scanner ( System . in ); String line = stdin . nextLine ();ถ้า( line.length () == 0 ) { throw new IOException ( "บรรทัดที่อ่านจากคอนโซลว่างเปล่า! " ) ; }System.out.printf ( " Hello % s !%n" , line ); System.out.println ( " The task executed successfully." ) ; } catch ( IOException e ) { System.out.println ( " Hello!" ) ; } catch ( Exception e ) { System.out.printf ( " Error : %s%n" , e.getMessage ( ) ) ; } finally { System.out.println ( " The program is now terminating . " ) ; }ภาษา C ไม่มีการจัดการข้อยกเว้นแบบ try-catch แต่ใช้รหัสส่งคืนสำหรับการตรวจสอบข้อผิดพลาด ฟังก์ชันไลบรารี setjmpมาตรฐานlongjmpสามารถใช้เพื่อดำเนินการจัดการ try-catch ผ่านมาโครได้[ 14 ]
Perl 5 ใช้diefor throwและtry-catch โดยมี โมดูล CPANที่ให้ความหมายของ try-catch [ 15 ]eval{}if($@){}
ความหมายของการยุติและการกลับมาเริ่มต้นใหม่
เมื่อเกิดข้อยกเว้น โปรแกรมจะค้นหาย้อนกลับไปในสแต็กของการเรียกฟังก์ชันจนกว่าจะพบตัวจัดการข้อยกเว้น ภาษาบางภาษากำหนดให้มีการคลายสแต็กขณะที่การค้นหาดำเนินไป กล่าวคือ ถ้าฟังก์ชัน ` fcontains` ซึ่งมีตัวจัดการข้อHยกเว้น `exception` Eเรียกฟังก์ชัน ` contains` gซึ่งในทางกลับกันเรียกฟังก์ชัน ` exception` และ เกิดข้อยกเว้นในฟังก์ชัน `contains` ฟังก์ชัน `contains` และh`exception` อาจถูกยุติ และ ฟังก์ชัน `contains` ใน ฟังก์ชัน `exception` จะ จัดการข้อยกเว้น นี่เรียกว่าความหมายของการยุติ (termination semantics) หรืออีกทางหนึ่ง กลไกการจัดการข้อยกเว้นอาจไม่คลายสแต็กเมื่อเข้า สู่ตัวจัดการข้อยกเว้น [หมายเหตุ 1 ]ทำให้ตัวจัดการข้อยกเว้นมีตัวเลือกในการเริ่มต้นการคำนวณใหม่ ดำเนินการต่อ หรือคลายสแต็ก สิ่งนี้ช่วยให้โปรแกรมสามารถดำเนินการคำนวณต่อจากจุดที่เกิดข้อผิดพลาด (ตัวอย่างเช่น เมื่อไฟล์ที่หายไปก่อนหน้านี้พร้อมใช้งานแล้ว) หรือเพื่อใช้งานการแจ้งเตือน การบันทึก การสอบถาม และตัวแปรแบบยืดหยุ่นบนกลไกการจัดการข้อยกเว้น (ดังที่ทำใน Smalltalk) การอนุญาตให้การคำนวณดำเนินการต่อจากจุดที่หยุดไปเรียกว่าความหมายของการดำเนินการต่อ (resumption semantics) EhhgHfE
มีข้อโต้แย้งเชิงทฤษฎีและการออกแบบที่สนับสนุนการตัดสินใจทั้งสองแบบ การอภิปรายเกี่ยวกับการกำหนดมาตรฐาน C++ ในปี 1989–1991 ส่งผลให้มีการตัดสินใจอย่างเด็ดขาดในการใช้ความหมายของการยุติใน C++ [ 16 ] Bjarne Stroustrupอ้างถึงการนำเสนอของJim Mitchellเป็นข้อมูลสำคัญ:
จิมใช้การจัดการข้อยกเว้นในภาษาโปรแกรมถึงครึ่งโหลตลอดระยะเวลา 20 ปี และเป็นผู้สนับสนุนแนวคิดเรื่องความหมายของการกลับมาทำงานต่อ (resumption semantics) ในยุคแรกๆ โดยเป็นหนึ่งในผู้ออกแบบและพัฒนาหลักของระบบ Cedar/Mesa ของซีร็อกซ์ ข้อความของเขาคือ
- “การยุติสัญญาเป็นวิธีที่เหมาะสมกว่าการกลับมาทำงานต่อ นี่ไม่ใช่เรื่องของความคิดเห็น แต่เป็นเรื่องของประสบการณ์หลายปี การกลับมาทำงานต่ออาจดูน่าดึงดูด แต่ไม่ใช่วิธีที่ถูกต้อง”
เขาสนับสนุนข้อความนี้ด้วยประสบการณ์จากระบบปฏิบัติการหลายระบบ ตัวอย่างสำคัญคือ Cedar/Mesa: มันถูกเขียนขึ้นโดยผู้คนที่ชื่นชอบและใช้การทำงานต่อ แต่หลังจากใช้งานมาสิบปี เหลือการใช้การทำงานต่อเพียงครั้งเดียวในระบบที่มีบรรทัดกว่าครึ่งล้านบรรทัด นั่นคือการสอบถามบริบท เนื่องจากจริงๆ แล้วการทำงานต่อไม่ได้จำเป็นสำหรับการสอบถามบริบทดังกล่าว พวกเขาจึงลบออกและพบว่าความเร็วในส่วนนั้นของระบบเพิ่มขึ้นอย่างมาก ในทุกกรณีที่มีการใช้การทำงานต่อ มันได้กลายเป็นปัญหาตลอดสิบปีที่ผ่านมา และการออกแบบที่เหมาะสมกว่าได้เข้ามาแทนที่ โดยพื้นฐานแล้ว การใช้การทำงานต่อทุกครั้งแสดงถึงความล้มเหลวในการรักษาระดับนามธรรมที่แยกจากกัน[ 10 ]
ภาษาที่จัดการข้อยกเว้นพร้อมการกลับมาทำงานต่อ ได้แก่Common Lispพร้อมCondition System , PL/I, Dylan, R , [ 17 ]และSmalltalkอย่างไรก็ตาม ภาษาโปรแกรมรุ่นใหม่ส่วนใหญ่ใช้ C++ และใช้ความหมายของการยุติการทำงาน
ใน C++ std::uncaught_exceptions()มีฟังก์ชันสำหรับนับจำนวนข้อยกเว้นในเธรดปัจจุบันที่ถูกโยน/โยนซ้ำและยังไม่เข้าสู่catchบล็อกที่ตรงกัน ก่อนC++20std::uncaught_exception()จะใช้ฟังก์ชันอื่น ที่ตรวจสอบว่ามีการคลายสแต็กเกิดขึ้นหรือไม่ [ 18 ]
การใช้งานการจัดการข้อยกเว้น
การนำการจัดการข้อยกเว้นไปใช้ในภาษาโปรแกรมโดยทั่วไปต้องอาศัยการสนับสนุนจากทั้งตัวสร้างโค้ดและระบบรันไทม์ ที่มาพร้อมกับคอมไพเลอร์ (การเพิ่มการจัดการข้อยกเว้นลงใน C++ ทำให้คอมไพเลอร์ C++ ดั้งเดิม Cfrontหมดอายุการใช้งาน[ 19 ] ) มีสองรูปแบบที่พบได้บ่อยที่สุด รูปแบบแรกการลงทะเบียนแบบไดนามิกจะสร้างโค้ดที่อัปเดตโครงสร้างเกี่ยวกับสถานะของโปรแกรมอย่างต่อเนื่องในแง่ของการจัดการข้อยกเว้น [ 20 ] โดยทั่วไปแล้ว วิธีนี้จะเพิ่มองค์ประกอบใหม่ลงในเค้าโครงเฟรมสแต็กที่ทราบว่ามีตัวจัดการใดบ้างสำหรับฟังก์ชันหรือเมธอดที่เกี่ยวข้องกับเฟรมนั้น หากมีการโยนข้อยกเว้น ตัวชี้ในเค้าโครงจะนำรันไทม์ไปยังโค้ดตัวจัดการที่เหมาะสม วิธีการนี้กะทัดรัดในแง่ของพื้นที่ แต่เพิ่มภาระการทำงานเมื่อเข้าและออกจากเฟรม วิธีนี้มักใช้กันทั่วไปในการใช้งาน Ada หลายๆ แบบ เช่น ในกรณีที่จำเป็นต้องมีการสร้างที่ซับซ้อนและการสนับสนุนรันไทม์สำหรับคุณสมบัติภาษาอื่นๆ อีกมากมายการจัดการข้อยกเว้นแบบมีโครงสร้าง(SEH) ของ Microsoft ใช้วิธีนี้กับสแต็กข้อยกเว้นแยกต่างหาก [ 21 ]การลงทะเบียนแบบไดนามิกนั้นค่อนข้างตรงไปตรงมาในการกำหนด จึงสามารถพิสูจน์ความถูกต้องได้ [ 22 ]
รูปแบบที่สอง ซึ่งถูกนำมาใช้ในคอมไพเลอร์ C++ คุณภาพสูงหลายตัวและ Microsoft SEH 64 บิตคือแนวทางที่ขับเคลื่อนด้วยตารางวิธีนี้สร้างตารางคงที่ในระหว่างการคอมไพล์และการเชื่อมโยงซึ่งเชื่อมโยงช่วงของตัวนับโปรแกรมกับสถานะของโปรแกรมที่เกี่ยวข้องกับการจัดการข้อยกเว้น [ 23 ] จากนั้น หากมีการโยนข้อยกเว้น ระบบรันไทม์จะค้นหาตำแหน่งคำสั่งปัจจุบันในตารางและพิจารณาว่าตัวจัดการใดกำลังทำงานอยู่และต้องทำอะไร วิธีนี้ช่วยลดภาระการทำงานของผู้บริหารในกรณีที่ไม่มีการโยนข้อยกเว้น ซึ่งต้องใช้พื้นที่บางส่วน แต่พื้นที่นี้สามารถจัดสรรให้กับส่วนข้อมูลแบบอ่านอย่างเดียวสำหรับวัตถุประสงค์พิเศษ ซึ่งจะไม่ถูกโหลดหรือย้ายตำแหน่งจนกว่าจะมีการโยนข้อยกเว้นเกิดขึ้นจริง [ 24 ] ตำแหน่ง (ในหน่วยความจำ) ของโค้ดสำหรับการจัดการข้อยกเว้นไม่จำเป็นต้องอยู่ใน (หรือแม้แต่ใกล้) บริเวณหน่วยความจำที่เก็บโค้ดส่วนที่เหลือของฟังก์ชัน ดังนั้นหากมีการโยนข้อยกเว้น อาจเกิดผลกระทบต่อประสิทธิภาพ – เทียบได้กับการเรียกฟังก์ชันโดยประมาณ [ 25 ] – หากต้องโหลด/แคชโค้ดการจัดการข้อยกเว้นที่จำเป็น อย่างไรก็ตาม แผนการนี้มีค่าใช้จ่ายด้านประสิทธิภาพน้อยที่สุดหากไม่มีการโยนข้อยกเว้น เนื่องจากข้อยกเว้นใน C++ ถือเป็นพิเศษ(เช่น เหตุการณ์ที่ไม่ปกติ/หายาก) วลี "ข้อยกเว้นต้นทุนเป็นศูนย์" [หมายเหตุ 2 ]จึงถูกใช้ในบางครั้งเพื่ออธิบายการจัดการข้อยกเว้นใน C++ เช่นเดียวกับการระบุประเภทขณะรันไทม์หลักการโอเวอร์เฮดเป็นศูนย์ของ C++เนื่องจากการใช้งานการจัดการข้อยกเว้นขณะรันไทม์ต้องใช้หน่วยความจำจำนวนหนึ่งที่ไม่เป็นศูนย์สำหรับตารางค้นหา [ 26 ]ด้วยเหตุนี้ การจัดการข้อยกเว้น (และ RTTI) จึงสามารถปิดใช้งานได้ในคอมไพเลอร์ C++ หลายตัว ซึ่งอาจเป็นประโยชน์สำหรับระบบที่มีหน่วยความจำจำกัดมาก [ 26 ] (เช่นระบบฝังตัว) แนวทางที่สองนี้ยังเหนือกว่าในแง่ของการบรรลุความปลอดภัยของเธรดด้วย
เมื่อเปรียบเทียบกับ C++ ที่สามารถโยนและดักจับข้อผิดพลาดของประเภทใดก็ได้ ใน Java java.lang.Throwableจะโยนและดักจับเฉพาะประเภทที่สืบทอดมาจาก `throw` เท่านั้น และjava.lang.Throwableมีกรรมที่สืบทอดโดยตรงสองประเภท ได้แก่ `throw` java.lang.Error(บ่งชี้ถึงปัญหาที่ร้ายแรงซึ่งโปรแกรมทั่วไปไม่จำเป็นต้องดักจับ) และjava.lang.Exception`throw` (เงื่อนไขอื่นๆ ที่โปรแกรมทั่วไปอาจต้องการดักจับและจัดการ) java.lang.Errorโดยทั่วไปแล้ว `throw` จะสงวนไว้สำหรับปัญหาที่ร้ายแรงมากเกินขอบเขตของโปรแกรม เช่นjava.lang.OutOfMemoryError`throw` , ` java.lang.ThreadDeaththrow` หรือ ` throw`java.lang.AssertionError
มีการเสนอแผนการกำหนดและการใช้งานอื่นๆ ด้วยเช่นกัน สำหรับภาษาที่รองรับการเขียนโปรแกรมแบบเมตามีการเสนอแนวทางที่ไม่ต้องเสียค่าใช้จ่ายใดๆ เลย (นอกเหนือจากการสนับสนุนการสะท้อน ที่มีอยู่แล้ว) [ 27 ]
การจัดการข้อยกเว้นตามการออกแบบโดยสัญญา
มุมมองที่แตกต่างเกี่ยวกับการยกเว้นนั้นอิงตามหลักการออกแบบโดยสัญญาและได้รับการสนับสนุนโดยเฉพาะจากภาษา Eiffelแนวคิดคือการจัดหาพื้นฐานที่เข้มงวดมากขึ้นสำหรับการจัดการข้อยกเว้นโดยการกำหนดอย่างแม่นยำว่าอะไรคือพฤติกรรม "ปกติ" และ "ผิดปกติ" โดยเฉพาะอย่างยิ่ง แนวทางนี้อิงตามสองแนวคิด:
- ความล้มเหลว : การที่การดำเนินการไม่สามารถทำตามข้อตกลงได้ ตัวอย่างเช่น การบวกอาจทำให้เกิดค่าเกินขีดจำกัดทางคณิตศาสตร์ (ไม่สามารถคำนวณค่าประมาณที่ดีของผลรวมทางคณิตศาสตร์ได้) หรือขั้นตอนการทำงานอาจไม่ตรงตามเงื่อนไขหลังการดำเนินการ
- ข้อยกเว้น : เหตุการณ์ผิดปกติที่เกิดขึ้นระหว่างการทำงานของรูทีน (รูทีนนั้นเป็น " ผู้รับ " ข้อยกเว้น) เหตุการณ์ผิดปกติดังกล่าวเกิดจากความล้มเหลวของการดำเนินการที่รูทีนเรียกใช้
หลักการ "การจัดการข้อยกเว้นอย่างปลอดภัย" ที่เบอร์ทรานด์ เมเยอร์ นำเสนอ ในหนังสือObject-Oriented Software Constructionระบุว่า มีเพียงสองวิธีที่เหมาะสมเท่านั้นที่รูทีนสามารถตอบสนองได้เมื่อเกิดข้อยกเว้น:
- ความล้มเหลว หรือ "ความตื่นตระหนกอย่างเป็นระบบ": รูทีนจะแก้ไขสถานะของวัตถุโดยการสร้างเงื่อนไขคงที่ขึ้นใหม่ (นี่คือส่วนที่เป็น "ระบบ") จากนั้นจึงล้มเหลว (ตื่นตระหนก) ทำให้เกิดข้อยกเว้นในผู้เรียก (เพื่อไม่ให้ละเลยเหตุการณ์ผิดปกติ)
- ลองใหม่: ขั้นตอนนี้จะลองใช้อัลกอริธึมอีกครั้ง โดยปกติหลังจากเปลี่ยนค่าบางอย่างเพื่อให้การลองครั้งต่อไปมีโอกาสประสบความสำเร็จมากขึ้น
โดยเฉพาะอย่างยิ่ง การเพิกเฉยต่อข้อยกเว้นนั้นไม่ได้รับอนุญาต บล็อกนั้นจะต้องถูกลองใหม่และดำเนินการให้สำเร็จ หรือส่งต่อข้อยกเว้นไปยังผู้เรียกใช้
นี่คือตัวอย่างที่แสดงในไวยากรณ์ของ Eiffel โดยสมมติว่าการใช้รูทีนsend_fastเป็นวิธีที่ดีกว่าในการส่งข้อความ แต่ก็อาจล้มเหลว ทำให้เกิดข้อยกเว้น หากเป็นเช่นนั้น อัลกอริทึมถัดไปจะใช้send_slowซึ่งมีโอกาสล้มเหลวน้อยกว่า หากsend_slowล้มเหลว รูทีนsendทั้งหมดควรล้มเหลว ทำให้ผู้เรียกได้รับข้อยกเว้น
ส่ง( m : MESSAGE ) คือ-- ส่ง m ผ่านลิงก์เร็ว ถ้าเป็นไปได้ มิฉะนั้นผ่านลิงก์ช้าlocal tried_fast , tried_slow : BOOLEAN do if tried_fast then tried_slow := True send_slow ( m ) else tried_fast := True send_fast ( m ) end rescue if not tried_slow then retry end endตัวแปรโลคอลแบบบูลีนจะถูกกำหนดค่าเริ่มต้นเป็น False ในตอนเริ่มต้น หาก send_fastล้มเหลว ส่วนเนื้อหา ( doส่วนเงื่อนไข) จะถูกดำเนินการอีกครั้ง ทำให้เกิดการดำเนินการของsend_slowหากการดำเนินการของsend_slowล้มเหลว อีกครั้ง rescueส่วนเงื่อนไขจะดำเนินการจนจบโดยไม่มีretry(ไม่มีelseส่วนเงื่อนไขในผลลัพธ์สุดท้ายif) ทำให้การดำเนินการรูทีนทั้งหมดล้มเหลว
แนวทางนี้มีข้อดีคือการกำหนดให้ชัดเจนว่ากรณี "ปกติ" และ "ผิดปกติ" คืออะไร: กรณีผิดปกติซึ่งก่อให้เกิดข้อยกเว้น คือกรณีที่กระบวนการทำงานไม่สามารถปฏิบัติตามข้อตกลงได้ นอกจากนี้ยังกำหนดการแบ่งบทบาทที่ชัดเจน: doข้อกำหนด (ส่วนปกติ) มีหน้าที่ในการทำให้สำเร็จ หรือพยายามทำให้สำเร็จตามข้อตกลงของกระบวนการทำงานrescueข้อกำหนดอีกข้อหนึ่งมีหน้าที่ในการสร้างบริบทขึ้นใหม่และเริ่มต้นกระบวนการใหม่ หากมีโอกาสที่จะประสบความสำเร็จ แต่ไม่มีหน้าที่ในการคำนวณใดๆ จริงๆ
แม้ว่าข้อยกเว้นใน Eiffel จะมีปรัชญาที่ค่อนข้างชัดเจน แต่ Kiniry (2006) ก็วิจารณ์การใช้งานข้อยกเว้นเหล่านั้นเนื่องจาก "ข้อยกเว้นที่เป็นส่วนหนึ่งของคำจำกัดความของภาษาจะถูกแทนด้วยค่า INTEGER ส่วนข้อยกเว้นที่กำหนดโดยนักพัฒนาจะถูกแทนด้วยค่า STRING [...] นอกจากนี้ เนื่องจากเป็นค่าพื้นฐานและไม่ใช่วัตถุ จึงไม่มีความหมายโดยเนื้อแท้ใดๆ นอกเหนือจากความหมายที่แสดงในรูทีนตัวช่วย ซึ่งไม่สามารถป้องกันความผิดพลาดได้เนื่องจากการโอเวอร์โหลดการแสดงผล (เช่น ไม่สามารถแยกความแตกต่างระหว่างจำนวนเต็มสองจำนวนที่มีค่าเดียวกันได้)" [ 3 ]
C++26เพิ่มการสนับสนุนสำหรับสัญญา ซึ่งใช้ดังต่อไปนี้[ 28 ]
int f ( const int x ) pre ( x != 1 ) // การตรวจสอบเงื่อนไขก่อนการทำงานpost ( r : r == x && r != 2 ) // การตรวจสอบเงื่อนไขหลังการทำงาน; r คือชื่อของอ็อบเจ็กต์ผลลัพธ์ของ f { contract_assert ( x != 3 ); // คำสั่งตรวจสอบreturn x ; }ข้อผิดพลาดที่ไม่ได้ถูกจัดการ
แอปพลิเคชันร่วมสมัยต้องเผชิญกับความท้าทายด้านการออกแบบมากมายเมื่อพิจารณากลยุทธ์การจัดการข้อยกเว้น โดยเฉพาะอย่างยิ่งในแอปพลิเคชันระดับองค์กรสมัยใหม่ ข้อยกเว้นมักจะต้องข้ามขอบเขตของกระบวนการและขอบเขตของเครื่อง ส่วนหนึ่งของการออกแบบกลยุทธ์การจัดการข้อยกเว้นที่แข็งแกร่งคือการรับรู้ว่าเมื่อใดที่กระบวนการล้มเหลวจนถึงจุดที่ไม่สามารถจัดการได้อย่างมีประสิทธิภาพโดยส่วนซอฟต์แวร์ของกระบวนการ[ 29 ]
หากเกิดข้อยกเว้นและไม่มีการดักจับ (ในทางปฏิบัติ ข้อยกเว้นจะเกิดขึ้นเมื่อไม่มีการระบุตัวจัดการที่เหมาะสม) ข้อยกเว้นที่ไม่ถูกดักจับนั้นจะถูกจัดการโดยรันไทม์ รูทีนที่ทำเช่นนี้เรียกว่า...ตัว จัดการข้อยกเว้นที่ไม่ได้ถูกจัดการ[ 30 ] [ 31 ]พฤติกรรมเริ่มต้นที่พบบ่อยที่สุดคือการยุติโปรแกรมและพิมพ์ข้อความแสดงข้อผิดพลาดไปยังคอนโซล ซึ่งโดยปกติจะรวมถึงข้อมูลการดีบัก เช่น การแสดงข้อยกเว้นในรูปแบบสตริงและการติดตามสแต็ก[ 30 ] [ 32 ] [ 33 ] มักจะหลีกเลี่ยงสิ่งนี้โดยการมีตัวจัดการระดับบนสุด (ระดับแอปพลิเคชัน) (เช่น ในลูปเหตุการณ์) ที่ดักจับข้อยกเว้นก่อนที่จะถึงรันไทม์ [ 30 ] [ 34 ]
โปรดทราบว่าถึงแม้ว่าข้อยกเว้นที่ไม่ได้ถูกจัดการอาจส่งผลให้โปรแกรมหยุดทำงานอย่างผิดปกติ (โปรแกรมอาจทำงานไม่ถูกต้องหากไม่จัดการข้อยกเว้น โดยเฉพาะอย่างยิ่งในกรณีที่ไม่ยกเลิกธุรกรรมที่เสร็จสมบูรณ์บางส่วน หรือไม่ปล่อยทรัพยากร) แต่กระบวนการจะสิ้นสุดลงอย่างปกติ (โดยสมมติว่ารันไทม์ทำงานได้อย่างถูกต้อง) เนื่องจากรันไทม์ (ซึ่งควบคุมการทำงานของโปรแกรม) สามารถรับประกันการปิดกระบวนการอย่างเป็นระเบียบได้
ในโปรแกรมแบบมัลติเธรด ข้อผิดพลาดที่ไม่ได้รับการจัดการในเธรดอาจส่งผลให้เธรดนั้นหยุดทำงานเท่านั้น ไม่ใช่กระบวนการทั้งหมด (ข้อผิดพลาดที่ไม่ได้รับการจัดการในตัวจัดการระดับเธรดจะถูกจัดการโดยตัวจัดการระดับบนสุด) นี่เป็นสิ่งสำคัญอย่างยิ่งสำหรับเซิร์ฟเวอร์ ตัวอย่างเช่นเซอร์ฟเล็ต (ที่ทำงานในเธรดของตัวเอง) สามารถหยุดทำงานได้โดยที่เซิร์ฟเวอร์โดยรวมไม่ได้รับผลกระทบ
ตัวจัดการข้อยกเว้นที่ไม่ได้รับการจัดการตามค่าเริ่มต้นนี้สามารถถูกแทนที่ได้ ทั้งในระดับสากลหรือต่อเธรด ตัวอย่างเช่น เพื่อให้มีการบันทึกข้อมูลทางเลือกหรือการรายงานข้อยกเว้นที่ไม่ได้รับการจัดการแก่ผู้ใช้ปลายทาง หรือเพื่อรีสตาร์ทเธรดที่หยุดทำงานเนื่องจากข้อยกเว้นที่ไม่ได้รับการจัดการ ตัวอย่างเช่น ใน Java จะทำได้โดยการตั้งค่าสำหรับเธรดเดียวผ่านThread.setUncaughtExceptionHandlerและในระดับสากลผ่านThread.setDefaultUncaughtExceptionHandlerใน Python จะทำได้โดยการsys.excepthookแก้ไข
ตรวจสอบข้อยกเว้นแล้ว
Javaได้นำเสนอแนวคิดของข้อยกเว้นแบบตรวจสอบ[ 35 ] [ 36 ]ซึ่งเป็นข้อยกเว้นประเภทพิเศษ ใน Java ข้อยกเว้นแบบตรวจสอบโดยเฉพาะคือข้อยกเว้นใดๆjava.lang.Throwableที่ไม่ได้ขยายjava.lang.RuntimeExceptionหรือjava.lang.Errorข้อยกเว้นแบบตรวจสอบที่เมธอดอาจยกขึ้นจะต้องเป็นส่วนหนึ่งของลายเซ็น ของเมธอด ตัวอย่างเช่น หากเมธอดอาจโยนjava.io.IOExceptionข้อยกเว้นประเภท `false` เมธอดนั้นจะต้องประกาศข้อเท็จจริงนี้อย่างชัดเจนในลายเซ็นของเมธอด การไม่ทำเช่นนั้นจะทำให้เกิดข้อผิดพลาดในระหว่างการคอมไพล์ ซึ่งจะถูกประกาศดังนี้ (รวมถึงด้วยjava.util.zip.DataFormatException):
import java.io.File ; import java.io.IOException ; import java.util.zip.DataFormatException ;// ระบุว่าอาจมีการโยน IOException และ DataFormatException public void operateOnFile ( File f ) throws IOException , DataFormatException { // ... }ตามที่ Hanspeter Mössenböck กล่าวไว้ ข้อยกเว้นที่ตรวจสอบแล้วนั้นไม่สะดวกเท่า แต่มีความแข็งแกร่งกว่า[ 37 ] ข้อยกเว้นที่ตรวจ สอบแล้วสามารถลดการเกิดข้อยกเว้นที่ไม่ได้จัดการซึ่งปรากฏขึ้นในระหว่างรันไทม์ในแอปพลิเคชันที่กำหนดได้ใน ระหว่างการคอมไพล์
Kiniry เขียนว่า "อย่างที่โปรแกรมเมอร์ Java ทุกคนรู้ ปริมาณtry catchโค้ดในแอปพลิเคชัน Java ทั่วไปบางครั้งมีขนาดใหญ่กว่าโค้ดที่เทียบเคียงได้ซึ่งจำเป็นสำหรับการตรวจสอบพารามิเตอร์และค่าส่งคืนอย่างเป็นทางการในภาษาอื่นที่ไม่มีข้อยกเว้นที่ตรวจสอบได้ อันที่จริง ความเห็นพ้องต้องกันโดยทั่วไปในหมู่โปรแกรมเมอร์ Java ที่ทำงานภาคสนามคือ การจัดการกับข้อยกเว้นที่ตรวจสอบได้นั้นเป็นงานที่น่าเบื่อพอๆ กับการเขียนเอกสาร ดังนั้น โปรแกรมเมอร์หลายคนจึงรายงานว่าพวกเขา "ไม่ชอบ" ข้อยกเว้นที่ตรวจสอบได้" [ 3 ] Martin Fowlerได้เขียนไว้ว่า "...โดยรวมแล้วฉันคิดว่าข้อยกเว้นนั้นดี แต่ข้อยกเว้นที่ตรวจสอบได้ใน Java นั้นยุ่งยากมากกว่าคุ้มค่า" [ 38 ]ณ ปี 2006 ไม่มีภาษาโปรแกรมหลักใดที่ปฏิบัติตาม Java ในการเพิ่มข้อยกเว้นที่ตรวจสอบได้[ 38 ]ตัวอย่างเช่นC#ไม่ต้องการหรืออนุญาตให้ประกาศข้อกำหนดข้อยกเว้นใดๆ โดยมีข้อความต่อไปนี้ที่โพสต์โดย Eric Gunnerson: [ 39 ] [ 3 ] [ 38 ]
"จากการตรวจสอบโปรแกรมขนาดเล็ก สรุปได้ว่า การกำหนดคุณสมบัติเฉพาะสำหรับการจัดการข้อยกเว้น อาจช่วยเพิ่มประสิทธิภาพการทำงานของนักพัฒนาและปรับปรุงคุณภาพโค้ดได้ แต่ประสบการณ์จากโครงการซอฟต์แวร์ขนาดใหญ่ชี้ให้เห็นผลลัพธ์ที่แตกต่างออกไป นั่นคือ ประสิทธิภาพการทำงานลดลง และคุณภาพโค้ดเพิ่มขึ้นเพียงเล็กน้อยหรือไม่เพิ่มขึ้นเลย"
Anders Hejlsbergอธิบายข้อกังวลสองประการเกี่ยวกับข้อยกเว้นที่ตรวจสอบแล้ว: [ 40 ]
- การกำหนดเวอร์ชัน: เมธอดอาจถูกประกาศให้โยนข้อยกเว้นได้
XและYในเวอร์ชันต่อมาของโค้ด เราไม่สามารถโยนข้อยกเว้นZจากเมธอดได้อีกต่อไป เพราะจะทำให้โค้ดใหม่ไม่เข้ากันกับการใช้งานก่อนหน้านี้ ข้อยกเว้นแบบตรวจสอบได้กำหนดให้ผู้เรียกใช้เมธอดต้องเพิ่ม เงื่อนไขลงZในส่วน throws หรือจัดการกับข้อยกเว้นนั้น หรืออีกทางหนึ่งZอาจถูกแสดงผิดเป็นหรือXก็ได้Y - ความสามารถในการขยายขนาด: ในการออกแบบแบบลำดับชั้น แต่ละระบบอาจมีระบบย่อยหลายระบบ แต่ละระบบย่อยอาจก่อให้เกิดข้อยกเว้นได้หลายอย่าง ระบบหลักแต่ละระบบต้องจัดการกับข้อยกเว้นของระบบย่อยทั้งหมดที่อยู่ต่ำกว่า ทำให้จำนวนข้อยกเว้นที่ต้องจัดการเพิ่มขึ้นแบบทวีคูณ ข้อยกเว้นแบบตรวจสอบได้ (Checked exceptions) กำหนดให้ต้องจัดการกับข้อยกเว้นทั้งหมดเหล่านี้อย่างชัดเจน
เพื่อแก้ไขปัญหาเหล่านี้ Hejlsberg กล่าวว่าโปรแกรมเมอร์มักจะใช้วิธีหลีกเลี่ยงคุณสมบัตินี้โดยใช้การประกาศ อีกวิธีหนึ่งในการหลีกเลี่ยงคือการใช้แฮนด์ เลอร์ (หรือแม้แต่แฮนด์เลอร์) [ 40 ]วิธีนี้เรียกว่าการจัดการข้อยกเว้นแบบจับทั้งหมด หรือการจัดการข้อยกเว้นแบบโปเกมอนตามวลีเด็ดของรายการ ว่า " Gotta Catch 'Em All! " [ 41 ]บทเรียน Java ไม่สนับสนุนการจัดการข้อยกเว้นแบบจับทั้งหมด เนื่องจากอาจจับข้อยกเว้น "ที่แฮนด์เลอร์ไม่ได้ตั้งใจไว้" [ 42 ]อีกวิธีหนึ่งที่ไม่สนับสนุนคือการทำให้ข้อยกเว้นทั้งหมดเป็นคลาสย่อย[ 43 ] ซึ่งทำให้ข้อยกเว้นไม่ได้รับการตรวจสอบ วิธีแก้ปัญหาที่สนับสนุนคือการใช้แฮนด์เลอร์แบบจับทั้งหมดหรือข้อความ throws แต่มีซูเปอร์คลาส เฉพาะ ของข้อยกเว้นที่อาจถูกโยนทั้งหมด แทนที่จะเป็นซูเปอร์คลาสทั่วไป วิธีแก้ปัญหาที่แนะนำอีกวิธีหนึ่งคือการกำหนดและประกาศประเภทข้อยกเว้นที่เหมาะสมกับระดับนามธรรมของเมธอดที่ถูกเรียก[ 44 ]และแมปข้อยกเว้นระดับล่างไปยังประเภทเหล่านี้โดยใช้การ เชื่อมโยงข้อยกเว้นthrowsExceptiontry{...}catch(Exceptione){...}try{...}catch(Throwablet){...}java.lang.RuntimeExceptionjava.lang.Throwable
Kotlinไม่มี checked exception และถึงแม้จะโยน checked exception ของ Java จาก Kotlin ก็ไม่ได้บังคับให้ไคลเอ็นต์จัดการมันจาก Java อย่างไรก็ตาม สามารถเปิดใช้งานได้โดยใช้@Throwsannotation ซึ่งจะส่งthrowsข้อมูลเมตาไปยัง JVM ตัวอย่างเช่น พิจารณาโค้ด Kotlin ต่อไปนี้:
import java.io.IOException@Throws ( IOException :: class ) fun readFile () { // ... throw IOException ( "อ่านไฟล์ไม่สำเร็จ!" ) }เมื่อแปลงกลับเป็นโค้ดใน JVM จะได้ประมาณดังนี้:
import java.io.IOException ;public static final void readFile () throws IOException { // ... throw new IOException ( "ไม่สามารถอ่านไฟล์ได้!" ); }กลไกที่คล้ายคลึงกัน
รากฐานของข้อยกเว้นที่ตรวจสอบได้นั้นย้อนกลับไปถึงแนวคิดเรื่องการกำหนดข้อยกเว้นของภาษาการเขียนโปรแกรม CLU [ 45 ]ฟังก์ชันสามารถยกเว้นได้เฉพาะข้อยกเว้นที่ระบุไว้ในประเภทของมันเท่านั้น แต่ข้อยกเว้นที่รั่วไหลจากฟังก์ชันที่ถูกเรียกจะถูกเปลี่ยนเป็นข้อยกเว้นรันไทม์เพียงอย่างเดียวโดยอัตโนมัติfailureแทนที่จะส่งผลให้เกิดข้อผิดพลาดในเวลาคอมไพล์[ 46 ]ต่อมาModula-3มีคุณสมบัติที่คล้ายกัน[ 47 ]คุณสมบัติเหล่านี้ไม่ได้รวมถึงการตรวจสอบในเวลาคอมไพล์ซึ่งเป็นหัวใจสำคัญของแนวคิดเรื่องข้อยกเว้นที่ตรวจสอบได้[ 45 ]
ภาษาโปรแกรม C++ เวอร์ชันแรกๆ มีกลไกเสริมที่คล้ายกับข้อยกเว้นแบบตรวจสอบได้ เรียกว่า ข้อกำหนดข้อยกเว้น ( exception specifications ) โดยค่าเริ่มต้น ฟังก์ชันใดๆ ก็สามารถโยนข้อยกเว้นใดๆ ก็ได้ แต่สามารถจำกัดได้ด้วยthrowข้อความ (คล้ายกับthrowsข้อความใน Java) ที่เพิ่มเข้าไปในลายเซ็นของฟังก์ชัน ซึ่งระบุว่าฟังก์ชันนั้นสามารถโยนข้อยกเว้นใดได้บ้าง ตัวอย่างเช่น โค้ดนี้ถูกต้องในC++03 :
#include <stdexcept>using std :: domain_error ; using std :: invalid_argument ;// นี่อาจคล้ายกับลายเซ็นของ Java // void performSomeOperation(int a, int b) throws InvalidArgumentException, ArithmeticException; void performSomeOperation ( int a , int b ) throw ( invalid_argument , domain_error ) { // ... }ข้อความ ใน C++ throwสามารถระบุชนิดข้อมูลได้จำนวนเท่าใดก็ได้ แม้กระทั่งชนิดข้อมูลพื้นฐานและคลาสที่ไม่สืบทอดมาจากstd::exception(เนื่องจาก C++ รองรับการโยนข้อยกเว้นของอ็อบเจ็กต์ทุกชนิด) หากไม่มีการระบุชนิดข้อมูลในthrowข้อความ ก็จะหมายความว่าฟังก์ชันนั้นจะไม่โยนข้อยกเว้นใดๆ เลย
ข้อกำหนดข้อยกเว้นไม่ได้ถูกบังคับใช้ในระหว่างการคอมไพล์ การละเมิดส่งผลให้มีการเรียกใช้ ฟังก์ชันไลบรารีมาตรฐาน std::unexpected()[หมายเหตุ 3 ] [ 48 ] สามารถกำหนดข้อกำหนดข้อยกเว้นว่างเปล่าได้ ซึ่งบ่งชี้ว่าฟังก์ชันจะไม่โยนข้อยกเว้นใดๆ สิ่งนี้ไม่ได้ถูกกำหนดให้เป็นค่าเริ่มต้นเมื่อมีการเพิ่มการจัดการข้อยกเว้นลงในภาษา เนื่องจากจะต้องมีการแก้ไขโค้ดที่มีอยู่มากเกินไป จะขัดขวางการโต้ตอบกับโค้ดที่เขียนด้วยภาษาอื่นๆ และจะล่อลวงให้โปรแกรมเมอร์เขียนตัวจัดการมากเกินไปในระดับท้องถิ่น[ 48 ]อย่างไรก็ตาม การใช้ข้อกำหนดข้อยกเว้นว่างเปล่าอย่างชัดเจนอาจทำให้คอมไพเลอร์ C++ สามารถดำเนินการเพิ่มประสิทธิภาพโค้ดและโครงสร้างสแต็กได้อย่างมีนัยสำคัญ ซึ่งไม่สามารถทำได้เมื่อการจัดการข้อยกเว้นอาจเกิดขึ้นในฟังก์ชัน[ 24 ] นักวิเคราะห์บางคนมองว่าการใช้ข้อกำหนดข้อยกเว้นอย่างถูกต้องใน C++ นั้นทำได้ยาก[ 49 ]การใช้ข้อกำหนดข้อยกเว้นนี้รวมอยู่ในC++98และC++03ถูกยกเลิกในมาตรฐานภาษา C++ ปี 2012 ( C++11 ) [ 50 ]และถูกลบออกจากภาษาในC++17ข้อความ throws ถูกแทนที่ด้วยnoexceptข้อความ ฟังก์ชันที่ไม่โยนข้อยกเว้นใดๆ จะถูกระบุด้วยnoexceptคำหลัก และระบุว่าฟังก์ชันจะโยนข้อยกเว้น แม้ว่าข้อความจะถูกลบออกจากภาษาแล้ว การเขียนเฉพาะในลายเซ็นยังคงถูกต้องตามกฎหมายและเทียบเท่ากับ(การไม่ระบุข้อยกเว้นใดๆ ในข้อความหมายความว่าไม่สามารถโยนข้อยกเว้นได้) อย่างไรก็ตาม วิธีนี้ยังคงถือว่าล้าสมัย สำหรับการเปลี่ยนผ่านโค้ดเบสที่ใช้ข้อความ ข้อความที่ถูกลบสามารถกำหนดใหม่เป็นมาโครได้ นี่เป็นเพียงการแก้ไขชั่วคราวเพื่อให้โค้ดสามารถคอมไพล์ได้ ไม่ใช่การใช้งานจริงของข้อยกเว้นที่ตรวจสอบได้ การใช้วิธีนี้ เราสามารถเลียนแบบข้อความ ของ Java ได้เช่นกันnoexcept(false)throwthrow()noexceptthrowthrowthrowthrows
// ถ้า throw() ว่างเปล่า มันจะขยายเป็น noexcept(true) (เหมือนกับ noexcept) // มิฉะนั้น มันจะขยายเป็น noexcept(false) // คำสั่ง throw ที่ไม่มีวงเล็บจะไม่ขยาย#define throw(...) noexcept(__VA_OPT__(!)true)import std ;การใช้งานstd :: runtime_error ;คลาสXException : public runtime_error {}; คลาสYException : public runtime_error {};// throw(Es...) จะขยายเป็น noexcept(false) void performSomeOperation ( int x , int y ) throw ( XException , YException ) { if ( x > y ) { // throw ที่ไม่มีวงเล็บจะไม่ขยายเป็นมาโครthrow XException ( "x > y" ); } else if ( y > x ) { throw YException ( "y > x" ); } std :: println ( "x = y = {}" , x ); }// throw() จะขยายเป็น noexcept(true) void willNotThrow ( int x ) throw () { std :: println ( "x = {}" , x ); }เราสามารถระบุได้ว่าฟังก์ชันหนึ่งทำงานnoexceptโดยมีเงื่อนไขว่าอีกฟังก์ชันหนึ่งต้องทำงานnoexceptเช่นกัน ดังนี้:
void mightThrow ();// noexcept ตัวแรกคือส่วนของ noexcept ส่วนตัวที่สองคือตัวดำเนินการ noexcept ซึ่งประเมินค่าเป็นค่าบูลีนvoid f () noexcept ( noexcept ( mightThrow ()));แม้ว่า C++ จะไม่มีข้อยกเว้นแบบตรวจสอบได้ (checked exceptions) แต่เราสามารถส่งต่ออ็อบเจ็กต์ที่ถูกโยนขึ้นไปบนสแต็กได้เมื่ออยู่ภายในcatchบล็อก โดยการเขียน(โดยไม่ต้องระบุอ็อบเจ็กต์) ซึ่งจะโยนอ็อบเจ็กต์ที่ถูกจับได้อีกครั้ง ทำให้สามารถดำเนินการต่างๆ ภายในบล็อกที่จับอ็อบเจ็กต์นั้นได้ ก่อนที่จะตัดสินใจว่าจะอนุญาตให้อ็อบเจ็กต์นั้นส่งต่อขึ้นไปด้านบนหรือไม่ throw;catch
มีเครื่องมือวิเคราะห์ข้อยกเว้นที่ไม่ได้จับสำหรับภาษาการเขียนโปรแกรมOCaml [ 51 ]เครื่องมือนี้รายงานชุดของข้อยกเว้นที่เกิดขึ้นเป็นลายเซ็นประเภทที่ขยายออกไป แต่ต่างจากข้อยกเว้นที่ตรวจสอบแล้ว เครื่องมือนี้ไม่ต้องการคำอธิบายประกอบทางไวยากรณ์ใดๆ และเป็นแบบภายนอก (กล่าวคือ สามารถคอมไพล์และรันโปรแกรมได้โดยไม่ต้องตรวจสอบข้อยกเว้น)
ในภาษา C++ เราสามารถจัดการข้อยกเว้นแบบ "โปเกมอน" ได้เช่นกัน เช่นเดียวกับในภาษา Java C++ รองรับบล็อกที่สามารถดักจับอ็อบเจ็กต์ที่ถูกโยนออกมาได้ อย่างไรก็ตามข้อเสียคือไม่ได้ระบุชื่อของอ็อบเจ็กต์ที่ถูกดักจับ ซึ่งหมายความว่าไม่สามารถอ้างอิงถึงได้ เนื่องจากในภาษาอย่าง Java เฉพาะคลาสที่สืบทอดมาจากคลาสแม่เท่านั้นที่สามารถถูกโยนข้อยกเว้นได้ ในขณะที่ใน C++ ประเภทใดก็ได้ (แม้แต่ประเภทพื้นฐาน) ก็สามารถถูกโยนข้อยกเว้นได้ ดังนั้นจึงไม่มีวิธีใดที่ปลอดภัยอย่างแน่นอนในการจัดเก็บการอ้างอิงถึงอ็อบเจ็กต์ที่ถูกดักจับของบล็อกดักจับทุกประเภท catch(Throwablet)catch(...)catch(...)java.lang.Throwable
import std ;การใช้std :: exception ;// ดักจับเฉพาะข้อยกเว้น: try { // ... } catch ( const exception & e ) { // ดักจับเฉพาะข้อยกเว้น: std :: println ( "พบข้อยกเว้น: {}" , e . what ()); } catch (...) { // ดักจับวัตถุที่ถูกโยนทั้งหมด: std :: println ( "พบข้อผิดพลาดที่ไม่ทราบสาเหตุ" ); }ภาษาRustแทนที่จะใช้ข้อยกเว้นโดยสิ้นเชิง จะแสดงข้อยกเว้นที่กู้คืนได้เป็นประเภทผลลัพธ์[ 52 ] [ 53 ]ซึ่งแสดงเป็นResult<T, E>(หรือexpected<T, E>ใน C++) ข้อดีของประเภทผลลัพธ์เหนือข้อยกเว้นที่ตรวจสอบได้คือ ในขณะที่ทั้งประเภทผลลัพธ์และข้อยกเว้นที่ตรวจสอบได้บังคับให้ผู้ใช้จัดการข้อผิดพลาดทันที แต่ยังสามารถแสดงเป็นประเภทการส่งคืนโดยตรงภายในระบบประเภท ของภาษา ได้ ซึ่งแตกต่างจากข้อยกเว้นที่ตรวจสอบได้ที่ข้อยกเว้นที่อาจถูกโยนที่ประกาศไว้เป็นส่วนหนึ่งของลายเซ็นฟังก์ชัน แต่ไม่ได้เป็นส่วนหนึ่งของประเภทการส่งคืนโดยตรง
การตรวจสอบข้อยกเว้นแบบไดนามิก
จุดประสงค์ของรูทีนการจัดการข้อยกเว้นคือเพื่อให้แน่ใจว่าโค้ดสามารถจัดการกับสภาวะข้อผิดพลาดได้ เพื่อให้แน่ใจว่ารูทีนการจัดการข้อยกเว้นมีความแข็งแกร่งเพียงพอ จำเป็นต้องป้อนอินพุตที่ไม่ถูกต้องหรือไม่คาดคิดหลากหลายรูปแบบให้กับโค้ด เช่น ที่สามารถสร้างได้ผ่านการฉีดข้อผิดพลาด ในซอฟต์แวร์ และการทดสอบการเปลี่ยนแปลง (ซึ่งบางครั้งเรียกว่าการทดสอบแบบฟัซซ์ ) ซอฟต์แวร์ประเภทหนึ่งที่ยากที่สุดในการเขียนรูทีนการจัดการข้อยกเว้นคือซอฟต์แวร์โปรโตคอล เนื่องจากต้องเตรียมการใช้งานโปรโตคอลที่แข็งแกร่งเพื่อรับอินพุตที่ไม่เป็นไปตามข้อกำหนดที่เกี่ยวข้อง
เพื่อให้มั่นใจได้ว่าการวิเคราะห์การถดถอย ที่มีความหมาย สามารถดำเนินการได้ตลอดกระบวนการพัฒนาซอฟต์แวร์การทดสอบการจัดการข้อยกเว้นใดๆ ควรเป็นแบบอัตโนมัติอย่างมาก และกรณีทดสอบต้องถูกสร้างขึ้นในลักษณะที่เป็นวิทยาศาสตร์และทำซ้ำได้ มีระบบเชิงพาณิชย์หลายระบบที่ทำการทดสอบดังกล่าว
ในสภาพแวดล้อมของเอนจินรันไทม์ เช่นJavaหรือ.NETมีเครื่องมือที่เชื่อมต่อกับเอนจินรันไทม์ และทุกครั้งที่เกิดข้อยกเว้นที่น่าสนใจ เครื่องมือเหล่านี้จะบันทึกข้อมูลการดีบักที่มีอยู่ในหน่วยความจำ ณ เวลาที่เกิดข้อยกเว้น ( ค่า ในสแต็กการเรียกและฮีป ) เครื่องมือเหล่านี้เรียกว่า เครื่องมือ จัดการข้อยกเว้นอัตโนมัติหรือเครื่องมือดักจับข้อผิดพลาด และให้ข้อมูลเกี่ยวกับ 'สาเหตุหลัก' ของข้อยกเว้น
ข้อยกเว้นแบบอะซิงโครนัส
ข้อยกเว้นแบบอะซิงโครนัสคือเหตุการณ์ที่เกิดขึ้นจากเธรดแยกต่างหากหรือกระบวนการภายนอก เช่น การกดCtrl-Cเพื่อขัดจังหวะโปรแกรม การรับสัญญาณหรือการส่งข้อความขัดจังหวะ เช่น "หยุด" หรือ "ระงับ" จากเธรดการทำงานอื่น[ 54 ] [ 55 ]ในขณะที่ข้อยกเว้นแบบซิงโครนัสเกิดขึ้นที่throwคำสั่งเฉพาะ ข้อยกเว้นแบบอะซิงโครนัสสามารถเกิดขึ้นได้ทุกเมื่อ ดังนั้น การจัดการข้อยกเว้นแบบอะซิงโครนัสจึงไม่สามารถเพิ่มประสิทธิภาพโดยคอมไพเลอร์ได้ เนื่องจากคอมไพเลอร์ไม่สามารถพิสูจน์ได้ว่าไม่มีข้อยกเว้นแบบอะซิงโครนัส นอกจากนี้ยังยากที่จะเขียนโปรแกรมให้ถูกต้อง เนื่องจากข้อยกเว้นแบบอะซิงโครนัสจะต้องถูกบล็อกในระหว่างการดำเนินการทำความสะอาดเพื่อหลีกเลี่ยงการรั่วไหลของทรัพยากร
โดยทั่วไป ภาษาโปรแกรมมักหลีกเลี่ยงหรือจำกัดการจัดการข้อยกเว้นแบบอะซิงโครนัส ตัวอย่างเช่น C++ ห้ามการยกเว้นจากตัวจัดการสัญญาณ และ Java ได้ยกเลิกการใช้งานjava.lang.ThreadDeatherror ใน Java 20 ซึ่งเคยใช้เพื่ออนุญาตให้เธรดหนึ่งหยุดเธรดอื่นได้[ 56 ]คุณสมบัติอีกอย่างหนึ่งคือกลไกแบบกึ่งอะซิงโครนัสที่ยกเว้นข้อยกเว้นแบบอะซิงโครนัสเฉพาะในระหว่างการดำเนินการบางอย่างของโปรแกรม ตัวอย่างเช่น Java จะ ส่งjava.lang.Thread::interrupt()ผลกระทบต่อเธรดก็ต่อเมื่อเธรดเรียกใช้การดำเนินการที่โยนข้อ ยกเว้น [ 57 ] API POSIX ที่คล้ายกันมีเงื่อนไขการแข่งขันซึ่งทำให้ไม่สามารถใช้งานได้อย่างปลอดภัย[ 58 ]java.lang.InterruptedExceptionpthread_cancel
ระบบปรับสภาพ
Common Lisp , R , [ 59 ] DylanและSmalltalkมีระบบเงื่อนไข[ 60 ] (ดูCommon Lisp Condition System ) ที่ครอบคลุมระบบการจัดการข้อยกเว้นที่กล่าวถึงข้างต้น ในภาษาหรือสภาพแวดล้อมเหล่านั้น การเกิดเงื่อนไข (ซึ่งเป็น "การสรุปทั่วไปของข้อผิดพลาด" ตามที่Kent Pitman กล่าวไว้ ) บ่งบอกถึงการเรียกฟังก์ชัน และเฉพาะในช่วงท้ายของตัวจัดการข้อยกเว้นเท่านั้นที่อาจมีการตัดสินใจที่จะคลายสแต็ก
เงื่อนไขเป็นการสรุปทั่วไปของข้อยกเว้น เมื่อเกิดเงื่อนไขขึ้น จะมีการค้นหาและเลือกตัวจัดการเงื่อนไขที่เหมาะสมตามลำดับสแต็กเพื่อจัดการเงื่อนไขนั้น เงื่อนไขที่ไม่แสดงถึงข้อผิดพลาดอาจไม่ได้รับการจัดการโดยสิ้นเชิง จุดประสงค์เดียวของเงื่อนไขเหล่านั้นอาจเป็นการส่งต่อคำแนะนำหรือคำเตือนไปยังผู้ใช้[ 61 ]
ข้อยกเว้นที่ต่อเนื่อง
สิ่งนี้เกี่ยวข้องกับสิ่งที่เรียกว่าโมเดลการดำเนินการต่อในการจัดการข้อยกเว้น ซึ่งข้อยกเว้นบางอย่างสามารถดำเนินการต่อได้ กล่าว คือ อนุญาตให้กลับไปยังนิพจน์ที่ส่งสัญญาณข้อยกเว้นได้ หลังจากดำเนินการแก้ไขในตัวจัดการแล้ว ระบบเงื่อนไขได้รับการขยายความดังนี้: ภายในตัวจัดการเงื่อนไขที่ไม่ร้ายแรง (หรือที่เรียกว่าข้อยกเว้นที่ดำเนินการต่อได้ ) สามารถกระโดดไปยังจุดเริ่มต้นใหม่ที่กำหนดไว้ล่วงหน้า (หรือที่เรียกว่าการเริ่มต้นใหม่ ) ซึ่งอยู่ระหว่างนิพจน์ที่ส่งสัญญาณและตัวจัดการเงื่อนไข การเริ่มต้นใหม่เป็นฟังก์ชันที่ปิดอยู่เหนือสภาพแวดล้อมทางศัพท์บางอย่าง ทำให้โปรแกรมเมอร์สามารถซ่อมแซมสภาพแวดล้อมนี้ก่อนที่จะออกจากตัวจัดการเงื่อนไขโดยสมบูรณ์หรือคลายสแต็กแม้เพียงบางส่วน
ตัวอย่างเช่น เงื่อนไข ENDPAGEใน PL/I หน่วย ON อาจเขียนบรรทัดท้ายหน้าและบรรทัดหัวหน้าสำหรับหน้าถัดไป จากนั้นจึงดำเนินการต่อเพื่อประมวลผลโค้ดที่ถูกขัดจังหวะ
การเริ่มต้นใหม่เป็นกลไกที่แยกต่างหากจากนโยบาย
นอกจากนี้ การจัดการเงื่อนไขยังช่วยแยกกลไกและนโยบายออกจากกัน การเริ่มต้นใหม่มีกลไกต่างๆ ที่เป็นไปได้สำหรับการกู้คืนจากข้อผิดพลาด แต่ไม่ได้เลือกกลไกที่เหมาะสมในสถานการณ์นั้นๆ นั่นเป็นหน้าที่ของตัวจัดการเงื่อนไข ซึ่ง (เนื่องจากอยู่ในโค้ดระดับสูงกว่า) จึงสามารถเข้าถึงมุมมองที่กว้างกว่าได้
ตัวอย่างเช่น สมมติว่ามีฟังก์ชันในไลบรารีที่ใช้สำหรับแยกวิเคราะห์ ข้อมูลในไฟล์ syslog เพียงรายการเดียว ฟังก์ชันนี้ควรทำอย่างไรหากข้อมูลนั้นผิดรูปแบบ? ไม่มีคำตอบที่ถูกต้องเพียงคำตอบเดียว เพราะไลบรารีเดียวกันนี้อาจถูกนำไปใช้ในโปรแกรมที่มีจุดประสงค์แตกต่างกันมากมาย ในโปรแกรมดูไฟล์บันทึกแบบโต้ตอบ การกระทำที่ถูกต้องอาจเป็นการส่งคืนข้อมูลโดยไม่แยกวิเคราะห์ เพื่อให้ผู้ใช้สามารถดูได้ แต่ในโปรแกรมสรุปบันทึกอัตโนมัติ การกระทำที่ถูกต้องอาจเป็นการใส่ค่าว่าง (null) สำหรับฟิลด์ที่ไม่สามารถอ่านได้ แต่ให้ยกเลิกการทำงานพร้อมแสดงข้อผิดพลาด หากมีข้อมูลผิดรูปแบบมากเกินไป
กล่าวคือ คำถามนี้สามารถตอบได้เฉพาะในแง่ของเป้าหมายที่กว้างขึ้นของโปรแกรม ซึ่งฟังก์ชันไลบรารีทั่วไปไม่ทราบ อย่างไรก็ตาม การออกจากโปรแกรมพร้อมข้อความแสดงข้อผิดพลาดนั้นแทบจะไม่ใช่คำตอบที่ถูกต้องเสมอไป ดังนั้น แทนที่จะออกจากโปรแกรมพร้อมข้อความแสดงข้อผิดพลาด ฟังก์ชันอาจสร้างการเริ่มต้นใหม่ที่เสนอวิธีการต่างๆ ในการดำเนินการต่อ เช่น การข้ามรายการบันทึก การใส่ค่าเริ่มต้นหรือค่าว่างสำหรับฟิลด์ที่ไม่สามารถอ่านได้ การขอค่าที่ขาดหายไปจากผู้ใช้หรือการย้อนกลับสแต็กและยกเลิกการประมวลผลพร้อมข้อความแสดงข้อผิดพลาด การเริ่มต้นใหม่ที่เสนอนั้นเป็นกลไกที่มีอยู่สำหรับการกู้คืนจากข้อผิดพลาด การเลือกการเริ่มต้นใหม่โดยตัวจัดการเงื่อนไขจะเป็นตัวกำหนด นโยบาย
การวิจารณ์
การจัดการข้อยกเว้นมักไม่ได้รับการจัดการอย่างถูกต้องในซอฟต์แวร์ โดยเฉพาะอย่างยิ่งเมื่อมีแหล่งที่มาของข้อยกเว้นหลายแหล่งการวิเคราะห์การไหลของข้อมูลของโค้ด Java 5 ล้านบรรทัดพบข้อบกพร่องในการจัดการข้อยกเว้นมากกว่า 1300 รายการ [ 13 ] โดยอ้างอิงจากการศึกษาหลายครั้งก่อนหน้านี้โดยผู้อื่น (1999–2004) และผลลัพธ์ของตนเอง Weimer และ Necula เขียนว่าปัญหาสำคัญของข้อยกเว้นคือ "มันสร้างเส้นทางควบคุมการไหลที่ซ่อนอยู่ซึ่งยากสำหรับโปรแกรมเมอร์ที่จะทำความเข้าใจ" [ 13 ] : 8:27 "ในขณะที่ try-catch-finally นั้นเรียบง่ายในเชิงแนวคิด แต่ก็มีคำอธิบายการดำเนินการที่ซับซ้อนที่สุดในข้อกำหนดของภาษา [Gosling et al. 1996] และต้องใช้ "if" ที่ซ้อนกันสี่ระดับในคำอธิบายภาษาอังกฤษอย่างเป็นทางการ กล่าวโดยสรุปคือ มี กรณีพิเศษจำนวนมากที่โปรแกรมเมอร์มักมองข้าม" [ 13 ] : 8:13–8:14
ข้อยกเว้น ซึ่งเป็นกระบวนการทำงานที่ไม่เป็นระเบียบ เพิ่มความเสี่ยงต่อการรั่วไหลของทรัพยากร (เช่น การหลุดออกจากส่วนที่ถูกล็อกด้วยmutexหรือส่วนที่เปิดไฟล์ไว้ชั่วคราว) หรือสถานะที่ไม่สอดคล้องกัน มีเทคนิคต่างๆ ในการจัดการทรัพยากรเมื่อมีข้อยกเว้น โดยส่วนใหญ่จะใช้รูปแบบ dispose ร่วม กับการป้องกันการคลายการทำงาน (เช่นfinallyclause) ซึ่งจะปล่อยทรัพยากรโดยอัตโนมัติเมื่อการควบคุมออกจากส่วนของโค้ด
ในปี พ.ศ. 2523 Tony Hoare ได้อธิบาย ภาษาการเขียนโปรแกรม Adaว่า "...มีคุณสมบัติและข้อกำหนดเชิงสัญลักษณ์มากมาย ซึ่งหลายอย่างไม่จำเป็น และบางอย่าง เช่น การจัดการข้อยกเว้น ก็เป็นอันตรายด้วย [...] ไม่ควรอนุญาตให้ใช้ภาษานี้ในสถานะปัจจุบันในแอปพลิเคชันที่ความน่าเชื่อถือเป็นสิ่งสำคัญ [...] จรวดลำต่อไปที่จะหลงทางอันเป็นผลมาจากข้อผิดพลาดของภาษาการเขียนโปรแกรม อาจไม่ใช่จรวดสำรวจอวกาศที่เดินทางไปยังดาวศุกร์อย่างไม่เป็นอันตราย แต่มันอาจเป็นหัวรบนิวเคลียร์ที่ระเบิดเหนือเมืองใดเมืองหนึ่งของเราเอง" [ 62 ]
นัก พัฒนา Goเชื่อว่าสำนวน try-catch-finally ทำให้การควบคุมการไหลของโปรแกรมคลุมเครือ[ 63 ]และได้นำกลไกที่คล้ายกับข้อยกเว้นpanicมาrecoverใช้[ 64 ] ซึ่งแตกต่างจากตรงที่สามารถเรียกใช้ได้เฉพาะภายในบล็อกโค้ดในฟังก์ชันเท่านั้น ดังนั้นตัวจัดการจึงสามารถทำความสะอาดและเปลี่ยนแปลงค่าส่งคืนของฟังก์ชันได้เท่านั้น และไม่สามารถส่งการควบคุมกลับไปยังจุดใด ๆ ภายในฟังก์ชันได้[ 65 ] บล็อก นั้นทำงานคล้ายกับข้อความ recover()catchdeferdeferfinally
ภาษาRustไม่มีข้อยกเว้น แต่จะใช้( ประเภทผลลัพธ์ ) สำหรับจัดการข้อผิดพลาดขณะรันไทม์ และสำหรับข้อผิดพลาดร้ายแรงจะใช้มาโคร Result<T,E>panic!()
ดูเพิ่มเติม
- การจัดการข้อผิดพลาดอัตโนมัติ
- ต่อเนื่อง
- การเขียนโปรแกรมเชิงป้องกัน
- ความปลอดภัยที่เป็นข้อยกเว้น
- ประเภทตัวเลือกและประเภทผลลัพธ์ : วิธีการจัดการข้อผิดพลาดทางเลือกในการเขียนโปรแกรมเชิงฟังก์ชันโดยไม่มีข้อยกเว้น
หมายเหตุ
- ^ในภาษาโปรแกรมอย่าง PL/I การออกจากตัวจัดการข้อยกเว้นตามปกติจะคลายสแต็กออก
- ^จะมี "ต้นทุน [การประมวลผล] เป็นศูนย์" ก็ต่อเมื่อไม่มีการโยนข้อยกเว้น (แม้ว่าจะมีต้นทุนด้านหน่วยความจำเนื่องจากจำเป็นต้องใช้หน่วยความจำสำหรับตารางค้นหา) จะมีต้นทุน (ซึ่งอาจมีนัยสำคัญ) หากมีการโยนข้อยกเว้น (นั่นคือ หาก มีการเรียกใช้) การใช้งานการจัดการข้อยกเว้นอาจจำกัด การเพิ่มประสิทธิภาพของคอมไพเลอร์ที่สามารถทำได้
throwด้วย - ^โปรดทราบว่าฟังก์ชันนี้ถูกลบออกใน C++17และชื่อนี้ได้รับการนำกลับมาใช้ใหม่ใน C++23ในรูปแบบคลาสประเภทผลลัพธ์
std::unexpected<T, E>
สรุปเนื้อหา
ข้อมูลสำคัญจากบทความ
ข้อมูลสำคัญเกี่ยวกับ การจัดการข้อผิดพลาด (การเขียนโปรแกรม)
ในการเขียนโปรแกรมคอมพิวเตอร์ มีกลไก ภาษาโปรแกรมหลายอย่างสำหรับการจัดการข้อยกเว้นคำว่าข้อยกเว้นโดยทั่วไปใช้เพื่อหมายถึงโครงสร้างข้อมูลที่เก็บข้อมูลเกี่ยวกับสภาวะผิดปกติ
การใช้งาน
ภาษาโปรแกรมแต่ละภาษามีความแตกต่างกันอย่างมากในแนวคิดเกี่ยวกับข้อยกเว้น ข้อยกเว้นสามารถใช้เพื่อแสดงและจัดการสถานการณ์ที่ผิดปกติ คาดเดาไม่ได้ หรือผิดพลาดได้ แต่ยังสามารถใช้เป็นโครงสร้างควบคุมการไหลเพื่อจัดการสถานการณ์ปกติได้อีกด้วย ตัวอย่างเช่น ตัววนซ้ำของ...
ประวัติศาสตร์
คอมไพเลอร์ Fortran รุ่นแรกๆ ของ IBM มีคำสั่งสำหรับทดสอบเงื่อนไขที่ผิดปกติ ซึ่งรวมถึง คำสั่ง `true` , `false` และ ` false` เพื่อความเป็นอิสระจากเครื่องจักร คำสั่งเหล่านี้จึงไม่ได้รวมอยู่ใน FORTRAN IV หรือมาตรฐาน Fortran 66 อย่างไรก็ตาม ตั้งแต่ Fortran 2003...
ไวยากรณ์
ภาษาคอมพิวเตอร์หลายภาษามีระบบรองรับทางไวยากรณ์ในตัวสำหรับข้อยกเว้นและการจัดการข้อยกเว้น ซึ่งรวมถึง ActionScript , Ada , BlitzMax , C++ , C# , Clojure , COBOL , D , ECMAScript , Eiffel , Java , ML , Object Pascal (เช่น Delphi , Free Pascal และอื่นๆ),...