โยนข้อยกเว้นเข้าไปในที่สุด


27

วิเคราะห์รหัส Static เช่นเสริมสร้าง "บ่น" เมื่อมีข้อยกเว้นอาจจะโยนภายในบล็อกบอกว่าfinally Using a throw statement inside a finally block breaks the logical progression through the try-catch-finallyปกติฉันเห็นด้วยกับเรื่องนี้ แต่เมื่อเร็ว ๆ นี้ฉันเจอโค้ดนี้:

SomeFileWriter writer = null; 
try { 
     //init the writer
     //write into the file
} catch (...) {
     //exception handling
} finally {
     if (writer!= null) writer.close();  
}

ตอนนี้ถ้าwriterไม่สามารถปิดได้อย่างถูกต้องwriter.close()วิธีการจะโยนข้อยกเว้น ควรโยนข้อยกเว้นเนื่องจาก (ส่วนใหญ่) ไฟล์ไม่ถูกบันทึกหลังจากเขียน

ฉันสามารถประกาศตัวแปรพิเศษตั้งค่าหากมีข้อผิดพลาดในการปิดwriterและโยนข้อยกเว้นหลังจากบล็อกในที่สุด แต่รหัสนี้ใช้งานได้ดีและฉันไม่แน่ใจว่าจะเปลี่ยนหรือไม่

อะไรคือข้อเสียของการโยนข้อยกเว้นภายในfinallyบล็อก


4
หากนี่คือ Java และคุณสามารถใช้ Java 7 ตรวจสอบว่าบล็อก ARM สามารถแก้ปัญหาของคุณได้หรือไม่
Landei

@Landei สิ่งนี้แก้ได้ แต่น่าเสียดายที่เราไม่ได้ใช้ Java 7
superM

ฉันจะบอกว่ารหัสที่คุณแสดงไม่ใช่ "การใช้คำสั่ง throw ภายในบล็อกสุดท้าย" และเนื่องจากความก้าวหน้าเชิงตรรกะนั้นก็ใช้ได้
Mike

@ ไมค์ฉันใช้สรุปมาตรฐานที่ Fortify แสดง แต่ไม่ว่าทางตรงหรือทางอ้อมจะมีข้อยกเว้นเกิดขึ้นในที่สุด
superM

โชคไม่ดีที่ Fortify ถูกบล็อกด้วยการพยายามทรัพยากรตรวจพบว่ามันเป็นข้อยกเว้นในที่สุด .. มันฉลาดเกินไป, เจ้ากรรม .. ยังไม่แน่ใจว่าจะเอาชนะมันได้อย่างไรดูเหมือนว่าเหตุผลสำหรับการลองกับทรัพยากรก็คือการปิด ทรัพยากรในที่สุดและตอนนี้แต่ละคำสั่งดังกล่าวมีรายงานโดยเสริมสร้างเป็นภัยคุกคามความปลอดภัย ..
mmona

คำตอบ:


18

โดยทั่วไปfinallyมีคำสั่งเพื่อให้แน่ใจว่ามีการปล่อยทรัพยากรอย่างเหมาะสม อย่างไรก็ตามหากมีข้อยกเว้นเกิดขึ้นในบล็อกสุดท้ายการรับประกันนั้นจะหายไป ยิ่งถ้าโค้ดหลักของคุณมีข้อผิดพลาดข้อยกเว้นที่เพิ่มในfinallyบล็อกจะซ่อน มันจะดูเหมือนข้อผิดพลาดที่เกิดจากการเรียกไปcloseไม่ใช่ด้วยเหตุผลที่แท้จริง

บางคนทำตามรูปแบบที่น่ารังเกียจของตัวจัดการข้อยกเว้นแบบซ้อนกลืนข้อยกเว้นใด ๆ ที่ถูกโยนลงในfinallyบล็อก

SomeFileWriter writer = null; 
try { 
     //init the writer
     //write into the file
} finally {
    if (writer!= null) {
        try {
            writer.close();
        } catch (...) {
        }
    }
}

