Là tốt hơn để trả về null hoặc bộ sưu tập trống?


420

Đó là một câu hỏi chung (nhưng tôi đang sử dụng C #), cách tốt nhất (thực tiễn tốt nhất) là gì, bạn có trả về bộ sưu tập rỗng hoặc rỗng cho phương thức có bộ sưu tập làm kiểu trả về không?


5
Ừm, không hẳn là CPerkins. rads.stackoverflow.com/amzn/click/0321545613

3
@CPerkins - Vâng, có. Nó được nêu rõ trong hướng dẫn thiết kế khung .NET riêng của Microsoft. Xem câu trả lời của RichardOD để biết chi tiết.
Greg Beech

51
CHỈ nếu ý nghĩa là "Tôi không thể tính kết quả" nếu bạn trả về null. Null không bao giờ nên có ngữ nghĩa của "trống rỗng", chỉ "thiếu" hoặc "không xác định". Thêm chi tiết trong bài viết của tôi về chủ đề này: blog.msdn.com/ericlippert/archive/2009/05/14/iêu
Eric Lippert

3
Bozho: "bất kỳ" là rất nhiều ngôn ngữ. Điều gì về Common Lisp, trong đó danh sách trống chính xác bằng giá trị null? :-)
Ken

9
Trên thực tế, "trùng lặp" là về các phương thức trả về một đối tượng, không phải là một bộ sưu tập. Đó là một kịch bản khác nhau với các câu trả lời khác nhau.
GalacticCowboy

Câu trả lời:


499

Bộ sưu tập trống. Luôn luôn.

Điều này thật tệ:

if(myInstance.CollectionProperty != null)
{
  foreach(var item in myInstance.CollectionProperty)
    /* arrgh */
}

Nó được coi là một cách thực hành tốt nhất để KHÔNG BAO GIỜ quay trở lại nullkhi trả lại một bộ sưu tập hoặc vô số. LUÔN LUÔN trả lại một vô số / bộ sưu tập trống. Nó ngăn chặn những điều vô nghĩa đã nói ở trên, và ngăn chặn chiếc xe của bạn bị đồng nghiệp và những người sử dụng các lớp học của bạn làm phiền.

Khi nói về tài sản, luôn đặt tài sản của bạn một lần và quên nó

public List<Foo> Foos {public get; private set;}

public Bar() { Foos = new List<Foo>(); }

Trong .NET 4.6.1, bạn có thể ngưng tụ khá nhiều thứ này:

public List<Foo> Foos { get; } = new List<Foo>();

Khi nói về các phương thức trả về liệt kê, bạn có thể dễ dàng trả về một vô số trống thay vì null...

public IEnumerable<Foo> GetMyFoos()
{
  return InnerGetFoos() ?? Enumerable.Empty<Foo>();
}

Sử dụng Enumerable.Empty<T>()có thể được coi là hiệu quả hơn so với trả về, ví dụ, một bộ sưu tập hoặc mảng trống mới.


24
Tôi đồng ý với Will, nhưng tôi nghĩ rằng "luôn luôn" là một chút quá mức. Mặc dù bộ sưu tập trống có thể có nghĩa là "0 mục", trả về Null có thể có nghĩa là "không có bộ sưu tập nào" - ví dụ: nếu bạn đang phân tích cú pháp HTML, tìm kiếm <ul> với id = "foo", <ul id = "foo"> </ ul> có thể trả về bộ sưu tập trống; nếu không có <ul> với id = "foo" thì trả về null sẽ tốt hơn (trừ khi bạn muốn xử lý trường hợp này bằng một ngoại lệ)
Patonza

30
không phải lúc nào cũng là câu hỏi liệu "bạn có thể dễ dàng trả về một mảng trống" hay không, mà là liệu một mảng trống có thể gây hiểu lầm trong bối cảnh hiện tại hay không. Một mảng trống thực sự có nghĩa là một cái gì đó, cũng như null. Để nói rằng bạn phải luôn trả về một mảng trống chứ không phải null, gần như là sai lầm khi nói với bạn một phương thức boolean phải luôn trả về true. Cả hai giá trị có thể truyền đạt một ý nghĩa.
David Hedlund

31
Bạn thực sự nên trả về System.Linq.Enumerable.Empty <Foo> () thay vì Foo mới [0]. Nó rõ ràng hơn và tiết kiệm cho bạn một cấp phát bộ nhớ (ít nhất là trong triển khai .NET đã cài đặt của tôi).
Trillian

