Có nên kiểm tra null nếu anh ta không mong đợi null?


113

Tuần trước, chúng tôi đã tranh cãi gay gắt về việc xử lý null trong lớp dịch vụ của ứng dụng. Câu hỏi là trong bối cảnh .NET, nhưng nó sẽ giống nhau trong Java và nhiều công nghệ khác.

Câu hỏi đặt ra là: bạn có nên luôn kiểm tra null và làm cho mã của bạn hoạt động không có vấn đề gì, hoặc để một bong bóng ngoại lệ xuất hiện khi nhận được null bất ngờ?

Ở một bên, việc kiểm tra null nơi bạn không mong đợi nó (nghĩa là không có giao diện người dùng để xử lý nó), theo tôi, giống như việc viết một khối thử với bắt trống. Bạn chỉ đang che giấu một lỗi. Lỗi có thể là một cái gì đó đã thay đổi trong mã và null hiện là giá trị mong đợi hoặc có một số lỗi khác và ID sai được truyền cho phương thức.

Mặt khác, kiểm tra null có thể là một thói quen tốt nói chung. Hơn nữa, nếu có kiểm tra, ứng dụng có thể tiếp tục hoạt động, chỉ với một phần nhỏ chức năng không có bất kỳ ảnh hưởng nào. Sau đó, khách hàng có thể báo cáo một lỗi nhỏ như "không thể xóa bình luận" thay vì lỗi nghiêm trọng hơn nhiều như "không thể mở trang X".

Bạn thực hành theo cách nào và lập luận của bạn để chống lại cách tiếp cận nào?

Cập nhật:

Tôi muốn thêm một số chi tiết về trường hợp cụ thể của chúng tôi. Chúng tôi đã lấy một số đối tượng từ cơ sở dữ liệu và thực hiện một số xử lý trên chúng (giả sử, xây dựng một bộ sưu tập). Nhà phát triển đã viết mã không lường trước được rằng đối tượng có thể là null nên anh ta không bao gồm bất kỳ kiểm tra nào và khi trang được tải thì có lỗi và toàn bộ trang không tải.

Rõ ràng, trong trường hợp này nên có một kiểm tra. Sau đó, chúng tôi đã tranh luận về việc liệu mọi đối tượng được xử lý có nên được kiểm tra hay không, ngay cả khi nó không bị thiếu và liệu xử lý cuối cùng có nên bị hủy bỏ một cách im lặng hay không.

Lợi ích giả định là trang sẽ tiếp tục hoạt động. Hãy nghĩ về một kết quả tìm kiếm trên Stack Exchange trong các nhóm khác nhau (người dùng, nhận xét, câu hỏi). Phương pháp có thể kiểm tra null và hủy bỏ quá trình xử lý của người dùng (do lỗi là null) nhưng trả về phần "bình luận" và "câu hỏi". Trang sẽ tiếp tục hoạt động ngoại trừ phần "người dùng" sẽ bị thiếu (đó là một lỗi). Chúng ta có nên thất bại sớm và phá vỡ toàn bộ trang hoặc tiếp tục làm việc và chờ ai đó nhận thấy rằng phần "người dùng" bị thiếu?


62
Nguyên tắc Fox Mulder: Tin tưởng không ai.
yannis

14
@YannisRizos: nguyên tắc đó là một nguyên tắc tốt - thật tốt khi những người tạo Java và C # để môi trường thời gian chạy ngôn ngữ tự động làm điều này. Vì vậy, thông thường không cần phải thực hiện kiểm tra rõ ràng cho null ở mọi nơi trong mã của bạn bằng các ngôn ngữ đó.
Doc Brown


Tôi đồng ý với câu trả lời tdammers. Tôi tin rằng việc kiểm tra null chỉ là sai vì bạn không có cách xử lý hợp lý. Tuy nhiên, trong kịch bản của bạn, bạn có thể xử lý một phần không thành công như xác thực người dùng (hoặc nhận ý kiến ​​hoặc bất cứ điều gì có thể) và có hộp "oh không có lỗi xảy ra". Cá nhân tôi đăng nhập tất cả các ngoại lệ trong ứng dụng của mình và lọc ra một số ngoại lệ nhất định như 404 và kết nối gần như bất ngờ

2
assert(foo != null, "foo is web control within the repeater, there's no reason to expect it to be null, etc, etc...");
zzzzBov

Câu trả lời:


105

Câu hỏi không phải là quá nhiều liệu bạn nên kiểm tra nullhay để cho bộ thực thi ném ngoại lệ; đó là cách bạn nên ứng phó với tình huống bất ngờ như vậy.

Các tùy chọn của bạn, sau đó, là:

  • Ném một ngoại lệ chung ( NullReferenceException) và để nó nổi bong bóng; nếu bạn không nulltự kiểm tra, đây là điều sẽ tự động xảy ra.
  • Ném một ngoại lệ tùy chỉnh mô tả vấn đề ở cấp độ cao hơn; điều này có thể đạt được bằng cách ném vào nullséc, hoặc bằng cách bắt NullReferenceExceptionvà ném ngoại lệ cụ thể hơn.
  • Phục hồi bằng cách thay thế một giá trị mặc định phù hợp.

Không có quy tắc chung nào là giải pháp tốt nhất. Cá nhân, tôi sẽ nói:

  • Ngoại lệ chung là tốt nhất nếu điều kiện lỗi sẽ là dấu hiệu của một lỗi nghiêm trọng trong mã của bạn, nghĩa là, giá trị null sẽ không bao giờ được phép đến đó ngay từ đầu. Một ngoại lệ như vậy sau đó có thể tạo ra tất cả các lỗi mà bạn đã thiết lập, để ai đó được thông báo và sửa lỗi.
  • Giải pháp giá trị mặc định là tốt nếu việc cung cấp đầu ra bán hữu ích quan trọng hơn tính chính xác, chẳng hạn như trình duyệt web chấp nhận HTML không chính xác về mặt kỹ thuật và nỗ lực hết sức để hiển thị nó hợp lý.
  • Nếu không, tôi sẽ đi với ngoại lệ cụ thể và xử lý nó ở đâu đó phù hợp.

