เหตุใดเราจึงไม่ทิ้งข้อยกเว้นเหล่านี้


111

ฉันได้ข้ามผ่าน หน้า MSDNที่ระบุว่า:

อย่าโยนException , SystemException , NullReferenceExceptionหรือIndexOutOfRangeExceptionโดยเจตนาจากซอร์สโค้ดของคุณเอง

น่าเสียดายที่ไม่ต้องอธิบายว่าทำไม ฉันเดาเหตุผลได้ แต่หวังว่าจะมีคนที่มีอำนาจมากกว่าในเรื่องนี้อาจให้ข้อมูลเชิงลึกได้

สองข้อแรกมีความหมายที่ชัดเจน แต่สองข้อหลังดูเหมือนว่าคุณต้องการจ้างงาน (และอันที่จริงฉันมี)

นอกจากนี้เป็นข้อยกเว้นเดียวที่ควรหลีกเลี่ยงหรือไม่? หากมีคนอื่นพวกเขาคืออะไรและทำไมจึงควรหลีกเลี่ยงเช่นกัน


22
จากmsdn.microsoft.com/en-us/library/ms182338.aspx : ถ้าคุณโยนประเภทข้อยกเว้นทั่วไปเช่น Exception หรือ SystemException ในไลบรารีหรือเฟรมเวิร์กจะบังคับให้ผู้บริโภคตรวจจับข้อยกเว้นทั้งหมดรวมถึงข้อยกเว้นที่ไม่รู้จักที่พวกเขาทำ ไม่รู้จะจัดการอย่างไร
Henrik

3
ทำไมคุณถึงโยน NullReferenceException?
Rik

5
@ ริก: คล้ายกับNullArgumentExceptionที่บางคนอาจสับสนทั้งสองอย่าง
Moslem Ben Dhaou

2
@Rik วิธีการขยายตามคำตอบของฉัน
Marc Gravell

3
อีกอันที่คุณไม่ควรโยนคือApplicationException
Matthew Watson

คำตอบ:


98

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

SystemExceptionด้วยเหตุผลเดียวกันยังนำไปใช้ หากคุณดูรายการประเภทที่ได้รับคุณจะเห็นข้อยกเว้นอื่น ๆ อีกมากมายที่มีความหมายที่แตกต่างกันมาก

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

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

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

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


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

7
@delnan หากคุณกำลังใช้งานIListคุณจะต้องโยนArgumentOutOfRangeExceptionตามที่เอกสารอินเทอร์เฟซแนะนำ IndexOutOfRangeExceptionมีไว้สำหรับอาร์เรย์และเท่าที่ฉันทราบคุณไม่สามารถใช้อาร์เรย์ซ้ำได้
โผล่

2
สิ่งที่อาจเกี่ยวข้องกันบ้างNullReferenceExceptionมักจะถูกโยนทิ้งภายในเป็นกรณีพิเศษของAccessViolationException(IIRC การทดสอบเป็นสิ่งที่ต้องการcmp [addr], addrกล่าวคือมันพยายามที่จะหักล้างตัวชี้และหากล้มเหลวด้วยการละเมิดการเข้าถึงจะจัดการความแตกต่างระหว่าง NRE และ AVE ในตัวจัดการขัดจังหวะผลลัพธ์) นอกเหนือจากเหตุผลทางความหมายแล้วยังมีการโกงอีกด้วย นอกจากนี้ยังอาจช่วยกีดกันคุณจากการตรวจสอบnullด้วยตนเองเมื่อไม่เป็นประโยชน์ - หากคุณกำลังจะโยน NRE ต่อไปทำไมไม่ให้. NET ทำ
Luaan

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

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

36

ฉันสงสัยว่าเจตนาของ 2 ข้อสุดท้ายคือเพื่อป้องกันความสับสนกับข้อยกเว้นในตัวที่มีความหมายที่คาดหวัง แต่ผมมีความเห็นว่าถ้าคุณกำลังรักษาความตั้งใจที่แน่นอนของข้อยกเว้น : throwมันเป็นหนึ่งที่ถูกต้อง ตัวอย่างเช่นถ้าคุณกำลังเขียนคอลเลกชันที่กำหนดเองดูเหมือนว่าเหมาะสมที่จะใช้ทั้งหมดIndexOutOfRangeException- เฉพาะที่ชัดเจนและมากขึ้น IMO ArgumentOutOfRangeExceptionกว่า และในขณะที่List<T>อาจเลือกอย่างหลัง แต่มีอย่างน้อย 41 แห่ง (มารยาทของตัวสะท้อนแสง) ใน BCL (ไม่รวมอาร์เรย์) ที่การโยนแบบกำหนดเองนั้นมีประโยชน์ในวิธีการขยาย - หากคุณต้องการรักษาความหมายที่:IndexOutOfRangeException - ไม่มีสถานที่ใดที่ "ระดับต่ำ" เพียงพอที่จะสมควรได้รับการยกเว้นพิเศษ ใช่ฉันคิดว่าคุณสามารถโต้แย้งได้ว่าแนวทางนั้นไร้สาระ ในทำนองเดียวกันNullReferenceException

obj.SomeMethod(); // this is actually an extension method

พ่นNullReferenceExceptionเมื่อเป็นobjnull


2
"ถ้าคุณรักษาเจตนาที่แท้จริงของข้อยกเว้น" - แน่นอนว่าหากเป็นเช่นนั้นข้อยกเว้นจะถูกโยนทิ้งโดยที่คุณไม่ต้องทดสอบตั้งแต่แรก? และถ้าคุณได้ทำการทดสอบแล้วมันก็ไม่มีข้อยกเว้นใช่หรือไม่?
PugFugly