ใน Java เวอร์ชันเก่าคุณสามารถ "ทำให้" รหัสนี้ง่ายขึ้นโดยการตัดทรัพยากรในคลาสที่ทำความสะอาด "ปลอดภัย" สำหรับคุณ เพื่อนที่ดีของฉันสร้างรายการประเภทที่ไม่ระบุชื่อซึ่งแต่ละรายการมีตรรกะสำหรับล้างทรัพยากรของพวกเขา จากนั้นโค้ดของเขาจะวนซ้ำในรายการและเรียกวิธีการจัดการภายในfinallyบล็อก


1
+1 แม้ว่าฉันจะเป็นคนที่ใช้รหัสที่น่ารังเกียจ แต่สำหรับเหตุผลของฉันฉันจะบอกว่าฉันไม่ทำเช่นนี้เสมอเฉพาะเมื่อข้อยกเว้นนั้นไม่สำคัญ
superM

2
ฉันพบว่าตัวเองทำเช่นกัน บางครั้งการสร้างตัวจัดการทรัพยากรทั้งหมด (ไม่ระบุชื่อหรือไม่) จะเบี่ยงเบนความสนใจจากจุดประสงค์ของรหัส
Travis Parks

+1 จากฉันด้วย คุณพูดในสิ่งที่ฉันกำลังจะทำ แต่ดีกว่า น่าสังเกตว่าการใช้งาน Stream บางอย่างใน Java ไม่สามารถโยนข้อยกเว้นเมื่อปิด () ได้ แต่อินเทอร์เฟซจะประกาศเนื่องจากบางส่วนทำ ดังนั้นในบางสถานการณ์คุณอาจเพิ่ม catch block ซึ่งไม่จำเป็นต้องใช้จริง
vaughandroid

1
สิ่งที่น่ารังเกียจเกี่ยวกับการใช้บล็อกลอง / จับภายในบล็อกในที่สุด? ฉันอยากจะทำอย่างนั้นมากกว่าการขยายโค้ดทั้งหมดของฉันโดยการปิดการเชื่อมต่อและสตรีมทั้งหมดของฉัน ฯลฯ เพื่อให้พวกเขาสามารถปิดได้อย่างเงียบ ๆ - ซึ่งในตัวเองแนะนำปัญหาใหม่โดยไม่รู้ว่าปิดการเชื่อมต่อหรือสตรีม หรืออะไรก็ตาม) ทำให้เกิดข้อยกเว้น - ซึ่งเป็นสิ่งที่คุณอาจจะจริงเหมือนรู้เกี่ยวกับหรือทำอะไรบางอย่างเกี่ยวกับ ...
ห้าม-geoengineering

10

สิ่งที่เทรวิสพาร์คพูดว่าเป็นความจริงที่ว่าข้อยกเว้นในfinallyบล็อกจะใช้ค่าส่งคืนหรือข้อยกเว้นจากtry...catchบล็อก

หากคุณใช้ Java 7 คุณสามารถแก้ไขปัญหาได้โดยใช้บล็อกtry-with-resources ตามเอกสารนั้นตราบใดที่ทรัพยากรของคุณมีการดำเนินการjava.lang.AutoCloseable(ผู้เขียน / ผู้อ่านห้องสมุดส่วนใหญ่ทำในขณะนี้) บล็อกลองกับทรัพยากรจะปิดเพื่อคุณ ประโยชน์เพิ่มเติมที่นี่คือข้อยกเว้นใด ๆ ที่เกิดขึ้นในขณะที่ปิดมันจะถูกระงับการอนุญาตให้ค่าส่งคืนเดิมหรือข้อยกเว้นที่จะผ่านขึ้น

จาก

FileWriter writer = null;
try {
  writer = new FileWriter("myFile.txt");
  writer.write("hello");
} catch(...) {
  // return/throw new exception
} finally {
  writer.close(); // an exception would consume the catch block's return/exception
}