4
@ Will: OP: "bạn trả về bộ sưu tập rỗng hay rỗng cho phương thức có bộ sưu tập dưới dạng kiểu trả về". Tôi nghĩ trong trường hợp này cho dù đó là IEnumerablehay ICollectionkhông quan trọng. Dù sao, nếu bạn chọn thứ gì đó ICollectionhọ cũng trả lại null... Tôi muốn họ trả lại một bộ sưu tập trống, nhưng tôi tình cờ thấy họ trở về null, vì vậy tôi nghĩ tôi chỉ đề cập đến nó ở đây. Tôi muốn nói mặc định của một bộ sưu tập là vô số không rỗng. Tôi không biết đó là một chủ đề nhạy cảm.
Matthijs Wessels


154

Từ Hướng dẫn thiết kế khung Phiên bản 2 (trang 256):

KHÔNG trả về giá trị null từ thuộc tính bộ sưu tập hoặc từ phương thức trả về bộ sưu tập. Trả về một bộ sưu tập trống hoặc một mảng trống thay thế.

Đây là một bài viết thú vị khác về lợi ích của việc không trả về null (Tôi đã cố gắng tìm một cái gì đó trên blog của Brad Abram và anh ấy đã liên kết với bài viết).

Chỉnh sửa- như Eric Lippert hiện đã nhận xét cho câu hỏi ban đầu, tôi cũng muốn liên kết đến bài viết xuất sắc của mình .


33
+1. Luôn luôn là thực hành tốt nhất để làm theo các hướng dẫn thiết kế khung trừ khi bạn có một lý do rất RẤT tốt để không.

@ Sẽ, hoàn toàn. Đó là những gì tôi làm theo. Tôi chưa bao giờ thấy cần phải làm gì khác
RichardOD

yup, đó là những gì bạn làm theo. Nhưng trong trường hợp các API mà bạn dựa vào không tuân theo nó, bạn sẽ bị 'mắc kẹt';)
Bozho

@ Bozho- yeap. Câu trả lời của bạn cung cấp một số câu trả lời hay của những trường hợp bên lề.
RichardOD

90

Phụ thuộc vào hợp đồng của bạn và trường hợp cụ thể của bạn . Nói chung , tốt nhất là trả lại các bộ sưu tập trống , nhưng đôi khi ( hiếm khi ):

  • null có thể có nghĩa là một cái gì đó cụ thể hơn;
  • API (hợp đồng) của bạn có thể buộc bạn quay trở lại null.

Một số ví dụ cụ thể:

  • một thành phần UI (từ một thư viện ngoài tầm kiểm soát của bạn), có thể hiển thị một bảng trống nếu một bộ sưu tập trống được thông qua hoặc hoàn toàn không có bảng nào, nếu null được thông qua.
  • trong Object-to-XML (JSON / bất cứ thứ gì), trong đó nullcó nghĩa là phần tử bị thiếu, trong khi một bộ sưu tập trống sẽ hiển thị dự phòng (và có thể không chính xác)<collection />
  • bạn đang sử dụng hoặc triển khai API trong đó tuyên bố rõ ràng rằng null nên được trả lại / thông qua

4
@ Bozho- một số ví dụ thú vị ở đây.
RichardOD

1
Tôi sẽ thấy quan điểm của bạn mặc dù tôi không nghĩ bạn nên xây dựng mã cho bạn xung quanh lỗi khác và theo định nghĩa 'null' trong c # không bao giờ có nghĩa là một cái gì đó cụ thể. Giá trị null được định nghĩa là "không có thông tin" và do đó để nói rằng nó mang thông tin cụ thể là một oximoron. Đó là lý do tại sao các nguyên tắc .NET nói rằng bạn nên trả về một tập hợp trống nếu tập thực sự trống. trở về null đang nói: "Tôi không biết bộ dự kiến ​​đã đi đâu"
Rune FS

13
không, nó có nghĩa là "không có tập hợp" thay vì "tập hợp không có yếu tố"
Bozho

Tôi đã từng tin rằng, sau đó tôi đã phải viết rất nhiều TSQL và học được rằng không phải lúc nào cũng vậy, heheh.

1
Phần với Object-To-Xml được phát hiện
Mihai Caracostea

36

Có một điểm khác chưa được đề cập. Hãy xem xét các mã sau đây:

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