5
@PugFugly ใช้เวลา 2 วินาทีในการดูตัวอย่างวิธีการขยาย: ไม่ซึ่งจะไม่ถูกโยนโดยที่คุณไม่ต้องทดสอบ หากSomeMethod()ไม่จำเป็นต้องเข้าถึงสมาชิกการบังคับให้เข้าใช้งานไม่ถูกต้อง ในทำนองเดียวกัน: ใช้จุดนั้นด้วย 41 สถานที่ใน BCL ที่สร้างขึ้นเองIndexOutOfRangeExceptionและ 16 สถานที่ที่สร้างแบบกำหนดเองNullReferenceException
Marc Gravell

16
ฉันจะเถียงว่าวิธีการขยายควรยังคงโยน a ArgumentNullExceptionแทนNullReferenceException. แม้ว่าน้ำตาลไวยากรณ์จากวิธีการส่วนขยายจะอนุญาตให้ใช้ไวยากรณ์เดียวกันกับการเข้าถึงของสมาชิกปกติ แต่ก็ยังคงทำงานแตกต่างกันมาก และการรับ NRE จากMyStaticHelpers.SomeMethod(obj)จะผิด
โผล่

1
@PugFugly BCL คือ "ไลบรารีคลาสพื้นฐาน" โดยพื้นฐานแล้วเป็นเนื้อหาหลักใน. NET
โผล่

1
@PugFugly: มีหลายสถานการณ์ที่ความล้มเหลวในการตรวจหาเงื่อนไขล่วงหน้าจะส่งผลให้มีการโยนข้อยกเว้นในเวลาที่ "ไม่สะดวก" หากการดำเนินการไม่ประสบความสำเร็จการทิ้งข้อยกเว้นไว้ก่อนจะดีกว่าการเริ่มดำเนินการผ่านไปครึ่งทางแล้วต้องล้างระเบียบที่ประมวลผลบางส่วนที่เป็นผลลัพธ์
supercat

5

ดังที่คุณชี้ให้เห็นในบทความการสร้างและการโยนข้อยกเว้น (คู่มือการเขียนโปรแกรม C #)ภายใต้หัวข้อสิ่งที่ควรหลีกเลี่ยงเมื่อโยนข้อยกเว้น Microsoft จะแสดงรายการSystem.IndexOutOfRangeExceptionเป็นประเภทข้อยกเว้นที่ไม่ควรโยนโดยเจตนาจากซอร์สโค้ดของคุณเอง

ในทางตรงกันข้ามในบทความ(อ้างอิง C #) Microsoft ดูเหมือนจะละเมิดแนวทางของตัวเอง นี่คือวิธีการที่ Microsoft รวมไว้ในตัวอย่าง:

static int GetNumber(int index)
{
    int[] nums = { 300, 600, 900 };
    if (index > nums.Length)
    {
        throw new IndexOutOfRangeException();
    }
    return nums[index];
}

ดังนั้น Microsoft เองก็ไม่ได้มีความสอดคล้องกันเนื่องจากแสดงให้เห็นถึงการขว้างปาIndexOutOfRangeExceptionในเอกสารสำหรับthrow!

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


1

เมื่อฉันอ่านคำถามของคุณฉันถามตัวเองภายใต้เงื่อนไขว่าใครอยากจะโยนประเภทยกเว้นNullReferenceException, หรือInvalidCastExceptionArgumentOutOfRangeException

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

PS: ตั้งแต่ปี 2003 ฉันได้พัฒนาข้อยกเว้นของตัวเองดังนั้นฉันจึงสามารถโยนมันได้ตามที่ต้องการ ฉันคิดว่ามันเป็นแนวทางปฏิบัติที่ดีที่สุดที่จะทำเช่นนั้น


จุดที่ดี อย่างไรก็ตามฉันคิดว่ามันน่าจะถูกต้องกว่าที่จะบอกว่าโปรแกรมเมอร์ควรปล่อยให้รันไทม์. NET Framework ทิ้งข้อยกเว้นประเภทนี้ (และโปรแกรมเมอร์ควรจัดการกับพวกมันอย่างเหมาะสม)
DavidRR

0

การอภิปรายเกี่ยวกับNullReferenceExceptionและIndexOutOfBoundsExceptionกัน:

System.Exceptionสิ่งที่เกี่ยวกับการจับและการขว้างปา ฉันได้โยนข้อยกเว้นประเภทนี้ในรหัสของฉันเป็นจำนวนมากและฉันก็ไม่เคยกังวลกับมัน ในทำนองเดียวกันบ่อยครั้งที่ฉันจับExceptionประเภทที่ไม่เฉพาะเจาะจงและมันก็ใช้ได้ดีสำหรับฉันด้วย แล้วทำไมล่ะ?

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

ดังนั้นเกี่ยวกับการขว้างปาExceptionฉันไม่เห็นเหตุผลที่จะห้ามสิ่งนี้ในทุกกรณี

แก้ไข: จากหน้า MSDN:

ไม่ควรใช้ข้อยกเว้นเพื่อเปลี่ยนโฟลว์ของโปรแกรมเป็นส่วนหนึ่งของการดำเนินการตามปกติ ควรใช้ข้อยกเว้นเพื่อรายงานและจัดการเงื่อนไขข้อผิดพลาดเท่านั้น

การใช้คำสั่งจับมากเกินไปด้วยตรรกะส่วนบุคคลสำหรับข้อยกเว้นประเภทต่างๆไม่ใช่แนวทางปฏิบัติที่ดีที่สุดเช่นกัน


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

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