1
@Stilgar cho enduser không có sự khác biệt. đối với nhà phát triển, một ngoại lệ cụ thể như "máy chủ cơ sở dữ liệu không thể truy cập" trực quan hơn là "con trỏ null ngoại trừ"
k3b

32
Không chăm chỉ và sớm có nghĩa là ít có nguy cơ xảy ra sự cố một cách tinh vi hơn, những thứ như xác thực sai, dữ liệu bị hỏng vẫn tồn tại để lưu trữ, rò rỉ thông tin và những thứ gây cười khác.
Lars Viklund

1
@Stilgar: Có hai mối quan tâm ở đây: hành vi không giám sát và khả năng duy trì. Mặc dù đối với hành vi không giám sát, có rất ít sự khác biệt giữa một ngoại lệ cụ thể và chung chung, đối với khả năng duy trì, nó có thể tạo ra sự khác biệt giữa "oh, vậy đó là lý do tại sao mọi thứ nổ tung" và gỡ lỗi nhiều giờ.
tdammers

3
tdammers là chính xác. "Tham chiếu đối tượng không được đặt thành phiên bản của đối tượng" là một trong những trường hợp ngoại lệ khó chịu nhất mà tôi thấy với tần suất cao nhất. Tôi rất muốn thấy một ngoại lệ cụ thể cho dù đó là ArgumentNullException với tên tham số hay ngoại lệ tùy chỉnh "Không có bản ghi bài đăng nào tồn tại cho người dùng TK421."
Sean Chase

1
@ k3b Đối với người dùng cuối cũng vậy, "máy chủ cơ sở dữ liệu không truy cập được" rất hữu ích. Ít nhất, đối với bất kỳ người dùng cuối nào có một chút manh mối về các vấn đề phổ biến - điều đó có thể giúp anh ta thấy rằng cáp bị lỏng, bộ định tuyến cần khởi động lại, v.v. thay vì tức giận về phần mềm lỗi.
Julia Hayward

58

IMHO cố gắng xử lý các giá trị null mà bạn không mong đợi dẫn đến mã quá phức tạp. Nếu bạn không mong đợi null, hãy làm rõ bằng cách ném ArgumentNullException. Tôi thực sự thất vọng khi mọi người kiểm tra xem giá trị có phải là null hay không và sau đó thử viết một số mã không có ý nghĩa gì. Điều tương tự cũng áp dụng cho việc sử dụng SingleOrDefault(hoặc thậm chí tệ hơn khi nhận bộ sưu tập) khi ai đó thực sự mong đợi Singlevà nhiều trường hợp khác khi mọi người sợ (tôi không thực sự biết điều gì) để nêu rõ logic của họ.


Thay vì ArgumentNullException, người ta nên sử dụng hợp đồng mã, (trừ khi đó là mã nguồn trước năm 2010 không hỗ trợ hợp đồng mã).
Arseni Mourzenko

Tôi đồng ý với bạn. Nếu null không được mong đợi thì không nên xử lý theo những cách lạ mà chỉ báo cáo.
Giorgio

9
nếu tôi đọc đúng câu hỏi, ném một ArgumentNullExceptionsố đếm là "xử lý" giá trị null: nó sẽ ngăn chặn ngoại lệ tham chiếu null sau này.
KutuluMike

@MichaelEdenfield: Tôi không nghĩ rằng việc đó được coi là xử lý thực sự theo câu hỏi đã cho (mục tiêu của chúng tôi là không làm cho mã của bạn hoạt động bất kể điều gì ). Thành thật mà nói, tôi thường quên viết khối thích hợp sẽ ném nếu null được thông qua. Tuy nhiên, tôi coi việc ném NullArgumentException là cách thực hành tốt nhất và theo tôi, đó là cách viết mã không thể xử lý null mà thay vào đó là trả về ngoại lệ, ví dụ null khác là kết quả phương thức (hoặc chuỗi rỗng hoặc bộ sưu tập trống hoặc -1 hoặc bỏ qua thực thi phương thức nếu kiểu trả về là void, v.v.).
empi

2
@Giorgio, ArgumentNullExceptionlàm cho nó rất rõ vấn đề là gì và làm thế nào để khắc phục nó. C # không có xu hướng sử dụng "không xác định". Nếu bạn gọi một cái gì đó theo cách không xác định, thì cách bạn gọi nó không có ý nghĩa. Một ngoại lệ là cách thích hợp để chỉ ra rằng. Bây giờ, đôi khi tôi sẽ tạo các phương thức phân tích cú pháp trả về null nếu int(ví dụ) không thể được phân tích cú pháp. Nhưng đó là một trường hợp trong đó một kết quả không thể nhìn thấy là một khả năng hợp pháp chứ không phải là lỗi sử dụng. Xem thêm bài viết này .
Kyralessa

35

Thói quen kiểm tra nulltrải nghiệm của tôi xuất phát từ các nhà phát triển C hoặc C ++ trước đây, trong các ngôn ngữ đó, bạn có khả năng che giấu một lỗi nghiêm trọng khi không kiểm tra NULL. Như bạn đã biết, trong Java hoặc C # mọi thứ đều khác nhau, sử dụng null ref mà không kiểm tra nullsẽ gây ra ngoại lệ, do đó lỗi sẽ không bị ẩn một cách bí mật miễn là bạn không bỏ qua nó ở phía gọi. Vì vậy, trong hầu hết các trường hợp, việc kiểm tra rõ ràng nullkhông có ý nghĩa nhiều và quá phức tạp mã hơn mức cần thiết. Đó là lý do tại sao tôi không coi kiểm tra null là một thói quen tốt trong các ngôn ngữ đó (ít nhất, không nói chung).

