มันจะดีกว่าที่จะกลับมาเป็นคอลเลกชันที่ว่างเปล่าหรือ?


420

นั่นเป็นคำถามทั่วไป (แต่ฉันใช้ C #) เป็นวิธีที่ดีที่สุด (แนวปฏิบัติที่ดีที่สุด) คุณส่งคืนคอลเลกชันที่ว่างเปล่าหรือว่างเปล่าสำหรับวิธีการที่มีคอลเลกชันเป็นประเภทที่กลับมา?


5
เอ่อไม่ใช่ CPerkins rads.stackoverflow.com/amzn/click/0321545613

3
@CPerkins - ใช่แล้ว มีการระบุไว้อย่างชัดเจนในแนวทางการออกแบบกรอบงาน. NET ของ Microsoft ดูคำตอบของ RichardOD สำหรับรายละเอียด
Greg Beech

51
ถ้าความหมายคือ "ฉันไม่สามารถคำนวณผลลัพธ์" ได้คุณควรคืนค่าว่าง นัลไม่ควรมีความหมายของ "ว่าง" เพียง "หายไป" หรือ "ไม่รู้จัก" รายละเอียดเพิ่มเติมในบทความของฉันเกี่ยวกับเรื่องนี้: blogs.msdn.com/ericlippert/archive/2009/05/14/…
Eric Lippert

3
Bozho: "อะไรก็ได้" เป็นภาษาที่แย่มาก Common Lisp มีอะไรที่ว่างเปล่ารายการเท่ากับค่า null? :-)
Ken

9
ที่จริงแล้ว "ซ้ำ" เป็นเรื่องเกี่ยวกับวิธีการส่งคืนวัตถุไม่ใช่การรวบรวม มันเป็นสถานการณ์ที่แตกต่างกับคำตอบที่แตกต่างกัน
GalacticCowboy

คำตอบ:


499

คอลเลกชันที่ว่างเปล่า เสมอ.

สิ่งนี้แย่มาก:

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>()สามารถถูกมองว่ามีประสิทธิภาพมากกว่าการส่งคืนตัวอย่างเช่นคอลเล็กชันหรืออาร์เรย์ว่างใหม่


24
ฉันเห็นด้วยกับ Will แต่ฉันคิดว่า "เสมอ" มากไปหน่อย ในขณะที่การรวบรวมที่ว่างเปล่าอาจหมายถึง "0 รายการ" การคืนค่า Null อาจหมายถึง "ไม่มีการรวบรวมเลย" - เช่น หากคุณแยกวิเคราะห์ HTML การค้นหา <ul> ด้วย id = "foo", <ul id = "foo"> </ul> อาจส่งคืนคอลเล็กชันที่ว่างเปล่า ถ้าไม่มี <ul> มี id = "foo" ผลตอบแทน null จะดีกว่า (ถ้าคุณต้องการที่จะจัดการกับกรณีนี้กับข้อยกเว้น)
Patonza

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

31
คุณควรกลับมา System.Linq.Enumerable.Empty <Foo> () แทน Foo ใหม่ [0] มีความชัดเจนมากขึ้นและช่วยให้คุณจัดสรรหน่วยความจำหนึ่งครั้ง (อย่างน้อยในการติดตั้ง. NET ของฉัน)
Trillian

4
@Will: OP: "คุณส่งคืนคอลเล็กชันที่เป็นโมฆะหรือว่างเปล่าสำหรับวิธีการที่มีการรวบรวมเป็นประเภทที่ส่งคืน" ฉันคิดว่าในกรณีนี้ไม่ว่าจะเป็นIEnumerableหรือICollectionไม่สำคัญมาก อย่างไรก็ตามถ้าคุณเลือกประเภทที่ICollectionพวกเขากลับมาnull... ฉันต้องการให้พวกเขากลับคอลเลกชันที่ว่างเปล่า แต่ฉันวิ่งเข้าไปในพวกเขากลับมาnullดังนั้นฉันคิดว่าฉันจะพูดถึงมันที่นี่ ฉันจะบอกว่าค่าเริ่มต้นของการรวบรวมนับไม่ว่างเปล่าเป็นโมฆะ ฉันไม่รู้ว่ามันเป็นเรื่องละเอียดอ่อน
Matthijs Wessels