ไปยัง

try (FileWriter writer = new FileWriter("myFile.txt")) {
  writer.write("hello");
} catch(...) {
  // return/throw new exception, always gets returned even if writer fails to close
}

http://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html


1
บางครั้งสิ่งนี้มีประโยชน์ แต่ในกรณีที่เมื่อฉันต้องโยนข้อยกเว้นเมื่อไม่สามารถปิดไฟล์ได้วิธีนี้จะซ่อนปัญหา
superM

1
@superM close()จริงลองที่มีทรัพยากรไม่ได้ซ่อนข้อยกเว้นจาก "ข้อยกเว้นใด ๆ ที่เกิดขึ้นในขณะที่ปิดก็จะถูกระงับการอนุญาตให้ค่าตอบแทนเดิมหรือข้อยกเว้นที่จะผ่านขึ้น" - นี้เป็นเพียงไม่เป็นความจริง ข้อยกเว้นจากclose()วิธีการจะถูกระงับเฉพาะเมื่อมีข้อยกเว้นอื่นจะถูกโยนออกจากบล็อกลอง / จับ ดังนั้นหากtryบล็อกไม่ส่งข้อยกเว้นใด ๆ ข้อยกเว้นจากclose()เมธอดจะถูกส่งออกไป (และค่าส่งคืนจะไม่ถูกส่งคืน) มันสามารถจับได้จากcatchบล็อกปัจจุบัน
Ruslan Stelmachenko

0

ฉันคิดว่านี่เป็นสิ่งที่คุณจะต้องระบุเป็นรายกรณี ในบางกรณีสิ่งที่ตัววิเคราะห์กำลังพูดนั้นถูกต้องในรหัสที่คุณมีนั้นไม่ค่อยดีนักและต้องคิดใหม่ แต่อาจมีกรณีอื่นเมื่อการขว้างหรือแม้กระทั่งการขว้างใหม่อาจเป็นสิ่งที่ดีที่สุด ไม่ใช่สิ่งที่คุณสามารถมอบอำนาจได้


0

นี่จะเป็นคำตอบที่มากกว่าแนวคิดว่าทำไมคำเตือนเหล่านี้อาจมีอยู่แม้ว่าด้วยวิธีการลองกับทรัพยากร แต่น่าเสียดายที่ไม่ใช่วิธีการแก้ปัญหาที่ง่ายที่คุณอาจคาดหวังว่าจะได้รับ

การกู้คืนข้อผิดพลาดไม่สามารถล้มเหลว

finally โมเดลโฟลว์การควบคุมหลังการทำธุรกรรมที่ดำเนินการโดยไม่คำนึงถึงว่าการทำธุรกรรมสำเร็จหรือล้มเหลว

ในกรณีที่ล้มเหลวfinallyจับตรรกะที่ดำเนินการในช่วงกลางของการกู้คืนจากข้อผิดพลาดก่อนที่จะได้รับการกู้คืนอย่างเต็มที่ (ก่อนที่เราจะไปถึงcatchปลายทางของเรา)

ลองนึกภาพปัญหาความคิดมันเป็นของขวัญที่จะพบข้อผิดพลาดในช่วงกลางของการกู้คืนจากข้อผิดพลาด

ลองนึกภาพเซิร์ฟเวอร์ฐานข้อมูลที่เราพยายามจะทำธุรกรรมและมันล้มเหลวไปครึ่งทาง (พูดว่าเซิร์ฟเวอร์มีหน่วยความจำเหลืออยู่ตรงกลาง) ตอนนี้เซิร์ฟเวอร์ต้องการย้อนกลับการทำธุรกรรมไปยังจุดราวกับว่าไม่มีอะไรเกิดขึ้น แต่ลองจินตนาการว่ามันเจอข้อผิดพลาดอื่นในกระบวนการย้อนกลับ ตอนนี้เราจบลงด้วยการทำธุรกรรมแบบกึ่งมุ่งมั่นไปยังฐานข้อมูล - อะตอมมิกและลักษณะที่แยกไม่ออกของธุรกรรมตอนนี้พังและความสมบูรณ์ของฐานข้อมูลจะถูกทำลาย

