นั่นเป็นคำถามทั่วไป (แต่ฉันใช้ C #) เป็นวิธีที่ดีที่สุด (แนวปฏิบัติที่ดีที่สุด) คุณส่งคืนคอลเลกชันที่ว่างเปล่าหรือว่างเปล่าสำหรับวิธีการที่มีคอลเลกชันเป็นประเภทที่กลับมา?
นั่นเป็นคำถามทั่วไป (แต่ฉันใช้ C #) เป็นวิธีที่ดีที่สุด (แนวปฏิบัติที่ดีที่สุด) คุณส่งคืนคอลเลกชันที่ว่างเปล่าหรือว่างเปล่าสำหรับวิธีการที่มีคอลเลกชันเป็นประเภทที่กลับมา?
คำตอบ:
คอลเลกชันที่ว่างเปล่า เสมอ.
สิ่งนี้แย่มาก:
if(myInstance.CollectionProperty != null)
{
foreach(var item in myInstance.CollectionProperty)
/* arrgh */
}
ถือว่าเป็นแนวปฏิบัติที่ดีที่สุดที่จะไม่ส่งคืนnull
เมื่อส่งคืนคอลเล็กชันหรือนับจำนวน เสมอกลับมานับคอลเลกชัน / ที่ว่างเปล่า มันป้องกันเรื่องไร้สาระดังกล่าวข้างต้นและป้องกันไม่ให้รถของคุณได้รับไข่โดยเพื่อนร่วมงานและผู้ใช้ในชั้นเรียนของคุณ
เมื่อพูดถึงคุณสมบัติให้ตั้งค่าคุณสมบัติของคุณหนึ่งครั้งและลืมมันไป
public List<Foo> Foos {public get; private set;}
public Bar() { Foos = new List<Foo>(); }
ใน. NET 4.6.1 คุณสามารถกลั่นตัวสิ่งนี้ได้ค่อนข้างมาก:
public List<Foo> Foos { get; } = new List<Foo>();
เมื่อพูดถึงวิธีที่คืนค่านับคุณสามารถคืนค่าว่างได้อย่างง่ายดายแทนnull
...
public IEnumerable<Foo> GetMyFoos()
{
return InnerGetFoos() ?? Enumerable.Empty<Foo>();
}
การใช้Enumerable.Empty<T>()
สามารถถูกมองว่ามีประสิทธิภาพมากกว่าการส่งคืนตัวอย่างเช่นคอลเล็กชันหรืออาร์เรย์ว่างใหม่
IEnumerable
หรือICollection
ไม่สำคัญมาก อย่างไรก็ตามถ้าคุณเลือกประเภทที่ICollection
พวกเขากลับมาnull
... ฉันต้องการให้พวกเขากลับคอลเลกชันที่ว่างเปล่า แต่ฉันวิ่งเข้าไปในพวกเขากลับมาnull
ดังนั้นฉันคิดว่าฉันจะพูดถึงมันที่นี่ ฉันจะบอกว่าค่าเริ่มต้นของการรวบรวมนับไม่ว่างเปล่าเป็นโมฆะ ฉันไม่รู้ว่ามันเป็นเรื่องละเอียดอ่อน
จากFramework Design Guidelines 2nd Edition (pg. 256):
ห้ามส่งคืนค่า null จากคุณสมบัติการรวบรวมหรือจากวิธีการคืนค่าคอลเล็กชัน ส่งคืนคอลเล็กชันว่างหรืออาร์เรย์ว่างแทน
นี่เป็นอีกบทความที่น่าสนใจเกี่ยวกับประโยชน์ของการไม่คืนค่า Null (ฉันพยายามค้นหาบางอย่างในบล็อกของ Brad Abram และเขาเชื่อมโยงกับบทความ)
Edit-เนื่องจาก Eric Lippert ได้แสดงความคิดเห็นกับคำถามเดิมแล้วฉันต้องการลิงก์ไปยังบทความที่ยอดเยี่ยมของเขาด้วย
ขึ้นอยู่กับคุณสัญญาของคุณและกรณีที่เป็นรูปธรรม โดยทั่วไปจะเป็นการดีที่สุดที่จะส่งคืนคอลเล็กชันที่ว่างเปล่าแต่บางครั้ง ( ไม่ค่อย )
null
อาจหมายถึงบางสิ่งที่เฉพาะเจาะจงมากขึ้นnull
อาจบังคับให้คุณกลับมาตัวอย่างที่เป็นรูปธรรม:
null
หมายความว่าองค์ประกอบหายไปในขณะที่การรวบรวมที่ว่างเปล่าจะทำให้เกิดการซ้ำซ้อน (และอาจไม่ถูกต้อง)<collection />
มีอีกประเด็นหนึ่งที่ยังไม่ได้กล่าวถึง พิจารณารหัสต่อไปนี้:
public static IEnumerable<string> GetFavoriteEmoSongs()
{
yield break;
}
ภาษา C # จะส่งคืนตัวแจงนับว่างเมื่อเรียกใช้วิธีนี้ ดังนั้นเพื่อให้สอดคล้องกับการออกแบบภาษา (และความคาดหวังของโปรแกรมเมอร์) จึงควรส่งคืนคอลเลกชันที่ว่างเปล่า
ว่างเปล่าเป็นมิตรกับผู้บริโภคมากขึ้น
มีวิธีการที่ชัดเจนในการสร้างจำนวนที่ว่างเปล่า:
Enumerable.Empty<Element>()
สำหรับฉันแล้วคุณควรคืนค่าที่ถูกต้องตามความหมายในบริบทไม่ว่าจะเป็นอะไรก็ตาม กฎที่ระบุว่า "ส่งคืนคอลเลกชันที่ว่างเปล่าเสมอ" ดูเหมือนจะง่ายสำหรับฉันเล็กน้อย
สมมติว่าเป็นระบบสำหรับโรงพยาบาลเรามีฟังก์ชั่นที่ควรส่งคืนรายการการรักษาในโรงพยาบาลก่อนหน้านี้ทั้งหมดในช่วง 5 ปีที่ผ่านมา หากลูกค้าไม่ได้อยู่ในโรงพยาบาลก็ควรกลับรายการเปล่า แต่ถ้าลูกค้าปล่อยส่วนนั้นของแบบฟอร์มอนุญาติให้ว่างเปล่า เราต้องการค่าที่แตกต่างเพื่อแยก "รายการว่าง" จาก "ไม่มีคำตอบ" หรือ "ไม่รู้" เราสามารถส่งข้อยกเว้นได้ แต่ไม่จำเป็นต้องเป็นเงื่อนไขข้อผิดพลาดและไม่จำเป็นว่าจะทำให้เราออกจากโปรแกรมปกติ
ฉันมักจะผิดหวังกับระบบที่ไม่สามารถแยกความแตกต่างระหว่างศูนย์และไม่มีคำตอบ ฉันมีหลายครั้งที่ระบบขอให้ฉันป้อนหมายเลขฉันป้อนศูนย์และฉันได้รับข้อความแสดงข้อผิดพลาดบอกฉันว่าฉันต้องป้อนค่าในฟิลด์นี้ ฉันเพิ่งทำ: ฉันป้อนเลขศูนย์! แต่มันจะไม่ยอมรับศูนย์เพราะมันไม่สามารถแยกความแตกต่างจากไม่มีคำตอบ
ตอบกลับไปยังแซนเดอร์:
ใช่ฉันสมมติว่ามีความแตกต่างระหว่าง "คนไม่ตอบคำถาม" และ "คำตอบคือศูนย์" นั่นคือประเด็นของวรรคสุดท้ายของคำตอบของฉัน หลายโปรแกรมไม่สามารถแยกแยะ "ไม่ทราบ" จากที่ว่างหรือเป็นศูนย์ซึ่งดูเหมือนว่าฉันมีข้อบกพร่องร้ายแรง ตัวอย่างเช่นฉันกำลังซื้อบ้านหลังหนึ่งปีหรือมากกว่านั้น ฉันไปที่เว็บไซต์อสังหาริมทรัพย์และมีบ้านหลายหลังในราคา $ 0 ฟังดูดีมากสำหรับฉัน: พวกเขาให้บ้านเหล่านี้ฟรี! แต่ฉันแน่ใจว่าความจริงที่น่าเศร้าก็คือพวกเขาไม่ได้ป้อนราคา ในกรณีนี้คุณอาจพูดว่า "อืมศูนย์อย่างเด็ดขาดหมายความว่าพวกเขาไม่ได้ป้อนราคา - ไม่มีใครจะให้บ้านฟรี" แต่เว็บไซต์ดังกล่าวก็ระบุราคาเฉลี่ยของการถามและขายบ้านในเมืองต่างๆ ฉันอดไม่ได้ที่จะสงสัยว่าค่าเฉลี่ยไม่รวมศูนย์ดังนั้นให้ค่าเฉลี่ยต่ำอย่างไม่ถูกต้องสำหรับบางแห่ง เช่นอะไรคือค่าเฉลี่ยของ $ 100,000; $ 120,000; และ "ไม่ทราบ" ในทางเทคนิคคำตอบคือ "ไม่รู้" สิ่งที่เราอาจต้องการเห็นจริงๆคือ $ 110,000 แต่สิ่งที่เราจะได้รับคือ $ 73,333 ซึ่งอาจผิดทั้งหมด นอกจากนี้จะเกิดอะไรขึ้นถ้าเราประสบปัญหานี้ในไซต์ที่ผู้ใช้สามารถสั่งซื้อออนไลน์ได้ (ไม่น่าสำหรับอสังหาริมทรัพย์ แต่ฉันแน่ใจว่าคุณเคยเห็นมันทำเพื่อผลิตภัณฑ์อื่น ๆ ) เราจะต้องการ "ราคายังไม่ได้ระบุ" จะถูกตีความว่าเป็น "ฟรี" หรือไม่? จึงให้ค่าเฉลี่ยต่ำอย่างไม่ถูกต้องสำหรับบางสถานที่ เช่นอะไรคือค่าเฉลี่ยของ $ 100,000; $ 120,000; และ "ไม่ทราบ" ในทางเทคนิคคำตอบคือ "ไม่รู้" สิ่งที่เราอาจต้องการเห็นจริงๆคือ $ 110,000 แต่สิ่งที่เราจะได้รับคือ $ 73,333 ซึ่งอาจผิดทั้งหมด นอกจากนี้จะเกิดอะไรขึ้นถ้าเราประสบปัญหานี้ในไซต์ที่ผู้ใช้สามารถสั่งซื้อออนไลน์ได้ (ไม่น่าสำหรับอสังหาริมทรัพย์ แต่ฉันแน่ใจว่าคุณเคยเห็นมันทำเพื่อผลิตภัณฑ์อื่น ๆ ) เราจะต้องการ "ราคายังไม่ได้ระบุ" จะถูกตีความว่าเป็น "ฟรี" หรือไม่? จึงให้ค่าเฉลี่ยต่ำอย่างไม่ถูกต้องสำหรับบางสถานที่ เช่นอะไรคือค่าเฉลี่ยของ $ 100,000; $ 120,000; และ "ไม่ทราบ" ในทางเทคนิคคำตอบคือ "ไม่รู้" สิ่งที่เราอาจต้องการเห็นจริงๆคือ $ 110,000 แต่สิ่งที่เราจะได้รับคือ $ 73,333 ซึ่งอาจผิดทั้งหมด นอกจากนี้จะเกิดอะไรขึ้นถ้าเราประสบปัญหานี้ในไซต์ที่ผู้ใช้สามารถสั่งซื้อออนไลน์ได้ (ไม่น่าสำหรับอสังหาริมทรัพย์ แต่ฉันแน่ใจว่าคุณเคยเห็นมันทำเพื่อผลิตภัณฑ์อื่น ๆ ) เราจะต้องการ "ราคายังไม่ได้ระบุ" จะถูกตีความว่าเป็น "ฟรี" หรือไม่? ซึ่งจะผิดอย่างสมบูรณ์ นอกจากนี้จะเกิดอะไรขึ้นถ้าเราประสบปัญหานี้ในไซต์ที่ผู้ใช้สามารถสั่งซื้อออนไลน์ได้ (ไม่น่าสำหรับอสังหาริมทรัพย์ แต่ฉันแน่ใจว่าคุณเคยเห็นมันทำเพื่อผลิตภัณฑ์อื่น ๆ ) เราจะต้องการ "ราคายังไม่ได้ระบุ" จะถูกตีความว่าเป็น "ฟรี" หรือไม่? ซึ่งจะผิดอย่างสมบูรณ์ นอกจากนี้จะเกิดอะไรขึ้นถ้าเราประสบปัญหานี้ในไซต์ที่ผู้ใช้สามารถสั่งซื้อออนไลน์ได้ (ไม่น่าสำหรับอสังหาริมทรัพย์ แต่ฉันแน่ใจว่าคุณเคยเห็นมันทำเพื่อผลิตภัณฑ์อื่น ๆ ) เราจะต้องการ "ราคายังไม่ได้ระบุ" จะถูกตีความว่าเป็น "ฟรี" หรือไม่?
RE มีฟังก์ชั่นแยกกันสองตัวคือ "มีอะไรบ้าง" และ "ถ้าเป็นเช่นนั้นมันคืออะไร" ใช่คุณสามารถทำได้อย่างแน่นอน แต่ทำไมคุณต้องการที่จะ? ตอนนี้โปรแกรมการโทรต้องทำการโทรสองครั้งแทนที่จะเป็นการโทรครั้งเดียว จะเกิดอะไรขึ้นหากโปรแกรมเมอร์ไม่สามารถเรียก "อะไร" และตรงไปที่ "มันคืออะไร" ? โปรแกรมจะคืนค่าศูนย์นำผิดหรือไม่? โยนข้อยกเว้นหรือไม่ ส่งคืนค่าที่ไม่ได้กำหนดหรือไม่? มันสร้างรหัสเพิ่มเติมทำงานได้มากขึ้นและมีข้อผิดพลาดมากขึ้น
ประโยชน์เดียวที่ฉันเห็นคือมันช่วยให้คุณสามารถปฏิบัติตามกฎเกณฑ์โดยพลการ มีข้อได้เปรียบสำหรับกฎนี้หรือไม่ที่ทำให้คุ้มค่ากับปัญหาในการเชื่อฟังมัน? ถ้าไม่สนใจทำไม
ตอบกลับ Jammycakes:
พิจารณาว่ารหัสที่แท้จริงจะเป็นอย่างไร ฉันรู้คำถามที่ว่า C # แต่ขอโทษถ้าฉันเขียน Java C # ของฉันไม่คมมากและหลักการก็เหมือนกัน
ด้วยผลตอบแทนที่เป็นโมฆะ:
HospList list=patient.getHospitalizationList(patientId);
if (list==null)
{
// ... handle missing list ...
}
else
{
for (HospEntry entry : list)
// ... do whatever ...
}
ด้วยฟังก์ชั่นแยก:
if (patient.hasHospitalizationList(patientId))
{
// ... handle missing list ...
}
else
{
HospList=patient.getHospitalizationList(patientId))
for (HospEntry entry : list)
// ... do whatever ...
}
อันที่จริงแล้วมันเป็นโค้ดหนึ่งหรือสองตัวที่น้อยกว่าโดยมีค่าส่งคืนเป็นโมฆะดังนั้นจึงไม่เป็นภาระเพิ่มเติมสำหรับผู้โทร
ฉันไม่เห็นว่ามันสร้างปัญหา DRY อย่างไร ไม่ใช่ว่าเราจะต้องดำเนินการโทรสองครั้ง หากเราต้องการทำสิ่งเดียวกันเสมอเมื่อไม่มีรายการอยู่บางทีเราสามารถกดลงไปที่ฟังก์ชั่น get-list แทนการให้ผู้โทรทำเช่นนั้น แต่เราเกือบจะไม่ต้องการทำสิ่งเดียวกันเสมอ ในฟังก์ชั่นที่เราต้องมีรายการที่ต้องดำเนินการรายการที่หายไปเป็นข้อผิดพลาดที่อาจหยุดการประมวลผล แต่ในหน้าจอแก้ไขเราไม่ต้องการหยุดการประมวลผลหากพวกเขายังไม่ได้ป้อนข้อมูล: เราต้องการให้พวกเขาป้อนข้อมูล ดังนั้นการจัดการ "ไม่มีรายการ" จะต้องทำในระดับผู้โทรไม่ทางใดก็ทางหนึ่ง และไม่ว่าเราจะทำสิ่งนั้นด้วยการคืนค่า Null หรือฟังก์ชั่นที่แยกออกจากกันก็ไม่ได้สร้างความแตกต่างให้กับหลักการที่ใหญ่กว่า
แน่นอนถ้าผู้โทรไม่ตรวจสอบ null โปรแกรมอาจล้มเหลวโดยมีข้อยกเว้น null-pointer แต่ถ้ามีฟังก์ชั่น "รับใด ๆ " แยกจากกันและผู้โทรไม่เรียกฟังก์ชั่นนั้น แต่ฟังก์ชั่น "รับรายการ" แบบสุ่มแล้วจะเกิดอะไรขึ้น ถ้ามันเกิดข้อผิดพลาดหรือล้มเหลวมันก็เหมือนกับว่าจะเกิดอะไรขึ้นถ้ามันกลับมาเป็นโมฆะและไม่ได้ตรวจสอบ ถ้ามันคืนค่ารายการเปล่านั่นก็ผิด คุณไม่สามารถแยกความแตกต่างระหว่าง "ฉันมีรายการที่มีองค์ประกอบเป็นศูนย์" และ "ฉันไม่มีรายการ" มันเหมือนกับคืนศูนย์สำหรับราคาเมื่อผู้ใช้ไม่ได้ป้อนราคาใด ๆ : มันผิด
ฉันไม่เห็นว่าการแนบแอตทริบิวต์เพิ่มเติมเข้ากับการรวบรวมช่วยอย่างไร ผู้โทรยังคงต้องตรวจสอบ จะดีไปกว่าการตรวจสอบ null เป็นอย่างไร สิ่งที่แย่ที่สุดที่อาจเกิดขึ้นคือโปรแกรมเมอร์ต้องลืมตรวจสอบและให้ผลลัพธ์ที่ไม่ถูกต้อง
ฟังก์ชั่นที่คืนค่า Null ไม่ใช่เรื่องที่น่าแปลกใจถ้าโปรแกรมเมอร์คุ้นเคยกับแนวคิดของความหมายของ Null ที่ "ไม่มีคุณค่า" ซึ่งฉันคิดว่าโปรแกรมเมอร์ที่มีความสามารถควรได้ยินชื่อไม่ว่าเขาคิดว่ามันเป็นความคิดที่ดีหรือไม่ ฉันคิดว่าการมีฟังก์ชั่นแยกต่างหากเป็นปัญหาที่ "แปลกใจ" มากกว่า หากโปรแกรมเมอร์ไม่คุ้นเคยกับ API เมื่อเขารันการทดสอบโดยไม่มีข้อมูลเขาจะค้นพบอย่างรวดเร็วว่าบางครั้งเขาก็จะกลับมาเป็นโมฆะ แต่เขาจะค้นพบการดำรงอยู่ของฟังก์ชั่นอื่นได้อย่างไรถ้ามันเกิดขึ้นกับเขาว่าอาจมีฟังก์ชั่นดังกล่าวและเขาตรวจสอบเอกสารและเอกสารนั้นสมบูรณ์และเข้าใจได้อย่างไร ฉันอยากจะมีฟังก์ชั่นหนึ่งที่ให้การตอบสนองที่มีความหมายมากกว่าฟังก์ชั่นสองอย่างที่ฉันต้องรู้และจำไว้ว่าให้เรียกทั้งสองอย่าง
หากคอลเลกชันที่ว่างเปล่าทำให้รู้สึกถึงความหมายนั่นคือสิ่งที่ฉันต้องการที่จะกลับมา การส่งคืนคอลเลกชันที่ว่างเปล่าสำหรับGetMessagesInMyInbox()
การสื่อสาร "คุณไม่มีข้อความใด ๆ ในกล่องจดหมายของคุณ" ในขณะที่การส่งคืนnull
อาจเป็นประโยชน์ในการสื่อสารว่ามีข้อมูลไม่เพียงพอที่จะบอกว่ารายการที่อาจถูกส่งกลับควรมีลักษณะอย่างไร
null
ค่าไม่น่าจะสมเหตุสมผลฉันคิดในแง่ทั่วไปมากขึ้นในอันนั้น ข้อยกเว้นเป็นสิ่งที่ดีในการสื่อสารความจริงที่ว่ามีบางอย่างผิดพลาด แต่ถ้า "ข้อมูลไม่เพียงพอ" ที่คาดว่ามีการคาดการณ์ไว้อย่างสมบูรณ์แล้วส่งข้อยกเว้นจะมีการออกแบบที่ไม่ดี ฉันค่อนข้างคิดว่าสถานการณ์ที่เป็นไปได้อย่างสมบูรณ์และไม่มีข้อผิดพลาดเลยสำหรับวิธีการบางครั้งไม่สามารถคำนวณการตอบสนอง
การคืนค่า null อาจมีประสิทธิภาพมากกว่าเนื่องจากไม่มีการสร้างวัตถุใหม่ อย่างไรก็ตามมันมักจะต้องมีการnull
ตรวจสอบ (หรือการจัดการข้อยกเว้น)
ความหมายnull
และรายการที่ว่างเปล่าไม่ได้หมายถึงสิ่งเดียวกัน ความแตกต่างนั้นบอบบางและตัวเลือกหนึ่งอาจดีกว่าตัวเลือกอื่นในบางกรณี
จัดทำเอกสารเพื่อหลีกเลี่ยงความสับสน
เราอาจโต้แย้งได้ว่าเหตุผลที่อยู่เบื้องหลังรูปแบบวัตถุ Nullนั้นคล้ายคลึงกับเหตุผลในการคืนคอลเลกชันที่ว่างเปล่า
ขึ้นอยู่กับสถานการณ์ ถ้าเป็นกรณีพิเศษให้ส่งคืน null หากฟังก์ชั่นเพิ่งเกิดขึ้นเพื่อส่งคืนคอลเลกชันที่ว่างเปล่าก็เห็นได้ชัดว่าการส่งคืนนั้นก็โอเค อย่างไรก็ตามการส่งคืนคอลเลกชันที่ว่างเปล่าเป็นกรณีพิเศษเนื่องจากพารามิเตอร์ที่ไม่ถูกต้องหรือเหตุผลอื่น ๆ ไม่ใช่ความคิดที่ดีเพราะเป็นการปกปิดเงื่อนไขกรณีพิเศษ
ที่จริงแล้วในกรณีนี้ฉันมักจะชอบที่จะโยนข้อยกเว้นเพื่อให้แน่ใจว่ามันไม่ได้ถูกละเว้นจริงๆ :)
การบอกว่ามันทำให้รหัสมีเสถียรภาพมากขึ้น (โดยการส่งคืนคอลเลกชันที่ว่างเปล่า) เนื่องจากพวกเขาไม่จำเป็นต้องจัดการกับเงื่อนไขที่ไม่ดีเนื่องจากเป็นเพียงการพรางตัวปัญหาที่ควรจัดการด้วยรหัสการโทร
ฉันจะโต้แย้งว่าnull
ไม่ใช่สิ่งเดียวกับคอลเล็กชั่นที่ว่างเปล่าและคุณควรเลือกว่าอันไหนที่แสดงถึงสิ่งที่ดีที่สุด ในกรณีส่วนใหญ่null
ไม่มีอะไร (ยกเว้นใน SQL) คอลเล็กชันที่ว่างเปล่าคือบางสิ่งแม้ว่าจะเป็นบางสิ่งที่ว่างเปล่า
หากคุณต้องเลือกอย่างใดอย่างหนึ่งฉันจะบอกว่าคุณควรมีแนวโน้มที่จะสะสมว่างเปล่ามากกว่าโมฆะ แต่มีบางครั้งที่คอลเลกชันที่ว่างเปล่าไม่เหมือนกับค่าว่าง
คิดเสมอเพื่อลูกค้าของคุณ (ซึ่งใช้ API ของคุณ):
การส่งคืน 'null' มักทำให้เกิดปัญหากับไคลเอนต์ที่ไม่จัดการกับการตรวจสอบ null อย่างถูกต้องซึ่งทำให้ NullPointerException ระหว่างรันไทม์ ฉันเคยเห็นกรณีที่การตรวจสอบ null ที่ขาดหายไปนั้นบังคับให้เกิดปัญหาการผลิตที่สำคัญ (ลูกค้าใช้ foreach (... ) บนค่า null) ในระหว่างการทดสอบปัญหาไม่ได้เกิดขึ้นเนื่องจากข้อมูลที่ดำเนินการอยู่แตกต่างกันเล็กน้อย
ฉันต้องการอธิบายที่นี่พร้อมตัวอย่างที่เหมาะสม
พิจารณากรณีที่นี่ ..
int totalValue = MySession.ListCustomerAccounts()
.FindAll(ac => ac.AccountHead.AccountHeadID
== accountHead.AccountHeadID)
.Sum(account => account.AccountValue);
ที่นี่ลองพิจารณาฟังก์ชั่นที่ฉันใช้ ..
1. ListCustomerAccounts() // User Defined
2. FindAll() // Pre-defined Library Function
ฉันสามารถใช้งานได้อย่างง่ายดายListCustomerAccount
และFindAll
แทน
int totalValue = 0;
List<CustomerAccounts> custAccounts = ListCustomerAccounts();
if(custAccounts !=null ){
List<CustomerAccounts> custAccountsFiltered =
custAccounts.FindAll(ac => ac.AccountHead.AccountHeadID
== accountHead.AccountHeadID );
if(custAccountsFiltered != null)
totalValue = custAccountsFiltered.Sum(account =>
account.AccountValue).ToString();
}
หมายเหตุ: เนื่องจาก AccountValue ไม่ใช่null
ฟังก์ชัน Sum () จะไม่ส่งคืนnull
ดังนั้นฉันจึงสามารถใช้งานได้โดยตรง
เรามีการพูดคุยกันระหว่างทีมพัฒนาในที่ทำงานหนึ่งสัปดาห์ก่อนหน้านี้และเราเกือบจะลงมติเป็นเอกฉันท์สำหรับการรวบรวมที่ว่างเปล่า บุคคลหนึ่งต้องการคืนค่า null ด้วยเหตุผลเดียวกับที่ไมค์ระบุไว้ข้างต้น
การเก็บว่างเปล่า หากคุณกำลังใช้ C # ข้อสันนิษฐานคือการเพิ่มทรัพยากรระบบไม่จำเป็น ในขณะที่มีประสิทธิภาพน้อยลงการเก็บค่าว่างกลับมาจะสะดวกกว่าสำหรับโปรแกรมเมอร์ที่เกี่ยวข้อง (ด้วยเหตุผลดังกล่าวข้างต้น)
ในกรณีส่วนใหญ่การส่งคืนคอลเลกชันเปล่าจะดีกว่า
เหตุผลที่สะดวกในการดำเนินการโทรสัญญาสอดคล้องและการใช้งานง่ายขึ้น
ถ้าเมธอดส่งคืน null เพื่อระบุผลลัพธ์ว่างผู้เรียกต้องใช้อะแด็ปเตอร์การตรวจสอบ null นอกเหนือจากการแจงนับ จากนั้นรหัสนี้จะถูกทำซ้ำในผู้เรียกที่หลากหลายดังนั้นทำไมไม่ใส่อะแดปเตอร์นี้เข้าไปในวิธีการจึงสามารถนำกลับมาใช้ใหม่ได้
การใช้งานที่ถูกต้องของค่า null สำหรับ IEnumerable อาจเป็นตัวบ่งชี้ผลลัพธ์ที่หายไปหรือความล้มเหลวในการดำเนินการ แต่ในกรณีนี้ควรพิจารณาเทคนิคอื่น ๆ เช่นการโยนข้อยกเว้น
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
namespace StackOverflow.EmptyCollectionUsageTests.Tests
{
/// <summary>
/// Demonstrates different approaches for empty collection results.
/// </summary>
class Container
{
/// <summary>
/// Elements list.
/// Not initialized to an empty collection here for the purpose of demonstration of usage along with <see cref="Populate"/> method.
/// </summary>
private List<Element> elements;
/// <summary>
/// Gets elements if any
/// </summary>
/// <returns>Returns elements or empty collection.</returns>
public IEnumerable<Element> GetElements()
{
return elements ?? Enumerable.Empty<Element>();
}
/// <summary>
/// Initializes the container with some results, if any.
/// </summary>
public void Populate()
{
elements = new List<Element>();
}
/// <summary>
/// Gets elements. Throws <see cref="InvalidOperationException"/> if not populated.
/// </summary>
/// <returns>Returns <see cref="IEnumerable{T}"/> of <see cref="Element"/>.</returns>
public IEnumerable<Element> GetElementsStrict()
{
if (elements == null)
{
throw new InvalidOperationException("You must call Populate before calling this method.");
}
return elements;
}
/// <summary>
/// Gets elements, empty collection or nothing.
/// </summary>
/// <returns>Returns <see cref="IEnumerable{T}"/> of <see cref="Element"/>, with zero or more elements, or null in some cases.</returns>
public IEnumerable<Element> GetElementsInconvenientCareless()
{
return elements;
}
/// <summary>
/// Gets elements or nothing.
/// </summary>
/// <returns>Returns <see cref="IEnumerable{T}"/> of <see cref="Element"/>, with elements, or null in case of empty collection.</returns>
/// <remarks>We are lucky that elements is a List, otherwise enumeration would be needed.</remarks>
public IEnumerable<Element> GetElementsInconvenientCarefull()
{
if (elements == null || elements.Count == 0)
{
return null;
}
return elements;
}
}
class Element
{
}
/// <summary>
/// http://stackoverflow.com/questions/1969993/is-it-better-to-return-null-or-empty-collection/
/// </summary>
class EmptyCollectionTests
{
private Container container;
[SetUp]
public void SetUp()
{
container = new Container();
}
/// <summary>
/// Forgiving contract - caller does not have to implement null check in addition to enumeration.
/// </summary>
[Test]
public void UseGetElements()
{
Assert.AreEqual(0, container.GetElements().Count());
}
/// <summary>
/// Forget to <see cref="Container.Populate"/> and use strict method.
/// </summary>
[Test]
[ExpectedException(typeof(InvalidOperationException))]
public void WrongUseOfStrictContract()
{
container.GetElementsStrict().Count();
}
/// <summary>
/// Call <see cref="Container.Populate"/> and use strict method.
/// </summary>
[Test]
public void CorrectUsaOfStrictContract()
{
container.Populate();
Assert.AreEqual(0, container.GetElementsStrict().Count());
}
/// <summary>
/// Inconvenient contract - needs a local variable.
/// </summary>
[Test]
public void CarefulUseOfCarelessMethod()
{
var elements = container.GetElementsInconvenientCareless();
Assert.AreEqual(0, elements == null ? 0 : elements.Count());
}
/// <summary>
/// Inconvenient contract - duplicate call in order to use in context of an single expression.
/// </summary>
[Test]
public void LameCarefulUseOfCarelessMethod()
{
Assert.AreEqual(0, container.GetElementsInconvenientCareless() == null ? 0 : container.GetElementsInconvenientCareless().Count());
}
[Test]
public void LuckyCarelessUseOfCarelessMethod()
{
// INIT
var praySomeoneCalledPopulateBefore = (Action)(()=>container.Populate());
praySomeoneCalledPopulateBefore();
// ACT //ASSERT
Assert.AreEqual(0, container.GetElementsInconvenientCareless().Count());
}
/// <summary>
/// Excercise <see cref="ArgumentNullException"/> because of null passed to <see cref="Enumerable.Count{TSource}(System.Collections.Generic.IEnumerable{TSource})"/>
/// </summary>
[Test]
[ExpectedException(typeof(ArgumentNullException))]
public void UnfortunateCarelessUseOfCarelessMethod()
{
Assert.AreEqual(0, container.GetElementsInconvenientCareless().Count());
}
/// <summary>
/// Demonstrates the client code flow relying on returning null for empty collection.
/// Exception is due to <see cref="Enumerable.First{TSource}(System.Collections.Generic.IEnumerable{TSource})"/> on an empty collection.
/// </summary>
[Test]
[ExpectedException(typeof(InvalidOperationException))]
public void UnfortunateEducatedUseOfCarelessMethod()
{
container.Populate();
var elements = container.GetElementsInconvenientCareless();
if (elements == null)
{
Assert.Inconclusive();
}
Assert.IsNotNull(elements.First());
}
/// <summary>
/// Demonstrates the client code is bloated a bit, to compensate for implementation 'cleverness'.
/// We can throw away the nullness result, because we don't know if the operation succeeded or not anyway.
/// We are unfortunate to create a new instance of an empty collection.
/// We might have already had one inside the implementation,
/// but it have been discarded then in an effort to return null for empty collection.
/// </summary>
[Test]
public void EducatedUseOfCarefullMethod()
{
Assert.AreEqual(0, (container.GetElementsInconvenientCarefull() ?? Enumerable.Empty<Element>()).Count());
}
}
}
ฉันเรียกมันว่าความผิดพลาดพันล้านดอลล่าร์ของฉัน ... ในเวลานั้นฉันกำลังออกแบบระบบการพิมพ์แบบครอบคลุมครั้งแรกสำหรับการอ้างอิงในภาษาเชิงวัตถุ เป้าหมายของฉันคือเพื่อให้แน่ใจว่าการใช้การอ้างอิงทั้งหมดควรจะปลอดภัยอย่างยิ่งโดยการตรวจสอบดำเนินการโดยอัตโนมัติโดยคอมไพเลอร์ แต่ฉันไม่สามารถต้านทานสิ่งล่อใจที่จะใส่ในการอ้างอิงโมฆะเพียงเพราะมันง่ายที่จะใช้ สิ่งนี้นำไปสู่ข้อผิดพลาดมากมายช่องโหว่และระบบล่มซึ่งอาจทำให้เกิดความเจ็บปวดและความเสียหายนับพันล้านดอลลาร์ในช่วงสี่สิบปีที่ผ่านมา - Tony Hoare ผู้ประดิษฐ์ ALGOL W.
ดูที่นี่สำหรับพายุอึที่ซับซ้อนเกี่ยวกับnull
โดยทั่วไป ฉันไม่เห็นด้วยกับข้อความที่undefined
เป็นอื่นnull
แต่ก็ยังคงมีมูลค่าการอ่าน และมันอธิบายว่าทำไมคุณควรหลีกเลี่ยงnull
เลยไม่ใช่แค่ในกรณีที่คุณถาม สาระสำคัญคือว่าnull
ในกรณีพิเศษภาษาใด ๆ คุณต้องคิดว่าnull
เป็นข้อยกเว้น undefined
จะแตกต่างกันในวิธีการที่รหัสที่เกี่ยวข้องกับพฤติกรรมที่ไม่ได้กำหนดในกรณีส่วนใหญ่เพียงข้อผิดพลาด C และภาษาอื่น ๆ ส่วนใหญ่มีพฤติกรรมที่ไม่ได้กำหนดเช่นกัน แต่ส่วนใหญ่ไม่มีตัวระบุสำหรับภาษานั้น
จากมุมมองของการจัดการความซับซ้อนซึ่งเป็นวัตถุประสงค์หลักของวิศวกรรมซอฟต์แวร์เราต้องการหลีกเลี่ยงการเผยแพร่ที่ไม่จำเป็นความซับซ้อนที่ให้กับลูกค้าของ API การส่งคืน null ให้กับลูกค้าก็เหมือนกับการส่งคืนค่าความซับซ้อนตามวงจรของสาขารหัสอื่น
(สิ่งนี้สอดคล้องกับภาระในการทดสอบหน่วยคุณจะต้องเขียนการทดสอบสำหรับกรณีส่งคืนที่เป็นโมฆะนอกเหนือจากกรณีส่งคืนการเก็บรวบรวมที่ว่างเปล่า)