Tất nhiên, có những trường hợp ngoại lệ từ quy tắc đó, đây là những điều tôi có thể nghĩ ra:

  • bạn muốn có một thông báo lỗi tốt hơn, có thể đọc được bằng con người, thông báo cho người dùng biết phần nào của chương trình tham chiếu null sai xảy ra. Vì vậy, bài kiểm tra của bạn cho null ném chỉ là một ngoại lệ khác, với một văn bản lỗi khác. Rõ ràng, không có lỗi sẽ được che dấu theo cách đó.

  • bạn muốn làm cho chương trình của bạn "thất bại sớm hơn". Ví dụ, bạn muốn kiểm tra giá trị null của tham số hàm tạo, trong đó tham chiếu đối tượng nếu không sẽ chỉ được lưu trữ trong một biến thành viên và được sử dụng sau này.

  • bạn có thể xử lý tình huống của một null ref một cách an toàn, không có nguy cơ xảy ra lỗi tiếp theo và không che giấu một lỗi nghiêm trọng.

  • bạn muốn một loại báo hiệu lỗi hoàn toàn khác (ví dụ: trả lại mã lỗi) trong ngữ cảnh hiện tại của bạn


3
"Bạn muốn một thông báo lỗi tốt hơn, dễ đọc hơn cho con người" - đó là lý do tốt nhất để làm điều đó, theo ý kiến ​​của tôi. "Tham chiếu đối tượng không được đặt thành phiên bản của đối tượng" hoặc "Ngoại lệ tham chiếu Null" không bao giờ hữu ích như "Sản phẩm không được khởi tạo".
pdr

@pdr: Một lý do khác để kiểm tra là để đảm bảo nó luôn được phát hiện.
Giorgio

13
"Cựu nhà phát triển C", có lẽ. "Các nhà phát triển C ++ trước đây" chỉ khi những người đó đang phục hồi các nhà phát triển C. Là một nhà phát triển C ++ hiện đại, tôi sẽ rất nghi ngờ về mã sử dụng con trỏ trần hoặc phân bổ động thiếu thận trọng. Trên thực tế, tôi rất muốn đưa ra lập luận và nói rằng C ++ không gặp phải vấn đề mà OP mô tả vì các biến của nó không có thuộc tính null đặc biệt bổ sung mà Java và C # có.
Kerrek SB

7
Đoạn đầu tiên của bạn chỉ là một nửa câu chuyện: có, nếu bạn sử dụng tham chiếu null trong C #, bạn sẽ có một ngoại lệ, trong khi trong C ++, bạn sẽ nhận được hành vi không xác định. Nhưng trong tốt bằng văn bản C #, bạn đang bị mắc kẹt với xa tài liệu tham khảo nullable hơn ở tốt bằng văn bản C ++.
James McNellis

Đóng gói càng tốt, câu trả lời này được áp dụng càng tốt.
radarbob

15

Sử dụng các xác nhận để kiểm tra và chỉ ra trước / postconditions và bất biến cho mã của bạn.

Nó làm cho nó dễ dàng hơn nhiều để hiểu những gì mã mong đợi và những gì nó có thể được dự kiến ​​sẽ xử lý.

Một khẳng định, IMO, là một kiểm tra phù hợp bởi vì nó là:

  • hiệu quả (thường không được sử dụng trong phát hành),
  • ngắn gọn (thường là một dòng) và
  • rõ ràng (điều kiện tiên quyết bị vi phạm, đây là một lỗi lập trình).

Tôi nghĩ mã tự ghi là quan trọng, do đó kiểm tra là tốt. Phòng thủ, không nhanh, lập trình giúp cho việc bảo trì dễ dàng hơn, đó là nơi thường dành phần lớn thời gian.

Cập nhật

Wrt công phu của bạn. Thông thường tốt để cung cấp cho người dùng cuối một ứng dụng hoạt động tốt trong các lỗi, tức là hiển thị càng nhiều càng tốt. Mạnh mẽ là tốt, vì trời chỉ biết những gì sẽ phá vỡ vào thời điểm quan trọng.

Tuy nhiên, các nhà phát triển và người kiểm tra phải nhận thức được lỗi ASAP, vì vậy bạn có thể muốn một khung đăng nhập với một cái móc có thể hiển thị cảnh báo cho người dùng rằng có vấn đề. Cảnh báo có thể được hiển thị cho tất cả người dùng, nhưng có lẽ có thể được điều chỉnh khác nhau tùy thuộc vào môi trường thời gian chạy.


khẳng định không có mặt trong bản phát hành là một tiêu cực lớn đối với tôi, bản phát hành là thời gian chính xác mà bạn muốn thông tin được thêm vào
jk.

1
Chà, cứ thoải mái sử dụng một hàm khẳng định ^ h ^ h ^ hcond điều kiện hoạt động trong quá trình phát hành nếu bạn cần điều đó. Tôi đã tự lăn khá thường xuyên.
Macke

7

Thất bại sớm, thất bại thường xuyên. nulllà một trong những giá trị bất ngờ tốt nhất bạn có thể nhận được, bởi vì bạn có thể thất bại nhanh chóng ngay khi bạn cố gắng sử dụng nó. Các giá trị bất ngờ khác không quá dễ dàng để phát hiện. Vì nulltự động thất bại cho bạn bất cứ khi nào bạn cố gắng sử dụng nó, tôi sẽ nói rằng nó không yêu cầu kiểm tra rõ ràng.


28
Tôi hy vọng bạn có nghĩa là: Thất bại sớm, thất bại nhanh chóng, không thường xuyên ...
empi

12
Vấn đề là nếu không có kiểm tra rõ ràng, đôi khi một giá trị null có thể lan truyền khá xa trước khi nó gây ra một ngoại lệ, và sau đó có thể rất khó để tìm ra nơi nó bắt nguồn.
Michael Borgwardt

@MichaelBorgwardt Không phải đó là dấu vết ngăn xếp để làm gì?
R0MANARMY

6
@ R0MANARMY: dấu vết ngăn xếp không giúp ích khi giá trị null được lưu vào một số trường và NullPulumException sẽ bị ném nhiều sau đó.
Michael Borgwardt

@ R0MANARMY ngay cả khi bạn có thể tin tưởng số dòng trong theo dõi ngăn xếp (trong nhiều trường hợp bạn không thể) một số dòng chứa nhiều biến có thể là null.
Ohad Schneider