154

จากFramework Design Guidelines 2nd Edition (pg. 256):

ห้ามส่งคืนค่า null จากคุณสมบัติการรวบรวมหรือจากวิธีการคืนค่าคอลเล็กชัน ส่งคืนคอลเล็กชันว่างหรืออาร์เรย์ว่างแทน

นี่เป็นอีกบทความที่น่าสนใจเกี่ยวกับประโยชน์ของการไม่คืนค่า Null (ฉันพยายามค้นหาบางอย่างในบล็อกของ Brad Abram และเขาเชื่อมโยงกับบทความ)

Edit-เนื่องจาก Eric Lippert ได้แสดงความคิดเห็นกับคำถามเดิมแล้วฉันต้องการลิงก์ไปยังบทความที่ยอดเยี่ยมของเขาด้วย


33
+1 เป็นแนวปฏิบัติที่ดีที่สุดเสมอในการปฏิบัติตามแนวทางการออกแบบกรอบงานเว้นแต่คุณจะมีเหตุผลที่ดีไม่ควรทำ

@ จะแน่นอน นั่นคือสิ่งที่ฉันติดตาม ฉันไม่เคยพบว่าจำเป็นที่จะต้องทำอย่างอื่นใด
RichardOD

ใช่นั่นคือสิ่งที่คุณติดตาม แต่ในกรณีที่คุณ APIs พึ่งพาไม่ได้ทำตามนั้นคุณจะ 'ติดอยู่';)
Bozho

@ Bozho- yeap คำตอบของคุณให้คำตอบที่ดีเกี่ยวกับคดีเหล่านั้น
RichardOD

90

ขึ้นอยู่กับคุณสัญญาของคุณและกรณีที่เป็นรูปธรรม โดยทั่วไปจะเป็นการดีที่สุดที่จะส่งคืนคอลเล็กชันที่ว่างเปล่าแต่บางครั้ง ( ไม่ค่อย )

  • null อาจหมายถึงบางสิ่งที่เฉพาะเจาะจงมากขึ้น
  • API ของคุณ (สัญญา) nullอาจบังคับให้คุณกลับมา

ตัวอย่างที่เป็นรูปธรรม:

  • องค์ประกอบ UI (จากไลบรารีที่อยู่นอกเหนือการควบคุมของคุณ) อาจแสดงผลตารางที่ว่างเปล่าหากมีการส่งผ่านคอลเลกชันที่ว่างเปล่าหรือไม่มีตารางเลยเลยหากมีการส่งผ่านค่าว่าง
  • ใน Object-to-XML (JSON / อะไรก็ตาม) ซึ่งnullหมายความว่าองค์ประกอบหายไปในขณะที่การรวบรวมที่ว่างเปล่าจะทำให้เกิดการซ้ำซ้อน (และอาจไม่ถูกต้อง)<collection />
  • คุณกำลังใช้หรือใช้งาน API ซึ่งระบุว่าควรส่งคืน / ส่งผ่าน null

4
@ Bozho- ตัวอย่างที่น่าสนใจที่นี่
RichardOD

1
ฉันจะเห็นจุดของคุณแม้ว่าฉันไม่คิดว่าคุณควรสร้างรหัสคุณรอบ ๆ ข้อผิดพลาดอื่น ๆ และโดยนิยาม 'null' ใน c # ไม่เคยหมายถึงสิ่งที่เฉพาะเจาะจง ค่าว่างถูกกำหนดเป็น "ไม่มีข้อมูล" และด้วยเหตุนี้จะบอกว่ามันมีข้อมูลเฉพาะคือ oximoron นั่นเป็นสาเหตุที่หลักเกณฑ์ของ. NET ระบุว่าคุณควรส่งคืนชุดว่างถ้าชุดว่างเปล่า กลับโมฆะพูดว่า: "ผมไม่ทราบว่าชุดที่คาดว่าจะไป"
Rune FS

13
ไม่มีก็หมายความว่า "มีชุดไม่มี" มากกว่า "ชุดมีองค์ประกอบไม่"
Bozho

