Là tài liệu tham khảo null thực sự là một điều xấu?


161

Tôi đã nghe nói rằng việc đưa các tài liệu tham khảo null vào các ngôn ngữ lập trình là "sai lầm tỷ đô". Nhưng tại sao? Chắc chắn, chúng có thể gây ra NullReferenceExceptions, nhưng vậy thì sao? Bất kỳ yếu tố nào của ngôn ngữ đều có thể là nguồn gây ra lỗi nếu sử dụng không đúng cách.

Và những gì thay thế? Tôi cho rằng thay vì nói điều này:

Customer c = Customer.GetByLastName("Goodman"); // returns null if not found
if (c != null)
{
    Console.WriteLine(c.FirstName + " " + c.LastName + " is awesome!");
}
else { Console.WriteLine("There was no customer named Goodman.  How lame!"); }

Bạn có thể nói điều này:

if (Customer.ExistsWithLastName("Goodman"))
{
    Customer c = Customer.GetByLastName("Goodman") // throws error if not found
    Console.WriteLine(c.FirstName + " " + c.LastName + " is awesome!"); 
}
else { Console.WriteLine("There was no customer named Goodman.  How lame!"); }

Nhưng làm thế nào là tốt hơn? Dù bằng cách nào, nếu bạn quên kiểm tra xem khách hàng có tồn tại hay không, bạn sẽ có một ngoại lệ.

Tôi cho rằng một CustomerNotFoundException dễ gỡ lỗi hơn một chút so với NullReferenceException nhờ tính mô tả nhiều hơn. Có phải đó là tất cả để có nó?


54
Tôi sẽ cảnh giác với cách tiếp cận "nếu Khách hàng tồn tại / sau đó có được Khách hàng", ngay khi bạn viết hai dòng mã như vậy, bạn sẽ mở cửa cho các điều kiện cuộc đua. Nhận, và sau đó kiểm tra null / bắt ngoại lệ là an toàn hơn.
Carson63000

26
Nếu chỉ chúng tôi có thể loại bỏ nguồn của tất cả các lỗi thời gian chạy, mã của chúng tôi sẽ được đảm bảo là hoàn hảo! Nếu tham chiếu NULL trong C # phá vỡ não của bạn, hãy thử không hợp lệ, nhưng không phải NULL, con trỏ trong C;)
LnxPrgr3

Mặc dù vậy, có các toán tử điều hướng an toàn kết hợp và an toàn trong một số ngôn ngữ
jk.

35
null per se không tệ. Một hệ thống loại không có sự khác biệt giữa loại T và loại T + null là xấu.
Ingo

27
Trở lại câu hỏi của riêng tôi một vài năm sau đó, giờ đây tôi hoàn toàn là một người chuyển đổi phương pháp Tùy chọn / Có thể.
Tim Goodman

Câu trả lời:


91

null là xấu xa

Có một bài thuyết trình trên InfoQ về chủ đề này: Tài liệu tham khảo Null: Sai lầm tỷ đô của Tony Hoare

Loại tùy chọn

Sự thay thế từ lập trình chức năng là sử dụng loại Tùy chọn , có thể chứa SOME valuehoặc NONE.

Một bài viết hay về Mô hình tùy chọn của nhóm thảo luận về kiểu Tùy chọn và cung cấp cách triển khai cho Java.

Tôi cũng đã tìm thấy một báo cáo lỗi cho Java về vấn đề này: Thêm các loại tùy chọn Nice vào Java để ngăn chặn NullPulumExceptions . Các tính năng được yêu cầu đã được giới thiệu trong Java 8.


5
Nullable <x> trong C # là một khái niệm tương tự nhưng không được triển khai theo mẫu tùy chọn. Lý do chính là trong .NET System.Nullable bị giới hạn ở các loại giá trị.
MattDavey

27
+1 Đối với loại Tùy chọn. Sau khi làm quen với Có lẽ của Haskell , nulls bắt đầu trông kỳ lạ ...
Andres F.

5
Nhà phát triển có thể mắc lỗi ở bất cứ đâu, vì vậy thay vì ngoại lệ con trỏ null, bạn sẽ nhận được ngoại lệ tùy chọn trống. Làm thế nào cái sau tốt hơn cái trước?
greenoldman

7
@greenoldman không có thứ gọi là "ngoại lệ tùy chọn trống". Hãy thử chèn các giá trị NULL vào các cột cơ sở dữ liệu được xác định là KHÔNG NULL.
Jonas

36
@greenoldman Vấn đề với null là mọi thứ đều có thể là null và là một nhà phát triển, bạn phải hết sức thận trọng. Một Stringloại không thực sự là một chuỗi. Đó là một String or Nullloại. Các ngôn ngữ tuân thủ đầy đủ ý tưởng về các loại tùy chọn không cho phép các giá trị null trong hệ thống loại của chúng, đảm bảo rằng nếu bạn xác định chữ ký loại yêu cầu String(hoặc bất cứ thứ gì khác), thì nó phải có giá trị. Các Optionloại là có để cung cấp cho một đại diện loại cấp của những điều mà có thể hoặc không có giá trị, vì vậy bạn biết nơi bạn phải xử lý một cách rõ ràng những tình huống này.
KChaloux

127

Vấn đề là bởi vì về lý thuyết, bất kỳ đối tượng nào cũng có thể là null và ném ngoại lệ khi bạn cố gắng sử dụng nó, mã hướng đối tượng của bạn về cơ bản là một tập hợp các quả bom chưa nổ.

Bạn đúng rằng việc xử lý lỗi duyên dáng có thể giống hệt về mặt chức năng với các ifcâu lệnh kiểm tra null . Nhưng điều gì xảy ra khi một thứ mà bạn tự thuyết phục bản thân không thể là một con số không, trên thực tế, là một con số không? Kerboom. Bất cứ điều gì xảy ra tiếp theo, tôi sẵn sàng đặt cược rằng 1) nó sẽ không duyên dáng và 2) bạn sẽ không thích nó.

Và đừng bỏ qua giá trị của "dễ gỡ lỗi." Mã sản xuất trưởng thành là một sinh vật điên cuồng, ngổn ngang; bất cứ điều gì cung cấp cho bạn cái nhìn sâu sắc hơn về những gì đã sai và nơi có thể giúp bạn tiết kiệm hàng giờ đào.


27
+1 Thông thường các lỗi trong mã sản xuất CẦN phải được xác định càng nhanh càng tốt để chúng có thể được sửa (thường là lỗi dữ liệu). Càng dễ gỡ lỗi thì càng tốt.

4
Câu trả lời này giống nhau nếu được áp dụng cho một trong các ví dụ của OP. Hoặc bạn kiểm tra các điều kiện lỗi hoặc bạn không, và bạn xử lý chúng một cách duyên dáng hoặc bạn không. Nói cách khác, loại bỏ các tham chiếu null khỏi ngôn ngữ sẽ không thay đổi các lớp lỗi mà bạn sẽ gặp phải, nó sẽ chỉ thay đổi một cách tinh tế biểu hiện của các lỗi đó.
dash-tom-bang

19
+1 cho bom chưa nổ. Với các tham chiếu null, nói public Foo doStuff()không có nghĩa là "làm công cụ và trả về kết quả Foo", nó có nghĩa là "làm công cụ và trả về kết quả Foo, HOẶC trả về null, nhưng bạn không biết điều đó có xảy ra hay không." Kết quả là bạn phải kiểm tra null MỌI NƠI để chắc chắn. Cũng có thể loại bỏ null và sử dụng một loại hoặc mẫu đặc biệt (như loại giá trị) để chỉ ra một giá trị vô nghĩa.
Matt Olenik