6
  • Kiểm tra null nên là bản chất thứ hai của bạn

  • API của bạn sẽ được người khác sử dụng và kỳ vọng của họ có thể khác với API của bạn

  • Các bài kiểm tra đơn vị kỹ lưỡng thường làm nổi bật nhu cầu kiểm tra tham chiếu null

  • Kiểm tra null không ngụ ý các điều kiện có điều kiện. Nếu bạn lo lắng rằng kiểm tra null làm cho mã của bạn ít đọc hơn, thì bạn có thể muốn xem xét các hợp đồng mã .NET.

  • Xem xét việc cài đặt ReSharper (hoặc tương tự) để tìm kiếm mã của bạn để kiểm tra tham chiếu null bị thiếu


Sử dụng hợp đồng mã cho các điều kiện tiên quyết chỉ đơn giản là tuyên bố điều kiện được tôn vinh.
một CVn

Vâng, tôi đồng ý, nhưng tôi cũng nghĩ rằng mã trông sạch hơn khi sử dụng hợp đồng mã, tức là khả năng đọc của nó được cải thiện.
CodeART

4

Tôi sẽ xem xét câu hỏi theo quan điểm ngữ nghĩa, tức là tự hỏi mình con trỏ NULL hoặc đối số tham chiếu null thể hiện điều gì.

Nếu một hàm hoặc phương thức foo () có đối số x loại T, x có thể là bắt buộc hoặc tùy chọn .

Trong C ++, bạn có thể truyền một đối số bắt buộc

  • Một giá trị, ví dụ void foo (T x)
  • Một tài liệu tham khảo, ví dụ void foo (T & x).

Trong cả hai trường hợp, bạn không gặp vấn đề khi kiểm tra con trỏ NULL. Vì vậy, trong nhiều trường hợp, bạn không cần một tấm séc con trỏ null ở tất cả cho các đối số bắt buộc.

Nếu bạn đang truyền một đối số tùy chọn , bạn có thể sử dụng một con trỏ và sử dụng giá trị NULL để chỉ ra rằng không có giá trị nào được đưa ra:

void foo(T* x)

Một cách khác là sử dụng một con trỏ thông minh như

void foo(shared_ptr<T> x)

Trong trường hợp này, bạn có thể muốn kiểm tra con trỏ trong mã, bởi vì nó tạo ra sự khác biệt cho phương thức foo () cho dù x có chứa một giá trị hay không có giá trị nào.

Trường hợp thứ ba và cuối cùng là bạn sử dụng một con trỏ cho một đối số bắt buộc . Trong trường hợp này, gọi foo (x) bằng x == NULL là một lỗi và bạn phải quyết định cách xử lý việc này (giống như với chỉ số ngoài giới hạn hoặc bất kỳ đầu vào không hợp lệ nào):

  1. Không kiểm tra: nếu có lỗi, hãy để nó gặp sự cố và hy vọng lỗi này xuất hiện sớm (trong quá trình thử nghiệm). Nếu nó không (nếu bạn không thất bại đủ sớm), hy vọng rằng nó sẽ không xuất hiện trong một hệ thống sản xuất vì trải nghiệm người dùng sẽ là họ thấy sự cố ứng dụng.
  2. Kiểm tra tất cả các đối số ở đầu phương thức và báo cáo các đối số NULL không hợp lệ, ví dụ: ném ngoại lệ từ foo () và để một số phương thức khác xử lý nó. Có lẽ điều tốt nhất để làm là hiển thị cho người dùng một cửa sổ hộp thoại nói rằng ứng dụng đã gặp lỗi nội bộ và sẽ bị đóng.

Ngoài cách thất bại đẹp hơn (cung cấp cho người dùng nhiều thông tin hơn), một ưu điểm của cách tiếp cận thứ hai là nhiều khả năng sẽ tìm thấy lỗi: chương trình sẽ thất bại mỗi khi phương thức được gọi với các đối số sai trong khi với Cách tiếp cận 1, lỗi sẽ chỉ xuất hiện nếu một câu lệnh hủy bỏ con trỏ được thực thi (ví dụ: nếu nhánh bên phải của câu lệnh if được nhập). Vì vậy, cách tiếp cận 2 cung cấp một hình thức "thất bại sớm" mạnh mẽ hơn.

Trong Java, bạn có ít lựa chọn hơn vì tất cả các đối tượng được truyền bằng các tham chiếu luôn có thể là null. Một lần nữa, nếu null đại diện cho giá trị KHÔNG của một đối số tùy chọn, thì việc kiểm tra null sẽ (có thể) là một phần của logic thực hiện.

Nếu null là một đầu vào không hợp lệ, thì bạn có lỗi và tôi sẽ áp dụng các cân nhắc tương tự như đối với trường hợp con trỏ C ++. Vì trong Java bạn có thể bắt ngoại lệ con trỏ null, cách tiếp cận 1 ở trên tương đương với cách tiếp cận 2 nếu phương thức foo () hủy bỏ tất cả các đối số đầu vào trong tất cả các đường dẫn thực thi (có thể xảy ra rất thường xuyên đối với các phương thức nhỏ).