ฉันเคยเชื่อว่าจากนั้นฉันต้องเขียน TSQL จำนวนมากและเรียนรู้ว่าไม่ใช่กรณีเสมอไปฮิฮิ

1
ส่วนที่มี Object-To-Xml อยู่ในจุด
Mihai Caracostea

36

มีอีกประเด็นหนึ่งที่ยังไม่ได้กล่าวถึง พิจารณารหัสต่อไปนี้:

    public static IEnumerable<string> GetFavoriteEmoSongs()
    {
        yield break;
    }

ภาษา C # จะส่งคืนตัวแจงนับว่างเมื่อเรียกใช้วิธีนี้ ดังนั้นเพื่อให้สอดคล้องกับการออกแบบภาษา (และความคาดหวังของโปรแกรมเมอร์) จึงควรส่งคืนคอลเลกชันที่ว่างเปล่า


1
ฉันไม่คิดว่าในทางเทคนิคแล้วสิ่งนี้จะส่งคืนคอลเล็กชันว่าง
FryGuy

3
@FryGuy - ไม่อย่างนั้น มันส่งคืนวัตถุที่นับได้ซึ่ง GetEnumerator () วิธีการส่งกลับ Enumerator ที่ (โดยการวิเคราะห์ด้วยคอลเลกชัน) ว่างเปล่า นั่นคือเมธอด MoveNext () ของ Enumerator ส่งคืนค่าเท็จเสมอ การเรียกเมธอดตัวอย่างไม่ส่งคืนค่า Null และไม่ส่งคืนออบเจกต์ Enumerable ที่เมธอด GetEnumerator () ส่งคืนค่า Null
Jeffrey L Whitledge

31

ว่างเปล่าเป็นมิตรกับผู้บริโภคมากขึ้น

มีวิธีการที่ชัดเจนในการสร้างจำนวนที่ว่างเปล่า:

Enumerable.Empty<Element>()

2
ขอบคุณสำหรับเคล็ดลับเรียบร้อย ฉันกำลังสร้างรายการว่าง <T> () เหมือนคนงี่เง่าทันที แต่นี่ดูสะอาดกว่าและน่าจะมีประสิทธิภาพมากกว่าด้วย
Jacobs Data Solutions

18

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

สมมติว่าเป็นระบบสำหรับโรงพยาบาลเรามีฟังก์ชั่นที่ควรส่งคืนรายการการรักษาในโรงพยาบาลก่อนหน้านี้ทั้งหมดในช่วง 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 เมื่อเขารันการทดสอบโดยไม่มีข้อมูลเขาจะค้นพบอย่างรวดเร็วว่าบางครั้งเขาก็จะกลับมาเป็นโมฆะ แต่เขาจะค้นพบการดำรงอยู่ของฟังก์ชั่นอื่นได้อย่างไรถ้ามันเกิดขึ้นกับเขาว่าอาจมีฟังก์ชั่นดังกล่าวและเขาตรวจสอบเอกสารและเอกสารนั้นสมบูรณ์และเข้าใจได้อย่างไร ฉันอยากจะมีฟังก์ชั่นหนึ่งที่ให้การตอบสนองที่มีความหมายมากกว่าฟังก์ชั่นสองอย่างที่ฉันต้องรู้และจำไว้ว่าให้เรียกทั้งสองอย่าง


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

2
แต่ถ้าคุณคืนค่า Null คุณก็กำลังเพิ่มภาระให้กับผู้โทรอยู่แล้ว! ผู้โทรต้องตรวจสอบค่าส่งคืนทุกค่าเป็นโมฆะ - การละเมิด DRY ที่น่ากลัว หากคุณต้องการระบุว่า "ผู้โทรไม่ตอบคำถาม" แยกต่างหากมันจะง่ายกว่าในการสร้างวิธีการโทรเพิ่มเติมเพื่อระบุความจริง อาจเป็นได้ว่าหรือใช้ประเภทคอลเลกชันที่ได้รับมาพร้อมกับคุณสมบัติพิเศษของ DeclinedToAnswer กฎที่จะไม่คืนค่าว่างนั้นไม่ได้เป็นกฎเกณฑ์ใด ๆ เลย แต่เป็นหลักการของความประหลาดใจน้อยที่สุด นอกจากนี้ประเภทการคืนสินค้าของวิธีการของคุณควรหมายถึงสิ่งที่ชื่อของมันบอกว่าทำ Null เกือบจะไม่แน่นอน
jammycakes