ปัญหาแนวคิดนี้มีอยู่ในภาษาใด ๆ ที่เกี่ยวข้องกับข้อผิดพลาดไม่ว่าจะเป็น C ที่มีการขยายพันธุ์รหัสคู่มือข้อผิดพลาดหรือ C ++ มีข้อยกเว้นและ destructors หรือ Java finallyและมีข้อยกเว้น

finally ไม่สามารถล้มเหลวในภาษาที่ให้ไว้ในลักษณะเดียวกับที่ตัวทำลายไม่สามารถล้มเหลวใน C ++ ในกระบวนการพบข้อยกเว้น

วิธีเดียวที่จะหลีกเลี่ยงปัญหาเกี่ยวกับแนวคิดและปัญหาที่ยากลำบากนี้คือการทำให้แน่ใจว่ากระบวนการย้อนกลับธุรกรรมและการปล่อยทรัพยากรที่อยู่ตรงกลางไม่อาจพบข้อยกเว้น / ข้อผิดพลาดซ้ำ

ดังนั้นการออกแบบที่ปลอดภัยเพียงอย่างเดียวที่นี่คือการออกแบบที่writer.close()ไม่สามารถล้มเหลวได้ มักจะมีวิธีในการออกแบบเพื่อหลีกเลี่ยงสถานการณ์ที่สิ่งต่าง ๆ สามารถล้มเหลวในช่วงกลางของการกู้คืนทำให้มันเป็นไปไม่ได้

เป็นวิธีเดียวที่น่าเสียดาย - การกู้คืนข้อผิดพลาดไม่สามารถล้มเหลว วิธีที่ง่ายที่สุดเพื่อให้แน่ใจว่านี่คือการทำให้ฟังก์ชั่น "การปลดปล่อยทรัพยากร" และ "ผลข้างเคียงย้อนกลับ" เหล่านั้นไม่สามารถล้มเหลวได้ ไม่ใช่เรื่องง่าย - การกู้คืนข้อผิดพลาดที่เหมาะสมนั้นยากและยังยากที่จะทดสอบ แต่วิธีการที่จะทำให้สำเร็จนั้นคือการทำให้แน่ใจว่าฟังก์ชั่นใด ๆ ที่ "ทำลาย", "ปิด", "ปิด", "ย้อนกลับ" ฯลฯ ไม่สามารถพบข้อผิดพลาดภายนอกในกระบวนการได้เนื่องจากฟังก์ชั่นดังกล่าวมักจะต้อง ถูกเรียกในช่วงกลางของการกู้คืนจากข้อผิดพลาดที่มีอยู่

ตัวอย่าง: การบันทึก

สมมติว่าคุณต้องการบันทึกสิ่งต่าง ๆ ภายในfinallyบล็อก นี้มักจะเป็นปัญหาใหญ่เว้นแต่การเข้าสู่ระบบไม่สามารถล้มเหลว การเข้าสู่ระบบเกือบจะแน่นอนอาจล้มเหลวเนื่องจากอาจต้องการผนวกข้อมูลเพิ่มเติมลงในไฟล์และที่สามารถหาสาเหตุหลายประการที่จะล้มเหลวได้อย่างง่ายดาย