Tóm tắt

  • Cả trong C ++ và Java, kiểm tra xem các đối số tùy chọn có phải là null hay không là một phần của logic chương trình.
  • Trong C ++, tôi sẽ luôn kiểm tra các đối số bắt buộc để đảm bảo rằng một ngoại lệ phù hợp được đưa ra để chương trình có thể xử lý nó một cách mạnh mẽ.
  • Trong Java (hoặc C #), tôi sẽ kiểm tra các đối số bắt buộc nếu có các đường dẫn thực thi sẽ không ném để có dạng "thất bại sớm" mạnh hơn.

BIÊN TẬP

Cảm ơn Stilgar để biết thêm chi tiết.

Trong trường hợp của bạn, có vẻ như bạn có null là kết quả của một phương thức. Một lần nữa, tôi nghĩ trước tiên bạn nên làm rõ (và sửa chữa) ngữ nghĩa của các phương pháp của mình trước khi bạn có thể đưa ra quyết định.

Vì vậy, bạn có phương thức gọi phương thức m1 () m2 () và m2 () trả về một tham chiếu (trong Java) hoặc một con trỏ (trong C ++) cho một đối tượng nào đó.

Ngữ nghĩa của m2 () là gì? Nên m2 () luôn trả về kết quả không null? Nếu đây là trường hợp, thì kết quả null là lỗi nội bộ (tính bằng m2 ()). Nếu bạn kiểm tra giá trị trả về trong m1 (), bạn có thể xử lý lỗi mạnh hơn. Nếu bạn không, có lẽ bạn sẽ có một ngoại lệ, sớm hay muộn. Có thể không. Hy vọng rằng ngoại lệ được ném trong quá trình thử nghiệm chứ không phải sau khi triển khai. Câu hỏi : nếu m2 () không bao giờ nên trả về null, tại sao nó lại trả về null? Có lẽ nó nên ném một ngoại lệ thay thế? m2 () có lẽ là lỗi.

Cách khác là trả về null là một phần của ngữ nghĩa của phương thức m2 (), tức là nó có kết quả tùy chọn, cần được ghi lại trong tài liệu Javadoc của phương thức. Trong trường hợp này, nó có giá trị null. Phương thức m1 () nên kiểm tra nó như bất kỳ giá trị nào khác: không có lỗi ở đây nhưng có lẽ kiểm tra xem kết quả có phải là null hay không chỉ là một phần của logic chương trình.


Trong trường hợp đó không phải là đối số là null. Chúng tôi đã thực hiện một cuộc gọi cơ sở dữ liệu cho một cái gì đó dự kiến ​​sẽ có mặt nhưng trong thực tế nó không có mặt. Câu hỏi là nếu chúng ta nên kiểm tra xem mọi đối tượng có tồn tại hay chúng ta chỉ nên kiểm tra nếu chúng ta hy vọng nó bị thiếu.
Stilgar

Vì vậy, kết quả được trả về bởi một phương thức là một tham chiếu đến một đối tượng và null là giá trị được sử dụng để biểu diễn kết quả: KHÔNG TÌM KIẾM. Tôi có hiểu đúng?
Giorgio

Tôi đã cập nhật câu hỏi với các chi tiết bổ sung.
Stilgar

3

Cách tiếp cận chung của tôi là không bao giờ kiểm tra một điều kiện lỗi mà bạn không biết làm thế nào để xử lý . Sau đó, câu hỏi cần được trả lời trong từng trường hợp cụ thể trở thành: bạn có thể làm điều gì đó hợp lý khi có nullgiá trị bất ngờ không ?

Nếu bạn có thể tiếp tục một cách an toàn bằng cách thay thế một giá trị khác (một bộ sưu tập trống, giả sử, hoặc một giá trị hoặc ví dụ mặc định), thì bằng mọi cách hãy làm như vậy. Ví dụ của @ Shahbaz từ World of Warcraft thay thế một hình ảnh cho một hình ảnh khác rơi vào thể loại đó; hình ảnh có thể chỉ là trang trí, và không có tác động chức năng . (Trong bản dựng gỡ lỗi, tôi sẽ sử dụng màu la hét để thu hút sự chú ý đến thực tế là sự thay thế đã xảy ra, nhưng điều đó phụ thuộc rất lớn vào bản chất của ứng dụng.) nếu không, bạn sẽ che giấu một lỗi mà bạn có thể muốn biết. Ẩn nó khỏi người dùng là một điều (một lần nữa, giả sử việc xử lý có thể tiếp tục an toàn); ẩn nó từ nhà phát triển là khá khác nhau.

Nếu bạn không thể tiếp tục an toàn khi có giá trị null, thì thất bại với lỗi hợp lý; nói chung, tôi thích thất bại càng sớm càng tốt khi rõ ràng là hoạt động không thể thành công, điều này ngụ ý kiểm tra các điều kiện tiên quyết. Có những lúc bạn không muốn thất bại ngay lập tức, nhưng trì hoãn thất bại càng lâu càng tốt; việc xem xét này được đưa ra ví dụ trong các ứng dụng liên quan đến mật mã, trong đó các cuộc tấn công kênh bên có thể tiết lộ thông tin mà bạn không muốn biết. Cuối cùng, hãy để bong bóng lỗi đến một số trình xử lý lỗi chung, từ đó ghi nhật ký các chi tiết có liên quan và hiển thị một thông báo lỗi đẹp (tuy nhiên chúng có thể tốt) cho người dùng.

Cũng đáng lưu ý rằng phản hồi thích hợp có thể rất khác nhau giữa các bản dựng thử nghiệm / QA / gỡ lỗi và bản dựng sản xuất / phát hành. Trong các bản dựng gỡ lỗi, tôi sẽ thất bại sớm và khó với các thông báo lỗi rất chi tiết, để làm cho nó hoàn toàn rõ ràng rằng có một vấn đề và cho phép một cái chết để xác định những gì cần phải làm để sửa nó. Các bản dựng sản xuất, khi gặp phải các lỗi có thể phục hồi an toàn , có lẽ nên ưu tiên phục hồi.


Tất nhiên là có lỗi chung xử lý chuỗi. Vấn đề với nhật ký là có thể không có ai kiểm tra chúng trong thời gian rất dài.
Stilgar

@Stilgar, nếu không có ai kiểm tra nhật ký, thì đó không phải là vấn đề với sự tồn tại hay vắng mặt của kiểm tra null.
một CVn

2
Có một vấn đề. Người dùng cuối có thể không nhận thấy rằng hệ thống không hoạt động chính xác trong một thời gian dài nếu có các kiểm tra null chỉ đơn giản là che giấu lỗi và ghi nó vào nhật ký.
Stilgar

@Stilgar, điều đó phụ thuộc hoàn toàn vào bản chất của lỗi. Như tôi đã nói trong bài đăng, chỉ khi bạn có thể tiếp tục an toàn thì null mới bị thay thế / bỏ qua. Ví dụ, sử dụng một hình ảnh thay vì hình ảnh khác trong bản dựng phát hành (trong bản dựng gỡ lỗi, thất bại sớm và khó nhất có thể để vấn đề trở nên rõ ràng) chẳng hạn như trả lại kết quả không hợp lệ cho một phép tính. (Đã chỉnh sửa câu trả lời để bao gồm điều đó.)
CVn

1
Tôi sẽ thất bại nhanh và sớm ngay cả trong sản xuất trừ khi bạn có thể bằng cách nào đó đăng nhập và báo cáo lỗi (mà không phụ thuộc quá nhiều vào người dùng thông thái). Ẩn các lỗi đã triển khai của bạn khỏi người dùng là một điều - ẩn chúng khỏi chính bạn là nguy hiểm. Ngoài ra, cho phép người dùng sống với các lỗi tinh vi có thể làm xói mòn sự tự tin của họ đối với ứng dụng của bạn. Đôi khi tốt hơn là cắn viên đạn và cho họ biết có lỗi bạn đã sửa nó nhanh chóng.
Merlyn Morgan-Graham

2

Chắc chắn NULLlà không mong đợi , nhưng luôn có lỗi!

Tôi tin rằng bạn nên kiểm tra NULLxem bạn có mong đợi hay không. Để tôi cho bạn ví dụ (mặc dù chúng không có trong Java).

  • Trong World of Warcraft, do một lỗi, hình ảnh của một trong những nhà phát triển đã xuất hiện trên một khối lập phương cho nhiều đối tượng. Hãy tưởng tượng nếu công cụ đồ họa không mong đợi NULL con trỏ, sẽ có một sự cố, trong khi nó đã trở thành một trải nghiệm không quá nguy hiểm (thậm chí buồn cười) cho người dùng. Ít nhất là, bạn sẽ không bất ngờ bị mất kết nối hoặc bất kỳ điểm lưu nào của bạn.

    Đây là một ví dụ trong đó việc kiểm tra NULL đã ngăn chặn sự cố và vụ việc được xử lý âm thầm.

  • Hãy tưởng tượng một chương trình giao dịch viên ngân hàng. Giao diện thực hiện một số kiểm tra và gửi dữ liệu đầu vào đến một phần cơ bản để thực hiện chuyển tiền. Nếu phần cốt lõi dự kiến không nhận được NULL, thì một lỗi trong giao diện có thể gây ra sự cố trong giao dịch, có thể một số tiền ở giữa bị mất (hoặc tệ hơn, bị trùng lặp).

    Theo "một vấn đề" Tôi có nghĩa là một sự cố (như trong sự cố sự cố (giả sử chương trình được viết bằng C ++), không phải là ngoại lệ con trỏ null). Trong trường hợp này, giao dịch cần quay trở lại nếu gặp phải NULL (điều này tất nhiên là không thể nếu bạn không kiểm tra các biến so với NULL!)

Tôi chắc rằng bạn có ý tưởng.

Kiểm tra tính hợp lệ của các đối số cho các hàm của bạn không làm lộn xộn mã của bạn, vì đó là một vài kiểm tra ở đầu chức năng của bạn (không lan truyền ở giữa) và làm tăng đáng kể độ mạnh mẽ.

Nếu bạn phải chọn giữa "chương trình cho người dùng biết rằng nó đã bị lỗi và duyên dáng tắt hoặc quay trở lại trạng thái nhất quán có thể tạo ra nhật ký lỗi" và "Vi phạm truy cập", bạn sẽ không chọn cái đầu tiên chứ? Điều đó có nghĩa là mọi chức năng nên được chuẩn bị để được gọi bằng mã lỗi thay vì mong đợi mọi thứ đều chính xác, đơn giản là vì lỗi luôn tồn tại.


Ví dụ thứ hai của bạn bác bỏ quan điểm của bạn. Trong một giao dịch ngân hàng nếu có gì sai sót, giao dịch nên hủy bỏ. Đó là chính xác khi bạn bao gồm lỗi rằng tiền có thể bị mất hoặc nhân đôi. Kiểm tra null sẽ giống như "Khách hàng gửi tiền null ... tôi sẽ chỉ gửi $ 10 (tương đương với hình ảnh của nhà phát triển)"
Stilgar

@Stilgar, Ngược lại! Kiểm tra NULL sẽ được hủy bỏ với tin nhắn. Không kiểm tra NULL sẽ bị sập . Bây giờ sự cố khi bắt đầu giao dịch có thể không tệ, nhưng ở giữa có thể là thảm họa. (Tôi không nói rằng chuyển khoản ngân hàng sẽ xử lý lỗi như trò chơi! Chỉ là thực tế là nó sẽ xử lý nó)
Shahbaz

2
Toàn bộ điểm của một "giao dịch" là nó không thể hoàn thành một nửa :) Nếu có lỗi thì nó sẽ được khôi phục.
Stilgar

