Nếu null là xấu, nên sử dụng cái gì khi một giá trị có thể vắng mặt một cách có ý nghĩa?


26

Đây là một trong những quy tắc được lặp đi lặp lại nhiều lần và điều đó làm tôi bối rối.

Nulls là ác và nên tránh bất cứ khi nào có thể .

Nhưng, nhưng - từ sự ngây thơ của tôi, hãy để tôi hét lên - đôi khi một giá trị CÓ THỂ vắng mặt một cách có ý nghĩa!

Xin vui lòng cho tôi hỏi điều này trên một ví dụ xuất phát từ mã khủng khiếp chống mô hình này hiện đang làm việc . Đây là, cốt lõi của nó, một trò chơi dựa trên lượt chơi nhiều người chơi, trong đó cả lượt của người chơi được chạy đồng thời (như trong Pokemon và ngược lại với Cờ vua).

Sau mỗi lượt, máy chủ sẽ phát một danh sách các bản cập nhật cho mã JS phía máy khách. Lớp Cập nhật:

public class GameUpdate
{
    public Player player;
    // other stuff
}

Lớp này được tuần tự hóa thành JSON và được gửi đến những người chơi được kết nối.

Hầu hết các bản cập nhật tự nhiên có một Người chơi được liên kết với chúng - sau tất cả, cần phải biết người chơi nào đã thực hiện bước chuyển này. Tuy nhiên, một số cập nhật không thể có Trình phát được liên kết với chúng một cách có ý nghĩa. Ví dụ: Trò chơi đã bị buộc bởi vì giới hạn lần lượt mà không có hành động đã bị vượt quá. Đối với những cập nhật như vậy, tôi nghĩ, thật có ý nghĩa khi người chơi bị vô hiệu hóa.

Tất nhiên, tôi có thể "sửa" mã này bằng cách sử dụng tính kế thừa:

public class GameUpdate
{
    // stuff
}

public class GamePlayerUpdate : GameUpdate
{
    public Player player;
    // other stuff
}

Tuy nhiên, tôi không thấy điều này có cải thiện như thế nào, vì hai lý do:

  • Bây giờ, mã JS sẽ chỉ nhận một đối tượng mà không có Trình phát như một thuộc tính được xác định, giống như nếu nó là null vì cả hai trường hợp đều yêu cầu kiểm tra xem giá trị có hay không có;
  • Nếu một trường không thể khác được thêm vào lớp GameUpdate, tôi sẽ phải có thể sử dụng nhiều kế thừa để tiếp tục với điều này - nhưng MI là ác theo cách riêng của nó (theo các lập trình viên có kinh nghiệm hơn) và quan trọng hơn, C # không có nó vì vậy tôi không thể sử dụng nó.

Tôi có một ý niệm mơ hồ rằng đoạn mã này là một trong số rất nhiều nơi mà một lập trình viên giỏi, có kinh nghiệm sẽ hét lên kinh hoàng. Đồng thời tôi không thể thấy, ở nơi này, điều này có hại gì không và thay vào đó nên làm gì.

Bạn có thể giải thích vấn đề này cho tôi?