Ngôn ngữ C # sẽ trả về một điều tra viên trống khi gọi phương thức này. Do đó, để phù hợp với thiết kế ngôn ngữ (và, do đó, kỳ vọng của lập trình viên) nên trả lại một bộ sưu tập trống.


1
Về mặt kỹ thuật tôi không nghĩ rằng điều này trả về một bộ sưu tập trống.
FryGuy

3
@FryGuy - Không, không. Nó trả về một đối tượng Enumerable, mà phương thức GetEnumerator () của nó trả về một Enumerator (bằng phương pháp analagy với một bộ sưu tập) trống. Đó là, phương thức MoveNext () của Enumerator luôn trả về false. Gọi phương thức ví dụ không trả về null, cũng không trả về một đối tượng Enumerable có phương thức GetEnumerator () trả về null.
Jeffrey L Whitledge

31

Trống rỗng thân thiện với người tiêu dùng hơn nhiều.

Có một phương pháp rõ ràng để tạo thành một vô số trống:

Enumerable.Empty<Element>()

2
Cảm ơn các mẹo gọn gàng. Tôi đã tạo ra một Danh sách trống <T> () giống như một thằng ngốc nhưng điều này có vẻ sạch sẽ hơn rất nhiều và có lẽ cũng hiệu quả hơn một chút.
Giải pháp dữ liệu Jacobs

18

Dường như với tôi rằng bạn nên trả về giá trị đúng về mặt ngữ nghĩa trong bối cảnh, bất cứ điều gì có thể. Một quy tắc nói rằng "luôn trả lại một bộ sưu tập trống" có vẻ hơi đơn giản đối với tôi.

Giả sử trong một hệ thống của một bệnh viện, chúng ta có một chức năng được cho là trả về một danh sách tất cả các lần nhập viện trước đó trong 5 năm qua. Nếu khách hàng chưa đến bệnh viện, việc trả lại một danh sách trống sẽ rất hợp lý. Nhưng điều gì sẽ xảy ra nếu khách hàng bỏ trống phần biểu mẫu nhận tiền đó? Chúng tôi cần một giá trị khác để phân biệt "danh sách trống" với "không trả lời" hoặc "không biết". Chúng ta có thể đưa ra một ngoại lệ, nhưng nó không nhất thiết là một điều kiện lỗi và nó không nhất thiết đẩy chúng ta ra khỏi dòng chương trình bình thường.

Tôi thường bị thất vọng bởi các hệ thống không thể phân biệt giữa không và không có câu trả lời. Tôi đã có một số lần hệ thống yêu cầu tôi nhập một số số, tôi nhập số không và tôi nhận được thông báo lỗi cho tôi biết rằng tôi phải nhập một giá trị trong trường này. Tôi chỉ làm: Tôi đã nhập số không! Nhưng nó sẽ không chấp nhận số 0 vì nó không thể phân biệt nó với không có câu trả lời.


Trả lời Saunders:

Có, tôi cho rằng có sự khác biệt giữa "Người không trả lời câu hỏi" và "Câu trả lời là số không". Đó là điểm của đoạn cuối câu trả lời của tôi. Nhiều chương trình không thể phân biệt "không biết" với khoảng trống hoặc số không, có vẻ như là một lỗ hổng nghiêm trọng. Ví dụ, tôi đã mua sắm một ngôi nhà cách đây một năm. Tôi đã đi đến một trang web bất động sản và có nhiều ngôi nhà được liệt kê với giá yêu cầu là 0 đô la. Nghe có vẻ khá tốt với tôi: Họ đang tặng những ngôi nhà này miễn phí! Nhưng tôi chắc chắn rằng thực tế đáng buồn là họ đã không nhập giá. Trong trường hợp đó, bạn có thể nói, "Chà, NGHIÊM TÚC bằng không có nghĩa là họ không nhập giá - không ai sẽ cho một ngôi nhà miễn phí." Nhưng trang web cũng liệt kê giá chào bán trung bình của các ngôi nhà ở các thị trấn khác nhau. Tôi không thể không tự hỏi nếu trung bình không bao gồm các số không, do đó đưa ra mức trung bình thấp không chính xác cho một số nơi. tức là trung bình 100.000 đô la là bao nhiêu; $ 120.000; và "không biết"? Về mặt kỹ thuật, câu trả lời là "không biết". Những gì chúng ta có thể thực sự muốn thấy là $ 110.000. Nhưng những gì chúng ta có thể nhận được là 73.333 đô la, điều này hoàn toàn sai. Ngoài ra, điều gì xảy ra nếu chúng tôi gặp sự cố này trên một trang web nơi người dùng có thể đặt hàng trực tuyến? (Không có khả năng cho bất động sản, nhưng tôi chắc chắn bạn đã thấy nó được thực hiện cho nhiều sản phẩm khác.) Chúng tôi có thực sự muốn "giá chưa được chỉ định" được hiểu là "miễn phí" không? do đó đưa ra mức trung bình thấp không chính xác cho một số nơi. tức là trung bình 100.000 đô la là bao nhiêu; $ 120.000; và "không biết"? Về mặt kỹ thuật, câu trả lời là "không biết". Những gì chúng ta có thể thực sự muốn thấy là $ 110.000. Nhưng những gì chúng ta có thể nhận được là 73.333 đô la, điều này hoàn toàn sai. Ngoài ra, điều gì xảy ra nếu chúng tôi gặp sự cố này trên một trang web nơi người dùng có thể đặt hàng trực tuyến? (Không có khả năng cho bất động sản, nhưng tôi chắc chắn bạn đã thấy nó được thực hiện cho nhiều sản phẩm khác.) Chúng tôi có thực sự muốn "giá chưa được chỉ định" được hiểu là "miễn phí" không? do đó đưa ra mức trung bình thấp không chính xác cho một số nơi. tức là trung bình 100.000 đô la là bao nhiêu; $ 120.000; và "không biết"? Về mặt kỹ thuật, câu trả lời là "không biết". Những gì chúng ta có thể thực sự muốn thấy là $ 110.000. Nhưng những gì chúng ta có thể nhận được là 73.333 đô la, điều này hoàn toàn sai. Ngoài ra, điều gì xảy ra nếu chúng tôi gặp sự cố này trên một trang web nơi người dùng có thể đặt hàng trực tuyến? (Không có khả năng cho bất động sản, nhưng tôi chắc chắn bạn đã thấy nó được thực hiện cho nhiều sản phẩm khác.) Chúng tôi có thực sự muốn "giá chưa được chỉ định" được hiểu là "miễn phí" không? Điều đó sẽ hoàn toàn sai. Ngoài ra, điều gì xảy ra nếu chúng tôi gặp sự cố này trên một trang web nơi người dùng có thể đặt hàng trực tuyến? (Không có khả năng cho bất động sản, nhưng tôi chắc chắn bạn đã thấy nó được thực hiện cho nhiều sản phẩm khác.) Chúng tôi có thực sự muốn "giá chưa được chỉ định" được hiểu là "miễn phí" không? Điều đó sẽ hoàn toàn sai. Ngoài ra, điều gì xảy ra nếu chúng tôi gặp sự cố này trên một trang web nơi người dùng có thể đặt hàng trực tuyến? (Không có khả năng cho bất động sản, nhưng tôi chắc chắn bạn đã thấy nó được thực hiện cho nhiều sản phẩm khác.) Chúng tôi có thực sự muốn "giá chưa được chỉ định" được hiểu là "miễn phí" không?

RE có hai chức năng riêng biệt, một "có bất kỳ?" và một "nếu vậy, nó là gì?" Vâng, bạn chắc chắn có thể làm điều đó, nhưng tại sao bạn muốn? Bây giờ chương trình gọi phải thực hiện hai cuộc gọi thay vì một cuộc gọi. Điều gì xảy ra nếu một lập trình viên không gọi được "bất kỳ?" và đi thẳng đến "nó là gì?" ? Chương trình sẽ trả về một số không dẫn đầu sai? Ném một ngoại lệ? Trả về một giá trị không xác định? Nó tạo ra nhiều mã hơn, nhiều công việc hơn và nhiều lỗi tiềm ẩn hơn.

Lợi ích duy nhất tôi thấy là nó cho phép bạn tuân thủ một quy tắc tùy ý. Có bất kỳ lợi thế cho quy tắc này làm cho nó có giá trị rắc rối của việc tuân theo nó? Nếu không, tại sao phải bận tâm?


Trả lời Jammycakes:

Xem xét những gì mã thực tế sẽ trông như thế nào. Tôi biết câu hỏi đã nói C # nhưng xin lỗi nếu tôi viết Java. C # của tôi không quá sắc nét và nguyên tắc là như vậy.

Với lợi nhuận không:

HospList list=patient.getHospitalizationList(patientId);
if (list==null)
{
   // ... handle missing list ...
}
else
{
  for (HospEntry entry : list)
   //  ... do whatever ...
}

Với chức năng riêng biệt:

if (patient.hasHospitalizationList(patientId))
{
   // ... handle missing list ...
}
else
{
  HospList=patient.getHospitalizationList(patientId))
  for (HospEntry entry : list)
   // ... do whatever ...
}

Đó thực sự là một dòng hoặc hai mã ít hơn với trả về null, vì vậy nó không gây gánh nặng nhiều hơn cho người gọi, nó ít hơn.

Tôi không thấy cách nó tạo ra vấn đề KHÔ. Nó không giống như chúng ta phải thực hiện cuộc gọi hai lần. Nếu chúng ta luôn muốn làm điều tương tự khi danh sách không tồn tại, có lẽ chúng ta có thể đẩy việc xử lý xuống chức năng get-list thay vì yêu cầu người gọi làm điều đó, và vì vậy việc đưa mã vào người gọi sẽ là vi phạm DRY. Nhưng chúng tôi gần như chắc chắn không muốn luôn luôn làm điều tương tự. Trong các chức năng mà chúng ta phải có danh sách để xử lý, danh sách bị thiếu là một lỗi có thể dừng xử lý. Nhưng trên màn hình chỉnh sửa, chúng tôi chắc chắn không muốn dừng xử lý nếu họ chưa nhập dữ liệu: chúng tôi muốn cho phép họ nhập dữ liệu. Vì vậy, việc xử lý "không có danh sách" phải được thực hiện ở cấp độ người gọi bằng cách này hay cách khác. Và cho dù chúng ta làm điều đó với trả về null hoặc một hàm riêng biệt sẽ không có sự khác biệt so với nguyên tắc lớn hơn.

Chắc chắn, nếu người gọi không kiểm tra null, chương trình có thể thất bại với ngoại lệ con trỏ null. Nhưng nếu có một chức năng "có bất kỳ" riêng biệt nào và người gọi không gọi chức năng đó mà gọi một cách mù quáng là chức năng "nhận danh sách" thì chuyện gì sẽ xảy ra? Nếu nó ném một ngoại lệ hoặc thất bại, thì, điều đó khá giống với những gì sẽ xảy ra nếu nó trả về null và không kiểm tra nó. Nếu nó trả về một danh sách trống, điều đó chỉ sai. Bạn không thể phân biệt giữa "Tôi có một danh sách không có phần tử nào" và "Tôi không có danh sách". Nó giống như trả về 0 cho giá khi người dùng không nhập bất kỳ giá nào: nó chỉ sai.

Tôi không thấy cách gắn một thuộc tính bổ sung vào bộ sưu tập giúp. Người gọi vẫn phải kiểm tra nó. Làm thế nào là tốt hơn so với việc kiểm tra null? Một lần nữa, điều tồi tệ nhất tuyệt đối có thể xảy ra là lập trình viên quên kiểm tra nó và đưa ra kết quả không chính xác.

Hàm trả về null không phải là một điều ngạc nhiên nếu lập trình viên quen thuộc với khái niệm null có nghĩa là "không có giá trị", mà tôi nghĩ rằng bất kỳ lập trình viên có thẩm quyền nào cũng nên nghe, cho dù anh ta nghĩ đó có phải là ý tưởng tốt hay không. Tôi nghĩ rằng có một chức năng riêng biệt là một vấn đề "bất ngờ". Nếu một lập trình viên không quen thuộc với API, khi anh ta chạy thử nghiệm không có dữ liệu, anh ta sẽ nhanh chóng phát hiện ra rằng đôi khi anh ta nhận lại được một giá trị rỗng. Nhưng làm thế nào anh ta phát hiện ra sự tồn tại của một chức năng khác trừ khi anh ta có thể có một chức năng như vậy và anh ta kiểm tra tài liệu, và tài liệu này hoàn chỉnh và dễ hiểu? Tôi muốn có một chức năng luôn mang lại cho tôi một phản hồi có ý nghĩa, thay vì hai chức năng mà tôi phải biết và nhớ để gọi cả hai.


4
Tại sao cần phải kết hợp câu trả lời "không trả lời" và "không" trong cùng một giá trị trả về? Thay vào đó, hãy trả lại phương thức "bất kỳ lần nhập viện nào trước đó trong năm năm qua" và có một phương pháp riêng để hỏi, "danh sách nhập viện trước đó có được điền không?". Điều đó giả định rằng có một sự khác biệt giữa một danh sách được điền mà không cần nhập viện trước đó và một danh sách không được điền vào.
John Saunders