Đây là lời khuyên khủng khiếp cho bất cứ điều gì mà ngữ nghĩa giao dịch thực sự được yêu cầu. Bạn nên làm cho tất cả các giao dịch của mình là nguyên tử và tạm thời, nếu không, bất kỳ lỗi nào bạn có sẽ đảm bảo tài nguyên bị mất hoặc trùng lặp (tiền). Nếu bạn phải đối phó với các hoạt động phi nguyên tử hoặc không nguyên tắc, bạn nên bọc nó rất cẩn thận trong các lớp đảm bảo hành vi nguyên tử và bình thường (ngay cả khi bạn phải tự viết các lớp đó). Luôn luôn nghĩ: điều gì xảy ra nếu một thiên thạch rơi vào máy chủ web tại thời điểm giao dịch này đang diễn ra?
Merlyn Morgan-Graham

1
@ MerlynMorgan-Graham, tôi đã làm như vậy. Hy vọng rằng sẽ làm sáng tỏ sự nhầm lẫn.
Shahbaz

2

Như các câu trả lời khác đã chỉ ra Nếu bạn không mong đợi NULL, hãy làm rõ bằng cách ném ArgumentNullException. Theo quan điểm của tôi, khi bạn gỡ lỗi dự án, nó giúp bạn phát hiện ra các lỗi trong logic của chương trình của bạn sớm hơn.

Vì vậy, bạn sẽ phát hành phần mềm của mình, nếu bạn khôi phục các NullRefrenceskiểm tra đó, bạn sẽ không bỏ lỡ bất kỳ điều gì về logic của phần mềm, điều đó chỉ chắc chắn rằng một lỗi nghiêm trọng sẽ không xuất hiện.

Một điểm khác là nếu NULLkiểm tra là trên các tham số của hàm, thì bạn có thể quyết định xem hàm đó có phải là nơi phù hợp để thực hiện hay không; nếu không, hãy tìm tất cả các tham chiếu đến hàm và sau đó trước khi chuyển một tham số cho các kịch bản có thể xem xét chức năng có thể dẫn đến một tham chiếu null.