2
Câu hỏi này có dành riêng cho JavaScript không? Nhiều ngôn ngữ có loại Tùy chọn cho mục đích này nhưng tôi không biết liệu js có một loại (Mặc dù bạn có thể tạo một ngôn ngữ) hoặc nếu nó hoạt động với json (Mặc dù đó là một phần của kiểm tra null khó chịu thay vì toàn bộ mã của bạn
Richard Tingle

9
Quy tắc đó chỉ là sai. Tôi hơi ngạc nhiên khi ai đó sẽ đăng ký vào khái niệm đó. Có một số lựa chọn thay thế tốt cho null trong một số trường hợp. null thực sự tốt cho việc đại diện cho một giá trị còn thiếu Đừng để các quy tắc internet ngẫu nhiên như thế ngăn bạn tin vào phán đoán hợp lý của chính bạn.
usr

1
@usr - Tôi hoàn toàn đồng ý. Null là bạn của bạn. IMHO, không có cách nào rõ ràng hơn để biểu thị một giá trị còn thiếu. Nó có thể giúp bạn tìm lỗi, nó có thể giúp bạn xử lý các điều kiện lỗi, nó có thể giúp bạn cầu xin sự tha thứ (thử / bắt). Có gì không thích?!
Scuba Steve

4
một trong những quy tắc đầu tiên của thiết kế phần mềm là "Luôn kiểm tra null". Tại sao điều này thậm chí là một vấn đề là tâm trí bogg với tôi.
MattE

1
@MattE - Hoàn toàn đúng. Tại sao tôi lại thiết kế một mẫu thiết kế khi thực sự, Null là một thứ tốt để có trong hộp công cụ cơ bản của bạn. Những gì Graham đã gợi ý, IMHO, đánh tôi là kỹ thuật quá mức.
Scuba Steve

Câu trả lời:


20

Rất nhiều thứ tốt hơn để trở về hơn null.

  • Một chuỗi rỗng ("")
  • Một bộ sưu tập trống
  • Một đơn vị "tùy chọn" hoặc "có thể"
  • Một chức năng lặng lẽ không làm gì
  • Một đối tượng đầy những phương thức lặng lẽ không làm gì cả
  • Một giá trị có ý nghĩa từ chối tiền đề không chính xác của câu hỏi
    (đó là điều khiến bạn coi null ở vị trí đầu tiên)

Bí quyết là nhận ra khi nào để làm điều này. Null thực sự rất giỏi trong việc thổi vào mặt bạn nhưng chỉ khi bạn chấm nó ra mà không kiểm tra nó. Nó không tốt để giải thích tại sao.

Khi bạn không cần phải nổ tung và bạn không muốn kiểm tra null, hãy sử dụng một trong những lựa chọn thay thế này. Việc trả lại một bộ sưu tập có vẻ kỳ lạ nếu nó chỉ có 0 hoặc 1 phần tử nhưng nó thực sự tốt khi cho phép bạn âm thầm xử lý các giá trị bị thiếu.

Monads nghe có vẻ lạ mắt nhưng ở đây tất cả những gì họ đang làm là cho phép bạn thấy rõ rằng kích thước hợp lệ cho bộ sưu tập là 0 và 1. Nếu bạn có chúng, hãy xem xét chúng.

Bất kỳ điều nào trong số này làm tốt hơn việc làm cho ý định của bạn rõ ràng hơn null. Đó là sự khác biệt quan trọng nhất.

Nó có thể giúp hiểu lý do tại sao bạn trở lại hoàn toàn thay vì chỉ đơn giản là ném một ngoại lệ. Các ngoại lệ về cơ bản là một cách để từ chối một giả định (chúng không phải là cách duy nhất). Khi bạn hỏi điểm mà hai đường thẳng giao nhau, bạn cho rằng chúng giao nhau. Chúng có thể song song. Bạn có nên ném một ngoại lệ "đường song song"? Vâng, bạn có thể nhưng bây giờ bạn phải xử lý ngoại lệ đó ở nơi khác hoặc để nó dừng hệ thống.

Nếu bạn muốn ở lại vị trí của mình, bạn có thể từ chối giả định đó thành một loại giá trị có thể biểu thị kết quả hoặc từ chối. Nó không phải là lạ. Chúng tôi làm điều đó trong đại số tất cả các thời gian. Bí quyết là làm cho giá trị đó có ý nghĩa để chúng ta có thể hiểu những gì đã xảy ra.

Nếu giá trị được trả về có thể biểu thị kết quả và từ chối, nó cần được gửi đến mã có thể xử lý cả hai. Đa hình thực sự mạnh mẽ để sử dụng ở đây. Thay vì chỉ giao dịch kiểm tra null để isPresent()kiểm tra, bạn có thể viết mã hoạt động tốt trong cả hai trường hợp. Hoặc bằng cách thay thế các giá trị trống bằng các giá trị mặc định hoặc âm thầm không làm gì cả.

Vấn đề với null là nó có thể có nghĩa là quá nhiều thứ. Vì vậy, nó chỉ kết thúc phá hủy thông tin.


Trong thí nghiệm suy nghĩ null yêu thích của tôi, tôi yêu cầu bạn tưởng tượng một cỗ máy Rube Goldbergian phức tạp , báo hiệu các thông điệp được mã hóa bằng ánh sáng màu bằng cách nhấc bóng đèn màu ra khỏi băng chuyền, vặn chúng vào ổ cắm và cấp nguồn cho chúng. Tín hiệu được điều khiển bởi các màu khác nhau của bóng đèn được đặt trên băng chuyền.

Bạn đã được yêu cầu làm cho thứ đắt tiền khủng khiếp này tuân thủ RFC3.14159, trong đó tuyên bố rằng cần có một điểm đánh dấu giữa các tin nhắn. Điểm đánh dấu nên không có ánh sáng nào cả. Nó sẽ kéo dài cho chính xác một xung. Cùng một lượng thời gian mà một ánh sáng màu đơn thường tỏa sáng.

Bạn không thể để khoảng trống giữa các tin nhắn vì phần thiết bị tắt báo thức và tạm dừng nếu không thể tìm thấy bóng đèn để đặt vào ổ cắm.

Mọi người quen thuộc với dự án này đều rùng mình khi chạm vào máy móc hoặc mã điều khiển. Nó không dễ thay đổi. Bạn làm nghề gì? Vâng, bạn có thể lặn trong và bắt đầu phá vỡ mọi thứ. Có thể cập nhật những điều này thiết kế gớm ghiếc. Vâng, bạn có thể làm cho nó tốt hơn nhiều. Hoặc bạn có thể nói chuyện với người gác cổng và bắt đầu thu thập bóng đèn bị cháy.

Đó là giải pháp đa hình. Hệ thống thậm chí không cần biết bất cứ điều gì đã thay đổi.


Thật tuyệt nếu bạn có thể làm được điều đó. Bằng cách mã hóa một từ chối có ý nghĩa của một giả định thành một giá trị, bạn không phải buộc câu hỏi phải thay đổi.

Bạn sẽ nợ tôi bao nhiêu quả táo nếu tôi cho bạn 1 quả táo? -1. Bởi vì tôi nợ bạn 2 quả táo từ hôm qua. Ở đây, giả định của câu hỏi, "bạn sẽ nợ tôi", bị từ chối với một số âm. Mặc dù câu hỏi đã sai, thông tin có ý nghĩa được mã hóa trong câu trả lời này. Bằng cách từ chối nó một cách có ý nghĩa, câu hỏi không bị buộc phải thay đổi.

Tuy nhiên, đôi khi thay đổi câu hỏi thực sự là cách để đi. Nếu giả định có thể được thực hiện đáng tin cậy hơn, trong khi vẫn hữu ích, hãy xem xét làm điều đó.

Vấn đề của bạn là, thông thường, cập nhật đến từ người chơi. Nhưng bạn đã phát hiện ra nhu cầu cập nhật không đến từ người chơi 1 hoặc người chơi 2. Bạn cần một bản cập nhật cho biết thời gian đã hết. Cả người chơi đều không nói điều này vậy bạn nên làm gì? Rời khỏi người chơi null dường như rất hấp dẫn. Nhưng nó phá hủy thông tin. Bạn đang cố mã hóa kiến ​​thức trong một lỗ đen.

Trường người chơi không cho bạn biết ai vừa mới di chuyển. Bạn nghĩ vậy nhưng vấn đề này chứng tỏ đó là lời nói dối. Trường người chơi cho bạn biết bản cập nhật đến từ đâu. Cập nhật hết hạn thời gian của bạn đang đến từ đồng hồ trò chơi. Đề nghị của tôi là cung cấp cho đồng hồ tín dụng mà nó xứng đáng và thay đổi tên của playerlĩnh vực thành một cái gì đó như thế nào source.

Bằng cách này, nếu máy chủ ngừng hoạt động và phải tạm dừng trò chơi, nó có thể gửi bản cập nhật báo cáo những gì đang xảy ra và liệt kê chính nó là nguồn của bản cập nhật.

Điều này có nghĩa là bạn không bao giờ nên bỏ đi một cái gì đó? Không. Nhưng bạn cần xem xét mức độ không có gì có thể được giả định thực sự có nghĩa là một giá trị mặc định tốt được biết đến duy nhất. Không có gì là thực sự dễ dàng để sử dụng quá mức. Vì vậy, hãy cẩn thận khi bạn sử dụng nó. Có một trường phái tư tưởng mà bao trùm thiên không có gì . Nó được gọi là quy ước về cấu hình .

Tôi thích điều đó bản thân mình nhưng chỉ khi hội nghị được truyền đạt rõ ràng theo một cách nào đó. Nó không phải là một cách để làm mờ mắt người mới. Nhưng ngay cả khi bạn đang sử dụng nó, null vẫn không phải là cách tốt nhất để làm điều đó. Null là một nguy hiểm . Null giả vờ là một cái gì đó bạn có thể sử dụng ngay cho đến khi bạn sử dụng nó, sau đó nó thổi một lỗ hổng trong vũ trụ (phá vỡ mô hình ngữ nghĩa của bạn). Trừ khi thổi một lỗ hổng trong vũ trụ là hành vi bạn cần tại sao bạn lại rối tung với điều này? Sử dụng một cái gì đó khác.


9
"trả lại một bộ sưu tập nếu nó chỉ có 0 hoặc 1 phần tử" - Tôi xin lỗi nhưng tôi không nghĩ rằng tôi đã nhận được nó :( Từ POV của tôi, việc này (a) thêm các trạng thái không hợp lệ không cần thiết vào lớp (b) yêu cầu ghi lại rằng bộ sưu tập phải có tối đa 1 phần tử (c) dường như không giải quyết được vấn đề, vì việc truy cập nếu bộ sưu tập có chứa phần tử duy nhất trước khi tích lũy thì dù sao cũng cần phải có ngoại lệ?
gaazkam

2
@gaazkam thấy chưa? Tôi nói với bạn nó lỗi người. Tuy nhiên, đó chính xác là những gì bạn đã làm mỗi khi bạn sử dụng chuỗi trống.
candied_orange

16
Điều gì xảy ra khi có một giá trị hợp lệ xảy ra là một chuỗi rỗng hoặc được đặt?
Blrfl

5
Dù sao @gaazkam, bạn không kiểm tra rõ ràng xem bộ sưu tập có các mặt hàng không. Vòng lặp (hoặc ánh xạ) trên một danh sách trống là một hành động an toàn không có hiệu lực.
RubberDuck

3
Tôi biết bạn đang trả lời câu hỏi về việc ai đó nên làm gì thay vì trả về null nhưng tôi thực sự không đồng ý với nhiều điểm này. Tuy nhiên vì chúng tôi không phải là ngôn ngữ cụ thể và có một lý do để phá vỡ mọi quy tắc, tôi từ chối từ chối
Liath

15

nullkhông thực sự là vấn đề ở đây. Vấn đề là làm thế nào hầu hết các ngôn ngữ lập trình hiện tại xử lý thông tin còn thiếu; họ không coi trọng vấn đề này (hoặc ít nhất là không cung cấp sự hỗ trợ và an toàn mà hầu hết những người trái đất cần để tránh những cạm bẫy). Điều này dẫn đến tất cả các vấn đề chúng tôi đã học để lo sợ (Javas NullPulumException, Segfaults, ...).

Hãy xem cách giải quyết vấn đề đó một cách tốt hơn.

Những người khác đề xuất Mẫu Null-Object . Trong khi tôi nghĩ rằng đôi khi nó có thể áp dụng được, có rất nhiều tình huống trong đó không có giá trị mặc định một kích cỡ phù hợp với tất cả. Trong những trường hợp đó, người tiêu dùng thông tin có thể vắng mặt phải có khả năng tự quyết định phải làm gì nếu thông tin đó bị thiếu.

Có các ngôn ngữ lập trình cung cấp sự an toàn chống lại con trỏ null (còn gọi là void safe) tại thời điểm biên dịch. Để đưa ra một số ví dụ hiện đại: Kotlin, Rust, Swift. Trong các ngôn ngữ đó, vấn đề thiếu thông tin đã được thực hiện nghiêm túc và sử dụng null hoàn toàn không gây đau đớn - trình biên dịch sẽ ngăn bạn khỏi các điểm dừng hội thảo.
Trong Java, những nỗ lực đã được thực hiện để thiết lập các chú thích @ Nullable / @ NotNull cùng với các trình biên dịch + trình cắm IDE để đạt được hiệu quả tương tự. Nó không thực sự thành công nhưng đó là ngoài phạm vi cho câu trả lời này.

Một loại rõ ràng, chung chung biểu thị sự vắng mặt có thể là một cách khác để đối phó với nó. Ví dụ, trong Java có java.util.Optional. Nó có thể hoạt động:
Ở cấp độ dự án hoặc công ty, bạn có thể thiết lập các quy tắc / hướng dẫn

  • chỉ sử dụng thư viện bên thứ 3 chất lượng cao
  • luôn luôn sử dụng java.util.Optionalthay vìnull

Cộng đồng ngôn ngữ / hệ sinh thái của bạn có thể đã đưa ra các cách khác để giải quyết vấn đề này tốt hơn trình biên dịch / trình thông dịch.


Bạn có thể vui lòng giải thích, làm thế nào các tùy chọn Java tốt hơn null? Khi tôi đọc tài liệu , cố gắng lấy một giá trị từ tùy chọn sẽ tạo ra một ngoại lệ nếu không có giá trị; vì vậy có vẻ như chúng ta đang ở cùng một nơi: một kiểm tra là cần thiết và nếu chúng ta quên một thứ, chúng ta có bị lừa không?
gaazkam

3
@gaazkam Bạn nói đúng, nó không cung cấp saftey thời gian biên dịch. Ngoài ra, bản thân Tùy chọn có thể là null, một vấn đề khác. Nhưng nó là rõ ràng (so với các biến chỉ có thể là bình luận null / doc). Điều này chỉ mang lại lợi ích thực sự nếu, đối với toàn bộ cơ sở mã, một quy tắc mã hóa được thiết lập. Bạn không thể đặt những thứ null. Nếu thông tin có thể vắng mặt, sử dụng Tùy chọn. Đó là lực lượng thám hiểm. Các kiểm tra null thông thường sau đó trở thành các cuộc gọi đếnOptional.isPresent
marstato 7/07/18

8
@marstato thay thế kiểm tra null bằng isPresent() không được khuyến nghị sử dụng Tùy chọn
candied_orange

10

Tôi không thấy bất kỳ công đức nào trong tuyên bố rằng null là xấu. Tôi đã kiểm tra các cuộc thảo luận về nó và chúng chỉ khiến tôi mất kiên nhẫn và cáu kỉnh.

Null có thể được sử dụng sai, như một "giá trị ma thuật", khi nó thực sự có nghĩa gì đó. Nhưng bạn sẽ phải làm một số chương trình xoắn khá để đạt được điều đó.

Null có nghĩa là "không có", điều đó chưa được tạo (chưa) hoặc (đã) biến mất hoặc không được cung cấp nếu đó là một đối số. Nó không phải là một nhà nước, toàn bộ điều còn thiếu. Trong cài đặt OO nơi mọi thứ được tạo và phá hủy mọi lúc là điều kiện hợp lệ, có ý nghĩa khác với bất kỳ trạng thái nào.

Với null, bạn bị tấn công trong thời gian chạy, nơi bạn bị tấn công vào thời gian biên dịch với một số loại thay thế null an toàn loại.

Không hoàn toàn đúng, tôi có thể nhận được cảnh báo về việc không kiểm tra null trước khi sử dụng biến. Và nó không giống nhau. Tôi có thể không muốn tiết kiệm tài nguyên của một đối tượng không có mục đích. Và quan trọng hơn, tôi sẽ phải kiểm tra thay thế giống nhau để có được hành vi mong muốn. Nếu tôi quên làm điều đó, tôi thà nhận được một NullReferenceException trong thời gian chạy hơn là một số hành vi không xác định / ngoài ý muốn.

Có một vấn đề cần lưu ý. Trong ngữ cảnh đa luồng, bạn sẽ phải bảo vệ chống lại các điều kiện chủng tộc bằng cách gán cho một biến cục bộ trước khi thực hiện kiểm tra null.

Vấn đề với null là nó có thể có nghĩa là nhiều thứ. Vì vậy, nó chỉ kết thúc phá hủy thông tin.

Không, nó không / không có nghĩa gì cả nên không có gì để phá hủy. Tên và loại của biến / đối số sẽ luôn cho biết những gì còn thiếu, đó là tất cả những gì cần biết.

Chỉnh sửa:

Tôi là một nửa video candied_orange được liên kết với một trong những câu trả lời khác của anh ấy về lập trình chức năng và nó rất thú vị. Tôi có thể thấy sự hữu ích của nó trong các kịch bản nhất định. Cách tiếp cận chức năng mà tôi đã thấy cho đến nay, với các đoạn mã bay xung quanh, thường khiến tôi co rúm người lại khi được sử dụng trong cài đặt OO. Nhưng tôi đoán nó có vị trí của nó. Một vài nơi. Và tôi đoán sẽ có những ngôn ngữ làm tốt công việc che giấu những gì tôi cho là một mớ hỗn độn khó hiểu mỗi khi tôi gặp nó trong C #, ngôn ngữ cung cấp một mô hình sạch và khả thi thay thế.

Nếu mô hình chức năng đó là bộ / khung suy nghĩ của bạn, thực sự không có cách nào khác để đẩy bất kỳ rủi ro nào về phía trước như mô tả lỗi được ghi lại và cuối cùng để người gọi xử lý rác tích lũy được kéo dọc theo điểm bắt đầu. Rõ ràng đây chỉ là cách lập trình chức năng hoạt động. Gọi nó là một giới hạn, gọi nó là một mô hình mới. Bạn có thể coi đó là một bản hack cho các môn đệ chức năng cho phép họ tiếp tục đi trên con đường vô định hơn một chút, bạn có thể vui mừng bởi cách lập trình mới này mở ra những khả năng mới thú vị. Dù thế nào đi nữa, dường như vô nghĩa đối với tôi khi bước vào thế giới đó, quay trở lại cách làm OO truyền thống (vâng, chúng ta có thể ném ngoại lệ, xin lỗi), chọn một số khái niệm từ nó,

Bất kỳ khái niệm có thể được sử dụng sai cách. Từ chỗ tôi đứng, các "giải pháp" được trình bày không có lựa chọn thay thế nào cho việc sử dụng null thích hợp. Họ là những khái niệm từ một thế giới khác.


9
Null phá hủy kiến ​​thức về loại không có gì nó đại diện. Có loại không có gì nên lặng lẽ bỏ qua. Loại không có gì cần phải dừng hệ thống để tránh trạng thái không hợp lệ. Các loại không có nghĩa là lập trình viên sai lầm và cần sửa mã. Loại không có gì có nghĩa là người dùng yêu cầu một cái gì đó không tồn tại và cần phải được nói một cách lịch sự. Xin lỗi không. Tất cả những thứ này đã được mã hóa thành null. Do đó, nếu bạn mã hóa bất kỳ thứ nào trong số này thành null thì bạn không nên ngạc nhiên nếu không ai biết ý của bạn là gì. Bạn đang buộc chúng tôi phải đoán từ ngữ cảnh.
candied_orange

3
@candied_orange Bạn có thể đưa ra lập luận tương tự chính xác về bất kỳ loại tùy chọn nào: Có Noneđại diện cho không.
Ded repeatator

3
@candied_orange Tôi vẫn không hiểu ý bạn. Các loại khác nhau của những gì? Nếu bạn sử dụng null khi bạn nên sử dụng một enum có lẽ? Chỉ có một loại không có gì. Lý do cho một cái gì đó không có gì có thể thay đổi nhưng điều đó không thay đổi sự hư vô cũng như phản ứng thích hợp cho một cái gì đó không là gì cả. Nếu bạn mong đợi một giá trị trong một cửa hàng không có ở đó và điều đó rất đáng chú ý, bạn ném hoặc đăng nhập tại thời điểm bạn truy vấn và không nhận được gì, bạn không chuyển null làm đối số cho một phương thức và sau đó trong phương thức đó hãy cố gắng tìm ra tại sao bạn có null Null sẽ không phải là sai lầm.
Martin Maat

2
Null là sai lầm vì bạn đã có cơ hội mã hóa ý nghĩa của không có gì. Sự hiểu biết của bạn về không có gì không đòi hỏi xử lý ngay lập tức. 0, null, NaN, chuỗi rỗng, tập rỗng, void, đối tượng null, không áp dụng, không được thực hiện ngoại lệ, không có gì có nhiều dạng. Bạn không cần phải quên những gì bạn biết bây giờ chỉ vì bạn không muốn đối phó với những gì bạn biết bây giờ. Nếu tôi yêu cầu bạn lấy căn bậc hai của -1, bạn có thể đưa ra một ngoại lệ để cho tôi biết điều đó là không thể và quên mọi thứ hoặc phát minh ra những con số tưởng tượng và bảo tồn những gì bạn biết.
candied_orange

1
@MartinMaat bạn làm cho nó có vẻ như một chức năng phải đưa ra một ngoại lệ mỗi khi kỳ vọng của bạn bị vi phạm. Đơn giản là nó sai. Thường có trường hợp góc để giải quyết. Nếu bạn thực sự không thể thấy điều đó hãy đọc
candied_orange

7

Câu hỏi của bạn.

Nhưng, nhưng - từ sự ngây thơ của tôi, hãy để tôi hét lên - đôi khi một giá trị CÓ THỂ vắng mặt một cách có ý nghĩa!

Hoàn toàn trên tàu với bạn ở đó. Tuy nhiên, có một số cảnh báo tôi sẽ nhận được trong một giây. Nhưng trước tiên:

Nulls là ác và nên tránh bất cứ khi nào có thể.

Tôi nghĩ rằng đây là một tuyên bố có thiện chí nhưng quá mức.

Nulls không xấu xa. Chúng không phải là một antipotype. Chúng không phải là thứ cần tránh bằng mọi giá. Tuy nhiên, null dễ bị lỗi (do không kiểm tra null).

Nhưng tôi không hiểu làm thế nào để không kiểm tra null là bằng chứng nào đó chứng minh rằng null vốn đã sai khi sử dụng.

Nếu null là ác, thì các chỉ mục mảng và con trỏ C ++ cũng vậy. Họ không phải. Họ dễ dàng tự bắn vào chân mình, nhưng họ không nên tránh bằng mọi giá.

Đối với phần còn lại của câu trả lời này, tôi sẽ điều chỉnh ý định "nulls là xấu xa" thành một " nulls sắc thái hơn nên được xử lý có trách nhiệm ".


Giải pháp của bạn.

Trong khi tôi đồng ý với ý kiến ​​của bạn về việc sử dụng null như một quy tắc toàn cầu; Tôi không đồng ý với việc bạn sử dụng null trong trường hợp cụ thể này.

Để tóm tắt các phản hồi dưới đây: bạn đang hiểu sai mục đích của kế thừa và đa hình. Mặc dù đối số của bạn để sử dụng null có hiệu lực, nhưng "bằng chứng" tiếp theo của bạn về lý do tại sao cách khác là xấu được xây dựng dựa trên việc lạm dụng quyền thừa kế, khiến cho các điểm của bạn có phần không liên quan đến vấn đề null.

Hầu hết các bản cập nhật tự nhiên có một Người chơi được liên kết với chúng - sau tất cả, cần phải biết người chơi nào đã thực hiện bước chuyển này. Tuy nhiên, một số cập nhật không thể có Trình phát được liên kết với chúng một cách có ý nghĩa.

Nếu các cập nhật này có ý nghĩa khác nhau (đó là), thì bạn cần hai loại cập nhật riêng biệt.

Xem xét tin nhắn, ví dụ. Bạn có thông báo lỗi và tin nhắn trò chuyện IM. Nhưng trong khi chúng có thể chứa dữ liệu rất giống nhau (tin nhắn chuỗi và người gửi), chúng không hoạt động theo cùng một cách. Chúng nên được sử dụng riêng biệt, như các lớp khác nhau.

Những gì bạn đang làm bây giờ là phân biệt hiệu quả giữa hai loại cập nhật dựa trên tính không hợp lệ của thuộc tính Người chơi. Điều đó tương đương với việc quyết định giữa thông báo IM và thông báo lỗi dựa trên sự tồn tại của mã lỗi. Nó hoạt động, nhưng nó không phải là cách tiếp cận tốt nhất.

  • Bây giờ, mã JS sẽ chỉ nhận một đối tượng mà không có Trình phát như một thuộc tính được xác định, giống như nếu nó là null vì cả hai trường hợp đều yêu cầu kiểm tra xem giá trị có hay không có;

Lập luận của bạn dựa trên những sai lầm của đa hình. Nếu bạn có một phương thức trả về một GameUpdateđối tượng, thì nó thường không bao giờ quan tâm đến việc phân biệt giữa GameUpdate, PlayerGameUpdatehoặc NewlyDevelopedGameUpdate.

Một lớp cơ sở cần phải có một hợp đồng làm việc đầy đủ, tức là các thuộc tính của nó được định nghĩa trên lớp cơ sở chứ không phải trên lớp dẫn xuất (có thể nói, giá trị của các thuộc tính cơ sở này tất nhiên có thể được ghi đè trong các lớp dẫn xuất).

Điều này làm cho tranh luận của bạn moot. Trong thực tế tốt, bạn không bao giờ nên quan tâm rằng GameUpdateđối tượng của bạn có hoặc không có người chơi. Nếu bạn đang cố truy cập vào Playertài sản của a GameUpdate, thì bạn đang làm điều gì đó phi logic.

Các ngôn ngữ được nhập như C # thậm chí sẽ không cho phép bạn thử và truy cập vào Playertài sản của GameUpdateGameUpdateđơn giản là không có tài sản. Javascript là cách lỏng lẻo hơn đáng kể trong cách tiếp cận của nó (và nó không yêu cầu tiền biên dịch) vì vậy nó không bận tâm để sửa lỗi cho bạn và thay vào đó nó sẽ nổ tung khi chạy.

  • Nếu một trường không thể khác được thêm vào lớp GameUpdate, tôi sẽ phải có thể sử dụng nhiều kế thừa để tiếp tục với điều này - nhưng MI là ác theo cách riêng của nó (theo các lập trình viên có kinh nghiệm hơn) và quan trọng hơn, C # không có nó vì vậy tôi không thể sử dụng nó.

Bạn không nên tạo các lớp dựa trên việc thêm các thuộc tính nullable. Bạn nên tạo các lớp dựa trên các loại cập nhật trò chơi độc đáo về chức năng .


Làm thế nào để tránh các vấn đề với null nói chung?

Tôi nghĩ rằng nó có liên quan để giải thích về cách bạn có thể diễn đạt một cách có ý nghĩa sự vắng mặt của một giá trị. Đây là một tập hợp các giải pháp (hoặc phương pháp xấu) không theo thứ tự cụ thể.

1. Sử dụng null nào.

Đây là cách tiếp cận dễ dàng nhất. Tuy nhiên, bạn đang đăng ký cho mình một tấn kiểm tra null và (khi không làm như vậy) xử lý sự cố ngoại lệ tham chiếu null.

2. Sử dụng một giá trị vô nghĩa không null

Một ví dụ đơn giản ở đây là indexOf():

"abcde".indexOf("b"); // 1
"abcde".indexOf("f"); // -1

Thay vì null, bạn nhận được -1. Ở cấp độ kỹ thuật, đây là một giá trị số nguyên hợp lệ. Tuy nhiên, giá trị âm là một câu trả lời vô nghĩa vì các chỉ số được dự kiến ​​là số nguyên dương.

Về mặt logic, điều này chỉ có thể được sử dụng cho các trường hợp loại trả về cho phép nhiều giá trị có thể hơn mức có thể được trả về một cách có ý nghĩa (indexOf = số nguyên dương; int = số nguyên âm và số nguyên dương)

Đối với các loại tham chiếu, bạn vẫn có thể áp dụng nguyên tắc này. Ví dụ: nếu bạn có một thực thể có ID số nguyên, bạn có thể trả về một đối tượng giả với ID của nó được đặt thành -1, trái ngược với null.
Bạn chỉ cần xác định một "trạng thái giả" mà theo đó bạn có thể đo xem một đối tượng có thực sự có thể sử dụng được hay không.

Lưu ý rằng bạn không nên dựa vào các giá trị chuỗi được xác định trước nếu bạn không kiểm soát giá trị chuỗi. Bạn có thể cân nhắc đặt tên người dùng thành "null" khi bạn muốn trả lại một đối tượng trống, nhưng bạn sẽ gặp phải sự cố khi Christopher Null đăng ký dịch vụ của bạn .

3. Thay vào đó hãy ném một ngoại lệ.

Không, nhất quyết không. Các ngoại lệ không nên được xử lý cho luồng logic.

Điều đó đang được nói, có một số trường hợp bạn không muốn ẩn ngoại lệ (nullreference), nhưng thay vào đó hiển thị một ngoại lệ thích hợp. Ví dụ:

var existingPerson = personRepository.GetById(123);

Điều này có thể ném một PersonNotFoundException(hoặc tương tự) khi không có người có ID 123.

Rõ ràng, điều này chỉ được chấp nhận đối với các trường hợp không tìm thấy một mục khiến cho không thể thực hiện thêm bất kỳ xử lý nào. Nếu không tìm thấy một mục là có thể và ứng dụng có thể tiếp tục, thì bạn KHÔNG BAO GIỜ nên ném ngoại lệ . Tôi không thể nhấn mạnh điều này đủ.


5

Điểm tại sao null là xấu không phải là giá trị thiếu được mã hóa thành null, mà bất kỳ giá trị tham chiếu nào cũng có thể là null. Nếu bạn có C #, dù sao bạn cũng có thể bị mất. Tôi không thấy một điểm ro sử dụng một số Tùy chọn ở đó vì loại tham chiếu đã là tùy chọn. Nhưng đối với Java có các chú thích @Notnull, mà bạn dường như có thể thiết lập ở cấp gói , sau đó đối với các giá trị có thể bị bỏ lỡ, bạn có thể sử dụng chú thích @Nullable.


Vâng! Vấn đề là một mặc định vô nghĩa.
Ded repeatator

Tôi không đồng ý. Lập trình theo kiểu tránh null dẫn đến ít biến tham chiếu là null, dẫn đến ít biến tham chiếu không nên null. Nó không phải là 100% nhưng có thể là 90%, đáng để IMO.
Phục hồi Monica

1

Nulls không phải là xấu xa, thực tế không có cách nào để thực hiện bất kỳ giải pháp thay thế nào mà không có lúc nào đó xử lý một cái gì đó trông giống như một null.

Nulls tuy nhiên nguy hiểm . Cũng giống như bất kỳ cấu trúc cấp thấp nào khác nếu bạn hiểu sai, không có gì bên dưới để bắt bạn.

Tôi nghĩ rằng có rất nhiều đối số hợp lệ chống lại null:

  • Rằng nếu bạn đã ở trong một miền cấp cao, có những mẫu an toàn hơn so với sử dụng mô hình cấp thấp.

  • Trừ khi bạn cần những lợi thế của hệ thống cấp thấp và được chuẩn bị để thận trọng phù hợp thì có lẽ bạn không nên sử dụng ngôn ngữ cấp thấp.

  • v.v ...

Đây là tất cả hợp lệ và hơn nữa không phải làm cho nỗ lực viết getters và setters vv được coi là một lý do phổ biến không làm theo lời khuyên đó. Không có gì đáng ngạc nhiên khi nhiều lập trình viên đã đi đến kết luận rằng null là nguy hiểm và lười biếng (một sự kết hợp tồi tệ!), Nhưng giống như rất nhiều lời khuyên "phổ quát" khác, chúng không phổ biến như chúng được nói.

Những người giỏi viết ngôn ngữ nghĩ rằng họ sẽ có ích cho ai đó. Nếu bạn hiểu sự phản đối và vẫn nghĩ rằng chúng phù hợp với bạn, không ai khác sẽ biết gì hơn.


Vấn đề là, "lời khuyên phổ quát" thường không được gọi là "phổ quát" như mọi người tuyên bố. Nếu bạn tìm thấy nguồn gốc của lời khuyên như vậy, họ thường nói về những cảnh báo mà các lập trình viên có kinh nghiệm biết về. Điều gì xảy ra là, khi người khác đọc lời khuyên, họ có xu hướng bỏ qua những lời cảnh báo đó và chỉ nhớ phần "lời khuyên phổ quát", vì vậy khi họ liên hệ lời khuyên đó với người khác, nó phát ra nhiều "phổ quát" hơn so với dự định của người khởi tạo .
Nicol Bolas

1
@NicolBolas, bạn nói đúng, nhưng nguồn ban đầu không phải là lời khuyên mà hầu hết mọi người được trích dẫn, đó là thông điệp được chuyển tiếp. Điểm tôi muốn đưa ra chỉ đơn thuần là rất nhiều lập trình viên đã được nói 'không bao giờ làm X, X là xấu / xấu / bất cứ điều gì' và, chính xác như bạn nói, nó thiếu rất nhiều cảnh báo. Có thể họ đã bị đánh rơi, có thể họ không bao giờ có mặt, kết quả cuối cùng là như vậy.
drjpizzle

0

Java có một Optionallớp với ifPresent+ lambda rõ ràng để truy cập bất kỳ giá trị nào một cách an toàn. Điều này cũng có thể được thực hiện trong JS:

Xem câu hỏi StackOverflow này .

Nhân tiện: nhiều người theo chủ nghĩa thuần túy sẽ thấy cách sử dụng này đáng ngờ, nhưng tôi không nghĩ vậy. Các tính năng tùy chọn không tồn tại.


Không thể một tham chiếu đến một java.lang.Optionalthể nullquá?
Ded

@Ded repeatator Không, Optional.of(null)sẽ đưa ra một ngoại lệ, Optional.ofNullable(null)sẽ cho Optional.empty()- giá trị không có.
Eggen

Optional<Type> a = null?
Ded

@Ded repeatator đúng, nhưng ở đó bạn nên sử dụng Optional<Optional<Type>> a = Optional.empty();;) - Nói cách khác trong JS (và các ngôn ngữ khác) những điều đó sẽ không khả thi. Scala với các giá trị mặc định sẽ làm. Java có thể với một enum. JS tôi nghi ngờ: Optional<Type> a = Optional.empty();.
Eggen

Vì vậy, chúng tôi đã chuyển từ một chỉ định và tiềm năng nullhoặc trống rỗng, sang hai cộng với một số phương thức bổ sung trên đối tượng xen kẽ. Đó là một thành tích khá.
Ded

0

CAR Hoare gọi nulls là "sai lầm tỷ đô" của mình. Tuy nhiên, điều quan trọng cần lưu ý là ông nghĩ rằng giải pháp không phải là "không có giá trị ở bất cứ đâu" mà thay vào đó là "con trỏ không thể rỗng như mặc định và chỉ có giá trị khi chúng được yêu cầu về mặt ngữ nghĩa".

Vấn đề với null trong các ngôn ngữ lặp lại lỗi của anh ta là bạn không thể nói rằng một hàm mong đợi dữ liệu không có giá trị; về cơ bản, nó không thể diễn tả được bằng ngôn ngữ. Điều đó buộc các chức năng phải bao gồm rất nhiều nồi hơi xử lý null vô dụng, và quên kiểm tra null là một nguồn phổ biến của các lỗi.

Để hack xung quanh sự thiếu biểu cảm cơ bản đó, một số cộng đồng (ví dụ cộng đồng Scala) không sử dụng null. Trong Scala, nếu bạn muốn một giá trị loại nullable T, bạn lấy một đối số của loại Option[T]và nếu bạn muốn một giá trị không nullable, bạn chỉ cần lấy một T. Nếu ai đó chuyển null vào mã của bạn, mã của bạn sẽ nổ tung. Tuy nhiên, nó được coi là mã gọi là mã lỗi. Optiontuy nhiên, đây là một sự thay thế khá hay cho null, bởi vì các mẫu xử lý null thông thường được trích xuất vào các hàm thư viện như .map(f)hoặc .getOrElse(someDefault).

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.