12
@greenoldman, ví dụ của bạn có hiệu quả gấp đôi để chứng minh quan điểm của chúng tôi ở chỗ việc sử dụng các kiểu nguyên thủy (dù là null hay số nguyên) làm mô hình ngữ nghĩa là thực tiễn kém. Nếu bạn có một loại mà số âm không phải là câu trả lời hợp lệ, bạn nên tạo một loại loại mới để triển khai mô hình ngữ nghĩa với ý nghĩa đó. Bây giờ tất cả các mã để xử lý mô hình của loại không âm đó được bản địa hóa cho lớp của nó, tách biệt với phần còn lại của hệ thống và mọi nỗ lực tạo giá trị âm cho nó có thể bị bắt ngay lập tức.
Huperniketes

9
@greenoldman, bạn tiếp tục chứng minh quan điểm rằng các kiểu nguyên thủy không đủ để tạo mô hình. Đầu tiên, nó là số âm. Bây giờ, nó vượt qua toán tử chia khi số 0 là ước số. Nếu bạn biết bạn không thể chia cho 0 thì bạn có thể dự đoán kết quả sẽ không đẹp và chịu trách nhiệm đảm bảo bạn kiểm tra và cung cấp giá trị "hợp lệ" cho trường hợp đó. Các nhà phát triển phần mềm có trách nhiệm biết các tham số trong đó mã của họ hoạt động và mã hóa phù hợp. Đó là những gì phân biệt kỹ thuật với mày mò.
Huperniketes

50

Có một số vấn đề với việc sử dụng tài liệu tham khảo null trong mã.

Đầu tiên, nó thường được sử dụng để chỉ một trạng thái đặc biệt . Thay vì xác định một lớp mới hoặc hằng số cho mỗi trạng thái như các chuyên môn thường được thực hiện, sử dụng tham chiếu null là sử dụng loại / giá trị tổng quát, mất mát .

Thứ hai, mã gỡ lỗi trở nên khó khăn hơn khi tham chiếu null xuất hiện và bạn cố gắng xác định cái gì đã tạo ra nó , trạng thái nào có hiệu lực và nguyên nhân của nó ngay cả khi bạn có thể theo dõi đường dẫn thực thi ngược dòng của nó.

Thứ ba, tham chiếu null giới thiệu các đường dẫn mã bổ sung để kiểm tra .

Thứ tư, một khi tham chiếu null được sử dụng làm trạng thái hợp lệ cho các tham số cũng như giá trị trả về, lập trình phòng thủ (đối với trạng thái do thiết kế) yêu cầu kiểm tra tham chiếu null nhiều hơn trong trường hợp khác nhau trong trường hợp.

Thứ năm, thời gian chạy của ngôn ngữ đã thực hiện kiểm tra kiểu khi nó thực hiện tra cứu bộ chọn trên bảng phương thức của đối tượng. Vì vậy, bạn đang nhân đôi nỗ lực bằng cách kiểm tra xem loại của đối tượng có hợp lệ / không hợp lệ hay không và sau đó kiểm tra thời gian chạy của loại đối tượng hợp lệ để gọi phương thức của nó.

Tại sao không sử dụng mẫu NullObject để tận dụng kiểm tra của thời gian chạy để nó gọi các phương thức NOP cụ thể cho trạng thái đó (tuân theo giao diện của trạng thái thông thường) trong khi cũng loại bỏ tất cả các kiểm tra bổ sung cho các tham chiếu null trong cơ sở mã của bạn?

liên quan đến nhiều công việc hơn bằng cách tạo một lớp NullObject cho mỗi giao diện mà bạn muốn đại diện cho một trạng thái đặc biệt. Nhưng ít nhất là chuyên môn hóa được phân lập cho từng trạng thái đặc biệt, thay vì mã trong đó trạng thái có thể có mặt. IOW, số lượng thử nghiệm được giảm vì bạn có ít đường dẫn thực hiện thay thế trong các phương thức của mình.


7
... bởi vì việc tìm ra ai đã tạo ra (hay đúng hơn là không bận tâm thay đổi hoặc khởi tạo) dữ liệu rác đang lấp đầy đối tượng được cho là hữu ích của bạn dễ dàng hơn nhiều so với theo dõi tham chiếu NULL? Tôi không mua nó, mặc dù tôi hầu hết bị mắc kẹt trong vùng đất được đánh máy tĩnh.
dash-tom-bang

Dữ liệu rác rất hiếm so với tài liệu tham khảo null. Đặc biệt trong các ngôn ngữ được quản lý.
Dean Harding

5
-1 cho đối tượng Null.
DeadMG

4
Điểm (4) và (5) là rắn, nhưng NullObject không phải là phương thuốc. Bạn sẽ rơi vào bẫy thậm chí còn tệ hơn - lỗi logic (quy trình làm việc). Khi bạn biết CHÍNH XÁC tình huống hiện tại, chắc chắn, nó có thể giúp ích, nhưng sử dụng nó một cách mù quáng (thay vì null) sẽ khiến bạn tốn nhiều tiền hơn.
greenoldman

1
@ gnasher729, trong khi thời gian chạy của Objective-C sẽ không đưa ra một ngoại lệ con trỏ null như Java và các hệ thống khác, nhưng nó không làm cho việc sử dụng các tham chiếu null trở nên tốt hơn vì những lý do tôi nêu ra trong câu trả lời của mình. Ví dụ: nếu không có navigationContoder trong [self.navigationController.presentingViewController dismissViewControllerAnimated: YES completion: nil];thời gian chạy xử lý nó như một chuỗi no-op, thì chế độ xem không bị xóa khỏi màn hình và bạn có thể ngồi trước Xcode gãi đầu hàng giờ để tìm hiểu tại sao.
Huperniketes

48

Nulls không quá tệ, trừ khi bạn không mong đợi chúng. Bạn nên cần phải xác định một cách rõ ràng trong mã mà bạn đang mong null, mà là một vấn đề ngôn ngữ thiết kế. Xem xét điều này:

Customer? c = Customer.GetByLastName("Goodman");
// note the question mark -- 'c' is either a Customer or null
if (c != null)
{
    // c is not null, therefore its type in this block is
    // now 'Customer' instead of 'Customer?'
    Console.WriteLine(c.FirstName + " " + c.LastName + " is awesome!");
}
else { Console.WriteLine("There was no customer named Goodman.  How lame!"); }

Nếu bạn cố gắng gọi các phương thức trên a Customer?, bạn sẽ gặp lỗi thời gian biên dịch. Lý do nhiều ngôn ngữ không làm điều này (IMO) là vì chúng không cung cấp loại biến để thay đổi tùy thuộc vào phạm vi của nó. Nếu ngôn ngữ có thể xử lý điều đó, thì vấn đề có thể được giải quyết hoàn toàn trong loại hệ thống.

Cũng có cách chức năng để xử lý vấn đề này, sử dụng các loại tùy chọn và Maybe, nhưng tôi không quen với nó. Tôi thích cách này vì mã chính xác về mặt lý thuyết chỉ cần thêm một ký tự để biên dịch chính xác.


11
Wow, thật tuyệt. Ngoài việc bắt được nhiều lỗi hơn trong thời gian biên dịch, nó không cho tôi phải kiểm tra tài liệu để tìm hiểu xem có GetByLastNametrả về null nếu không tìm thấy (trái ngược với việc ném ngoại lệ) - Tôi chỉ có thể xem liệu nó có trả về Customer?hay không Customer.
Tim Goodman

