บทความนี้มุ่งเน้นไปที่ความเข้าใจผิดบางอย่างในการเลือกและการใช้ข้อยกเว้น Java ฉันหวังว่าผู้อ่านจะสามารถเชี่ยวชาญบางประเด็นและหลักการของการจัดการข้อยกเว้นและให้ความสนใจกับการสรุปและการเหนี่ยวนำ โดยการจัดการข้อยกเว้นเท่านั้นที่เราสามารถปรับปรุงคุณภาพพื้นฐานของนักพัฒนาปรับปรุงความทนทานของระบบเพิ่มประสบการณ์ผู้ใช้และปรับปรุงคุณค่าของผลิตภัณฑ์
ความเข้าใจผิด 1. ตัวเลือกที่ผิดปกติ
รูปที่ 1. การจำแนกความผิดปกติ
รูปที่ 1 อธิบายโครงสร้างของข้อยกเว้น ในความเป็นจริงเราทุกคนรู้ว่าความผิดปกติถูกแบ่งออกเป็นการตรวจจับความผิดปกติและความผิดปกติที่ไม่ตรวจจับ แต่ในทางปฏิบัติการประยุกต์ใช้ความผิดปกติทั้งสองนี้สับสน เนื่องจากข้อยกเว้นที่ไม่ตรวจพบนั้นใช้งานง่ายนักพัฒนาหลายคนคิดว่าการตรวจจับข้อยกเว้นนั้นไร้ประโยชน์ ในความเป็นจริงสถานการณ์การใช้งานที่ผิดปกติสามารถสรุปได้ดังนี้:
1. รหัสการโทรไม่สามารถดำเนินการต่อไปและจำเป็นต้องยกเลิกทันที มีความเป็นไปได้มากเกินไปสำหรับสถานการณ์นี้เช่นเซิร์ฟเวอร์ไม่ได้เชื่อมต่อพารามิเตอร์ไม่ถูกต้อง ฯลฯ ข้อยกเว้นที่ไม่ได้ตรวจจับจะใช้ในเวลาเหล่านี้และไม่จำเป็นต้องเรียกการจับภาพและประมวลผลรหัสที่ชัดเจนและรหัสนั้นกระชับและชัดเจน
2. รหัสการโทรต้องการการประมวลผลและการกู้คืนเพิ่มเติม หาก Sqlexception ถูกกำหนดให้เป็นข้อยกเว้นที่ไม่ถูกตรวจพบนักพัฒนาจะเชื่อตามธรรมชาติว่า Sqlexception ไม่จำเป็นต้องเรียกการจับภาพและการประมวลผลอย่างชัดเจนซึ่งจะนำไปสู่สถานการณ์ที่ร้ายแรงเช่นไม่ปิดการเชื่อมต่อ มันเป็นเพราะ Sqlexception ถูกกำหนดให้เป็นตรวจจับข้อยกเว้นที่นักพัฒนาจะถูกผลักดันให้จับและทำความสะอาดทรัพยากรอย่างชัดเจนหลังจากรหัสสร้างข้อยกเว้น แน่นอนหลังจากทำความสะอาดทรัพยากรคุณสามารถทำการยกเว้นข้อยกเว้นที่ไม่ได้ตรวจพบเพื่อป้องกันการดำเนินการของโปรแกรม จากการสังเกตและความเข้าใจการตรวจจับข้อยกเว้นส่วนใหญ่สามารถนำไปใช้กับคลาสเครื่องมือได้ Java Learning Group 669823128
ความเข้าใจผิด 2: แสดงข้อยกเว้นโดยตรงบนหน้าหรือไคลเอนต์
เป็นเรื่องปกติที่จะพิมพ์ข้อยกเว้นโดยตรงทางฝั่งไคลเอ็นต์ การใช้ JSP เป็นตัวอย่างเมื่อรหัสรันคอนเทนเนอร์จะพิมพ์ข้อมูลสแต็กข้อยกเว้นโดยตรงบนหน้าโดยค่าเริ่มต้น ในความเป็นจริงจากมุมมองของลูกค้าข้อยกเว้นใด ๆ ไม่มีความสำคัญในทางปฏิบัติและลูกค้าส่วนใหญ่ไม่สามารถเข้าใจข้อมูลข้อยกเว้นได้เลย การพัฒนาซอฟต์แวร์ควรพยายามหลีกเลี่ยงการนำเสนอข้อยกเว้นโดยตรงกับผู้ใช้
รายการ 1
<strong> แพ็คเกจ </strong> com.ibm.dw.sample.exception;/*** custom runtimeexception* เพิ่มแอตทริบิวต์รหัสข้อผิดพลาด*/<strong> สาธารณะ </strong> <strong> คลาส </strong> <strong> RuntimeException </strong> <strong> รหัสข้อผิดพลาด <strong> สาธารณะ </strong> <strong> คงที่ </strong> <strong> สุดท้าย </strong> จำนวนเต็มทั่วไป = 1000000; // รหัสข้อผิดพลาด <strong> ส่วนตัว </strong> INTEGER ERRORCODE; <strong> สาธารณะ </strong> <strong> RuntimeException </strong> (Integer Errorcode, สาเหตุที่โยนได้) {<strong> สิ่งนี้ </strong> (Errorcode, <strong> Null </strong>, สาเหตุ); } <strong> สาธารณะ </strong> <strong> runtimeException </strong> (ข้อความสตริงสาเหตุที่โยนได้) {// ใช้รหัสข้อผิดพลาดทั่วไป <strong> </strong> นี้ (ทั่วไป, ข้อความ, สาเหตุ); } <strong> สาธารณะ </strong> <strong> runtimeException </strong> (จำนวนเต็มข้อผิดพลาดรหัสข้อความสตริงสาเหตุที่โยนได้) {<strong> Super </strong> (ข้อความ, สาเหตุ); <strong> สิ่งนี้ </strong> .errorCode = ErrorCode; } <strong> สาธารณะ </strong> จำนวนเต็ม <strong> getERRORCODE </strong> () {<strong> return </strong> ErrorCode; -เมื่อรหัสตัวอย่างแสดงให้แนะนำรหัสข้อผิดพลาดเป็นข้อยกเว้น เมื่อมีข้อยกเว้นเกิดขึ้นเราเพิ่งนำเสนอรหัสข้อผิดพลาดของข้อยกเว้นต่อผู้ใช้หรือแปลงรหัสข้อผิดพลาดเป็นพรอมต์ที่เข้าใจได้มากขึ้น ในความเป็นจริงรหัสข้อผิดพลาดที่นี่ยังมีฟังก์ชั่นอื่นและนักพัฒนาสามารถทราบได้อย่างถูกต้องประเภทของข้อยกเว้นที่เกิดขึ้นตามรหัสข้อผิดพลาด
ความเข้าใจผิด 3: มลพิษของลำดับชั้นของรหัส
เรามักจะแบ่งรหัสออกเป็นลำดับชั้นที่แตกต่างกันเช่นบริการตรรกะทางธุรกิจ DAO ฯลฯ ชั้น DAO จะมีวิธีการขว้างข้อยกเว้นดังแสดงในรายการ 2:
รายการ 2
<strong> สาธารณะ </strong> ลูกค้า <strong> RetrievEcustomerById </strong> (Long ID) <strong> โยน </strong> sqlexception {// สอบถามฐานข้อมูลตาม ID}เมื่อมองแวบแรกไม่มีปัญหากับรหัสข้างต้น แต่ถ้าคุณคิดอย่างรอบคอบจากมุมมองของการมีเพศสัมพันธ์การออกแบบ SQLException ที่นี่ทำให้เกิดมลพิษต่อรหัสการโทรด้านบน เลเยอร์การโทรต้องใช้ลองจับเพื่อจับภาพอย่างชัดเจนหรือโยนลงไปในระดับที่สูงขึ้น ตามหลักการแยกการออกแบบเราสามารถปรับเปลี่ยนได้อย่างเหมาะสมเป็น:
รายการ 3
<strong> สาธารณะ </strong> ลูกค้า <strong> RetrievEcustomerById </strong> (Long ID) {<strong> ลอง </strong> {// การสืบค้นฐานข้อมูลที่ใช้ ID} <strong> จับ </strong> RuntimeException (SQLERRORCODE, E); } <strong> ในที่สุด </strong> {// ปิดการเชื่อมต่อและทำความสะอาดทรัพยากร}}ความเข้าใจผิด 4: ละเว้นข้อยกเว้น
การจัดการข้อยกเว้นต่อไปนี้เป็นเพียงการส่งสัญญาณข้อยกเว้นไปยังคอนโซลและมันก็ไม่สมเหตุสมผล นอกจากนี้ข้อยกเว้นจะปรากฏขึ้นที่นี่และโปรแกรมไม่ขัดจังหวะและรหัสการโทรยังคงดำเนินการต่อไปส่งผลให้มีข้อยกเว้นมากขึ้น
รายการ 4
<strong> สาธารณะ </strong> <strong> เป็นโมฆะ </strong> <strong> retrieveObjectById </strong> (id ยาว) {<strong> ลอง </strong> {// .. รหัสบางอย่างที่โยน sqlexception} <strong> จับ </strong> (sqlexception ex) {/*** * ในสภาพแวดล้อมการผลิตสแต็กข้อผิดพลาดจะต้องส่งออกไปยังบันทึก * และโปรแกรมยังคงดำเนินการหลังจากกระบวนการจับซึ่งจะนำไปสู่ปัญหาเพิ่มเติม*/ ex.printstacktrace (); -สามารถสร้างใหม่:
รายการ 5
<strong> สาธารณะ </strong> <strong> เป็นโมฆะ </strong> <strong> RetrieveObjectById </strong> (Long ID) {<strong> ลอง </strong> {// .. รหัสบางอย่างที่โยน sqlexception} <strong> จับ </strong> retrieveObjectbyid ", ex); } <strong> ในที่สุด </strong> {// ทำความสะอาดผลลัพธ์, คำสั่ง, การเชื่อมต่อ ฯลฯ }}ความเข้าใจผิดนี้ค่อนข้างพื้นฐานและภายใต้สถานการณ์ปกติคุณจะไม่ทำผิดระดับต่ำนี้
ความเข้าใจผิด 5: รวมข้อยกเว้นในบล็อกคำสั่งลูป
ดังที่แสดงในรหัสต่อไปนี้ข้อยกเว้นมีอยู่ในบล็อกคำสั่ง Loop
รายการ 6
<strong> สำหรับ </strong> (<strong> int </strong> i = 0; i <100; i ++) {<strong> ลอง </strong> {} <strong> จับ </strong> (xxxexception e) {// … -เราทุกคนรู้ว่าการจัดการข้อยกเว้นนั้นใช้ทรัพยากรระบบ เมื่อมองแวบแรกทุกคนคิดว่าพวกเขาจะไม่ทำผิดพลาด จากมุมมองอื่นลูปจะถูกดำเนินการในคลาส A และวิธีการของคลาส B ถูกเรียกในลูป แต่วิธีที่เรียกว่าในคลาส B มีบล็อกคำสั่งเช่นลองจับ ลำดับชั้นของคลาสได้จางหายไปและรหัสนั้นเหมือนกับข้างต้น
ความเข้าใจผิด 6: ใช้ข้อยกเว้นเพื่อจับข้อยกเว้นที่อาจเกิดขึ้นทั้งหมด
ข้อยกเว้นหลายประเภทจะถูกโยนลงในระหว่างการดำเนินการของวิธีการ เพื่อความเรียบง่ายของรหัสข้อยกเว้นคลาสพื้นฐานจะใช้ในการจับข้อยกเว้นที่อาจเกิดขึ้นทั้งหมดดังแสดงในตัวอย่างต่อไปนี้:
รายการ 7
<strong> สาธารณะ </strong> <strong> เป็นโมฆะ </strong> <strong> RetrieveObjectById </strong> (Long ID) {<strong> ลอง </strong> {// …การโทรรหัสที่พ่น IOException // …การโทรรหัส หากหลายระดับจับสิ่งนี้ข้อมูลที่ถูกต้องของข้อยกเว้นเดิมจะหายไป <strong> โยน </strong> <strong> ใหม่ </strong> RuntimeException ("Exception <strong> ใน </strieve> RetrieveObjectById", e); -สามารถสร้างใหม่ได้
รายการ 8
<strong> สาธารณะ </strong> <strong> เป็นโมฆะ </strong> <strong> RetrieveObjectById </strong> (Long ID) {<strong> ลอง </strong> {// .. รหัสบางอย่างที่พุ่งเข้าหา RuntimeException, IoException, Sqlexception} runtimeException (/*ระบุรหัสข้อผิดพลาดที่สอดคล้องกับ ioException ที่นี่*/รหัส "ข้อยกเว้น <strong> ใน </strong> retrieveObjectById", e); } <strong> catch </strong> (sqlexception e) {// เพียงแค่จับ sqlexception <strong> โยน </strong> <strong> ใหม่ </strong> runtimeException (/*ระบุรหัสข้อผิดพลาดที่สอดคล้องกับ sqlexception ที่นี่*/รหัส " -ความเข้าใจผิด 7: การห่อหุ้มหลายระดับทำให้เกิดข้อยกเว้นที่ไม่ถูกตรวจพบ
หากเรายืนยันเสมอว่าข้อยกเว้นประเภทต่าง ๆ จะต้องใช้คำสั่งจับภาพที่แตกต่างกันตัวอย่างส่วนใหญ่สามารถข้ามส่วนนี้ได้ อย่างไรก็ตามหากมีการโทรรหัสเพียงชิ้นเดียวเท่านั้นที่จะมีข้อยกเว้นมากกว่าหนึ่งข้อมักจะไม่จำเป็นต้องเขียนคำสั่งจับสำหรับข้อยกเว้นแต่ละประเภทที่แตกต่างกัน สำหรับการพัฒนาข้อยกเว้นใด ๆ ก็เพียงพอที่จะอธิบายปัญหาเฉพาะของโปรแกรม
รายการ 9
<strong> ลอง </strong> {// runtimeException, ioexeption หรือคนอื่น ๆ อาจถูกโยน; // หมายเหตุความแตกต่างระหว่างที่นี่และความเข้าใจผิดหกนี่คือส่วนหนึ่งของรหัสที่มีข้อยกเว้นหลายประการ ข้างต้นเป็นหลายส่วนของรหัสแต่ละการขว้างข้อยกเว้นที่แตกต่างกัน} <strong> catch </strong> (<strong> ข้อยกเว้น </strong> e) {// เช่นเคยแปลงข้อยกเว้นเป็น RuntimeException แต่ E ที่นี่เป็นอินสแตนซ์ของ RuntimeException และถูกห่อหุ้มไว้ในรหัสก่อนหน้า runtimeException (/**/code,/**/, e);}หากเราแปลงข้อยกเว้นทั้งหมดเป็น runtimeException ดังที่แสดงในตัวอย่างข้างต้นดังนั้นเมื่อประเภทข้อยกเว้นเป็น runtimeException อยู่แล้วเราจะทำ encapsulation อื่น RuntimeException ได้รับการจัดสรรใหม่อีกครั้งและข้อมูลที่ถูกต้องที่ดำเนินการโดย RuntimeException ดั้งเดิมนั้นหายไป
วิธีแก้ปัญหาคือเราสามารถเพิ่มการตรวจสอบที่เกี่ยวข้องในคลาส RuntimeException เพื่อยืนยันว่าพารามิเตอร์ที่โยนได้ไม่ได้เป็นอินสแตนซ์ของ RuntimeException ถ้าเป็นเช่นนั้นให้คัดลอกแอตทริบิวต์ที่เกี่ยวข้องไปยังอินสแตนซ์ที่สร้างขึ้นใหม่ หรือใช้บล็อกคำสั่งจับที่แตกต่างกันเพื่อจับ RuntimeException และข้อยกเว้นอื่น ๆ วิธีการตั้งค่าส่วนบุคคล 1 ผลประโยชน์ที่ชัดเจนในตัวเอง
ความเข้าใจผิด 8: ข้อยกเว้นการพิมพ์หลายระดับ
ก่อนอื่นให้ดูตัวอย่างต่อไปนี้ซึ่งกำหนด 2 คลาส A และ B B. class B รหัส B ถูกเรียกในคลาส A และทั้งการจับภาพคลาส A และคลาส B และการพิมพ์ข้อยกเว้น
รายการ 10
<strong> สาธารณะ </strong> <strong> คลาส </strong> <strong> a </strong> {<strong> ส่วนตัว </strong> <strong> คงที่ </strong> logger logger = loggerFactory.getLogger (a.class); <strong> สาธารณะ </strong> <strong> โมฆะ </strong> <strong> กระบวนการ </strong> () {<strong> ลอง </strong> {// อินสแตนซ์คลาส B คุณสามารถเปลี่ยนเป็นวิธีการฉีดอื่น ๆ เช่น B B = <strong> ใหม่ </strong> B (); b.process (); // รหัสอื่นอาจทำให้เกิดข้อยกเว้น} <strong> catch </strong> (xxxException e) {// ถ้าวิธีกระบวนการคลาส B ส่งข้อยกเว้นข้อยกเว้นจะอยู่ใน B คลาสจะถูกพิมพ์และมันจะถูกพิมพ์ที่นี่ดังนั้น logger.error (e); <strong> โยน </strong> <strong> ใหม่ </strong> runtimeException (/*รหัสข้อผิดพลาด*/errorcode,/*ข้อมูลข้อยกเว้น*/msg, e); }}} <strong> สาธารณะ </strong> <strong> คลาส </strong> <strong> b </strong> {<strong> ส่วนตัว </strong> <strong> คงที่ </strong> logger logger = loggerFactory.getLogger (b.class); <strong> สาธารณะ </strong> <strong> เป็นโมฆะ </strong> <strong> กระบวนการ </strong> () {<strong> ลอง </strong> {// รหัสที่อาจโยนข้อยกเว้น} <strong> จับ </strong> (xxxexception e) {logger.error (e); <strong> โยน </strong> <strong> ใหม่ </strong> runtimeException (/*รหัสข้อผิดพลาด*/errorcode,/*ข้อมูลข้อยกเว้น*/msg, e); -ข้อยกเว้นเดียวกันนี้จะถูกพิมพ์ 2 ครั้ง หากระดับมีความซับซ้อนมากขึ้นเล็กน้อยมันเป็นอาการปวดหัวที่จะไม่พิจารณาประสิทธิภาพของระบบของบันทึกการพิมพ์และเพียงค้นหาปัญหาเฉพาะในบันทึกข้อยกเว้น
ในความเป็นจริงบันทึกการพิมพ์ต้องใช้การจับและการพิมพ์ที่ชั้นนอกสุดของรหัสเท่านั้น การพิมพ์ข้อยกเว้นสามารถเขียนเป็น AOP และทอเป็นชั้นนอกสุดของเฟรม
ความเข้าใจผิด 9: ปัญหาที่ข้อมูลที่มีอยู่ในข้อยกเว้นไม่สามารถอยู่ได้อย่างสมบูรณ์
ข้อยกเว้นไม่เพียง แต่อนุญาตให้นักพัฒนาทราบว่ามีอะไรผิดปกติ แต่บ่อยครั้งที่นักพัฒนาต้องการยังต้องรู้ว่าอะไรเป็นสาเหตุของปัญหา เรารู้ว่า java .lang.exception มีตัวสร้างพารามิเตอร์ประเภทสตริงและสตริงนี้สามารถปรับแต่งให้เป็นข้อมูลแจ้งที่ง่ายต่อการเข้าใจ
นักพัฒนาข้อมูลที่กำหนดเองอย่างง่ายสามารถรู้ได้ว่าข้อยกเว้นปรากฏขึ้นที่ไหน แต่ในหลายกรณีนักพัฒนาจำเป็นต้องรู้เพิ่มเติมเกี่ยวกับพารามิเตอร์ที่ทำให้เกิดข้อยกเว้นดังกล่าว ในเวลานี้เราจำเป็นต้องผนวกข้อมูลพารามิเตอร์ของวิธีการโทรไปยังข้อมูลที่กำหนดเอง ตัวอย่างต่อไปนี้แสดงเฉพาะกรณีของพารามิเตอร์เดียว ในกรณีของพารามิเตอร์หลายตัวคุณสามารถเขียนคลาสเครื่องมือเพื่อจัดระเบียบสตริงดังกล่าว
รายการ 11
สาธารณะ <strong> โมฆะ </strong> retrieveObjectById (Long ID) {<strong> ลอง </strong> {//.. บางรหัสที่พ่น Sqlexception} <strong> จับ </strong> (SQLException Ex) {// เพิ่มข้อมูลพารามิเตอร์ <strong> ด้วย </strong> ID วัตถุ: "+ id, ex); -ความเข้าใจผิด 10: ไม่สามารถทำนายความผิดปกติที่อาจเกิดขึ้นได้
ในระหว่างกระบวนการเขียนโค้ดเนื่องจากขาดความเข้าใจอย่างลึกซึ้งเกี่ยวกับรหัสการโทรจึงเป็นไปไม่ได้ที่จะตรวจสอบได้อย่างถูกต้องว่ารหัสที่เรียกว่าจะสร้างข้อยกเว้นหรือไม่ดังนั้นการประมวลผลจะถูกละเว้น หลังจากสร้างข้อผิดพลาดในการผลิตฉันจำได้ว่าฉันควรเพิ่มการดึงข้อยกเว้นให้กับรหัสบางส่วนและฉันไม่สามารถชี้ให้เห็นสาเหตุของข้อยกเว้นได้อย่างถูกต้อง สิ่งนี้ต้องการนักพัฒนาไม่เพียง แต่รู้ว่าพวกเขากำลังทำอะไร แต่ยังต้องรู้มากที่สุดเท่าที่จะเป็นไปได้ในสิ่งที่คนอื่นทำและผลลัพธ์ที่เป็นไปได้อาจเกิดขึ้นและเพื่อพิจารณากระบวนการประมวลผลของแอปพลิเคชันทั้งหมดจากมุมมองระดับโลก แนวคิดเหล่านี้จะส่งผลต่อการเขียนและการประมวลผลรหัสของเรา
ความเข้าใจผิด 11: การใช้ไลบรารีบันทึกของบุคคลที่สามหลายครั้ง
ทุกวันนี้มีห้องสมุดบันทึกบุคคลที่สามของ Java มากขึ้นเรื่อย ๆ เฟรมเวิร์กต่างๆจะถูกนำมาใช้ในโครงการขนาดใหญ่และกรอบเหล่านี้จะขึ้นอยู่กับการใช้งานไลบรารีบันทึกที่แตกต่างกัน ปัญหาที่ลำบากที่สุดคือไม่แนะนำไลบรารีบันทึกทั้งหมดที่จำเป็นปัญหาคือไลบรารีบันทึกที่แนะนำนั้นเข้ากันไม่ได้ หากมันอาจจะง่ายต่อการแก้ปัญหาในระยะแรกของโครงการคุณสามารถนำไลบรารีบันทึกทั้งหมดไว้ในรหัสของคุณได้ตามต้องการหรือเปลี่ยนเป็นเฟรมเวิร์ก แต่ค่าใช้จ่ายประเภทนี้ไม่แพงสำหรับทุกโครงการและความเสี่ยงที่มากขึ้นเมื่อโครงการดำเนินไป
เราจะหลีกเลี่ยงปัญหาที่คล้ายกันได้อย่างมีประสิทธิภาพได้อย่างไร? เฟรมเวิร์กส่วนใหญ่ตอนนี้ได้คำนึงถึงปัญหาที่คล้ายกัน พวกเขาสามารถกำหนดค่าคุณสมบัติหรือไฟล์ XML พารามิเตอร์หรือคลาสการใช้งานการสแกนแบบรันไทม์ในไลบรารี LIB และเฉพาะเมื่อแอปพลิเคชันกำลังทำงานอยู่เราสามารถกำหนดไลบรารีบันทึกเฉพาะที่จะใช้
ในความเป็นจริงขึ้นอยู่กับหลักการของการไม่ต้องการการพิมพ์บันทึกหลายระดับเราสามารถทำให้คลาสหลายคลาสที่เรียกว่ารหัสการพิมพ์บันทึกง่ายขึ้น ในหลายกรณีเราสามารถใช้ interceptors หรือตัวกรองเพื่อพิมพ์บันทึกเพื่อลดค่าใช้จ่ายในการบำรุงรักษารหัสและการโยกย้าย
บทสรุป
ข้างต้นเป็นประสบการณ์ส่วนตัวและบทสรุปอย่างหมดจด สิ่งต่าง ๆ เป็นวิภาษวิธีและไม่มีหลักการที่แน่นอน หลักการที่มีประสิทธิภาพมากที่สุดเหมาะสำหรับคุณ ฉันหวังว่าคำอธิบายและการวิเคราะห์ข้างต้นจะเป็นประโยชน์กับคุณ