ดังนั้นทางออกที่นี่คือการทำให้ฟังก์ชั่นการบันทึกใด ๆ ที่ใช้ในfinallyบล็อกไม่สามารถส่งไปยังผู้โทร (มันอาจจะล้มเหลว แต่มันจะไม่โยน) เราจะทำอย่างนั้นได้อย่างไร? หากภาษาของคุณอนุญาตให้มีการขว้างปาภายในบริบทของการจัดให้มีบล็อก try / catch ที่ซ้อนกันซึ่งเป็นวิธีหนึ่งในการหลีกเลี่ยงการขว้างปาไปยังผู้โทรโดยการกลืนข้อยกเว้นและเปลี่ยนเป็นรหัสข้อผิดพลาดเช่นอาจบันทึกแยกกัน กระบวนการหรือเธรดที่สามารถล้มเหลวแยกจากกันและอยู่นอกสแต็กการกู้คืนข้อผิดพลาดที่มีอยู่คลายออก ตราบใดที่คุณสามารถสื่อสารกับกระบวนการนั้นโดยไม่มีความเป็นไปได้ที่จะเกิดข้อผิดพลาดนั่นก็จะเป็นข้อยกเว้นที่ปลอดภัยเนื่องจากปัญหาด้านความปลอดภัยมีอยู่เฉพาะในสถานการณ์นี้ถ้าเราโยนซ้ำภายในเธรดเดียวกัน.

ในกรณีนี้เราสามารถหลีกเลี่ยงการบันทึกล้มเหลวหากไม่ได้บันทึกเนื่องจากไม่สามารถบันทึกและไม่ทำอะไรเลยไม่ใช่จุดจบของโลก (ไม่รั่วไหลของทรัพยากรหรือล้มเหลวในการย้อนกลับผลข้างเคียงเช่น)

อย่างไรก็ตามฉันมั่นใจว่าคุณสามารถเริ่มจินตนาการได้แล้วว่ามันยากอย่างเหลือเชื่อที่จะสร้างข้อยกเว้นซอฟต์แวร์อย่างแท้จริงได้อย่างไร อาจไม่จำเป็นต้องค้นหาสิ่งนี้อย่างเต็มที่ แต่เป็นซอฟต์แวร์ที่มีความสำคัญต่อภารกิจ แต่มันควรค่าที่จะทราบว่าจะทำอย่างไรเพื่อให้เกิดข้อยกเว้นด้านความปลอดภัยอย่างแท้จริงเนื่องจากผู้เขียนห้องสมุดที่ใช้งานทั่วไปมักจะคลำหาที่นี่และทำให้เกิดข้อยกเว้นด้านความปลอดภัยทั้งหมดของแอปพลิเคชันของคุณ

SomeFileWriter

หากSomeFileWriterสามารถเข้าไปข้างในได้closeฉันจะบอกว่าโดยทั่วไปแล้วไม่สามารถเข้ากันได้กับการจัดการข้อยกเว้นเว้นแต่คุณจะไม่พยายามปิดมันในบริบทที่เกี่ยวข้องกับการกู้คืนจากข้อยกเว้นที่มีอยู่ หากรหัสนี้อยู่นอกเหนือการควบคุมของคุณเราอาจเป็น SOL แต่มันจะคุ้มค่าที่จะแจ้งให้ผู้เขียนของปัญหาความปลอดภัยข้อยกเว้นโจ่งแจ้งนี้ หากอยู่ในการควบคุมของคุณคำแนะนำหลักของฉันคือการทำให้แน่ใจว่าการปิดนั้นจะไม่สามารถล้มเหลวด้วยวิธีการใดก็ได้ที่จำเป็น

ลองจินตนาการว่าระบบปฏิบัติการอาจล้มเหลวในการปิดไฟล์จริงหรือไม่ ตอนนี้โปรแกรมใด ๆ ที่พยายามปิดไฟล์เมื่อปิดระบบจะไม่สามารถปิดได้ เราควรจะทำอะไรตอนนี้เพียงแค่เปิดแอปพลิเคชันและในบริเวณขอบรก (อาจจะไม่ใช่) เพียงแค่รั่วไฟล์ทรัพยากรและละเลยปัญหา (อาจจะไม่เป็นไรถ้ามันไม่สำคัญมาก)? การออกแบบที่ปลอดภัยที่สุด: ทำให้เป็นไปไม่ได้ที่จะปิดไฟล์ไม่ได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.