1

Mỗi nulltrong hệ thống của bạn là một quả bom hẹn giờ đang chờ được khám phá. Kiểm tra nulltại các ranh giới nơi mã của bạn tương tác với mã bạn không kiểm soát và cấm nulltrong mã của riêng bạn. Điều này giúp bạn giải quyết vấn đề mà không tin tưởng vào mã nước ngoài. Sử dụng ngoại lệ hoặc một số loại Maybe/ Nothingloại thay thế.


0

Mặc dù một ngoại lệ con trỏ null cuối cùng sẽ bị ném, nó thường sẽ bị ném xa khỏi điểm mà bạn có con trỏ null. Tự mình làm và rút ngắn thời gian sửa lỗi bằng cách ít nhất đăng nhập thực tế là bạn git một con trỏ null càng sớm càng tốt.

Ngay cả khi bạn không biết phải làm gì bây giờ, đăng nhập sự kiện sẽ giúp bạn tiết kiệm thời gian sau đó. Và có lẽ anh chàng nhận được vé sẽ có thể sử dụng thời gian tiết kiệm để tìm ra phản ứng thích hợp sau đó.


-1

Fail early, fail fastnhư đã nêu bởi @DeadMG (@empi) là không thể truy cập được vì ứng dụng web phải hoạt động tốt trong các lỗi, bạn nên thực thi quy tắc

không có ngoại lệ bắt mà không đăng nhập

vì vậy bạn là nhà phát triển nhận thức được các vấn đề tiềm ẩn bằng cách kiểm tra nhật ký.

nếu bạn đang sử dụng một khung ghi nhật ký như log4net hoặc log4j, bạn có thể thiết lập một đầu ra ghi nhật ký đặc biệt bổ sung (còn gọi là appender) để gửi cho bạn các lỗi và các lỗi nghiêm trọng. vì vậy bạn có được thông tin .

[cập nhật]

nếu người dùng cuối trang không nên biết về lỗi, bạn có thể thiết lập một trình nối thêm gửi cho bạn các lỗi và các lỗi nghiêm trọng mà bạn được thông báo.

nếu nó ổn thì bộ xử lý của trang sẽ nhìn thấy các lỗi sai về mật mã, bạn có thể thiết lập một trình nối thêm đặt lỗi cho quá trình xử lý trang ở cuối trang.

Cho dù bạn sử dụng cách ghi nhật ký như thế nào nếu bạn nuốt ngoại lệ, chương trình vẫn tiếp tục với dữ liệu có ý nghĩa và việc nuốt không còn im lặng nữa.


1
Câu hỏi là những gì xảy ra với trang?
Stilgar

Làm thế nào để chúng ta biết rằng dữ liệu có ý nghĩa và hệ thống ở trạng thái nhất quán. Sau tất cả các lỗi là không mong muốn, vì vậy chúng tôi không biết tác động là gì.
Stilgar

"Vì Thất bại sớm, thất bại nhanh như đã nêu bởi @DeadMG (@empi) là không thể thực hiện được vì ứng dụng web phải hoạt động tốt trong các lỗi mà bạn nên thực thi quy tắc": Người ta luôn có thể bắt được tất cả các ngoại lệ (bắt (Ném được t)) và chuyển hướng đến một số trang lỗi. Điều này có khả thi không?
Giorgio

-1

Đã có câu trả lời tốt cho câu hỏi này, có thể là một bổ sung cho chúng: Tôi thích các xác nhận trong mã của mình. Nếu mã của bạn chỉ có thể hoạt động với các đối tượng hợp lệ, nhưng nut với null, bạn nên xác nhận giá trị null.

Các xác nhận trong tình huống này sẽ cho phép bất kỳ bài kiểm tra đơn vị và / hoặc tích hợp nào thất bại với thông điệp chính xác, vì vậy bạn có thể khắc phục vấn đề khá nhanh trước khi sản xuất. Nếu một lỗi như vậy trượt qua các bài kiểm tra và đi vào sản xuất (trong đó các xác nhận sẽ bị hủy kích hoạt), bạn sẽ nhận được NpEx, và một lỗi nghiêm trọng, nhưng tốt hơn là một số lỗi nhỏ mờ hơn và xuất hiện ở một nơi khác trong mã, và sẽ tốn kém hơn để sửa chữa.

Hơn nữa nếu ai đó phải làm việc với các xác nhận mã của bạn cho anh ta biết về các giả định của bạn trong quá trình thiết kế / viết mã của bạn (trong trường hợp này: Này anh bạn, đối tượng này phải được trình bày!), Giúp duy trì dễ dàng hơn.


Bạn có thể vui lòng viết một cái gì đó để bỏ phiếu? Tôi sẽ đánh giá cao một cuộc thảo luận. Thanx
GeT

Tôi đã không bỏ phiếu và tôi đồng ý với ý kiến ​​chung của bạn. Ngôn ngữ cần một số công việc mặc dù. Các xác nhận của bạn xác định hợp đồng API của bạn và bạn thất bại sớm / thất bại nhanh nếu các hợp đồng đó bị vi phạm. Làm điều này tại giao diện ứng dụng của bạn sẽ cho phép người dùng mã của bạn biết họ đã làm gì đó sai trong mã của họ. Nếu họ thấy một ngăn xếp theo dõi trong phần lõm của mã của bạn, họ có thể sẽ nghĩ rằng mã của bạn có lỗi - và bạn cũng có thể nghĩ như vậy cho đến khi bạn nhận được một bản sửa lỗi và gỡ lỗi.
Merlyn Morgan-Graham

-1

Tôi thường kiểm tra null nhưng tôi "xử lý" null bất ngờ bằng cách ném một ngoại lệ cung cấp thêm một chút chi tiết về chính xác nơi null xảy ra.

Chỉnh sửa: Nếu bạn nhận được null khi bạn không mong đợi một điều gì đó không ổn - chương trình sẽ chết.


-1