14
@JBRWilkinson: Đây chỉ là một ví dụ được nấu chín để thể hiện ý tưởng, không phải là một ví dụ về việc thực hiện thực tế. Tôi thực sự nghĩ rằng đó là một ý tưởng khá gọn gàng.
Dean Harding

1
Mặc dù vậy, bạn đúng rằng đó không phải là mẫu đối tượng null.
Dean Harding

13
Điều này trông giống như cái mà Haskell gọi là Maybe- A Maybe CustomerJust c(trong đó clà a Customer) hoặc Nothing. Trong các ngôn ngữ khác, nó được gọi là Option- xem một số câu trả lời khác cho câu hỏi này.
MatrixFrog

2
@SimonBarker: bạn thể chắc chắn nếu Customer.FirstNamethuộc loại String(trái ngược với String?). Đó là lợi ích thực sự - chỉ các biến / thuộc tính có giá trị không có ý nghĩa mới được khai báo là nullable.
Yogu

20

Có rất nhiều câu trả lời xuất sắc bao gồm các triệu chứng đáng tiếc null, vì vậy tôi muốn trình bày một lập luận khác: Null là một lỗ hổng trong hệ thống loại.

Mục đích của một hệ thống loại là đảm bảo rằng các thành phần khác nhau của chương trình "khớp với nhau" đúng cách; một chương trình được đánh máy tốt không thể "tắt đường ray" thành hành vi không xác định.

Hãy xem xét một phương ngữ giả định của Java hoặc bất kỳ ngôn ngữ gõ tĩnh ưa thích nào của bạn, nơi bạn có thể gán chuỗi "Hello, world!"cho bất kỳ biến nào thuộc bất kỳ loại nào:

Foo foo1 = new Foo();  // Legal
Foo foo2 = "Hello, world!"; // Also legal
Foo foo3 = "Bonjour!"; // Not legal - only "Hello, world!" is allowed

Và bạn có thể kiểm tra các biến như vậy:

if (foo1 != "Hello, world!") {
    bar(foo1);
} else {
    baz();
}

Không có gì là không thể về điều này - ai đó có thể thiết kế một ngôn ngữ như vậy nếu họ muốn. Giá trị đặc biệt không cần phải có "Hello, world!"- đó có thể là số 42, bộ dữ liệu (1, 4, 9), hoặc, nói , null. Nhưng tại sao bạn lại làm điều này? Một biến loại Foochỉ nên giữ Foos - đó là toàn bộ điểm của hệ thống loại! nullkhông phải là một Foobất kỳ hơn "Hello, world!"là. Tệ hơn, nullkhông phải là một giá trị của bất kỳ loại nào , và bạn không thể làm gì với nó!

Lập trình viên không bao giờ có thể chắc chắn rằng một biến thực sự giữ một Foo, và chương trình cũng không thể; để tránh hành vi không xác định, nó phải kiểm tra các biến "Hello, world!"trước khi sử dụng chúng dưới dạng Foos. Lưu ý rằng việc thực hiện kiểm tra chuỗi trong đoạn trích trước không truyền bá thực tế rằng foo1 thực sự là một Foo- barcó khả năng cũng sẽ có kiểm tra riêng, chỉ để đảm bảo an toàn.

So sánh điều đó với việc sử dụng Maybe/ Optionloại với khớp mẫu:

case maybeFoo of
 |  Just foo => bar(foo)
 |  Nothing => baz()

Trong Just foomệnh đề, cả bạn và chương trình đều biết chắc chắn rằng Maybe Foobiến của chúng tôi thực sự có chứa một Foogiá trị - thông tin đó được truyền xuống chuỗi cuộc gọi và barkhông cần thực hiện bất kỳ kiểm tra nào. Bởi vì Maybe Foolà một loại khác biệt Foo, bạn buộc phải xử lý khả năng nó có thể chứa Nothing, do đó bạn không bao giờ có thể bị che mắt bởi a NullPointerException. Bạn có thể suy luận về chương trình của mình dễ dàng hơn nhiều và trình biên dịch có thể bỏ qua các kiểm tra null khi biết rằng tất cả các biến kiểu Foothực sự có chứa Foos. Mọi người đều thắng.


Điều gì về các giá trị giống như null trong Perl 6? Chúng vẫn là null, ngoại trừ chúng luôn được gõ. Nó vẫn là một vấn đề bạn có thể viết my Int $variable = Int, Intgiá trị null của loại ở Intđâu?
Konrad Borowski

@xfix Tôi không quen thuộc với Perl, nhưng miễn là bạn không có cách thực thi this type only contains integerstrái ngược với this type contains integers and this other value, bạn sẽ gặp vấn đề mỗi khi bạn chỉ muốn số nguyên.
Doval

1
+1 cho khớp mẫu. Với số lượng câu trả lời ở trên có đề cập đến các loại Tùy chọn, bạn nghĩ rằng ai đó sẽ đề cập đến thực tế là một tính năng ngôn ngữ đơn giản như trình so khớp mẫu có thể khiến chúng hoạt động trực quan hơn nhiều so với null.
Jules

1
Tôi nghĩ ràng buộc đơn âm thậm chí còn hữu ích hơn so với khớp mẫu (mặc dù tất nhiên ràng buộc cho Có thể được thực hiện bằng cách sử dụng khớp mẫu). Tôi nghĩ thật thú vị khi thấy các ngôn ngữ như C # đặt cú pháp đặc biệt cho nhiều thứ ( ??, ?.v.v.) cho những thứ mà các ngôn ngữ như haskell thực hiện như các chức năng của thư viện. Tôi nghĩ rằng nó gọn gàng hơn. null/ Nothingtuyên truyền không phải là "đặc biệt" theo bất kỳ cách nào, vậy tại sao chúng ta phải học thêm cú pháp để có nó?
sara

14

(Ném mũ của tôi vào vòng cho một câu hỏi cũ;))

Vấn đề cụ thể với null là nó phá vỡ kiểu gõ tĩnh.

Nếu tôi có một Thing t, thì trình biên dịch có thể đảm bảo tôi có thể gọi t.doSomething(). Vâng, UNLESS tlà null trong thời gian chạy. Bây giờ tất cả các cược đã tắt. Trình biên dịch nói rằng nó ổn, nhưng tôi phát hiện ra nhiều điều sau đó tKHÔNG thực tế doSomething(). Vì vậy, thay vì có thể tin tưởng trình biên dịch để bắt lỗi loại, tôi phải đợi cho đến khi thời gian chạy để bắt chúng. Tôi cũng có thể sử dụng Python!

Vì vậy, theo một nghĩa nào đó, null giới thiệu cách gõ động vào một hệ thống gõ tĩnh, với kết quả mong đợi.

Sự khác biệt giữa việc chia cho 0 hoặc log âm, v.v. là khi i = 0, nó vẫn là một số nguyên. Trình biên dịch vẫn có thể đảm bảo loại của nó. Vấn đề là logic áp dụng sai giá trị đó theo cách không được logic cho phép ... nhưng nếu logic thực hiện điều đó, thì đó gần như là định nghĩa của một lỗi.

(Trình biên dịch có thể bắt được một số vấn đề đó, BTW. Giống như các biểu thức gắn cờ như i = 1 / 0tại thời gian biên dịch. Nhưng bạn thực sự không thể mong đợi trình biên dịch tuân theo một hàm và đảm bảo rằng các tham số đều phù hợp với logic của hàm)

