คุณควรใช้ทั้งสองอย่าง สิ่งที่เป็นในการตัดสินใจเมื่อใช้แต่ละคน
มีบางสถานการณ์ที่ข้อยกเว้นเป็นทางเลือกที่ชัดเจน :
ในบางสถานการณ์คุณไม่สามารถทำอะไรกับรหัสข้อผิดพลาดได้และคุณเพียงแค่ต้องจัดการกับรหัสข้อผิดพลาดในระดับสูงกว่าใน call stackโดยปกติแล้วเพียงแค่บันทึกข้อผิดพลาดแสดงบางอย่างให้ผู้ใช้หรือปิดโปรแกรม ในกรณีเหล่านี้รหัสข้อผิดพลาดจะทำให้คุณต้องใส่รหัสข้อผิดพลาดด้วยตนเองทีละระดับซึ่งเห็นได้ชัดว่าง่ายกว่ามากในการทำข้อยกเว้น ประเด็นคือนี่เป็นสถานการณ์ที่ไม่คาดคิดและไม่สามารถรับมือได้
แต่เกี่ยวกับสถานการณ์ที่ 1 (ที่บางสิ่งบางอย่างที่ไม่คาดคิดเกิดขึ้นและ unhandleable คุณเพียงเข้าสู่ระบบ wan't มัน) ข้อยกเว้นจะมีประโยชน์เพราะคุณอาจจะเพิ่มข้อมูลตามบริบท ตัวอย่างเช่นถ้าฉันได้รับ SqlException ในตัวช่วยข้อมูลระดับล่างของฉันฉันจะต้องการตรวจจับข้อผิดพลาดนั้นในระดับต่ำ (ซึ่งฉันรู้คำสั่ง SQL ที่ทำให้เกิดข้อผิดพลาด) เพื่อที่ฉันจะได้จับข้อมูลนั้นและสร้างข้อมูลเพิ่มเติมอีกครั้ง . โปรดทราบคำวิเศษที่นี่: rethrow และไม่กลืน
กฎข้อแรกของการจัดการข้อยกเว้น:ไม่กลืนข้อยกเว้น นอกจากนี้โปรดทราบว่าการจับภายในของฉันไม่จำเป็นต้องบันทึกอะไรเลยเพราะตัวจับด้านนอกจะมีการติดตามสแต็กทั้งหมดและอาจบันทึกได้
ในบางสถานการณ์คุณมีลำดับของคำสั่งและหากคำสั่งใดล้มเหลวคุณควรล้าง / กำจัดทรัพยากร (*) ไม่ว่านี่จะเป็นสถานการณ์ที่ไม่สามารถกู้คืนได้ (ซึ่งควรถูกโยนทิ้ง) หรือสถานการณ์ที่สามารถกู้คืนได้ (ซึ่งในกรณีนี้คุณสามารถทำได้ จัดการในเครื่องหรือในรหัสผู้โทร แต่คุณไม่ต้องการข้อยกเว้น) เห็นได้ชัดว่ามันง่ายกว่ามากที่จะใส่คำสั่งเหล่านั้นทั้งหมดในการลองครั้งเดียวแทนที่จะทดสอบรหัสข้อผิดพลาดหลังจากแต่ละวิธีและล้างข้อมูล / กำจัดในบล็อกสุดท้าย โปรดทราบว่าหากคุณต้องการให้ข้อผิดพลาดเกิดขึ้น (ซึ่งอาจเป็นสิ่งที่คุณต้องการ) คุณไม่จำเป็นต้องจับมัน - คุณเพียงแค่ใช้ขั้นตอนสุดท้ายสำหรับการล้าง / กำจัด - คุณควรใช้ catch / retrow เท่านั้นหากคุณต้องการ เพื่อเพิ่มข้อมูลบริบท (ดูหัวข้อย่อย 2)
ตัวอย่างหนึ่งจะเป็นลำดับของคำสั่ง SQL ภายในบล็อกธุรกรรม อีกครั้งนี่เป็นสถานการณ์ที่ "ไม่สามารถจัดการได้" แม้ว่าคุณจะตัดสินใจที่จะจับมัน แต่เนิ่น ๆ (ปฏิบัติในพื้นที่แทนที่จะเป็นฟองขึ้นไปด้านบน) แต่ก็ยังคงเป็นสถานการณ์ที่ร้ายแรงซึ่งผลลัพธ์ที่ดีที่สุดคือการยกเลิกทุกอย่างหรืออย่างน้อยก็แท้งครั้งใหญ่ เป็นส่วนหนึ่งของกระบวนการ
(*) นี่เหมือนกับon error goto
ที่เราใช้ใน Visual Basic รุ่นเก่า
ในตัวสร้างคุณสามารถโยนข้อยกเว้นเท่านั้น
ต้องบอกว่าในสถานการณ์อื่น ๆ ทั้งหมดที่คุณส่งคืนข้อมูลบางอย่างซึ่งผู้โทรสามารถ / ควรดำเนินการบางอย่างการใช้รหัสส่งคืนอาจเป็นทางเลือกที่ดีกว่า ซึ่งรวมถึง"ข้อผิดพลาด" ที่คาดไว้ทั้งหมดเนื่องจากอาจเป็นไปได้ว่าผู้โทรควรจัดการและแทบจะไม่ต้องเพิ่มระดับมากเกินไปในสแต็ก
แน่นอนว่ามันเป็นไปได้เสมอที่จะถือว่าข้อผิดพลาดที่คาดไว้เป็นข้อยกเว้นและจับจากนั้นทันทีหนึ่งระดับด้านบนและยังสามารถรวมโค้ดทุกบรรทัดในการลองจับและดำเนินการกับข้อผิดพลาดที่เป็นไปได้แต่ละข้อ IMO นี้คือการออกแบบที่ไม่ดีไม่เพียงเพราะมันเป็นมากขึ้น verbose แต่เป็นพิเศษเพราะมีข้อยกเว้นที่เป็นไปได้ที่อาจจะโยนไม่ได้ที่เห็นได้ชัดโดยไม่ต้องอ่านรหัสที่มา - และข้อยกเว้นอาจจะโยนลงมาจากวิธีการใด ๆ ลึก, การสร้างgotos ที่มองไม่เห็น พวกเขาทำลายโครงสร้างโค้ดโดยการสร้างจุดออกที่มองไม่เห็นหลายจุดซึ่งทำให้โค้ดอ่านและตรวจสอบได้ยาก กล่าวอีกนัยหนึ่งคุณไม่ควรใช้ข้อยกเว้นเป็นการควบคุมการไหลเพราะนั่นจะเป็นเรื่องยากสำหรับคนอื่นที่จะเข้าใจและรักษา อาจเป็นเรื่องยากที่จะเข้าใจโฟลว์โค้ดที่เป็นไปได้ทั้งหมดสำหรับการทดสอบ
อีกครั้ง: สำหรับการล้าง / กำจัดที่ถูกต้องคุณสามารถใช้การลองโดยไม่ต้องจับอะไรเลย
คำวิจารณ์ที่ได้รับความนิยมมากที่สุดเกี่ยวกับรหัสส่งคืนคือ "บางคนสามารถเพิกเฉยต่อรหัสข้อผิดพลาด แต่ในแง่เดียวกันใครบางคนก็สามารถกลืนข้อยกเว้นได้เช่นกันการจัดการข้อยกเว้นที่ไม่ดีทำได้ง่ายทั้งสองวิธี แต่การเขียนโปรแกรมที่ใช้รหัสข้อผิดพลาดที่ดีนั้นยังง่ายกว่ามาก มากกว่าการเขียนโปรแกรมที่ใช้ข้อยกเว้นและถ้าเหตุผลใดก็ตามที่ตัดสินใจที่จะเพิกเฉยต่อข้อผิดพลาดทั้งหมด (เก่าon error resume next
) คุณสามารถทำได้อย่างง่ายดายโดยใช้รหัสส่งคืนและคุณไม่สามารถทำได้โดยไม่ต้องใช้สำเร็จรูปจำนวนมาก
คำวิจารณ์ที่ได้รับความนิยมอันดับสองเกี่ยวกับรหัสส่งคืนคือ "มันยากที่จะเกิดฟอง" แต่นั่นเป็นเพราะผู้คนไม่เข้าใจว่ามีข้อยกเว้นสำหรับสถานการณ์ที่ไม่สามารถกู้คืนได้ในขณะที่รหัสข้อผิดพลาดไม่ใช่
การตัดสินใจระหว่างข้อยกเว้นและรหัสข้อผิดพลาดเป็นพื้นที่สีเทา เป็นไปได้ว่าคุณจำเป็นต้องได้รับรหัสข้อผิดพลาดจากวิธีการทางธุรกิจที่สามารถนำกลับมาใช้ใหม่ได้จากนั้นคุณจึงตัดสินใจรวมข้อยกเว้น (อาจเพิ่มข้อมูล) และปล่อยให้มันเกิดขึ้น แต่เป็นความผิดพลาดในการออกแบบที่จะถือว่าข้อผิดพลาดทั้งหมดควรถูกโยนทิ้งเป็นข้อยกเว้น
สรุปได้:
ฉันชอบใช้ข้อยกเว้นเมื่อฉันมีสถานการณ์ที่ไม่คาดคิดซึ่งไม่มีอะไรให้ทำมากนักและโดยปกติแล้วเราต้องการยกเลิกบล็อกโค้ดขนาดใหญ่หรือแม้แต่การดำเนินการหรือโปรแกรมทั้งหมด นี่ก็เหมือนกับ "on error goto" แบบเก่า
ฉันชอบใช้รหัสส่งคืนเมื่อฉันคาดว่าจะเกิดสถานการณ์ที่รหัสผู้โทรสามารถ / ควรดำเนินการบางอย่างได้ ซึ่งรวมถึงวิธีการทางธุรกิจส่วนใหญ่ API การตรวจสอบความถูกต้องและอื่น ๆ
ความแตกต่างระหว่างข้อยกเว้นและรหัสข้อผิดพลาดนี้เป็นหลักการออกแบบอย่างหนึ่งของภาษา GO ซึ่งใช้ "ความตื่นตระหนก" สำหรับสถานการณ์ที่ไม่คาดคิดที่ร้ายแรงในขณะที่สถานการณ์ปกติที่คาดไว้จะถูกส่งกลับเป็นข้อผิดพลาด
แต่เกี่ยวกับ GO ยังอนุญาตให้ส่งคืนค่าหลายค่าซึ่งเป็นสิ่งที่ช่วยได้มากในการใช้รหัสส่งคืนเนื่องจากคุณสามารถส่งคืนข้อผิดพลาดและอย่างอื่นได้พร้อมกัน บน C # / Java เราสามารถบรรลุสิ่งนั้นได้โดยไม่ต้องใช้พารามิเตอร์ Tuples หรือ Generics (โปรดของฉัน) ซึ่งรวมกับ enums สามารถให้รหัสข้อผิดพลาดที่ชัดเจนแก่ผู้โทร:
public MethodResult<CreateOrderResultCodeEnum, Order> CreateOrder(CreateOrderOptions options)
{
....
return MethodResult<CreateOrderResultCodeEnum>.CreateError(CreateOrderResultCodeEnum.NO_DELIVERY_AVAILABLE, "There is no delivery service in your area");
...
return MethodResult<CreateOrderResultCodeEnum>.CreateSuccess(CreateOrderResultCodeEnum.SUCCESS, order);
}
var result = CreateOrder(options);
if (result.ResultCode == CreateOrderResultCodeEnum.OUT_OF_STOCK)
// do something
else if (result.ResultCode == CreateOrderResultCodeEnum.SUCCESS)
order = result.Entity; // etc...
ถ้าฉันเพิ่มผลตอบแทนที่เป็นไปได้ใหม่ในวิธีการของฉันฉันยังสามารถตรวจสอบผู้โทรทั้งหมดได้ว่าพวกเขาครอบคลุมค่าใหม่นั้นในคำสั่ง switch หรือไม่ คุณไม่สามารถทำได้ด้วยข้อยกเว้น เมื่อคุณใช้รหัสส่งคืนโดยปกติคุณจะทราบข้อผิดพลาดที่เป็นไปได้ทั้งหมดล่วงหน้าและทดสอบข้อผิดพลาดเหล่านี้ ด้วยข้อยกเว้นคุณมักไม่รู้ว่าจะเกิดอะไรขึ้น การห่อ enums ภายในข้อยกเว้น (แทนที่จะเป็น Generics) เป็นอีกทางเลือกหนึ่ง (ตราบเท่าที่มีการระบุประเภทของข้อยกเว้นที่แต่ละวิธีจะใช้อย่างชัดเจน) แต่ IMO ก็ยังออกแบบไม่ดี