Nó phụ thuộc vào việc thực hiện ngôn ngữ của các ngoại lệ. Các ngoại lệ cho phép bạn thực hiện một số hành động để ngăn chặn thiệt hại cho dữ liệu hoặc giải phóng sạch tài nguyên trong trường hợp hệ thống của bạn rơi vào trạng thái hoặc điều kiện không dự đoán được. Điều này khác với xử lý lỗi là điều kiện bạn có thể dự đoán. Triết lý của tôi là bạn nên có ít xử lý ngoại lệ nhất có thể. Đó là tiếng ồn. Phương pháp của bạn có thể làm bất cứ điều gì hợp lý bằng cách xử lý ngoại lệ? Nó có thể phục hồi? Nó có thể sửa chữa hoặc ngăn ngừa thiệt hại thêm? Nếu bạn chỉ bắt ngoại lệ và sau đó quay trở lại từ phương thức hoặc thất bại theo một cách vô hại khác thì thực sự không có lý do gì để bắt ngoại lệ. Bạn sẽ chỉ thêm tiếng ồn và độ phức tạp vào phương thức của mình và bạn có thể đang che giấu nguồn lỗi trong thiết kế của mình. Trong trường hợp này tôi sẽ để cho lỗi bong bóng nổi lên và bị bắt bởi một vòng lặp bên ngoài. Nếu bạn có thể làm một cái gì đó như sửa chữa một đối tượng hoặc đánh dấu nó là hỏng hoặc giải phóng một số tài nguyên quan trọng như tệp khóa hoặc bằng cách đóng sạch một ổ cắm thì đây là một cách sử dụng ngoại lệ tốt. Nếu bạn thực sự mong đợi NULL xuất hiện thường xuyên như một trường hợp cạnh hợp lệ thì bạn nên xử lý điều này trong luồng logic thông thường bằng các câu lệnh if-the-other hoặc switch-case hoặc bất cứ điều gì, không phải với xử lý ngoại lệ. Ví dụ: một trường bị vô hiệu hóa trong một biểu mẫu có thể được đặt thành NULL, khác với một chuỗi trống biểu thị một trường để trống. Đây là một lĩnh vực mà bạn phải sử dụng phán đoán tốt và ý thức chung. Tôi chưa bao giờ nghe nói về các quy tắc vững chắc về cách đối phó với vấn đề này trong mọi tình huống. Nếu bạn có thể làm một cái gì đó như sửa chữa một đối tượng hoặc đánh dấu nó là hỏng hoặc giải phóng một số tài nguyên quan trọng như tệp khóa hoặc bằng cách đóng sạch một ổ cắm thì đây là một cách sử dụng ngoại lệ tốt. Nếu bạn thực sự mong đợi NULL xuất hiện thường xuyên như một trường hợp cạnh hợp lệ thì bạn nên xử lý điều này trong luồng logic thông thường bằng các câu lệnh if-the-other hoặc switch-case hoặc bất cứ điều gì, không phải với xử lý ngoại lệ. Ví dụ: một trường bị vô hiệu hóa trong một biểu mẫu có thể được đặt thành NULL, khác với một chuỗi trống biểu thị một trường để trống. Đây là một lĩnh vực mà bạn phải sử dụng phán đoán tốt và ý thức chung. Tôi chưa bao giờ nghe nói về các quy tắc vững chắc về cách đối phó với vấn đề này trong mọi tình huống. Nếu bạn có thể làm một cái gì đó như sửa chữa một đối tượng hoặc đánh dấu nó là hỏng hoặc giải phóng một số tài nguyên quan trọng như tệp khóa hoặc bằng cách đóng sạch một ổ cắm thì đây là một cách sử dụng ngoại lệ tốt. Nếu bạn thực sự mong đợi NULL xuất hiện thường xuyên như một trường hợp cạnh hợp lệ thì bạn nên xử lý điều này trong luồng logic thông thường bằng các câu lệnh if-the-other hoặc switch-case hoặc bất cứ điều gì, không phải với xử lý ngoại lệ. Ví dụ: một trường bị vô hiệu hóa trong một biểu mẫu có thể được đặt thành NULL, khác với một chuỗi trống biểu thị một trường để trống. Đây là một lĩnh vực mà bạn phải sử dụng phán đoán tốt và ý thức chung. Tôi chưa bao giờ nghe nói về các quy tắc vững chắc về cách đối phó với vấn đề này trong mọi tình huống.

Java thất bại trong việc thực hiện xử lý ngoại lệ của nó với "các ngoại lệ được kiểm tra" trong đó mọi ngoại lệ có thể xảy ra bởi các đối tượng được sử dụng trong một phương thức phải được bắt hoặc khai báo trong mệnh đề "ném" của phương thức. Điều ngược lại với C ++ và Python nơi bạn có thể chọn xử lý các trường hợp ngoại lệ khi bạn thấy phù hợp. Trong Java nếu bạn sửa đổi phần thân của một phương thức để bao gồm một cuộc gọi có thể đưa ra một ngoại lệ mà bạn phải đối mặt với lựa chọn thêm xử lý rõ ràng cho một ngoại lệ mà bạn có thể không quan tâm do đó thêm tiếng ồn và độ phức tạp vào mã của bạn, hoặc bạn phải thêm ngoại lệ đó vào mệnh đề "ném" của phương thức bạn đang sửa đổi, nó không chỉ thêm nhiễu và lộn xộn mà còn thay đổi chữ ký của phương thức của bạn. Sau đó, bạn phải đi sửa đổi mã ở bất cứ nơi nào phương thức của bạn được sử dụng để xử lý ngoại lệ mới mà phương thức của bạn bây giờ có thể tăng hoặc thêm ngoại lệ vào mệnh đề "ném" của các phương thức đó để kích hoạt hiệu ứng domino của thay đổi mã. "Bắt hoặc chỉ định yêu cầu" phải là một trong những khía cạnh khó chịu nhất của Java. Ngoại lệ ngoại lệ cho RuntimeExceptions và hợp lý hóa Java chính thức cho lỗi thiết kế này là khập khiễng.

Đoạn cuối đó có rất ít liên quan đến việc trả lời câu hỏi của bạn. Tôi chỉ ghét Java.

- Nô-ê


bài này khá khó đọc (tường văn bản). Bạn có phiền chỉnh sửa ing nó thành một hình dạng tốt hơn?
gnat
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.