it-swarm-vi.com

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

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ó?

165
Tim Goodman

null là ác

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

Loại tùy chọn

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 value hoặc NONE.

Một bài viết hay Mẫu Tùy chọn của Tùy chọn thảo luận về loại Tùy chọn và cung cấp triển khai thực hiện 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 NullPulumExceptions . tính năng được yêu cầ đã được giới thiệu trong Java 8.

93
Jonas

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 khi 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 câu lệnh if kiểm tra null. Nhưng điều gì xảy ra khi một thứ mà bạn tự thuyết phục mình không thể có khả năng là một null là, trong thực tế, một null? 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.

129
BlairHippo

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 để biểu thị 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 một loại mất mát, loại/giá trị tổng quát ồ ạ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 hiện ngược dòng của nó.

Thứ ba, các 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 được thực hiện ở nhiều nơi khác nhau Chỉ 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 loại 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 to tận dụng kiểm tra của bộ thực thi để gọi nó NOP phương thức dành riêng cho trạng thái đó (tuân theo giao diện của trạng thái thông thường) đồng thời loại bỏ tất 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, 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 kiểm tra bị 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 bạn.

50
Huperniketes

Nulls không quá tệ, trừ khi bạn không mong đợi chúng. Bạn nên cần xác định rõ ràng trong mã mà bạn đang mong đợi null, đây là một vấn đề về thiết kế ngôn ngữ. 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 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.

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 của null, vì vậy tôi muốn trình bày một lập luận thay thế: 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 Rails" 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 là "Hello, world!" - đó có thể là số 42, Tuple (1, 4, 9), Hoặc, giả sử, null. Nhưng tại sao bạn lại làm điều này? Một biến loại Foo chỉ nên giữ Foos - đó là toàn bộ điểm của hệ thống loại! null không phải là Foo nhiều hơn "Hello, world!". Tồi tệ hơn, null không phải là 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 cho "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 mã trước không truyền bá thực tế rằng foo1 thực sự là một Foo - bar cũng có thể có kiểm tra riêng, chỉ để an toàn.

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

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

Trong mệnh đề Just foo, Cả bạn và chương trình đều biết chắc chắn rằng biến Maybe Foo Của chúng tôi thực sự có chứa giá trị Foo - thông tin đó được truyền xuống chuỗi cuộc gọi và bar không cần thực hiện bất kỳ kiểm tra nào. Vì Maybe Foo Là một loại khác biệt với Foo, nên 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ị mù bởi 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 kiểm tra null khi biết rằng tất cả các biến thuộc loại Foo thực sự có chứa Foos. Mọi người đều thắng.

20
Doval

Một con trỏ null 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 điển hình con trỏ không hợp lệ, 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ỏ so với null. Đó là một PITA để xác minh xem một 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 trỏ null không quá xa.


EDIT : Mặc dù mẫu NullObject dường như là một giải pháp tốt hơn so với các tham chiếu có thể là null, nhưng nó tự đưa ra các vấn đề:

  • Một tham chiếu giữ một NullObject nê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 NullObject hoạt động bằng cách 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 bị lỗi ngay lập tức, ngay bây giờ, vì lỗi logic/dữ liệu đột nhiên xuất hiện sau đó xuống đường 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 NullObject khô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à int và 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, NullObject không nên làm gì cả, nhưng khi được yêu cầu 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 giao dịch nó với một ngoại lệ khác (không được thực hiện, chia cho 0, ...), phải không? Vì 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 mẫu NullObject 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ì 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.

15
JensG

(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ó Thing t, Thì trình biên dịch có thể đảm bảo tôi có thể gọi t.doSomething(). Chà, UNLESS t là null khi 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 sau đó tôi phát hiện ra rằng t thực tế KHÔNG phải là 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ể nắm bắt một số vấn đề đó, BTW. Giống như các biểu thức gắn cờ như i = 1 / 0 Tại thời gian biên dịch. Nhưng bạn thực sự không thể mong muốn 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 "this is một tham chiếu đến một cái gì đó được mong đợi", bạn đang để ngôn ngữ nói "this cũng có thể một tham chiếu đến một cái gì đó được mong đợi. "

14
Rob

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!");
}
8
fredoverflow

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 ...

8
Matthieu M.

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 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ó.

6
luis.espinal

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
Winston Ewert

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 được kiểm tra. 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

4
Michael Parker

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ụ cho 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.

3
Loren Pechtel

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

Việc 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, trong đó 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à đưa ra một 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, ra khách hàng)' mẫu. Tham chiếu chéo với int.Pude và int.TryPude.

1
JBRWilkinson

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. Bao giờ khi bạn thực hiện 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.

0
Ilya Gazman

Không.

Một ngôn ngữ không thể chiếm một giá trị đặc biệt như null là vấn đề của ngôn ngữ. Nếu bạn xem các ngôn ngữ như Kotlin hoặc Swift , điều này 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ó nguy hiểm.

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

0
Ben Leggiero

Nulls có 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.

Đưa 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, giá trị bị 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 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ị bị thiếu, nhưng nó không 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à mà 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 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 theo cách rất tự nhiên, không có câu lệnh if rõ ràng.

Ngược lại với null, trong đó loại kiểm tra chỉ 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 khi chạy, nếu bạn may mắn trong quá trình sử dụng trường hợp đơn vị của bạn kiểm tra bài tập. Nó chỉ không đáng để rắc rối.

0
Karl Bielefeldt

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 tham chiếu Null bằng cách tạo chỉ đọc NullableClass<T> (hai lựa 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 thuộc loại 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
}

Cách tiếp cận này có thể tăng đáng kể ứng dụng độ tin cậy và chất lượng phần mềm. Design by Contract là một trường hợp Hoare logic , được đưa ra 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
Artru