2
Nhưng nếu bạn trả về null, bạn vẫn đang đặt thêm gánh nặng cho người gọi! Người gọi phải kiểm tra mọi giá trị trả về cho null - một vi phạm DRY khủng khiếp. Nếu bạn muốn chỉ rõ "người gọi không trả lời câu hỏi" một cách riêng biệt, việc tạo một cuộc gọi phương thức bổ sung để chỉ ra thực tế sẽ đơn giản hơn. Hoặc, hoặc sử dụng loại bộ sưu tập dẫn xuất có thuộc tính bổ sung là DeclinesToAnswer. Quy tắc để không bao giờ trả về null hoàn toàn không phải là tùy tiện, đó là Nguyên tắc bất ngờ tối thiểu. Ngoài ra, kiểu trả về của phương thức của bạn có nghĩa là tên của nó nói. Null gần như chắc chắn không.
jammycakes

2
Bạn đang giả định rằng getHospitalizationList của bạn chỉ được gọi từ một nơi và / hoặc tất cả những người gọi nó sẽ muốn phân biệt giữa các trường hợp "không trả lời" và "không". Có sẽ có trường hợp (gần như chắc chắn đa số), nơi người gọi của bạn không cần phải thực hiện phân biệt đó, và vì vậy bạn đang buộc họ phải thêm kiểm tra null trong những nơi này không cần thiết. Điều này làm tăng rủi ro đáng kể cho cơ sở mã của bạn bởi vì mọi người có thể quá dễ dàng quên làm điều đó - và vì có rất ít lý do chính đáng để trả về null thay vì bộ sưu tập, điều này sẽ có nhiều khả năng hơn.
jammycakes

3
Tên RE: Không có tên hàm nào có thể mô tả hoàn toàn chức năng làm gì trừ khi nó dài như hàm, và do đó cực kỳ không thực tế. Nhưng trong mọi trường hợp, nếu hàm trả về một danh sách trống khi không có câu trả lời nào, thì với lý do tương tự, không nên gọi nó là "getHospitalizationListOrEmptyList IfNoAnswer"? Nhưng thực sự, bạn có nhấn mạnh rằng hàm Java Reader.read nên được đổi tên thành readCharacterOrReturnMinusOneOnEndOfStream không? Kết quả đó Set.getInt thực sự phải là "getIntOrZero IfValueWasNull"? V.v.
Jay

4
RE mỗi cuộc gọi đều muốn phân biệt: Vâng, vâng, tôi cho rằng, hoặc ít nhất là tác giả của một người gọi nên đưa ra quyết định có ý thức mà anh ta không quan tâm. Nếu hàm trả về một danh sách trống cho "không biết" và người gọi đối xử mù quáng với "không" này, thì nó có thể cho kết quả không chính xác nghiêm trọng. Hãy tưởng tượng nếu hàm là "getAllergicReactionToMedicationList". Một chương trình được điều trị một cách mù quáng "danh sách không được đưa vào" vì "bệnh nhân không có phản ứng dị ứng đã biết" theo nghĩa đen có thể dẫn đến việc giết chết một giáo dân. Bạn sẽ nhận được kết quả tương tự, nếu kết quả kém ấn tượng hơn, trong nhiều hệ thống khác. ...
Jay

10

Nếu một bộ sưu tập trống có ý nghĩa về mặt ngữ nghĩa, đó là những gì tôi muốn trả lại. Trả lại một bộ sưu tập trống để GetMessagesInMyInbox()liên lạc "bạn thực sự không có bất kỳ tin nhắn nào trong hộp thư đến của mình", trong khi trả lạinull có thể hữu ích để liên lạc rằng không đủ dữ liệu để nói danh sách có thể được trả về như thế nào.


6
Trong ví dụ bạn đưa ra, có vẻ như phương thức có thể sẽ đưa ra một ngoại lệ nếu nó không thể thực hiện yêu cầu thay vì chỉ trả về null. Các ngoại lệ hữu ích hơn nhiều cho việc chẩn đoán các vấn đề so với null.
Greg Beech

