อื่น ๆ ได้สรุปค่อนข้างดีทำไมจะโยนต้น ขอให้ฉันจดจ่อกับเหตุผลที่จะมาสายแทนซึ่งฉันไม่ได้เห็นคำอธิบายที่น่าพอใจสำหรับรสนิยมของฉัน
เหตุใดจึงเป็นข้อยกเว้น
ดูเหมือนจะค่อนข้างสับสนว่าทำไมข้อยกเว้นอยู่ในสถานที่แรก ผมขอแสดงความลับใหญ่ที่นี่: เหตุผลในข้อยกเว้นและการจัดการข้อยกเว้นคือ ... สิ่งที่เป็นนามธรรม
คุณเห็นรหัสเช่นนี้หรือไม่:
static int divide(int dividend, int divisor) throws DivideByZeroException {
if (divisor == 0)
throw new DivideByZeroException(); // that's a checked exception indeed
return dividend / divisor;
}
static void doDivide() {
int a = readInt();
int b = readInt();
try {
int res = divide(a, b);
System.out.println(res);
} catch (DivideByZeroException e) {
// checked exception... I'm forced to handle it!
System.out.println("Nah, can't divide by zero. Try again.");
}
}
นั่นไม่ใช่วิธีการยกเว้นที่ควรใช้ รหัสดังกล่าวมีอยู่ในชีวิตจริง แต่มีความผิดปกติมากขึ้นและเป็นข้อยกเว้น (ปุน) คำจำกัดความของการแบ่งตัวอย่างเช่นแม้แต่ในคณิตศาสตร์ล้วนเป็นเงื่อนไข: มันเป็น "รหัสผู้โทร" ที่ต้องจัดการกับกรณีพิเศษของศูนย์เพื่อ จำกัด โดเมนข้อมูลเข้า มันน่าเกลียด. มันเป็นความเจ็บปวดสำหรับผู้โทร ยังสำหรับสถานการณ์เช่นนี้รูปแบบการตรวจสอบแล้วทำเป็นวิธีธรรมชาติที่จะไป:
static int divide(int dividend, int divisor) {
// throws unchecked ArithmeticException for 0 divisor
return dividend / divisor;
}
static void doDivide() {
int a = readInt();
int b = readInt();
if (b != 0) {
int res = divide(a, b);
System.out.println(res);
} else {
System.out.println("Nah, can't divide by zero. Try again.");
}
}
หรือคุณสามารถใช้คอมมานโดแบบเต็มในสไตล์ OOP เช่นนี้:
static class Division {
final int dividend;
final int divisor;
private Division(int dividend, int divisor) {
this.dividend = dividend;
this.divisor = divisor;
}
public boolean check() {
return divisor != 0;
}
public int eval() {
return dividend / divisor;
}
public static Division with(int dividend, int divisor) {
return new Division(dividend, divisor);
}
}
static void doDivide() {
int a = readInt();
int b = readInt();
Division d = Division.with(a, b);
if (d.check()) {
int res = d.eval();
System.out.println(res);
} else {
System.out.println("Nah, can't divide by zero. Try again.");
}
}
อย่างที่คุณเห็นรหัสผู้โทรมีภาระในการตรวจสอบล่วงหน้า แต่ไม่ได้ทำการจัดการข้อยกเว้นใด ๆ หลังจากนั้น หากผู้ArithmeticException
ที่เคยมาจากการเรียกdivide
หรือeval
แล้วมันเป็นคุณcheck()
ที่มีการทำข้อยกเว้นการจัดการและแก้ไขรหัสของคุณเพราะคุณลืม สำหรับเหตุผลที่คล้ายกันที่การจับเป็นNullPointerException
สิ่งที่ต้องทำ
ขณะนี้มีคนบางคนที่กล่าวว่าพวกเขาต้องการที่จะเห็นกรณีพิเศษในลายเซ็นวิธี / ฟังก์ชั่นคืออย่างชัดเจนขยายการส่งออกโดเมน พวกเขาเป็นคนที่ชื่นชอบข้อยกเว้นการตรวจสอบ แน่นอนว่าการเปลี่ยนโดเมนที่ส่งออกควรบังคับให้รหัสผู้โทรโดยตรงสามารถปรับตัวได้ แต่คุณไม่ต้องการข้อยกเว้นสำหรับสิ่งนั้น! นั่นเป็นเหตุผลที่คุณต้องNullable<T>
เรียนทั่วไป , เรียนกรณี , ชนิดข้อมูลเกี่ยวกับพีชคณิตและประเภทยูเนี่ยน บางคน OOอาจต้องการกลับมา null
เพราะกรณีข้อผิดพลาดง่าย ๆ เช่นนี้:
static Integer divide(int dividend, int divisor) {
if (divisor == 0) return null;
return dividend / divisor;
}
static void doDivide() {
int a = readInt();
int b = readInt();
Integer res = divide(a, b);
if (res != null) {
System.out.println(res);
} else {
System.out.println("Nah, can't divide by zero. Try again.");
}
}
ข้อยกเว้นในทางเทคนิคสามารถนำมาใช้เพื่อวัตถุประสงค์เช่นข้างต้น แต่ที่นี่เป็นจุด: ข้อยกเว้นไม่อยู่สำหรับการใช้งานดังกล่าว ข้อยกเว้นเป็นสิ่งที่เป็นนามธรรม ข้อยกเว้นเป็นเรื่องเกี่ยวกับทางอ้อม ข้อยกเว้นอนุญาตให้ขยายโดเมน "ผลลัพธ์" โดยไม่ทำสัญญาลูกค้าโดยตรงและเลื่อนการจัดการข้อผิดพลาดไปยัง "ที่อื่น" หากรหัสของคุณมีข้อยกเว้นที่จัดการในผู้โทรโดยตรงของรหัสเดียวกันโดยไม่มีเลเยอร์ของสิ่งที่เป็นนามธรรมในระหว่างนั้นแสดงว่าคุณกำลังทำผิด
จะจับ LATE ได้อย่างไร
ดังนั้นเราอยู่ที่นี่ ฉันได้ถกเถียงกันในทางของฉันผ่านเพื่อแสดงให้เห็นว่าการใช้ข้อยกเว้นในสถานการณ์ข้างต้นไม่ใช่วิธีการยกเว้นที่จะใช้ มีกรณีการใช้งานของแท้อยู่แล้วซึ่งสิ่งที่เป็นนามธรรมและทางอ้อมที่เสนอโดยการจัดการข้อยกเว้นนั้นเป็นสิ่งที่ขาดไม่ได้ การทำความเข้าใจการใช้งานดังกล่าวจะช่วยให้เข้าใจคำแนะนำที่ล่าช้ากว่ากำหนดเช่นกัน
กรณีที่ใช้คือ: การเขียนโปรแกรมกับ Abstractions ทรัพยากร ...
ใช่ตรรกะทางธุรกิจควรตั้งโปรแกรมให้กับ abstractionsไม่ใช่การใช้งานที่เป็นรูปธรรม รหัส "การเดินสาย" IOCระดับบนสุดจะยกตัวอย่างการนำนามธรรมของทรัพยากรไปใช้อย่างเป็นรูปธรรมและส่งต่อไปยังตรรกะทางธุรกิจ ไม่มีอะไรใหม่ที่นี่ แต่การใช้งานที่เป็นรูปธรรมของ abstractions ทรัพยากรเหล่านั้นอาจจะทิ้งข้อยกเว้นเฉพาะในการใช้งานของพวกเขาเองใช่ไหม?
ดังนั้นใครบ้างที่สามารถจัดการกับข้อยกเว้นเฉพาะของการใช้ เป็นไปได้ไหมที่จะจัดการกับข้อยกเว้นเฉพาะของทรัพยากรในตรรกะทางธุรกิจแล้ว? ไม่มันไม่ใช่ ตรรกะทางธุรกิจได้รับการโปรแกรมกับนามธรรมซึ่งไม่รวมความรู้ของรายละเอียดข้อยกเว้นเฉพาะการใช้งานเหล่านั้น
"Aha!" คุณอาจพูดว่า: "แต่นั่นเป็นสาเหตุที่เราสามารถยกเว้นคลาสย่อยและสร้างลำดับชั้นของข้อยกเว้น" (ลองดูMr. Spring !) ให้ฉันบอกคุณว่าเป็นความเข้าใจผิด ประการแรกหนังสือที่สมเหตุสมผลทุกเล่มเกี่ยวกับ OOP กล่าวว่าการสืบทอดที่เป็นรูปธรรมนั้นไม่ดี แต่อย่างใดองค์ประกอบหลักของ JVM นี้การจัดการข้อยกเว้นมีการเชื่อมโยงอย่างใกล้ชิดกับการสืบทอดที่เป็นรูปธรรม กระแทกแดกดัน Joshua Bloch ไม่สามารถเขียนหนังสือ Java ที่มีประสิทธิภาพของเขาก่อนที่เขาจะได้รับประสบการณ์กับ JVM ที่ทำงานได้หรือไม่ มันเป็นหนังสือ "บทเรียนที่เรียนรู้" มากกว่าสำหรับคนรุ่นต่อไป ประการที่สองและที่สำคัญกว่านั้นถ้าคุณได้รับข้อยกเว้นระดับสูงคุณจะจัดการกับมันอย่างไรPatientNeedsImmediateAttentionException
: เราต้องให้ยาหรือตัดขาของเธอ! คำสั่ง switch เกี่ยวกับ subclasses ที่เป็นไปได้ทั้งหมดเป็นอย่างไร? ความแตกต่างของคุณมีไปสิ่งที่เป็นนามธรรม คุณได้รับประเด็น
ดังนั้นใครสามารถจัดการข้อยกเว้นเฉพาะของทรัพยากรได้ มันจะต้องเป็นคนที่รู้ว่า concretions! ผู้ที่สร้างอินสแตนซ์ของทรัพยากร! รหัส "เดินสาย" แน่นอน! ลองดู:
ตรรกะทางธุรกิจที่เข้ารหัสกับ abstractions ... ไม่มีความผิดพลาดในการจัดการความผิดทางคอนกรีต!
static interface InputResource {
String fetchData();
}
static interface OutputResource {
void writeData(String data);
}
static void doMyBusiness(InputResource in, OutputResource out, int times) {
for (int i = 0; i < times; i++) {
System.out.println("fetching data");
String data = in.fetchData();
System.out.println("outputting data");
out.writeData(data);
}
}
ในขณะเดียวกันการใช้งานที่เป็นรูปธรรมอื่น ...
static class ConstantInputResource implements InputResource {
@Override
public String fetchData() {
return "Hello World!";
}
}
static class FailingInputResourceException extends RuntimeException {
public FailingInputResourceException(String message) {
super(message);
}
}
static class FailingInputResource implements InputResource {
@Override
public String fetchData() {
throw new FailingInputResourceException("I am a complete failure!");
}
}
static class StandardOutputResource implements OutputResource {
@Override
public void writeData(String data) {
System.out.println("DATA: " + data);
}
}
และในที่สุดรหัสการเดินสาย ... ใครจัดการข้อยกเว้นที่เป็นรูปธรรม? คนที่รู้เกี่ยวกับพวกเขา!
static void start() {
InputResource in1 = new FailingInputResource();
InputResource in2 = new ConstantInputResource();
OutputResource out = new StandardOutputResource();
try {
ReusableBusinessLogicClass.doMyBusiness(in1, out, 3);
}
catch (FailingInputResourceException e)
{
System.out.println(e.getMessage());
System.out.println("retrying...");
ReusableBusinessLogicClass.doMyBusiness(in2, out, 3);
}
}
ตอนนี้ทนกับฉัน โค้ดด้านบนนั้นง่าย คุณอาจบอกว่าคุณมีแอปพลิเคชันระดับองค์กร / เว็บคอนเทนเนอร์ที่มีหลายขอบเขตของทรัพยากรที่มีการจัดการคอนเทนเนอร์ของ IOC และคุณต้องลองใหม่โดยอัตโนมัติและกำหนดค่าเริ่มต้นใหม่ของเซสชันหรือทรัพยากรขอบเขตการร้องขอ ฯลฯ ตรรกะการเดินสายบนขอบเขตระดับล่าง สร้างทรัพยากรดังนั้นจึงไม่ได้ตระหนักถึงการใช้งานที่แน่นอน เฉพาะขอบเขตระดับที่สูงขึ้นเท่านั้นที่จะรู้ว่าอะไรคือข้อยกเว้นที่ทรัพยากรระดับต่ำกว่าสามารถส่งได้ เดี๋ยวก่อน!
น่าเสียดายที่ข้อยกเว้นอนุญาตให้มีการอ้อมในการเรียกสแต็กเท่านั้นและขอบเขตที่แตกต่างกันซึ่งมีความสำคัญเชิงซ้อนที่แตกต่างกันมักจะทำงานในหลายเธรดที่แตกต่างกัน ไม่มีวิธีการสื่อสารผ่านสิ่งนั้นโดยมีข้อยกเว้น เราต้องการบางสิ่งที่มีประสิทธิภาพมากขึ้นที่นี่ คำตอบ: async ข้อความผ่าน ตรวจสอบทุกข้อยกเว้นที่รูทของขอบเขตระดับล่าง อย่าเพิกเฉยสิ่งใดอย่าให้อะไรผ่านไป สิ่งนี้จะปิดและกำจัดทรัพยากรทั้งหมดที่สร้างขึ้นในสแตกการโทรของขอบเขตปัจจุบัน จากนั้นเผยแพร่ข้อความแสดงข้อผิดพลาดเพื่อกำหนดขอบเขตให้สูงขึ้นโดยใช้คิว / ช่องข้อความในรูทีนการจัดการข้อยกเว้นจนกว่าคุณจะถึงระดับที่ทราบข้อตกลง นั่นคือคนที่รู้วิธีจัดการกับมัน
SUMMA SUMMARUM
ดังนั้นตามการตีความของฉันจับปลายหมายถึงการจับข้อยกเว้นในสถานที่ที่สะดวกที่สุดที่คุณจะไม่ BREAKING นามธรรมอีกต่อไป อย่ารีบเร็วเกินไป! ตรวจจับข้อยกเว้นที่เลเยอร์ที่คุณสร้างข้อยกเว้นที่เป็นรูปธรรมในการขว้างอินสแตนซ์ของ abstractions ทรัพยากรเลเยอร์ที่รู้ถึงข้อสรุปของ abstractions ชั้น "เดินสาย"
HTH การเข้ารหัสที่มีความสุข!