2
คุณสมมติว่า getHospitalizationList ของคุณถูกเรียกจากที่เดียวเท่านั้นและ / หรือผู้โทรทุกคนต้องการแยกแยะระหว่างกรณี "no answer" และ "zero" มีจะเป็นกรณี (เกือบจะแน่นอนส่วนใหญ่) ที่โทรของคุณไม่จำเป็นต้องสร้างความแตกต่างที่และเพื่อให้คุณจะบังคับให้พวกเขาเพื่อเพิ่มการตรวจสอบ null ในสถานที่ที่ไม่ควรเป็นสิ่งที่จำเป็น สิ่งนี้เพิ่มความเสี่ยงที่สำคัญให้กับ codebase ของคุณเพราะทุกคนสามารถลืมได้ง่ายเช่นกัน - และเนื่องจากมีเหตุผลที่ถูกต้องน้อยมากที่จะส่งคืน null แทนที่จะเป็นคอลเล็กชัน
jammycakes

3
RE the name: ไม่มีชื่อฟังก์ชั่นใดสามารถอธิบายสิ่งที่ฟังก์ชั่นทำได้อย่างสมบูรณ์เว้นแต่มันจะมีความยาวเท่ากับฟังก์ชั่น แต่ในกรณีใด ๆ ถ้าฟังก์ชั่นส่งกลับรายการว่างเปล่าเมื่อไม่ได้รับคำตอบจากนั้นด้วยเหตุผลเดียวกันไม่ควรเรียกว่า "getHospitalizationListOrEmptyListIfNoAnswer" แต่จริงๆคุณจะยืนยันว่าควรเปลี่ยนชื่อฟังก์ชั่น Java Reader.read readCharacterOrReturnMinusOneOnEndOfStream หรือไม่ ResultSet.getInt นั้นควรเป็น "getIntOrZeroIfValueWasNull" จริงๆหรือ ฯลฯ
Jay

4
RE ทุกการโทรต้องการแยกแยะ: ใช่ฉันกำลังสมมติว่าหรืออย่างน้อยที่สุดผู้เขียนผู้โทรควรตัดสินใจอย่างมีสติว่าเขาไม่สนใจ หากฟังก์ชั่นส่งคืนรายการว่างสำหรับ "ไม่รู้" และผู้ที่โทรเข้ามารับการรักษาแบบ "ไม่มี" นี้จะให้ผลลัพธ์ที่ไม่ถูกต้องอย่างจริงจัง ลองคิดดูว่าฟังก์ชันนั้นเป็น "getAllergicReactionToMedicationList" หรือไม่ โปรแกรมที่ไม่ได้เข้าสู่รายการ "การรักษาแบบสุ่มสี่สุ่มห้า" เนื่องจาก "ผู้ป่วยไม่มีอาการแพ้ที่รู้จัก" อาจส่งผลให้เกิดการฆ่าคนตาย คุณจะได้ผลลัพธ์ที่คล้ายกันหากผลลัพธ์ที่น่าทึ่งน้อยลงในระบบอื่น ๆ ...
Jay

10

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


6
ในตัวอย่างที่คุณให้มาดูเหมือนว่าวิธีอาจจะมีข้อยกเว้นหากไม่สามารถทำตามคำขอได้แทนที่จะส่งคืนค่าว่าง ข้อยกเว้นมีประโยชน์มากกว่าสำหรับการวินิจฉัยปัญหามากกว่าค่า Null
เกร็กบีช

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

6

การคืนค่า null อาจมีประสิทธิภาพมากกว่าเนื่องจากไม่มีการสร้างวัตถุใหม่ อย่างไรก็ตามมันมักจะต้องมีการnullตรวจสอบ (หรือการจัดการข้อยกเว้น)

ความหมายnullและรายการที่ว่างเปล่าไม่ได้หมายถึงสิ่งเดียวกัน ความแตกต่างนั้นบอบบางและตัวเลือกหนึ่งอาจดีกว่าตัวเลือกอื่นในบางกรณี