vâng, trong ví dụ trong hộp thư đến, một nullgiá trị chắc chắn không có vẻ hợp lý, tôi đã suy nghĩ theo các thuật ngữ chung hơn về điều đó. Các trường hợp ngoại lệ cũng rất tuyệt để truyền đạt thực tế rằng có điều gì đó không ổn, nhưng nếu "dữ liệu không đủ" được đề cập là hoàn toàn được mong đợi, thì việc ném một ngoại lệ sẽ có thiết kế kém. Tôi đang nghĩ về một kịch bản trong đó hoàn toàn có thể xảy ra và không có lỗi nào cho phương thức đôi khi không thể tính toán được phản hồi.
David Hedlund

6

Trả về null có thể hiệu quả hơn, vì không có đối tượng mới nào được tạo. Tuy nhiên, nó cũng thường yêu cầu nullkiểm tra (hoặc xử lý ngoại lệ.)

Về mặt ngữ nghĩa nullvà một danh sách trống không có nghĩa tương tự. Sự khác biệt là tinh tế và một lựa chọn có thể tốt hơn so với lựa chọn khác trong các trường hợp cụ thể.

Bất kể sự lựa chọn của bạn, tài liệu để tránh nhầm lẫn.


8
Hiệu quả gần như không bao giờ là một yếu tố khi xem xét tính chính xác của thiết kế API. Trong một số trường hợp rất cụ thể như nguyên thủy đồ họa thì có thể là như vậy, nhưng khi xử lý danh sách và hầu hết những thứ cấp cao khác thì tôi rất nghi ngờ điều đó.
Greg Beech

Đồng ý với Greg, đặc biệt là mã người dùng API phải viết để bù cho "tối ưu hóa" này có thể không hiệu quả hơn so với việc sử dụng thiết kế tốt hơn ngay từ đầu.
Craig Stuntz

Đồng ý và trong hầu hết các trường hợp, đơn giản là nó không đáng để tối ưu hóa. Danh sách trống thực tế là miễn phí với quản lý bộ nhớ hiện đại.
Jason Baker

6

Người ta có thể lập luận rằng lý do đằng sau Null Object Pattern tương tự như lý do ủng hộ việc trả lại bộ sưu tập trống.


4

Tùy theo hoàn cảnh. Nếu đó là một trường hợp đặc biệt, sau đó trả về null. Nếu hàm chỉ xảy ra để trả về một bộ sưu tập trống, thì rõ ràng trả về đó là ok. Tuy nhiên, trả lại một bộ sưu tập trống dưới dạng trường hợp đặc biệt vì các tham số không hợp lệ hoặc lý do khác KHÔNG phải là ý tưởng hay, vì nó che giấu một điều kiện trường hợp đặc biệt.

Trên thực tế, trong trường hợp này tôi thường thích ném một ngoại lệ để đảm bảo rằng nó THỰC SỰ không bị bỏ qua :)

Nói rằng nó làm cho mã mạnh mẽ hơn (bằng cách trả về một bộ sưu tập trống) vì chúng không phải xử lý điều kiện null là xấu, vì nó chỉ đơn giản là che giấu một vấn đề cần được xử lý bởi mã gọi.


4

Tôi sẽ lập luận rằng đó nullkhông giống như một bộ sưu tập trống và bạn nên chọn cái nào đại diện tốt nhất cho những gì bạn sẽ trở lại. Trong hầu hết các trường hợpnull là không có gì (ngoại trừ trong SQL). Một bộ sưu tập trống là một cái gì đó, mặc dù một cái gì đó trống rỗng.

Nếu bạn phải chọn cái này hay cái khác, tôi sẽ nói rằng bạn nên hướng tới một bộ sưu tập trống thay vì null. Nhưng đôi khi một bộ sưu tập trống không giống với giá trị null.


4

Hãy suy nghĩ luôn có lợi cho khách hàng của bạn (đang sử dụng api của bạn):

Việc trả lại 'null' rất thường xuyên gây ra sự cố với các máy khách không xử lý kiểm tra null một cách chính xác, điều này gây ra một NullPulumException trong thời gian chạy. Tôi đã thấy các trường hợp kiểm tra null bị thiếu như vậy buộc phải có vấn đề sản xuất ưu tiên (một khách hàng đã sử dụng foreach (...) trên giá trị null). Trong quá trình thử nghiệm, sự cố không xảy ra, vì dữ liệu được vận hành hơi khác nhau.


3

Tôi muốn đưa ra giải thích ở đây, với ví dụ phù hợp.

Hãy xem xét một trường hợp ở đây ..

