การโยน ArgumentNullException ช่วยได้อย่างไร


27

สมมติว่าฉันมีวิธี:

public void DoSomething(ISomeInterface someObject)
{
    if(someObject == null) throw new ArgumentNullException("someObject");
    someObject.DoThisOrThat();
}

ฉันได้รับการฝึกฝนให้เชื่อว่าการขว้างArgumentNullExceptionเป็น "ถูกต้อง" แต่ข้อผิดพลาด "การอ้างอิงวัตถุไม่ได้ถูกตั้งค่าเป็นอินสแตนซ์ของวัตถุ" หมายความว่าฉันมีข้อผิดพลาด

ทำไม?

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

แก้ไข :

มันเพิ่งเกิดขึ้นกับฉัน ... ความกลัวของโมฆะที่ลงทะเบียนไม่ได้มาจากภาษาอย่าง C ++ ที่ไม่ได้ตรวจสอบคุณ (นั่นเป็นเพียงแค่พยายามที่จะเรียกใช้วิธีการบางอย่างที่ตำแหน่งหน่วยความจำ zero + method offset)?


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

คำตอบ:


34

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

นอกจากนี้ผู้ดูแลในภายหลังมีแนวโน้มที่จะทำอย่างไร เพิ่ม ArgumentNullException หากพวกเขาเพิ่มรหัสก่อนการอ้างอิงแรกหรือเพียงแค่เพิ่มรหัสและในภายหลังอนุญาตให้ NullReferenceException ถูกโยนทิ้งได้หรือไม่


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

ฉันมักจะไม่เพิ่มการตรวจสอบเหล่านี้ลงในวิธีการส่วนตัว แต่ฉันยังอาจเพิ่มการตรวจสอบเหล่านี้ลงในวิธีสาธารณะของชั้นเรียนส่วนตัวเพื่อให้สอดคล้องกัน สำหรับวิธีการส่วนตัวฉันมักจะสันนิษฐานว่าข้อโต้แย้งได้รับการตรวจสอบก่อนหน้านี้ในระดับการโทรสาธารณะ
Matt H

54

ฉันได้รับการฝึกฝนให้เชื่อว่าการโยน ArgumentNullException นั้นเป็น "ถูกต้อง" แต่ข้อผิดพลาด "การอ้างอิงวัตถุไม่ได้ถูกตั้งค่าเป็นอินสแตนซ์ของวัตถุ" หมายความว่าฉันมีข้อผิดพลาด ทำไม?

สมมติว่าฉันเรียกวิธีการM(x)ที่คุณเขียน ฉันผ่านโมฆะ ฉันได้รับ ArgumentNullException โดยตั้งชื่อเป็น "x" ข้อยกเว้นนั้นอย่างไม่น่าสงสัยหมายความว่าฉันมีข้อผิดพลาด; ฉันไม่ควรผ่านค่าว่างสำหรับ x

สมมติว่าฉันเรียกวิธี M ที่คุณเขียน ฉันผ่านโมฆะ ฉันได้รับข้อยกเว้น null ฉันควรจะรู้ได้อย่างไรว่าฉันมีข้อบกพร่องหรือคุณมีข้อบกพร่องหรือมีห้องสมุดบุคคลที่สามที่คุณโทรหาในนามของฉันมีข้อบกพร่องหรือไม่? ฉันไม่รู้อะไรเลยว่าฉันต้องแก้ไขรหัสของฉันหรือโทรหาคุณเพื่อบอกให้คุณแก้ไขของคุณ

ข้อยกเว้นทั้งสองบ่งบอกถึงข้อบกพร่อง; ไม่ควรที่เคยถูกโยนในรหัสการผลิต คำถามคือข้อยกเว้นใดที่สื่อสารผู้ที่มีข้อบกพร่องดีกว่ากับคนที่ทำการดีบัก? ข้อยกเว้น Null deref ไม่บอกอะไรคุณเลย ข้อยกเว้นโมฆะอาร์กิวเมนต์บอกคุณมาก มีเมตตาต่อผู้ใช้ของคุณ โยนข้อยกเว้นที่ทำให้พวกเขาเรียนรู้จากความผิดพลาดของพวกเขา


ฉันไม่แน่ใจว่าฉันเข้าใจ"neither should ever be thrown in production code"โค้ดประเภทใด / สถานการณ์ใดที่พวกเขาจะใช้? พวกเขาควรจะรวบรวมออกมาเป็นDebug.Assertอย่างไร หรือคุณหมายความว่าจะไม่โยนออกจาก API ใช่ไหม
StuperUser

6
@StuperUser: ฉันคิดว่าคุณเข้าใจผิดในสิ่งที่ฉันหมายถึงเพราะฉันเขียนมันในทางที่สับสน คุณควรมีthrow new ArgumentNullExceptionในรหัสการผลิตของคุณและมันไม่ควรทำงานนอกกรณีทดสอบ ข้อยกเว้นไม่ควรถูกโยนเนื่องจากผู้โทรไม่ควรมีข้อผิดพลาดนั้น
Eric Lippert