Vấn đề thực tế là bạn làm rất nhiều việc, và thêm kiểm tra null để bảo vệ chính mình khi chạy, nhưng nếu bạn quên một thì sao? Trình biên dịch ngăn bạn gán:

Chuỗi s = số nguyên mới (1234);

Vậy tại sao nó phải cho phép gán cho một giá trị (null) sẽ phá vỡ các tham chiếu đến s?

Bằng cách trộn "không có giá trị" với các tham chiếu "đã nhập" trong mã của bạn, bạn sẽ tạo thêm gánh nặng cho các lập trình viên. Và khi NullPulumExceptions xảy ra, việc theo dõi chúng có thể còn tốn thời gian hơn nữa. Thay vì dựa vào việc gõ tĩnh để nói "đây một tham chiếu đến một cái gì đó được mong đợi", bạn đang để ngôn ngữ nói "đây có thể là một tham chiếu đến một cái gì đó được mong đợi."


3
Trong C, một biến loại *inthoặc xác định một inthoặc NULL. Tương tự như vậy trong C ++, một biến loại *Widgethoặc xác định a Widget, một vật có loại xuất phát từ Widget, hoặc NULL. Vấn đề với Java và các dẫn xuất như C # là họ sử dụng vị trí lưu trữ Widgetcó nghĩa *Widget. Giải pháp không phải là giả vờ rằng *Widgetkhông nên giữ NULL, mà là có một Widgetloại vị trí lưu trữ thích hợp .
supercat

14

Một nullcon trỏ là một công cụ

không phải là kẻ thù Bạn chỉ nên sử dụng chúng đúng.

Hãy suy nghĩ chỉ mất vài phút để tìm và sửa một lỗi con trỏ không hợp lệ điển hình , so với việc định vị và sửa lỗi con trỏ null. Thật dễ dàng để kiểm tra một con trỏ chống lại null. Đó là một PITA để xác minh xem con trỏ không null có trỏ đến dữ liệu hợp lệ hay không.

Nếu vẫn không bị thuyết phục, hãy thêm một liều lượng đa luồng tốt vào kịch bản của bạn, sau đó suy nghĩ lại.

Đạo đức của câu chuyện: Đừng ném những đứa trẻ với nước. Và đừng đổ lỗi cho các công cụ cho vụ tai nạn. Người đàn ông đã phát minh ra con số 0 từ lâu khá thông minh, vì ngày đó bạn có thể đặt tên là "không có gì". Con nulltrỏ không quá xa.


EDIT: Mặc dù NullObjectmô hình dường như là một giải pháp tốt hơn so với các tài liệu tham khảo có thể là null, nhưng nó tự đưa ra các vấn đề:

  • Một tham chiếu giữ một NullObjectnên (theo lý thuyết) không làm gì khi một phương thức được gọi. Do đó, các lỗi tinh vi có thể được đưa ra do các tham chiếu không được gán sai, hiện được đảm bảo là không null (yehaa!) Nhưng thực hiện một hành động không mong muốn: không có gì. Với một NPE, vấn đề nằm ở chỗ rõ ràng. Với một NullObjecthành vi nào đó (nhưng sai), chúng tôi đưa ra nguy cơ lỗi được phát hiện (quá) muộn. Đây không phải là ngẫu nhiên tương tự như một vấn đề con trỏ không hợp lệ và có hậu quả tương tự: Các tham chiếu trỏ đến một cái gì đó, trông giống như dữ liệu hợp lệ, nhưng không, giới thiệu lỗi dữ liệu và có thể khó theo dõi. Thành thật mà nói, trong những trường hợp này, tôi sẽ không chớp mắt thích một NPE thất bại ngay lập tức, ngay bây giờ,do lỗi logic / dữ liệu đột nhiên xuất hiện sau đó. Hãy nhớ nghiên cứu của IBM về chi phí lỗi là một chức năng của giai đoạn nào chúng được phát hiện?

  • Khái niệm không làm gì khi một phương thức được gọi trên NullObjectkhông giữ, khi một getter thuộc tính hoặc một hàm được gọi hoặc khi phương thức trả về các giá trị thông qua out. Giả sử, giá trị trả về là một intvà chúng tôi quyết định rằng "không làm gì" có nghĩa là "trả về 0". Nhưng làm thế nào chúng ta có thể chắc chắn, rằng 0 là "không có gì" được trả lại (không)? Rốt cuộc, NullObjectkhông nên làm gì cả, nhưng khi được hỏi về một giá trị, nó phải phản ứng bằng cách nào đó. Thất bại không phải là một lựa chọn: Chúng tôi sử dụng NullObjectđể ngăn chặn NPE và chúng tôi chắc chắn sẽ không đánh đổi nó với một ngoại lệ khác (không được thực hiện, chia cho số không, ...), phải không? Vậy làm thế nào để bạn trả lại chính xác không có gì khi bạn phải trả lại một cái gì đó?

Tôi không thể giúp, nhưng khi ai đó cố gắng áp dụng NullObjectmô hình cho từng vấn đề, có vẻ như cố gắng sửa chữa một lỗi bằng cách thực hiện một lỗi khác. Không nghi ngờ gì nữa, đây là một giải pháp tốt và hữu ích trong một số trường hợp, nhưng chắc chắn đó không phải là viên đạn ma thuật cho mọi trường hợp.


Bạn có thể trình bày một lập luận cho lý do tại sao nullnên tồn tại? Bạn có thể rửa tay về bất kỳ lỗi ngôn ngữ nào với "đó không phải là vấn đề, chỉ cần đừng phạm sai lầm".
Doval

Đến giá trị nào bạn sẽ đặt một con trỏ không hợp lệ?
JensG

Chà, bạn không đặt chúng thành bất kỳ giá trị nào, vì bạn hoàn toàn không cho phép chúng. Thay vào đó, bạn sử dụng một biến có kiểu khác, có thể được gọi là Maybe Tcó thể chứa một Nothinghoặc một Tgiá trị. Điều quan trọng ở đây là bạn buộc phải kiểm tra xem Maybethực sự có chứa Ttrước khi bạn được phép sử dụng hay không và cung cấp một quá trình hành động trong trường hợp không. Và bởi vì bạn không cho phép các con trỏ không hợp lệ, bây giờ bạn không bao giờ phải kiểm tra bất cứ thứ gì không phải là a Maybe.
Doval

1
@JensG trong ví dụ mới nhất của bạn, trình biên dịch có khiến bạn thực hiện kiểm tra null trước mỗi cuộc gọi tới t.Something()không? Hoặc có một số cách để biết bạn đã thực hiện kiểm tra và không bắt bạn làm lại? Điều gì nếu bạn đã kiểm tra trước khi bạn chuyển tsang phương pháp này? Với loại Tùy chọn, trình biên dịch sẽ biết bạn đã thực hiện kiểm tra hay chưa bằng cách xem loại t. Nếu đó là một Tùy chọn, bạn đã không kiểm tra nó, trong khi nếu đó là giá trị chưa được mở thì bạn có.
Tim Goodman

1
Những gì bạn null đối tượng trả lại khi được yêu cầu một giá trị?
JensG

8

Tham chiếu Null là một sai lầm vì chúng cho phép mã không nhạy cảm:

foo = null
foo.bar()

Có những lựa chọn thay thế, nếu bạn tận dụng hệ thống loại:

Maybe<Foo> foo = null
foo.bar() // error{Maybe<Foo> does not have any bar method}

Ý tưởng chung là đặt biến trong một hộp và điều duy nhất bạn có thể làm là bỏ hộp, tốt nhất là tranh thủ sự trợ giúp của trình biên dịch như đề xuất cho Eiffel.

Haskell có nó từ đầu ( Maybe), trong C ++, bạn có thể tận dụng boost::optional<T>nhưng bạn vẫn có thể có hành vi không xác định ...


6
-1 cho "đòn bẩy"!
Đánh dấu C

8
Nhưng chắc chắn rất nhiều cấu trúc lập trình phổ biến cho phép mã vô nghĩa, phải không? Chia cho số 0 là (có thể nói là vô nghĩa), nhưng chúng tôi vẫn cho phép phân chia và hy vọng lập trình viên nhớ kiểm tra xem số chia có khác không (hoặc xử lý ngoại lệ).
Tim Goodman

3
Tôi không chắc ý của bạn là "từ đầu", nhưng Maybekhông được tích hợp vào ngôn ngữ cốt lõi, định nghĩa của nó chỉ nằm trong khúc dạo đầu tiêu chuẩn : data Maybe t = Nothing | Just t.
dòng chảy

4
@FredOverflow: do phần mở đầu được tải theo mặc định, tôi sẽ coi nó là "từ đầu", rằng Haskell có một hệ thống loại đủ đáng kinh ngạc để cho phép các đặc tính này (và khác) được xác định theo quy tắc chung của ngôn ngữ chỉ là phần thưởng :)
Matthieu M.

2
@Timoodman Mặc dù phép chia cho số 0 có thể không phải là ví dụ tốt nhất cho tất cả các loại giá trị số (IEEE 754 xác định kết quả cho phép chia cho số 0), trong trường hợp nó sẽ đưa ra một ngoại lệ, thay vì có các giá trị loại Tchia cho các giá trị loại khác T, bạn sẽ giới hạn hoạt động ở các giá trị loại Tchia cho các giá trị của loại NonZero<T>.
kbolino

8

Và những gì thay thế?

Tùy chọn loại và mẫu phù hợp. Vì tôi không biết C #, đây là một đoạn mã bằng ngôn ngữ hư cấu có tên là Scala # :-)

Customer.GetByLastName("Goodman")    // returns Option[Customer]
match
{
    case Some(customer) =>
    Console.WriteLine(customer.FirstName + " " + customer.LastName + " is awesome!");

    case None =>
    Console.WriteLine("There was no customer named Goodman.  How lame!");
}

6

Vấn đề với null là các ngôn ngữ cho phép chúng buộc bạn phải lập trình phòng thủ chống lại nó. Phải mất rất nhiều nỗ lực (hơn nhiều so với cố gắng sử dụng các khối if phòng thủ) để đảm bảo rằng

  1. các đối tượng bạn mong đợi chúng không phải là null thực sự không bao giờ là null và
  2. rằng các cơ chế phòng thủ của bạn thực sự đối phó với tất cả các NPE tiềm năng một cách hiệu quả.

Vì vậy, thực sự, nulls cuối cùng là một thứ đắt tiền để có.


1
Nó có thể hữu ích nếu bạn bao gồm một ví dụ trong đó phải mất nhiều nỗ lực hơn để xử lý null. Trong ví dụ đơn giản hóa được thừa nhận của tôi, nỗ lực cũng giống như vậy. Tôi không đồng ý với bạn, tôi chỉ tìm các ví dụ mã cụ thể (giả) vẽ một bức tranh rõ ràng hơn các tuyên bố chung.
Tim Goodman

2
Ah, tôi đã không giải thích rõ ràng cho mình. Nó không phải là khó khăn hơn để đối phó với null. Điều cực kỳ khó khăn (nếu không nói là không thể) để đảm bảo rằng tất cả quyền truy cập vào các tài liệu tham khảo có khả năng null đã được bảo vệ an toàn. Đó là, trong một ngôn ngữ cho phép tham chiếu null, không thể đảm bảo rằng một đoạn mã có kích thước tùy ý không có ngoại lệ con trỏ null, không phải thông qua một số khó khăn và nỗ lực lớn. Đó là những gì làm cho null tham khảo một điều đắt tiền. Nó là cực kỳ tốn kém để đảm bảo hoàn toàn không có ngoại lệ con trỏ null.
luis.espinal

4

Vấn đề ở mức độ nào ngôn ngữ lập trình của bạn cố gắng chứng minh tính đúng đắn của chương trình trước khi nó chạy nó. Trong một ngôn ngữ gõ tĩnh, bạn chứng minh rằng bạn có các loại chính xác. Bằng cách chuyển sang mặc định các tham chiếu không nullable (với các tham chiếu nullable tùy chọn), bạn có thể loại bỏ nhiều trường hợp null được thông qua và không nên. Câu hỏi đặt ra là liệu nỗ lực bổ sung trong việc xử lý các tài liệu tham khảo không có giá trị có xứng đáng với lợi ích về tính chính xác của chương trình hay không.


4

Vấn đề không phải là quá nhiều, đó là bạn không thể chỉ định loại tham chiếu không null trong nhiều ngôn ngữ hiện đại.

Ví dụ, mã của bạn có thể trông giống như

public void MakeCake(Egg egg, Flour flour, Milk milk)
{
    if (egg == null) { throw ... }
    if (flour == null) { throw ... }
    if (milk == null) { throw ... }

    egg.Crack();
    MixingBowl.Mix(egg, flour, milk);
    // etc
}

// inside Mixing bowl class
public void Mix(Egg egg, Flour flour, Milk milk)
{
    if (egg == null) { throw ... }
    if (flour == null) { throw ... }
    if (milk == null) { throw ... }

    //... etc
}

Khi các tham chiếu lớp được truyền xung quanh, lập trình phòng thủ khuyến khích bạn kiểm tra lại tất cả các tham số cho null, ngay cả khi bạn chỉ kiểm tra chúng cho null ngay trước đó, đặc biệt là khi xây dựng các đơn vị có thể tái sử dụng đang thử nghiệm. Tham chiếu tương tự có thể dễ dàng được kiểm tra null 10 lần trên cơ sở mã!

Sẽ không tốt hơn nếu bạn có thể có một loại nullable bình thường khi bạn nhận được thứ đó, xử lý null và ở đó, sau đó chuyển nó thành một loại không null cho tất cả các hàm và lớp trợ giúp nhỏ của bạn mà không kiểm tra null thời gian?

Đó là điểm của giải pháp Tùy chọn - không cho phép bạn bật trạng thái lỗi, nhưng cho phép thiết kế các hàm mà hoàn toàn không thể chấp nhận đối số null. Việc triển khai các loại "mặc định" có thể là nullable hay không nullable hay không ít quan trọng hơn sự linh hoạt của việc có sẵn cả hai công cụ.

http://twistedoakstudios.com/blog/Post330_non-nullable-types-vs-c-fixing-the-billion-dollar-mistake

Đây cũng được liệt kê là tính năng được bình chọn nhiều thứ 2 cho C # -> https://visualstudio.uservoice.com/forums/121579-visual-studio/carget/30931-lacular-c


Tôi nghĩ rằng một điểm quan trọng khác về Tùy chọn <T> là nó là một đơn nguyên (và do đó cũng là một endofunctor), vì vậy bạn có thể tính toán các phép tính phức tạp trên các luồng giá trị có chứa các loại tùy chọn mà không cần kiểm tra rõ ràng cho Nonetừng bước.
sara

