เหตุผลที่ข้อยกเว้นเป็นวิธีการดีบักที่ทรงพลังคือพวกเขาตอบคำถามสามข้อต่อไปนี้:
1. เกิดอะไรขึ้น?
2. คุณทำผิดพลาดที่ไหน?
3. ทำไมมีบางอย่างผิดปกติเกิดขึ้น?
ในกรณีที่มีการใช้ข้อยกเว้นอย่างมีประสิทธิภาพคำตอบประเภทข้อยกเว้น "สิ่งที่" ถูกโยนลงไปคำตอบของการติดตามสแต็กข้อยกเว้น "ที่" ถูกโยนลงไปข้อมูลข้อยกเว้นคำตอบ "ทำไม" ถูกโยนลงไปและหากข้อยกเว้นของคุณไม่ตอบคำถามทั้งหมดข้างต้นคุณอาจไม่ได้ใช้งานได้ดี
มีสามหลักการที่สามารถช่วยให้คุณใช้ข้อยกเว้นที่ดีได้สูงสุดระหว่างการดีบัก หลักการทั้งสามนี้คือ:
1. เฉพาะและชัดเจน
2. โยนมันเร็ว
3. การจับหน่วงเวลา
เพื่ออธิบายหลักการทั้งสามของการจัดการข้อยกเว้นที่มีประสิทธิภาพบทความนี้กล่าวถึงมันโดยการสร้างผู้จัดการฝ่ายการเงินส่วนบุคคล JCheckbook ซึ่งใช้ในการบันทึกและติดตามกิจกรรมบัญชีธนาคารเช่นเงินฝากและการถอนและการออกใบเรียกเก็บเงิน
คอนกรีตและชัดเจน
Java กำหนดลำดับชั้นของคลาสข้อยกเว้นซึ่งเริ่มต้นด้วยการโยนได้ขยายข้อผิดพลาดและข้อยกเว้นและข้อยกเว้นขยาย RuntimeException ดังแสดงในรูปที่ 1
คลาสทั้งสี่นี้เป็นแบบทั่วไปและไม่ได้ให้ข้อมูลข้อผิดพลาดมากนัก แม้ว่าการสร้างอินสแตนซ์คลาสเหล่านี้จะถูกกฎหมายตามกฎหมาย (เช่น: ใหม่ throwable ()) แต่ก็เป็นการดีที่สุดที่จะถือว่าพวกเขาเป็นคลาสฐานเสมือนจริงและใช้คลาสย่อยที่มีความเชี่ยวชาญมากขึ้น Java ได้จัดทำคลาสย่อยข้อยกเว้นจำนวนมาก หากคุณต้องการเฉพาะเจาะจงมากขึ้นคุณสามารถกำหนดคลาสข้อยกเว้นของคุณเองได้
ตัวอย่างเช่น: แพ็คเกจ java.io กำหนดคลาสย่อยของคลาสข้อยกเว้น ioexception ซึ่งมีความเชี่ยวชาญมากขึ้นและคลาสย่อยของ iOException เช่น filenotfoundException, eOfexception และ ObjectStreamexception แต่ละคนอธิบายถึงคลาสเฉพาะของข้อผิดพลาด I/O: การสูญเสียไฟล์การสิ้นสุดไฟล์ข้อยกเว้นและการสตรีมวัตถุที่เป็นอนุกรม ข้อยกเว้นที่เฉพาะเจาะจงมากขึ้นโปรแกรมของเราจะสามารถตอบคำถาม "สิ่งที่ผิดพลาด"
นอกจากนี้ยังเป็นสิ่งสำคัญที่จะพยายามให้ชัดเจนที่สุดเท่าที่จะเป็นไปได้เมื่อจับข้อยกเว้น ตัวอย่างเช่น: JCheckbook สามารถจัดการ FilenotFoundException โดยการเรียกร้องชื่อไฟล์ของผู้ใช้อีกครั้ง สำหรับ eOfexception มันสามารถดำเนินการต่อเพื่อดำเนินการตามข้อมูลที่อ่านก่อนที่จะมีการโยนข้อยกเว้น หากมีการโยน ObjectStreamexception โปรแกรมควรแจ้งให้ผู้ใช้ทราบว่าไฟล์นั้นเสียหายและควรใช้ไฟล์สำรองหรือไฟล์อื่น ๆ
Java ทำให้ง่ายต่อการจับข้อยกเว้นอย่างชัดเจนเนื่องจากเราสามารถกำหนดบล็อกการจับหลายบล็อกสำหรับบล็อกลองเดียวกันเพื่อให้สามารถจัดการข้อยกเว้นแต่ละข้อได้อย่างเหมาะสม
ไฟล์ prefsfile = ไฟล์ใหม่ (prefsfilename); ลอง {readpreferences (prefsfile);} catch (filenotfoundexception e) {// แจ้งเตือนผู้ใช้ว่าไฟล์ที่ระบุ // ไม่มีอยู่} catch (eofexception e) {// แจ้งเตือนผู้ใช้ว่าการสิ้นสุดของไฟล์ //) ผู้ใช้ที่เกิดข้อผิดพลาด I/O // อื่น ๆ เกิดขึ้น}JCheckbook ให้ข้อมูลที่ชัดเจนเกี่ยวกับการจับข้อยกเว้นโดยใช้การจับหลายบล็อก ตัวอย่างเช่น: หากถูกจับ FilenotFoundException ก็สามารถแจ้งให้ผู้ใช้ระบุไฟล์อื่น ในบางกรณีความพยายามในการเข้ารหัสเพิ่มเติมที่เกิดจากการจับหลายบล็อกอาจเป็นภาระที่ไม่จำเป็น แต่ในตัวอย่างนี้รหัสพิเศษจะช่วยให้โปรแกรมให้การตอบสนองที่เป็นมิตรกับผู้ใช้มากขึ้น
นอกเหนือจากข้อยกเว้นที่จัดการโดยบล็อกการจับสามครั้งแรกบล็อกจับสุดท้ายยังให้ข้อมูลข้อผิดพลาดทั่วไปแก่ผู้ใช้เมื่อมีการโยน IOException ด้วยวิธีนี้โปรแกรมสามารถให้ข้อมูลเฉพาะให้มากที่สุด แต่ยังมีความสามารถในการจัดการข้อยกเว้นที่ไม่คาดคิดอื่น ๆ
บางครั้งนักพัฒนาตรวจจับข้อยกเว้นปกติและแสดงชื่อคลาสข้อยกเว้นหรือพิมพ์ข้อมูลสแต็กสำหรับ "ความจำเพาะ" อย่าทำอย่างนี้! ผู้ใช้จะมีอาการปวดหัวเมื่อเห็น java.io.EOFException หรือข้อมูลสแต็กแทนที่จะขอความช่วยเหลือ ควรจับข้อยกเว้นเฉพาะและผู้ใช้ควรได้รับการแจ้งเตือนด้วย "คำมนุษย์" อย่างไรก็ตามสแต็กข้อยกเว้นสามารถพิมพ์ในไฟล์บันทึกของคุณ โปรดจำไว้ว่ามีการใช้ข้อยกเว้นและข้อมูลสแต็กเพื่อช่วยนักพัฒนามากกว่าผู้ใช้
ในที่สุดก็ควรสังเกตว่า JCheckbook ไม่ได้รับข้อยกเว้นใน readPreferences() แต่ออกจากการจับและจัดการข้อยกเว้นไปยังเลเยอร์อินเทอร์เฟซผู้ใช้เพื่อให้ผู้ใช้สามารถได้รับการแจ้งเตือนโดยใช้กล่องโต้ตอบหรือวิธีอื่น ๆ สิ่งนี้เรียกว่า "การจับความล่าช้า" ซึ่งจะกล่าวถึงด้านล่าง
ทิ้งก่อน
ข้อมูลสแต็คข้อยกเว้นให้ลำดับที่แน่นอนของห่วงโซ่การเรียกวิธีการที่ทำให้เกิดข้อยกเว้นที่จะเกิดขึ้นรวมถึงชื่อคลาสชื่อเมธอดชื่อไฟล์รหัสและแม้แต่หมายเลขบรรทัดของการโทรแต่ละวิธีเพื่อค้นหาฉากที่เกิดขึ้นอย่างถูกต้อง
java.lang.nullpointerexceptionat java.io.fileinputstream.open (วิธีการดั้งเดิม) ที่ java.io.fileinputstream. <init> (fileinputstream.java:103) ที่ jcheckbook.jcheckbook.readpreferences jcheckbook.jcheckbook.startup (jcheckbook.java:116) ที่ jcheckbook.jcheckbook. <init> (jcheckbook.java:27) ที่ jcheckbook.jcheckbook.main (jcheckbook.java:318)
ด้านบนแสดงกรณีที่วิธีการเปิด () ของคลาส FileInputStream โยน nullpointerexception อย่างไรก็ตามโปรดทราบว่า FileInputStream.close() เป็นส่วนหนึ่งของไลบรารีคลาส Java มาตรฐานและเป็นไปได้ว่าปัญหาของข้อยกเว้นนี้คือรหัสของเราเองไม่ใช่ Java API ดังนั้นปัญหามีแนวโน้มที่จะปรากฏในวิธีการหนึ่งก่อนหน้านี้และโชคดีที่มันถูกพิมพ์ในข้อมูลสแต็ก
น่าเสียดายที่ nullpointerexception เป็นข้อมูลน้อยที่สุด (แต่ยังเป็นข้อยกเว้นที่พบบ่อยที่สุดและล้มเหลว) ใน Java มันไม่ได้พูดถึงสิ่งที่เราใส่ใจมากที่สุด: Null อยู่ที่ไหน ดังนั้นเราต้องย้อนกลับไปไม่กี่ก้าวและค้นหาว่ามีอะไรผิดพลาด
โดยขั้นตอนการสำรองข้อมูลการติดตามสแต็กและการตรวจสอบรหัสเราสามารถกำหนดได้ว่าสาเหตุของข้อผิดพลาดคือพารามิเตอร์ชื่อไฟล์ที่ว่างเปล่าถูกส่งผ่านไปยัง readPreferences() เนื่องจาก readPreferences() รู้ว่าไม่สามารถจัดการชื่อไฟล์ที่ว่างเปล่าตรวจสอบเงื่อนไขได้ทันที:
โมฆะสาธารณะ readpreferences (ชื่อไฟล์สตริง) พ่น unledalargumentException {ถ้า (filename == null) {โยน unlegalargumentException ใหม่ ("ชื่อไฟล์เป็นโมฆะ"); } // if //...Perform การดำเนินการอื่น ๆ ... inputStream ใน = new FileInputStream (ชื่อไฟล์); //... อ่านไฟล์การตั้งค่า ... } ด้วยการขว้างข้อยกเว้นก่อน (หรือที่เรียกว่า "ความล้มเหลวอย่างรวดเร็ว") ข้อยกเว้นนั้นชัดเจนและแม่นยำ ข้อมูลสแต็กสะท้อนให้เห็นถึงสิ่งที่ผิดพลาดทันที (มีค่าพารามิเตอร์ที่ผิดกฎหมาย) ทำไมมันผิดพลาด (ชื่อไฟล์ไม่สามารถเป็นโมฆะ) และที่มันผิดไป (ส่วนแรกของ readPreferences() ) ด้วยวิธีนี้ข้อมูลสแต็กของเราสามารถให้ความเป็นจริงได้:
java.lang.illegalargumentException: ชื่อไฟล์เป็น nullat jcheckbook.jcheckbook.readpreferences (jcheckbook.java:207) ที่ jcheckbook.jcheckbook.startup (jcheckbook.java:116) jcheckbook.jcheckbook.main (jcheckbook.java:318)
นอกจากนี้ข้อมูลข้อยกเว้นที่มีอยู่ในนั้น ("ชื่อไฟล์ว่างเปล่า") ทำให้ข้อยกเว้นยิ่งขึ้นโดยการตอบคำถามของสิ่งที่ว่างเปล่าอย่างชัดเจนซึ่งไม่สามารถใช้ได้กับเราในรหัสก่อนหน้า
ความล้มเหลวทำได้โดยการทิ้งข้อยกเว้นทันทีเมื่อตรวจพบข้อผิดพลาดการสร้างวัตถุที่ไม่จำเป็นหรือการใช้ทรัพยากรเช่นการเชื่อมต่อไฟล์หรือเครือข่ายสามารถหลีกเลี่ยงได้อย่างมีประสิทธิภาพ ในทำนองเดียวกันการดำเนินการทำความสะอาดที่เกิดขึ้นจากการเปิดทรัพยากรเหล่านี้สามารถบันทึกได้
การจับกุมล่าช้า
ข้อผิดพลาดอย่างหนึ่งที่ทั้งสามเณรและอาจารย์สามารถทำได้คือการจับโปรแกรมก่อนที่จะมีความสามารถในการจัดการข้อยกเว้น คอมไพเลอร์ Java ทางอ้อมอำนวยความสะดวกในพฤติกรรมนี้โดยกำหนดให้ข้อยกเว้นที่ตรวจสอบจะต้องถูกจับหรือถูกโยน วิธีที่เป็นธรรมชาติคือการปิดรหัสทันทีในบล็อกลองและใช้ Catch เพื่อจับข้อยกเว้นเพื่อหลีกเลี่ยงข้อผิดพลาดของคอมไพเลอร์
คำถามคือฉันควรทำอย่างไรถ้าฉันได้รับข้อยกเว้นหลังจากถูกจับ? สิ่งที่แย่ที่สุดที่ต้องทำคือไม่ทำอะไรเลย บล็อกจับที่ว่างเปล่านั้นเทียบเท่ากับการขว้างข้อยกเว้นทั้งหมดลงในหลุมดำและข้อมูลทั้งหมดที่สามารถอธิบายได้ว่าเมื่อใดที่เกิดข้อผิดพลาดที่ผิดจะหายไปตลอดกาล เป็นการดีกว่าที่จะเขียนข้อยกเว้นลงในบันทึกอย่างน้อยก็มีระเบียนที่จะตรวจสอบ แต่เราไม่สามารถคาดหวังให้ผู้ใช้อ่านหรือเข้าใจไฟล์บันทึกและข้อมูลข้อยกเว้น นอกจากนี้ยังไม่เหมาะสมที่จะมี readPreferences() แสดงกล่องโต้ตอบข้อความแสดงข้อผิดพลาดเนื่องจากในขณะที่ JCheckBook เป็นแอปพลิเคชันเดสก์ท็อปเรายังวางแผนที่จะเปลี่ยนเป็นเว็บแอปพลิเคชันที่ใช้ HTML ในกรณีนั้นการแสดงกล่องโต้ตอบข้อผิดพลาดไม่ใช่ตัวเลือก ในเวลาเดียวกันโดยไม่คำนึงถึงรุ่น HTML หรือ C/S ข้อมูลการกำหนดค่าจะถูกอ่านบนเซิร์ฟเวอร์และต้องแสดงข้อความแสดงข้อผิดพลาดไปยังเว็บเบราว์เซอร์หรือโปรแกรมไคลเอนต์ readPreferences() ควรคำนึงถึงความต้องการในอนาคตเหล่านี้เมื่อออกแบบ การแยกรหัสอินเทอร์เฟซผู้ใช้ที่เหมาะสมและตรรกะของโปรแกรมสามารถปรับปรุงความสามารถในการใช้ซ้ำของรหัสของเรา
การจับข้อยกเว้นก่อนเวลาอันควรก่อนการจัดการแบบมีเงื่อนไขมักจะส่งผลให้เกิดข้อผิดพลาดที่ร้ายแรงและข้อยกเว้นอื่น ๆ ตัวอย่างเช่นหากเมธอด readPreferences() ด้านบนจับภาพและบันทึก filenotFoundException ทันทีที่อาจถูกโยนทิ้งเมื่อเรียกตัวสร้าง FileInputStream รหัสจะกลายเป็นเช่นนี้:
โมฆะสาธารณะ ReadPreferences (ชื่อไฟล์สตริง) {// ... inputStream ใน = null; // อย่าทำสิ่งนี้ !!! ลอง {in = new fileInputStream (ชื่อไฟล์);} catch (filenotfoundException e) {logger.log (e);} in.read (... ); -รหัสข้างต้นจับมันโดยไม่สามารถกู้คืนจาก filenotfoundexception หากไม่พบไฟล์วิธีการต่อไปนี้จะไม่สามารถอ่านได้ จะเกิดอะไรขึ้นถ้า ReadPreferences () ถูกขอให้อ่านไฟล์ที่ไม่มีอยู่? แน่นอนว่า FilenotFoundException จะถูกบันทึกและถ้าเราดูไฟล์บันทึกในเวลานั้นเราจะรู้ อย่างไรก็ตามจะเกิดอะไรขึ้นเมื่อโปรแกรมพยายามอ่านข้อมูลจากไฟล์ เนื่องจากไฟล์ไม่มีอยู่ตัวแปรในนั้นว่างเปล่าและจะมีการโยน nullpointerexception
เมื่อทำการดีบักโปรแกรม Instinct บอกให้เราดูข้อมูลในตอนท้ายของบันทึก นั่นจะเป็น nullpointerexception และสิ่งที่น่ารำคาญมากคือข้อยกเว้นนี้ไม่ได้จำเพาะมาก ข้อความแสดงข้อผิดพลาดไม่เพียง แต่ทำให้เราเข้าใจผิดว่าเกิดอะไรขึ้น (ข้อผิดพลาดจริงคือ FilenotFoundException แทนที่จะเป็น nullpointerexception) แต่ยังทำให้เข้าใจผิดแหล่งที่มาของข้อผิดพลาด ปัญหาที่แท้จริงอยู่นอกจำนวนแถวที่ nullpointerexception โยนซึ่งอาจมีวิธีการโทรหลายวิธีและการทำลายชั้นเรียน ความสนใจของเราถูกดึงมาจากความผิดพลาดที่แท้จริงโดยปลาตัวน้อยจนกว่าเราจะมองย้อนกลับไปที่บันทึกเพื่อค้นหาแหล่งที่มาของปัญหา
เนื่องจากสิ่งที่ readPreferences() ควรทำจริง ๆ ไม่ได้รับข้อยกเว้นเหล่านี้จึงควรเป็นอย่างไร? ดูเหมือนว่าจะขัดกับความเข้าใจง่ายและวิธีที่เหมาะสมที่สุดคือไม่ทำอะไรเลยและไม่ได้รับข้อยกเว้นทันที ปล่อยให้ความรับผิดชอบต่อผู้โทร readPreferences() และปล่อยให้มันศึกษาวิธีที่เหมาะสมในการจัดการกับไฟล์การกำหนดค่าที่ขาดหายไป อาจแจ้งให้ผู้ใช้ระบุไฟล์อื่น ๆ หรือใช้ค่าเริ่มต้น หากไม่ได้ผลจริงๆมันอาจเตือนผู้ใช้และออกจากโปรแกรม
วิธีที่จะผ่านความรับผิดชอบสำหรับข้อยกเว้นการจัดการต้นน้ำของห่วงโซ่การโทรคือการประกาศข้อยกเว้นในประโยคการโยนของวิธีการ เมื่อประกาศข้อยกเว้นที่เป็นไปได้ให้ระวังยิ่งดีขึ้นเท่านั้น สิ่งนี้ใช้เพื่อระบุประเภทของข้อยกเว้นที่โปรแกรมที่เรียกวิธีการของคุณจำเป็นต้องรู้และเตรียมพร้อมที่จะจัดการ ตัวอย่างเช่นเวอร์ชัน "ล่าช้าการจับ" ของ readPreferences() อาจมีลักษณะเช่นนี้:
โมฆะสาธารณะ readpreferences (ชื่อไฟล์สตริง) พ่น unledalargumentException, filenotFoundException, ioexception {ถ้า (filename == null) {โยน unlegalargumentException ใหม่ ("ชื่อไฟล์เป็นโมฆะ"); } // ถ้า // ... inputStream ใน = new FileInputStream (ชื่อไฟล์); -ในทางเทคนิคแล้วข้อยกเว้นเพียงอย่างเดียวที่เราต้องประกาศคือ IOException แต่เราประกาศอย่างชัดเจนว่าวิธีการนี้อาจส่ง FilenotFoundException ไม่จำเป็นต้องประกาศผิดกฎหมายเนื่องจากเป็นข้อยกเว้นที่ไม่ได้ตรวจสอบ (เช่นคลาสย่อยของ RuntimeException) อย่างไรก็ตามมีการประกาศให้บันทึกรหัสของเรา (ข้อยกเว้นเหล่านี้ควรทำเครื่องหมายใน Javadocs ของวิธีการ)
แน่นอนว่าในที่สุดโปรแกรมของคุณจะต้องจับข้อยกเว้นมิฉะนั้นจะยุติโดยไม่คาดคิด แต่เคล็ดลับที่นี่คือการจับข้อยกเว้นในระดับที่เหมาะสมเพื่อให้โปรแกรมของคุณสามารถกู้คืนได้อย่างมีความหมายจากข้อยกเว้นและดำเนินการต่อโดยไม่ทำให้เกิดข้อผิดพลาดที่ลึกซึ้งยิ่งขึ้น หรือสามารถให้ข้อมูลที่ชัดเจนแก่ผู้ใช้รวมถึงการแนะนำให้กู้คืนจากข้อผิดพลาด หากวิธีการของคุณไม่สามารถทำได้อย่าจัดการกับข้อยกเว้นให้ทิ้งไว้ข้างหลังเพื่อจับและจัดการในระดับที่เหมาะสม
สรุป
นักพัฒนาที่มีประสบการณ์รู้ว่าปัญหาที่ใหญ่ที่สุดในการดีบักโปรแกรมไม่ใช่การแก้ไขข้อบกพร่อง แต่เพื่อค้นหาสถานที่ซ่อนของข้อบกพร่องจากรหัสจำนวนมาก ตราบใดที่คุณปฏิบัติตามหลักการสามข้อของบทความนี้ข้อยกเว้นของคุณสามารถช่วยคุณติดตามและกำจัดข้อบกพร่องทำให้โปรแกรมของคุณแข็งแกร่งและใช้งานง่ายมากขึ้น ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้และฉันหวังว่ามันจะช่วยคุณในการศึกษาหรือทำงาน