int totalValue = MySession.ListCustomerAccounts()
                          .FindAll(ac => ac.AccountHead.AccountHeadID 
                                         == accountHead.AccountHeadID)
                          .Sum(account => account.AccountValue);

Ở đây hãy xem xét các chức năng tôi đang sử dụng ..

1. ListCustomerAccounts() // User Defined
2. FindAll()              // Pre-defined Library Function

Tôi có thể dễ dàng sử dụng ListCustomerAccountFindAllthay vì.,

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();
}

LƯU Ý: Vì AccountValue không có null, nên hàm Sum () sẽ không trả về null. Do đó tôi có thể sử dụng trực tiếp.


2

Chúng tôi đã có cuộc thảo luận này giữa nhóm phát triển tại nơi làm việc cách đây một tuần và chúng tôi gần như nhất trí đi tìm bộ sưu tập trống. Một người muốn trả về null vì lý do tương tự Mike đã chỉ định ở trên.


2

Bộ sưu tập trống. Nếu bạn đang sử dụng C #, giả định là tối đa hóa tài nguyên hệ thống là không cần thiết. Mặc dù kém hiệu quả hơn, việc trả về Bộ sưu tập rỗng sẽ thuận tiện hơn nhiều cho các lập trình viên tham gia (vì lý do Will đã nêu ở trên).


2

Trả lại một bộ sưu tập trống là tốt hơn trong hầu hết các trường hợp.

Lý do cho điều đó là sự thuận tiện trong việc thực hiện của người gọi, hợp đồng nhất quán và thực hiện dễ dàng hơn.

Nếu một phương thức trả về null để chỉ ra kết quả trống, người gọi phải thực hiện bộ điều hợp kiểm tra null ngoài việc liệt kê. Mã này sau đó được sao chép trong nhiều người gọi khác nhau, vậy tại sao không đặt bộ điều hợp này vào bên trong phương thức để nó có thể được sử dụng lại.

Việc sử dụng null hợp lệ cho IEnumerable có thể là dấu hiệu của kết quả vắng mặt hoặc lỗi hoạt động, nhưng trong trường hợp này cần xem xét các kỹ thuật khác, chẳng hạn như ném ngoại lệ.

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

Tôi gọi đó là sai lầm hàng tỷ đô la của tôi Lúc đó, tôi đang thiết kế hệ thống loại toàn diện đầu tiên để tham khảo bằng ngôn ngữ hướng đối tượng. Mục tiêu của tôi là đảm bảo rằng tất cả việc sử dụng tài liệu tham khảo phải tuyệt đối an toàn, với việc kiểm tra được thực hiện tự động bởi trình biên dịch. Nhưng tôi không thể cưỡng lại sự cám dỗ để đưa vào một tài liệu tham khảo null, đơn giản vì nó rất dễ thực hiện. Điều này đã dẫn đến vô số lỗi, lỗ hổng và sự cố hệ thống, có thể gây ra hàng tỷ đô la đau đớn và thiệt hại trong bốn mươi năm qua. - Tony Hoare, nhà phát minh của ALGOL W.

Xem ở đây cho một cơn bão shit công phu nullnói chung. Tôi không đồng ý với tuyên bố đó undefinedlà khác null, nhưng nó vẫn đáng đọc. Và nó giải thích, tại sao bạn nên tránh nulltất cả và không chỉ trong trường hợp bạn đã hỏi. Bản chất là, nulltrong bất kỳ ngôn ngữ nào là một trường hợp đặc biệt. Bạn phải nghĩ về nullmột ngoại lệ. undefinedlà khác nhau theo cách đó, mã xử lý hành vi không xác định trong hầu hết các trường hợp chỉ là một lỗi. C và hầu hết các ngôn ngữ khác cũng có hành vi không xác định nhưng hầu hết trong số họ không có định danh cho ngôn ngữ đó.


1

Từ góc độ quản lý sự phức tạp, mục tiêu kỹ thuật phần mềm chính, chúng tôi muốn tránh tuyên truyền không cần thiết phức tạp chu kỳ cho các máy khách của API. Trả lại null cho khách hàng cũng giống như trả lại cho họ chi phí phức tạp theo chu kỳ của một nhánh mã khác.

(Điều này tương ứng với gánh nặng kiểm tra đơn vị. Bạn sẽ cần phải viết một bài kiểm tra cho trường hợp trả về null, ngoài trường hợp trả về bộ sưu tập trống.)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.