3

Null là ác. Tuy nhiên, việc thiếu một null có thể là một điều ác lớn hơn.

Vấn đề là trong thế giới thực, bạn thường gặp phải tình huống không có dữ liệu. Ví dụ về phiên bản không có null của bạn vẫn có thể nổ tung - hoặc bạn đã mắc lỗi logic và quên kiểm tra Goodman hoặc có lẽ Goodman đã kết hôn giữa khi bạn kiểm tra và khi bạn nhìn cô ấy. (Nó giúp đánh giá logic nếu bạn cho rằng Moriarty đang xem từng bit mã của bạn và cố gắng vượt qua bạn.)

Việc tra cứu Goodman sẽ làm gì khi cô ấy không ở đó? Nếu không có null, bạn phải trả lại một số loại khách hàng mặc định - và bây giờ bạn đang bán thứ đó cho mặc định đó.

Về cơ bản, điều quan trọng là liệu mã có hoạt động hay không bất kể nó hoạt động chính xác hay không. Nếu hành vi không phù hợp tốt hơn là không có hành vi thì bạn không muốn null. (Để biết ví dụ về cách đây có thể là lựa chọn đúng đắn, hãy xem xét lần phóng đầu tiên của Ariane V. Lỗi không rõ / 0 đã khiến tên lửa quay đầu khó và tự hủy khi nó bị tách ra do điều này. nó đã cố gắng tính toán thực sự không còn phục vụ bất kỳ mục đích nào ngay lập tức khi máy tăng áp được thắp sáng - nó sẽ tạo ra quỹ đạo mặc dù rác thải trở lại thường lệ.)

Ít nhất 99 lần trong số 100 tôi sẽ chọn sử dụng null. Mặc dù vậy, tôi sẽ nhảy lên vì vui mừng với phiên bản của chúng.


1
Đây là một câu trả lời tốt. Cấu trúc null trong lập trình không phải là vấn đề, đó là tất cả các trường hợp của giá trị null. Nếu bạn tạo một đối tượng mặc định, cuối cùng tất cả các null của bạn sẽ được thay thế bằng các giá trị mặc định đó và UI của bạn, DB và mọi nơi ở giữa sẽ được lấp đầy bằng những thứ đó, có lẽ không còn hữu ích nữa. Nhưng bạn sẽ ngủ vào ban đêm vì ít nhất chương trình của bạn không bị sập tôi đoán.
Chris McCall

2
Bạn cho rằng đó nulllà cách duy nhất (hoặc thậm chí là một cách thích hợp hơn) để mô hình hóa việc không có giá trị.
sara

1

Tối ưu hóa cho trường hợp phổ biến nhất.

Phải kiểm tra null mọi lúc là tẻ nhạt - bạn muốn có thể chỉ cần nắm giữ đối tượng Khách hàng và làm việc với nó.

Trong trường hợp bình thường, điều này sẽ hoạt động tốt - bạn thực hiện tra cứu, lấy đối tượng và sử dụng nó.

Trong trường hợp đặc biệt , khi bạn (ngẫu nhiên) tìm kiếm khách hàng theo tên, không biết liệu bản ghi / đối tượng đó có tồn tại hay không, bạn cần một số dấu hiệu cho thấy điều này thất bại. Trong tình huống này, câu trả lời là ném ngoại lệ RecordNotFound (hoặc để nhà cung cấp SQL bên dưới làm điều này cho bạn).

Nếu bạn đang ở trong một tình huống mà bạn không biết liệu bạn có thể tin tưởng vào dữ liệu đến không (tham số), có lẽ vì nó được nhập bởi người dùng, thì bạn cũng có thể cung cấp 'TryGetCustomer (tên, khách hàng đã ra)' mẫu. Tham chiếu chéo với int.Pude và int.TryPude.


Tôi sẽ khẳng định rằng bạn không bao giờ biết liệu bạn có thể tin tưởng vào dữ liệu đến hay không bởi vì nó đến với một chức năng và là một loại không thể. Mã có thể được tái cấu trúc để làm cho đầu vào hoàn toàn khác nhau. Vì vậy, nếu không có khả năng bạn luôn phải kiểm tra. Vì vậy, bạn kết thúc với một hệ thống trong đó mọi biến được kiểm tra null mỗi lần trước khi nó được truy cập. Các trường hợp không được kiểm tra trở thành tỷ lệ thất bại cho chương trình của bạn.
Chris McCall

0

Ngoại lệ không phải là eval!

Họ ở đó vì một lý do. Nếu mã của bạn đang chạy xấu, có một mẫu thiên tài, được gọi là ngoại lệ và nó cho bạn biết rằng có điều gì đó không ổn.

Bằng cách tránh sử dụng các đối tượng null, bạn đang che giấu một phần của các ngoại lệ đó. Tôi không nói về ví dụ OP khi anh ấy chuyển đổi ngoại lệ con trỏ null sang ngoại lệ được gõ tốt, đây thực sự có thể là điều tốt vì nó làm tăng khả năng đọc. Tuy nhiên, khi bạn sử dụng giải pháp loại Tùy chọn như @Jonas đã chỉ ra, bạn đang ẩn các ngoại lệ.

Hơn trong ứng dụng sản xuất của bạn, thay vì ném ngoại lệ khi bạn nhấp vào nút để chọn tùy chọn trống, không có gì chỉ xảy ra. Thay vì ngoại lệ con trỏ null sẽ bị ném và có lẽ chúng ta sẽ nhận được báo cáo về ngoại lệ đó (như trong nhiều ứng dụng sản xuất chúng ta có cơ chế như vậy), và hơn là chúng ta thực sự có thể sửa nó.

Làm cho mã của bạn chống đạn bằng cách tránh ngoại lệ là ý tưởng tồi, làm cho bạn mã chống đạn bằng cách sửa ngoại lệ, đây là con đường mà tôi sẽ chọn.


1
Bây giờ tôi biết khá nhiều về loại Tùy chọn so với khi tôi đăng câu hỏi vài năm trước, vì bây giờ tôi sử dụng chúng thường xuyên trong Scala. Điểm quan trọng của Tùy chọn là nó thực hiện việc gán Nonecho một tùy chọn không phải là Tùy chọn thành lỗi thời gian biên dịch và tương tự sử dụng Optionnhư thể nó có giá trị mà không kiểm tra trước đó là lỗi thời gian biên dịch. Lỗi biên dịch chắc chắn đẹp hơn ngoại lệ thời gian chạy.
Tim Goodman

Ví dụ: Bạn không thể nói s: String = None, bạn phải nói s: Option[String] = None. Và nếu slà Tùy chọn, bạn không thể nói s.length, tuy nhiên, bạn có thể kiểm tra xem nó có giá trị không và trích xuất giá trị cùng một lúc, với khớp mẫu:s match { case Some(str) => str.length; case None => throw RuntimeException("Where's my value?") }
Tim Goodman

Thật không may trong Scala bạn vẫn có thể nói s: String = null, nhưng đó là cái giá của khả năng tương tác với Java. Chúng tôi thường không cho phép điều đó trong mã Scala của chúng tôi.
Tim Goodman

2
Tuy nhiên, tôi đồng ý với nhận xét chung rằng các ngoại lệ (được sử dụng đúng cách) không phải là một điều xấu và "sửa chữa" một ngoại lệ bằng cách nuốt nó nói chung là một ý tưởng tồi tệ. Nhưng với loại Tùy chọn, mục tiêu là biến các ngoại lệ thành các lỗi thời gian biên dịch, đây là một điều tốt hơn.
Tim Goodman

