TL; DR
หลักฐาน
- ข้อยกเว้นรันไทม์ควรถูกโยนทิ้งเมื่อข้อผิดพลาดไม่สามารถกู้คืนได้: เมื่อข้อผิดพลาดอยู่ในรหัสและไม่ได้ขึ้นอยู่กับสถานะภายนอก (ดังนั้นการกู้คืนจะแก้ไขรหัส)
- ควรโยนข้อยกเว้นที่ตรวจสอบเมื่อรหัสถูกต้อง แต่สถานะภายนอกไม่เป็นไปตามที่คาดไว้: ไม่พบการเชื่อมต่อเครือข่ายไม่พบไฟล์หรือเสียหาย ฯลฯ
ข้อสรุป
เราอาจ rethrow ข้อยกเว้นที่ตรวจสอบเป็นข้อยกเว้นรันไทม์หากรหัส propagating หรืออินเตอร์เฟซที่ถือว่าการใช้งานพื้นฐานขึ้นอยู่กับสถานะภายนอกเมื่อมันไม่ชัดเจน
ส่วนนี้อธิบายหัวข้อเมื่อใดควรโยนทิ้งข้อยกเว้น คุณสามารถข้ามไปที่แถบแนวนอนถัดไปหากคุณต้องการอ่านคำอธิบายโดยละเอียดเพิ่มเติมเพื่อหาข้อสรุป
เมื่อใดที่เหมาะสมที่จะส่งข้อยกเว้นรันไทม์? คุณสร้างข้อยกเว้นรันไทม์เมื่อเห็นได้ชัดว่ารหัสไม่ถูกต้องและการกู้คืนนั้นเหมาะสมโดยการปรับเปลี่ยนรหัส
ตัวอย่างเช่นมีความเหมาะสมที่จะโยนข้อยกเว้นรันไทม์สำหรับสิ่งต่อไปนี้:
float nan = 1/0;
สิ่งนี้จะทำให้เกิดการหารด้วยข้อยกเว้นรันไทม์ของศูนย์ สิ่งนี้เหมาะสมเนื่องจากรหัสมีข้อบกพร่อง
หรือตัวอย่างเช่นนี่เป็นส่วนหนึ่งของคอนHashMap
สตรัคเตอร์:
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
// more irrelevant code...
}
เพื่อแก้ไขความจุเริ่มต้นหรือตัวประกอบโหลดเป็นสิ่งที่เหมาะสมที่คุณแก้ไขรหัสเพื่อให้แน่ใจว่ามีการส่งค่าที่ถูกต้องไม่ขึ้นอยู่กับเซิร์ฟเวอร์ที่อยู่ห่างออกไปในสถานะปัจจุบันของดิสก์ ไฟล์หรือโปรแกรมอื่น คอนสตรัคเตอร์นั้นถูกเรียกด้วยอาร์กิวเมนต์ที่ไม่ถูกต้องขึ้นอยู่กับความถูกต้องของรหัสการโทรไม่ว่าจะเป็นการคำนวณที่ผิดที่นำไปสู่พารามิเตอร์ที่ไม่ถูกต้องหรือการไหลที่ไม่เหมาะสมที่พลาดข้อผิดพลาด
เมื่อใดควรเลือกที่จะโยนข้อยกเว้นที่เลือก คุณโยนข้อยกเว้นที่ตรวจสอบเมื่อปัญหาสามารถกู้คืนได้โดยไม่ต้องเปลี่ยนรหัส หรือหากต้องการระบุไว้ในเงื่อนไขที่แตกต่างกันคุณจะต้องทำการตรวจสอบข้อผิดพลาดเมื่อข้อผิดพลาดเกี่ยวข้องกับสถานะในขณะที่รหัสนั้นถูกต้อง
ตอนนี้คำว่า "กู้คืน" อาจเป็นเรื่องยากที่นี่ อาจหมายความว่าคุณค้นหาวิธีอื่นเพื่อให้บรรลุเป้าหมาย: ตัวอย่างเช่นหากเซิร์ฟเวอร์ไม่ตอบสนองคุณควรลองเซิร์ฟเวอร์ถัดไป หากการกู้คืนแบบนั้นเป็นไปได้สำหรับกรณีของคุณถือว่ายอดเยี่ยม แต่นั่นไม่ใช่วิธีการกู้คืนแบบเดียว - การกู้คืนอาจแสดงกล่องโต้ตอบข้อผิดพลาดไปยังผู้ใช้ที่อธิบายสิ่งที่เกิดขึ้นหรือถ้าเป็นแอปพลิเคชันเซิร์ฟเวอร์ ส่งอีเมลไปยังผู้ดูแลระบบหรือเพียงแค่บันทึกข้อผิดพลาดอย่างเหมาะสมและรัดกุม
ลองมาตัวอย่างที่กล่าวถึงในคำตอบ mrmuggles:
public void dataAccessCode(){
try{
..some code that throws SQLException
}catch(SQLException ex){
throw new RuntimeException(ex);
}
}
นี่ไม่ใช่วิธีที่ถูกต้องในการจัดการข้อยกเว้นที่เลือก ความสามารถเพียงอย่างเดียวในการจัดการข้อยกเว้นในขอบเขตของวิธีนี้ไม่ได้หมายความว่าแอปจะทำงานผิดพลาด แต่เป็นการเหมาะสมที่จะเผยแพร่ไปยังขอบเขตที่สูงขึ้นเช่น:
public Data dataAccessCode() throws SQLException {
// some code that communicates with the database
}
ซึ่งช่วยให้เป็นไปได้ของการกู้คืนโดยโทร:
public void loadDataAndShowUi() {
try {
Data data = dataAccessCode();
showUiForData(data);
} catch(SQLException e) {
// Recover by showing an error alert dialog
showCantLoadDataErrorDialog();
}
}
การตรวจสอบข้อยกเว้นเป็นเครื่องมือวิเคราะห์แบบสแตติกพวกเขาทำให้ชัดเจนสำหรับโปรแกรมเมอร์ว่าสิ่งใดที่ผิดพลาดในการโทรบางอย่างโดยไม่ต้องให้พวกเขาเรียนรู้การใช้งานหรือผ่านกระบวนการทดลองและข้อผิดพลาด สิ่งนี้ทำให้ง่ายต่อการตรวจสอบว่าไม่มีส่วนใดของโฟลว์ข้อผิดพลาดที่จะถูกละเว้น การสร้างข้อยกเว้นที่เลือกใหม่เป็นข้อยกเว้นรันไทม์กำลังทำงานกับคุณลักษณะการวิเคราะห์แบบคงที่ที่ช่วยประหยัดแรงงาน
เป็นสิ่งที่ควรค่าแก่การกล่าวถึงว่าเลเยอร์การเรียกนั้นมีบริบทที่ดีกว่าของโครงการที่น่าเกรงขามมากขึ้นดังที่ได้แสดงไว้ข้างต้น อาจมีสาเหตุหลายประการที่dataAccessCode
จะเรียกเหตุผลที่เฉพาะเจาะจงสำหรับการโทรจะปรากฏเฉพาะกับผู้โทร - ดังนั้นจึงสามารถตัดสินใจได้ดีขึ้นในการกู้คืนที่ถูกต้องเมื่อล้มเหลว
ตอนนี้เรามีความแตกต่างที่ชัดเจนนี้เราอาจดำเนินการสรุปเมื่อมันตกลงที่จะ rethrow ข้อยกเว้นที่ตรวจสอบเป็นข้อยกเว้นรันไทม์
จากที่กล่าวมาข้างต้นเมื่อใดที่เหมาะสมที่จะสร้างข้อยกเว้นที่เลือกใหม่เป็น RuntimeException เมื่อรหัสที่คุณใช้นั้นขึ้นอยู่กับสถานะภายนอก แต่คุณสามารถยืนยันได้อย่างชัดเจนว่าไม่ขึ้นอยู่กับสถานะภายนอก
พิจารณาสิ่งต่อไปนี้:
StringReader sr = new StringReader("{\"test\":\"test\"}");
try {
doesSomethingWithReader(sr); // calls #read, so propagates IOException
} catch (IOException e) {
throw new IllegalStateException(e);
}
ในตัวอย่างนี้รหัสกำลังเผยแพร่IOException
เนื่องจาก API ของReader
ถูกออกแบบมาเพื่อเข้าถึงสถานะภายนอกอย่างไรก็ตามเรารู้ว่าStringReader
การใช้งานไม่สามารถเข้าถึงสถานะภายนอก ในขอบเขตนี้ซึ่งเราสามารถยืนยันได้อย่างแน่นอนว่าชิ้นส่วนที่เกี่ยวข้องในการโทรไม่สามารถเข้าถึง IO หรือสถานะภายนอกอื่น ๆ เราสามารถสร้างข้อยกเว้นใหม่ได้อย่างปลอดภัยเป็นข้อยกเว้นแบบรันไทม์โดยไม่มีเพื่อนร่วมงานที่น่าทึ่งที่ไม่รู้ สมมติว่ารหัสการเข้าถึง IO จะส่งผลให้IOException
)
เหตุผลที่ทำให้การตรวจสอบข้อยกเว้นขึ้นอยู่กับสถานะภายนอกอย่างเคร่งครัดก็คือพวกเขาไม่ได้กำหนดไว้ (ซึ่งแตกต่างจากข้อยกเว้นขึ้นอยู่กับตรรกะซึ่งคาดว่าจะทำซ้ำทุกครั้งสำหรับรุ่นของรหัส) ตัวอย่างเช่นหากคุณพยายามหารด้วย 0 คุณจะสร้างข้อยกเว้นเสมอ หากคุณไม่หารด้วย 0 คุณจะไม่เกิดข้อยกเว้นและคุณไม่ต้องจัดการกับข้อยกเว้นนั้นเพราะมันจะไม่เกิดขึ้น ในกรณีของการเข้าถึงไฟล์อย่างไรก็ตามการทำสำเร็จครั้งเดียวไม่ได้หมายความว่าคุณจะประสบความสำเร็จในครั้งต่อไป - ผู้ใช้อาจเปลี่ยนสิทธิ์กระบวนการอื่นอาจลบหรือแก้ไข ดังนั้นคุณต้องจัดการกับกรณีพิเศษเสมอหรือคุณอาจมีข้อบกพร่อง