จัดทำเอกสารเพื่อหลีกเลี่ยงความสับสน


8
ประสิทธิภาพแทบจะไม่เคยเป็นปัจจัยเมื่อพิจารณาความถูกต้องของการออกแบบ API ในบางกรณีเช่นกราฟิกดั้งเดิมมันอาจเป็นเช่นนั้น แต่เมื่อต้องจัดการกับรายการและสิ่งอื่น ๆ ในระดับสูงแล้วฉันก็สงสัยอย่างมาก
เกร็กบีช

เห็นด้วยกับ Greg โดยเฉพาะอย่างยิ่งเนื่องจากรหัสที่ผู้ใช้ API ต้องเขียนเพื่อชดเชยสำหรับ "การเพิ่มประสิทธิภาพ" นี้อาจไม่มีประสิทธิภาพมากกว่าการใช้การออกแบบที่ดีกว่าในตอนแรก
Craig Stuntz

เห็นด้วยและในกรณีส่วนใหญ่มันก็ไม่คุ้มค่าการเพิ่มประสิทธิภาพ รายการที่ว่างเปล่าไม่มีค่าใช้จ่ายด้วยการจัดการหน่วยความจำที่ทันสมัย
4490 Jason Jason Baker


4

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

ที่จริงแล้วในกรณีนี้ฉันมักจะชอบที่จะโยนข้อยกเว้นเพื่อให้แน่ใจว่ามันไม่ได้ถูกละเว้นจริงๆ :)

การบอกว่ามันทำให้รหัสมีเสถียรภาพมากขึ้น (โดยการส่งคืนคอลเลกชันที่ว่างเปล่า) เนื่องจากพวกเขาไม่จำเป็นต้องจัดการกับเงื่อนไขที่ไม่ดีเนื่องจากเป็นเพียงการพรางตัวปัญหาที่ควรจัดการด้วยรหัสการโทร


4

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

หากคุณต้องเลือกอย่างใดอย่างหนึ่งฉันจะบอกว่าคุณควรมีแนวโน้มที่จะสะสมว่างเปล่ามากกว่าโมฆะ แต่มีบางครั้งที่คอลเลกชันที่ว่างเปล่าไม่เหมือนกับค่าว่าง


4

คิดเสมอเพื่อลูกค้าของคุณ (ซึ่งใช้ API ของคุณ):

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


3

ฉันต้องการอธิบายที่นี่พร้อมตัวอย่างที่เหมาะสม

พิจารณากรณีที่นี่ ..

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ดังนั้นฉันจึงสามารถใช้งานได้โดยตรง


2

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


2

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


2

ในกรณีส่วนใหญ่การส่งคืนคอลเลกชันเปล่าจะดีกว่า

เหตุผลที่สะดวกในการดำเนินการโทรสัญญาสอดคล้องและการใช้งานง่ายขึ้น

ถ้าเมธอดส่งคืน 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());
        }
    }
}

2

ฉันเรียกมันว่าความผิดพลาดพันล้านดอลล่าร์ของฉัน ... ในเวลานั้นฉันกำลังออกแบบระบบการพิมพ์แบบครอบคลุมครั้งแรกสำหรับการอ้างอิงในภาษาเชิงวัตถุ เป้าหมายของฉันคือเพื่อให้แน่ใจว่าการใช้การอ้างอิงทั้งหมดควรจะปลอดภัยอย่างยิ่งโดยการตรวจสอบดำเนินการโดยอัตโนมัติโดยคอมไพเลอร์ แต่ฉันไม่สามารถต้านทานสิ่งล่อใจที่จะใส่ในการอ้างอิงโมฆะเพียงเพราะมันง่ายที่จะใช้ สิ่งนี้นำไปสู่ข้อผิดพลาดมากมายช่องโหว่และระบบล่มซึ่งอาจทำให้เกิดความเจ็บปวดและความเสียหายนับพันล้านดอลลาร์ในช่วงสี่สิบปีที่ผ่านมา - Tony Hoare ผู้ประดิษฐ์ ALGOL W.

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


1

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

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

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