@Timoodman là chiến thuật chỉ scala hoặc nó có thể được sử dụng trong các ngôn ngữ khác như Java và ActionScript 3 không? Ngoài ra, sẽ rất tuyệt nếu bạn có thể chỉnh sửa câu trả lời của mình với ví dụ đầy đủ.
Ilya Gazman

0

Nullscó vấn đề vì chúng phải được kiểm tra rõ ràng, tuy nhiên trình biên dịch không thể cảnh báo bạn rằng bạn đã quên kiểm tra chúng. Chỉ phân tích tĩnh tốn thời gian mới có thể cho bạn biết điều đó. May mắn thay, có một số lựa chọn thay thế tốt.

Lấy biến ra khỏi phạm vi. Cách quá thường xuyên, nullđược sử dụng như một trình giữ chỗ khi lập trình viên khai báo một biến quá sớm hoặc giữ nó quá lâu. Cách tiếp cận tốt nhất chỉ đơn giản là thoát khỏi biến. Đừng khai báo cho đến khi bạn có một giá trị hợp lệ để đặt vào đó. Đây không phải là một hạn chế khó khăn như bạn nghĩ, và nó làm cho mã của bạn sạch hơn rất nhiều.

Sử dụng mẫu đối tượng null. Đôi khi, một giá trị thiếu là trạng thái hợp lệ cho hệ thống của bạn. Mẫu đối tượng null là một giải pháp tốt ở đây. Nó tránh sự cần thiết phải kiểm tra rõ ràng liên tục, nhưng vẫn cho phép bạn đại diện cho trạng thái null. Nó không phải là "vượt qua một điều kiện lỗi", như một số người tuyên bố, bởi vì trong trường hợp này, trạng thái null là trạng thái ngữ nghĩa hợp lệ. Điều đó đang được nói, bạn không nên sử dụng mẫu này khi trạng thái null không phải là trạng thái ngữ nghĩa hợp lệ. Bạn chỉ nên đưa biến của bạn ra khỏi phạm vi.

Sử dụng Có thể / Tùy chọn. Trước hết, điều này cho phép trình biên dịch cảnh báo bạn rằng bạn cần kiểm tra một giá trị còn thiếu, nhưng nó không chỉ thay thế một loại kiểm tra rõ ràng bằng một loại kiểm tra rõ ràng khác. Sử dụng Options, bạn có thể xâu chuỗi mã của mình và tiếp tục như thể giá trị của bạn tồn tại, không cần thực sự kiểm tra cho đến phút cuối cùng tuyệt đối. Trong Scala, mã ví dụ của bạn sẽ trông giống như:

val customer = customerDb getByLastName "Goodman"
val awesomeMessage =
  customer map (c => s"${c.firstName} ${c.lastName} is awesome!")
val notFoundMessage = "There was no customer named Goodman.  How lame!"
println(awesomeMessage getOrElse notFoundMessage)

Trên dòng thứ hai, nơi chúng tôi đang xây dựng thông điệp tuyệt vời, chúng tôi không kiểm tra rõ ràng nếu khách hàng được tìm thấy và vẻ đẹp là chúng tôi không cần . Mãi cho đến dòng cuối cùng, có thể là nhiều dòng sau, hoặc thậm chí trong một mô-đun khác, nơi chúng tôi rõ ràng quan tâm đến chính mình những gì xảy ra nếu OptionNone. Không chỉ vậy, nếu chúng ta quên làm điều đó, nó sẽ không được kiểm tra. Ngay cả sau đó, nó được thực hiện một cách rất tự nhiên, không có iftuyên bố rõ ràng .

Trái ngược với a null, trong đó loại kiểm tra tốt, nhưng bạn phải kiểm tra rõ ràng trên từng bước hoặc toàn bộ ứng dụng của bạn sẽ nổ tung trong thời gian chạy, nếu bạn may mắn trong trường hợp sử dụng bài kiểm tra đơn vị của bạn. Nó chỉ không đáng để phiền phức.


0

Vấn đề trung tâm của NULL là nó làm cho hệ thống không đáng tin cậy. Năm 1980 Tony Hoare trong bài viết dành cho Giải thưởng Turing của mình đã viết:

Và vì vậy, lời khuyên tốt nhất của tôi cho các nhà sáng lập và thiết kế của ADA đã bị bỏ qua. Sầu. Không cho phép ngôn ngữ này ở trạng thái hiện tại được sử dụng trong các ứng dụng mà độ tin cậy là rất quan trọng, tức là các nhà máy điện hạt nhân, tên lửa hành trình, hệ thống cảnh báo sớm, hệ thống phòng thủ tên lửa chống đối kháng. Tên lửa tiếp theo đi lạc hướng do lỗi ngôn ngữ lập trình có thể không phải là tên lửa không gian thăm dò trong chuyến đi vô hại tới Sao Kim: Nó có thể là một đầu đạn hạt nhân phát nổ trên một trong những thành phố của chúng ta. Một ngôn ngữ lập trình không đáng tin cậy tạo ra các chương trình không đáng tin cậy tạo ra rủi ro lớn hơn nhiều cho môi trường và xã hội của chúng ta so với ô tô không an toàn, thuốc trừ sâu độc hại hoặc tai nạn tại các nhà máy điện hạt nhân. Hãy thận trọng để giảm thiểu rủi ro, không làm tăng nó.

Ngôn ngữ ADA đã thay đổi rất nhiều kể từ đó, tuy nhiên những vấn đề như vậy vẫn tồn tại trong Java, C # và nhiều ngôn ngữ phổ biến khác.

Nhiệm vụ của nhà phát triển là tạo hợp đồng giữa khách hàng và nhà cung cấp. Ví dụ: trong C #, như trong Java, bạn có thể sử dụng Genericsđể giảm thiểu tác động của Nulltham chiếu bằng cách tạo chỉ đọc NullableClass<T>(hai Tùy chọn):

class NullableClass<T>
{
     public HasValue {get;}
     public T Value {get;}
}

và sau đó sử dụng nó như là

NullableClass<Customer> customer = dbRepository.GetCustomer('Mr. Smith');
if(customer.HasValue){
  // one logic with customer.Value
}else{
  // another logic
} 

hoặc sử dụng hai kiểu tùy chọn với các phương thức mở rộng C #:

customer.Do(
      // code with normal behaviour
      ,
      // what to do in case of null
) 

Sự khác biệt là đáng kể. Là một khách hàng của một phương pháp, bạn biết những gì mong đợi. Một nhóm có thể có quy tắc:

Nếu một lớp không có kiểu NullableClass thì thể hiện của nó không phải là null .

Nhóm có thể củng cố ý tưởng này bằng cách sử dụng Thiết kế theo Hợp đồng và kiểm tra tĩnh tại thời điểm biên dịch, ví dụ: với điều kiện tiên quyết:

function SaveCustomer([NotNullAttribute]Customer customer){
     // there is no need to check whether customer is null 
     // it is a client problem, not this supplier
}

hoặc cho một chuỗi

function GetCustomer([NotNullAndNotEmptyAttribute]String customerName){
     // there is no need to check whether customerName is null or empty 
     // it is a client problem, not this supplier
}

