Tôi nghĩ rằng tóm tắt ngắn gọn về lý do tại sao null là không mong muốn là các trạng thái vô nghĩa không nên được đại diện .
Giả sử tôi đang làm mẫu một cánh cửa. Nó có thể ở một trong ba trạng thái: mở, đóng nhưng mở khóa và đóng và khóa. Bây giờ tôi có thể mô hình hóa nó dọc theo dòng
class Door
private bool isShut
private bool isLocked
và rõ ràng làm thế nào để ánh xạ ba trạng thái của tôi vào hai biến boolean này. Nhưng điều này để lại một trạng thái thứ tư, không mong muốn có sẵn : isShut==false && isLocked==true
. Bởi vì các loại tôi đã chọn làm đại diện của mình thừa nhận trạng thái này, tôi phải dành nỗ lực tinh thần để đảm bảo rằng lớp không bao giờ rơi vào trạng thái này (có lẽ bằng cách mã hóa rõ ràng một bất biến). Ngược lại, nếu tôi đang sử dụng ngôn ngữ có kiểu dữ liệu đại số hoặc bảng liệt kê được kiểm tra cho phép tôi xác định
type DoorState =
| Open | ShutAndUnlocked | ShutAndLocked
sau đó tôi có thể định nghĩa
class Door
private DoorState state
và không còn lo lắng nữa. Hệ thống loại sẽ đảm bảo rằng chỉ có ba trạng thái có thể xảy ra đối với một trường hợp class Door
. Đây là loại hệ thống nào tốt - loại trừ rõ ràng cả một loại lỗi tại thời điểm biên dịch.
Vấn đề với null
là mọi loại tham chiếu đều có trạng thái bổ sung này trong không gian của nó thường không mong muốn. Một string
biến có thể là bất kỳ chuỗi ký tự nào, hoặc nó có thể là null
giá trị bổ sung điên rồ này không ánh xạ vào miền vấn đề của tôi. Một Triangle
đối tượng có ba Point
s, bản thân chúng có X
và Y
giá trị, nhưng thật không may Point
, Triangle
chính nó hoặc có thể là giá trị null điên rồ này vô nghĩa đối với miền đồ thị mà tôi đang làm việc. V.v.
Khi bạn có ý định mô hình hóa một giá trị có thể không tồn tại, thì bạn nên chọn tham gia một cách rõ ràng. Nếu cách tôi định làm người mẫu là mọi người đều Person
có một FirstName
và một LastName
, nhưng chỉ một số người có MiddleName
s, thì tôi muốn nói điều gì đó như
class Person
private string FirstName
private Option<string> MiddleName
private string LastName
trong đó string
ở đây được coi là một loại không nullable. Sau đó, không có bất biến khó khăn nào để thiết lập và không có bất ngờ NullReferenceException
nào khi cố gắng tính độ dài của tên người. Hệ thống loại đảm bảo rằng bất kỳ mã nào xử lý các MiddleName
tài khoản cho khả năng của nó None
, trong khi bất kỳ mã nào xử lý FirstName
một cách an toàn đều có thể có giá trị ở đó.
Vì vậy, ví dụ, bằng cách sử dụng loại ở trên, chúng ta có thể tạo ra hàm ngớ ngẩn này:
let TotalNumCharsInPersonsName(p:Person) =
let middleLen = match p.MiddleName with
| None -> 0
| Some(s) -> s.Length
p.FirstName.Length + middleLen + p.LastName.Length
không phải lo lắng Ngược lại, trong một ngôn ngữ có tham chiếu nullable cho các loại như chuỗi, sau đó giả sử
class Person
private string FirstName
private string MiddleName
private string LastName
bạn kết thúc tác giả như
let TotalNumCharsInPersonsName(p:Person) =
p.FirstName.Length + p.MiddleName.Length + p.LastName.Length
sẽ nổ tung nếu đối tượng Person đến không có bất biến mọi thứ đều không rỗng, hoặc
let TotalNumCharsInPersonsName(p:Person) =
(if p.FirstName=null then 0 else p.FirstName.Length)
+ (if p.MiddleName=null then 0 else p.MiddleName.Length)
+ (if p.LastName=null then 0 else p.LastName.Length)
hoặc có thể
let TotalNumCharsInPersonsName(p:Person) =
p.FirstName.Length
+ (if p.MiddleName=null then 0 else p.MiddleName.Length)
+ p.LastName.Length
giả sử rằng p
đảm bảo đầu tiên / cuối cùng là có nhưng giữa có thể là null hoặc có thể bạn thực hiện kiểm tra đưa ra các loại ngoại lệ khác nhau, hoặc ai biết cái gì. Tất cả những lựa chọn thực hiện điên rồ này và những điều cần suy nghĩ về việc trồng trọt bởi vì có giá trị đại diện ngu ngốc này mà bạn không muốn hoặc không cần.
Null thường thêm phức tạp không cần thiết. Sự phức tạp là kẻ thù của tất cả các phần mềm, và bạn nên cố gắng giảm độ phức tạp bất cứ khi nào hợp lý.
(Lưu ý rằng có nhiều sự phức tạp hơn ngay cả với những ví dụ đơn giản này. Ngay cả khi FirstName
không thể null
, một chuỗi string
có thể đại diện ""
(chuỗi trống), có lẽ cũng không phải là tên người mà chúng tôi dự định mô hình. Như vậy, ngay cả với không phải Các chuỗi không thể, vẫn có thể là trường hợp chúng ta "đại diện cho các giá trị vô nghĩa". Một lần nữa, bạn có thể chọn chiến đấu với điều này thông qua bất biến và mã điều kiện trong thời gian chạy hoặc bằng cách sử dụng hệ thống loại (ví dụ: có một NonEmptyString
loại). sau này có lẽ không được khuyến khích (các loại "tốt" thường bị "đóng" trong một tập hợp các hoạt động chung và ví dụ: NonEmptyString
không được đóng lại.SubString(0,0)
), nhưng nó thể hiện nhiều điểm hơn trong không gian thiết kế. Vào cuối ngày, trong bất kỳ hệ thống loại nhất định nào, có một số phức tạp sẽ rất tốt trong việc loại bỏ, và sự phức tạp khác thực sự khó loại bỏ hơn. Chìa khóa cho chủ đề này là trong gần như mọi hệ thống loại, sự thay đổi từ "tham chiếu không thể theo mặc định" thành "tham chiếu không thể rỗng theo mặc định" gần như luôn là một thay đổi đơn giản giúp hệ thống loại tốt hơn rất nhiều trong việc chống lại sự phức tạp và loại trừ một số loại lỗi và trạng thái vô nghĩa. Vì vậy, thật điên rồ khi nhiều ngôn ngữ cứ lặp đi lặp lại lỗi này.)