1
ไม่เพียง แต่มันสื่อสารที่มีข้อผิดพลาดการสื่อสารที่มีข้อบกพร่องอยู่ แม้ว่าทั้งสองพื้นที่เป็นของฉันมันไม่ง่ายเลยที่จะเข้าใจว่าตัวแปรใดเป็นโมฆะ
Ohad Schneider

5

ประเด็นคือรหัสของคุณควรทำสิ่งที่มีความหมาย

A NullReferenceExceptionไม่ได้มีความหมายอย่างยิ่งเพราะอาจหมายถึงทุกสิ่ง โดยไม่ได้ดูว่ามีข้อผิดพลาดเกิดขึ้นที่ไหน (และฉันไม่สามารถใช้รหัสได้) ฉันไม่สามารถเข้าใจได้ว่าเกิดอะไรขึ้น

ArgumentNullExceptionมีความหมายเพราะมันบอกฉัน: การดำเนินการล้มเหลวเนื่องจากการโต้แย้งเป็นsomeObject nullฉันไม่จำเป็นต้องดูรหัสของคุณเพื่อค้นหา

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

จุดของข้อยกเว้นคือการสื่อสารข้อมูลในกรณีของความล้มเหลวซึ่งสามารถจัดการได้ที่รันไทม์เพื่อกู้คืนจากความล้มเหลวหรือในเวลาดีบั๊กเพื่อให้เข้าใจสิ่งที่ผิดพลาดได้ดีขึ้น


2

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

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


1

ฉันได้รับการฝึกฝนให้เชื่อว่าการโยน ArgumentNullException นั้นเป็น "ถูกต้อง" แต่ข้อผิดพลาด "การอ้างอิงวัตถุไม่ได้ถูกตั้งค่าเป็นอินสแตนซ์ของวัตถุ" หมายความว่าฉันมีข้อผิดพลาด

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

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

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

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


1

การตรวจสอบทำให้ชัดเจนยิ่งขึ้นว่าเกิดอะไรขึ้น แต่ปัญหาที่แท้จริงมาพร้อมกับรหัสเช่นนี้ (contrived) ตัวอย่าง:

public void DoSomething(Foo someOtherObject, ISomeInterface someObject)
{
    someOtherObject.DoThisOrThat(this, someObject);
}

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

โดยทั่วไปจะดีกว่าเพื่อให้สอดคล้องกับสิ่งต่าง ๆ นี้และเพิ่มการตรวจสอบค่า Null - ซึ่งทำให้ง่ายต่อการตรวจสอบหากพบว่าหายไปแทนที่จะเลือกและเลือกเป็นกรณี ๆ ไป (สมมติว่าคุณรู้ว่าเป็นโมฆะ) ไม่ถูกต้องเสมอ)


1

อีกเหตุผลที่ควรพิจารณาคือหากการประมวลผลบางอย่างเกิดขึ้นก่อนที่จะใช้วัตถุ null

สมมติว่าคุณมีไฟล์ข้อมูลของรหัส บริษัท ที่เกี่ยวข้อง (Cid) และรหัสบุคคล (Pid) มันถูกบันทึกเป็นสตรีมหมายเลขคู่:

ปิดการใช้งาน cid pid cid pid cid pid cid pid

และฟังก์ชั่น VB นี้เขียนบันทึกลงไฟล์:

Sub WriteRecord(Company As CompanyInfo, Person As PersonInfo)
    Using File As New StringWriter(DataFileName)
        File.Write(Company.ID)
        File.Write(Person.ID)
    End Using
End Sub

หากPersonเป็นการอ้างอิงที่ไม่ถูกต้องบรรทัดFile.Write(Person.ID)นั้นจะมีข้อยกเว้น ซอฟต์แวร์สามารถตรวจจับข้อยกเว้นและกู้คืน แต่สิ่งนี้ทำให้เกิดปัญหา มีการเขียนรหัส บริษัท โดยไม่มี ID บุคคลที่เกี่ยวข้อง หากเป็นจริงเมื่อคุณเรียกใช้ฟังก์ชันนี้ในครั้งต่อไปคุณจะต้องใส่รหัส บริษัท ที่คาดว่าจะใช้รหัสบุคคลก่อนหน้า เช่นเดียวกับบันทึกที่ไม่ถูกต้องทุกระเบียนที่ตามมาจะมี ID บุคคลตามด้วย ID บริษัท ซึ่งเป็นวิธีที่ผิด

ปิดการใช้งาน cid pid cid pid cid pid cid pid cid

โดยการตรวจสอบพารามิเตอร์ที่จุดเริ่มต้นของฟังก์ชันคุณจะป้องกันการประมวลผลใด ๆ ที่เกิดขึ้นเมื่อวิธีการนั้นรับประกันว่าจะล้มเหลว

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