Những cách tiếp cận này có thể làm tăng đáng kể độ tin cậy của ứng dụng và chất lượng phần mềm. Design by Contract là một trường hợp logic Hoare , được phổ biến bởi Bertrand Meyer trong cuốn sách Xây dựng phần mềm hướng đối tượng nổi tiếng và ngôn ngữ Eiffel vào năm 1988, nhưng nó không được sử dụng một cách không hợp lệ trong chế tạo phần mềm hiện đại.


0

Không.

Sự thất bại của ngôn ngữ đối với một giá trị đặc biệt như nullvấn đề của ngôn ngữ. Nếu bạn nhìn vào các ngôn ngữ như Kotlin hoặc Swift , điều đó buộc người viết phải xử lý rõ ràng khả năng có giá trị null trong mỗi lần gặp gỡ, thì không có gì nguy hiểm.

Trong các ngôn ngữ như một trong câu hỏi, ngôn ngữ cho phép nulltrở thành một giá trị của bất kỳ -ish loại, nhưng không cung cấp bất kỳ cấu trúc cho thừa nhận rằng sau đó, dẫn đến việc bị nullbất ngờ (đặc biệt là khi truyền cho bạn từ một nơi khác), và do đó bạn nhận được tai nạn. Đó là cái ác: cho phép bạn sử dụng nullmà không khiến bạn phải đối phó với nó.


-1

Về cơ bản, ý tưởng trung tâm là các vấn đề tham chiếu con trỏ null sẽ bị bắt tại thời gian biên dịch thay vì thời gian chạy. Vì vậy, nếu bạn đang viết một hàm lấy một số đối tượng và bạn không muốn ai gọi nó bằng tham chiếu null thì gõ hệ thống sẽ cho phép bạn chỉ định yêu cầu đó để trình biên dịch có thể sử dụng nó để đảm bảo rằng lệnh gọi đến hàm của bạn sẽ không bao giờ biên dịch nếu người gọi không đáp ứng yêu cầu của bạn. Điều này có thể được thực hiện bằng nhiều cách, ví dụ, trang trí các loại của bạn hoặc sử dụng các loại tùy chọn (còn được gọi là loại không thể sử dụng được trong một số ngôn ngữ) nhưng rõ ràng đó là công việc nhiều hơn cho các nhà thiết kế trình biên dịch.

Tôi nghĩ có thể hiểu được tại sao vào những năm 1960 và 70, nhà thiết kế trình biên dịch đã không tham gia để thực hiện ý tưởng này vì máy móc bị hạn chế về tài nguyên, trình biên dịch cần phải được giữ tương đối đơn giản và không ai thực sự biết điều này sẽ tệ đến mức nào năm xuống dòng.


-1

Tôi có một phản đối đơn giản chống lại null:

Nó phá vỡ hoàn toàn ngữ nghĩa của mã của bạn bằng cách đưa ra sự mơ hồ .

Thông thường, bạn mong đợi kết quả là một loại nhất định. Đây là âm thanh ngữ nghĩa. Giả sử, bạn đã hỏi cơ sở dữ liệu cho một người dùng nhất định id, bạn hy vọng resut sẽ thuộc một loại nhất định (= user). Nhưng, nếu không có người dùng id đó thì sao? Một giải pháp (xấu) là: thêm một nullgiá trị. Vì vậy, kết quả là mơ hồ : hoặc là một userhoặc nó là null. Nhưng nullkhông phải là loại dự kiến. Và đây là nơi mùi mã bắt đầu.

Để tránh null, bạn làm cho mã của bạn rõ ràng về mặt ngữ nghĩa.

Luôn có những cách xung quanh null: tái cấu trúc các bộ sưu tập Null-objects, optionalsv.v.


-2

Nếu có thể truy cập vào vị trí lưu trữ của loại tham chiếu hoặc loại con trỏ trước khi mã được chạy để tính giá trị cho nó, thì không có sự lựa chọn hoàn hảo nào về những gì sẽ xảy ra. Có các vị trí như vậy mặc định cho các tham chiếu con trỏ null có thể được đọc và sao chép tự do, nhưng sẽ bị lỗi nếu bị hủy đăng ký hoặc lập chỉ mục, là một lựa chọn có thể. Các lựa chọn khác bao gồm:

  1. Có các vị trí mặc định cho một con trỏ null, nhưng không cho phép mã nhìn vào chúng (hoặc thậm chí xác định rằng chúng là null) mà không gặp sự cố. Có thể cung cấp một phương tiện rõ ràng để đặt một con trỏ thành null.
  2. Có các vị trí mặc định cho một con trỏ null và gặp sự cố nếu một nỗ lực được thực hiện để đọc một con trỏ, nhưng cung cấp một phương tiện không thử nghiệm để xem liệu một con trỏ có giữ null hay không. Cũng cung cấp một phương tiện bùng nổ để đặt một con trỏ thành null.
  3. Có các vị trí mặc định cho một con trỏ tới một số thể hiện mặc định do trình biên dịch cụ thể cung cấp.
  4. Có các vị trí mặc định cho một con trỏ tới một số thể hiện mặc định do chương trình cụ thể cung cấp.
  5. Có bất kỳ quyền truy cập con trỏ null (không chỉ các hoạt động hội nghị) gọi một số thường trình do chương trình cung cấp để cung cấp một thể hiện.
  6. Thiết kế ngữ nghĩa ngôn ngữ sao cho các bộ sưu tập các vị trí lưu trữ sẽ không tồn tại, ngoại trừ một trình biên dịch không thể truy cập tạm thời, cho đến khi các thói quen khởi tạo đã được chạy trên tất cả các thành viên (một cái gì đó giống như một hàm tạo mảng sẽ phải được cung cấp với một thể hiện mặc định, một hàm sẽ trả về một thể hiện, hoặc có thể là một cặp hàm - một để trả về một thể hiện và một hàm sẽ được gọi trong các thể hiện được xây dựng trước đó nếu có một ngoại lệ xảy ra trong quá trình xây dựng mảng).

Lựa chọn cuối cùng có thể có một số kháng cáo, đặc biệt là nếu một ngôn ngữ bao gồm cả hai loại nullable và không nullable (người ta có thể gọi các hàm tạo mảng đặc biệt cho bất kỳ loại nào, nhưng người ta chỉ được yêu cầu gọi chúng khi tạo mảng các loại không null), nhưng có lẽ sẽ không khả thi trong khoảng thời gian con trỏ null được phát minh. Trong số các lựa chọn khác, không có lựa chọn nào có vẻ hấp dẫn hơn việc cho phép các con trỏ null được sao chép nhưng không được quy định hoặc lập chỉ mục. Cách tiếp cận số 4 có thể thuận tiện khi có tùy chọn và nên khá rẻ để thực hiện, nhưng chắc chắn đó không phải là lựa chọn duy nhất. Yêu cầu con trỏ phải theo mặc định trỏ đến một số đối tượng hợp lệ cụ thể còn tệ hơn nhiều so với việc con trỏ mặc định thành giá trị null có thể được đọc hoặc sao chép nhưng không được quy định hoặc lập chỉ mục.


-2

Vâng, NULL là một thiết kế khủng khiếp , trong thế giới hướng đối tượng. Tóm lại, việc sử dụng NULL dẫn đến:

  • xử lý lỗi đặc biệt (thay vì ngoại lệ)
  • ngữ nghĩa mơ hồ
  • chậm thay vì thất bại nhanh
  • tư duy máy tính so với tư duy đối tượng
  • đối tượng có thể thay đổi và không đầy đủ

Kiểm tra bài đăng trên blog này để được giải thích chi tiết: http://www.yegor256.com/2014/05/13/why-null-is-bad.html

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.