ผมคิดว่าสรุปรวบรัดว่าทำไม null เป็นที่พึงปรารถนาคือการที่รัฐไม่มีความหมายไม่ควรจะแทนได้
สมมติว่าฉันกำลังสร้างแบบจำลองประตู มันสามารถเป็นหนึ่งในสามสถานะ: เปิดปิด แต่ปลดล็อคและปิดและล็อค ตอนนี้ฉันสามารถสร้างโมเดลตามแนวของ
class Door
private bool isShut
private bool isLocked
และชัดเจนว่าจะแมปสามสถานะของฉันเป็นตัวแปรบูลีนสองตัวนี้ได้อย่างไร isShut==false && isLocked==true
แต่ใบนี้เป็นหนึ่งในสี่ของรัฐที่ไม่พึงประสงค์ที่ใช้ได้: เนื่องจากประเภทที่ฉันเลือกเป็นตัวแทนของฉันยอมรับสถานะนี้ฉันต้องใช้ความพยายามทางจิตเพื่อให้แน่ใจว่าชั้นไม่เคยเข้าสู่สถานะนี้ (บางทีโดยการเข้ารหัสค่าคงที่) ในทางตรงกันข้ามถ้าฉันใช้ภาษาที่มีประเภทข้อมูลพีชคณิตหรือการแจกแจงแบบตรวจสอบที่ให้ฉันกำหนด
type DoorState =
| Open | ShutAndUnlocked | ShutAndLocked
จากนั้นฉันสามารถกำหนด
class Door
private DoorState state
และไม่ต้องกังวลอีกต่อไป ระบบประเภทจะทำให้มั่นใจได้ว่ามีเพียงสามสถานะที่เป็นไปได้สำหรับอินสแตนซ์class Door
ที่จะเข้ามานี่คือสิ่งที่ประเภทของระบบที่ดี - - พิจารณาข้อผิดพลาดทั้งชั้นในเวลารวบรวม
ปัญหาnull
คือว่าทุกประเภทอ้างอิงได้รับสถานะพิเศษนี้ในพื้นที่ของมันที่ไม่พึงประสงค์โดยทั่วไป string
ตัวแปรอาจจะมีลำดับของตัวอักษรใด ๆ หรือมันอาจจะเป็นพิเศษนี้บ้าnull
ค่าที่ไม่ map ในโดเมนปัญหาของฉัน Triangle
วัตถุมีสามPoint
s ซึ่งตัวเองมีX
และY
ค่า แต่โชคร้ายPoint
s หรือTriangle
ตัวเองอาจจะมีค่า Null นี้บ้าที่มีความหมายกับโดเมนกราฟผมทำงานใน. ฯลฯ
เมื่อคุณตั้งใจจะสร้างแบบจำลองค่าที่อาจไม่มีอยู่จริงคุณควรเลือกใช้อย่างชัดเจน หากวิธีที่ฉันตั้งใจจะทำตัวเป็นแบบคนก็คือทุกคนPerson
มี a FirstName
และ a LastName
แต่มีเพียงบางคนที่มีMiddleName
s แล้วฉันอยากจะพูดอะไรบางอย่างเช่น
class Person
private string FirstName
private Option<string> MiddleName
private string LastName
โดยที่string
นี่ถือว่าเป็นประเภทที่ไม่สามารถลบล้างได้ จากนั้นไม่มีค่าคงที่ที่ยุ่งยากในการสร้างและไม่คาดคิดNullReferenceException
เมื่อพยายามคำนวณความยาวของชื่อของใครบางคน ระบบประเภททำให้มั่นใจได้ว่ารหัสใด ๆ ที่เกี่ยวข้องกับMiddleName
บัญชีเพื่อความเป็นไปได้ของมันNone
ในขณะที่รหัสใด ๆ ที่จัดการกับFirstName
สามารถสันนิษฐานได้อย่างปลอดภัยว่ามีค่าอยู่ที่นั่น
ตัวอย่างเช่นเมื่อใช้ประเภทด้านบนเราสามารถเขียนฟังก์ชันโง่ ๆ นี้ได้:
let TotalNumCharsInPersonsName(p:Person) =
let middleLen = match p.MiddleName with
| None -> 0
| Some(s) -> s.Length
p.FirstName.Length + middleLen + p.LastName.Length
ไม่ต้องกังวล ในทางตรงกันข้ามในภาษาที่มีการอ้างอิง nullable สำหรับประเภทเช่นสตริงแล้วถือว่า
class Person
private string FirstName
private string MiddleName
private string LastName
คุณจบลงด้วยการเขียนสิ่งต่าง ๆ เช่น
let TotalNumCharsInPersonsName(p:Person) =
p.FirstName.Length + p.MiddleName.Length + p.LastName.Length
ซึ่งระเบิดขึ้นหากวัตถุบุคคลที่เข้ามาไม่มีค่าคงที่ของทุกสิ่งที่ไม่เป็นโมฆะหรือ
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)
หรืออาจจะ
let TotalNumCharsInPersonsName(p:Person) =
p.FirstName.Length
+ (if p.MiddleName=null then 0 else p.MiddleName.Length)
+ p.LastName.Length
สมมติว่าp
ทำให้แน่ใจว่าเป็นคนแรก / คนสุดท้าย แต่ตรงกลางอาจเป็นโมฆะหรือคุณอาจตรวจสอบว่ามีข้อยกเว้นประเภทต่าง ๆ หรือใครจะรู้ ตัวเลือกการนำไปใช้ที่บ้าคลั่งเหล่านี้และสิ่งต่าง ๆ ที่คิดเกี่ยวกับการครอบตัดเพราะมันมีคุณค่าอันน่าทึ่งที่คุณไม่ต้องการหรือต้องการ
โดยปกติแล้ว Null จะเพิ่มความซับซ้อนที่ไม่จำเป็น ความซับซ้อนเป็นศัตรูของซอฟต์แวร์ทั้งหมดและคุณควรพยายามลดความซับซ้อนเมื่อใดก็ตามที่สมเหตุสมผล
(โปรดทราบว่ามีความซับซ้อนมากยิ่งขึ้นสำหรับตัวอย่างง่ายๆเหล่านี้แม้ว่า a FirstName
ไม่สามารถทำได้null
a string
สามารถเป็นตัวแทน""
(สตริงว่าง) ซึ่งอาจเป็นชื่อบุคคลที่เราตั้งใจจะทำเช่นนั้นแม้ว่าจะไม่ใช่ สตริงที่ไม่มีค่าอาจเป็นกรณีที่เราเป็น "แทนค่าที่ไม่มีความหมาย" อีกครั้งคุณสามารถเลือกที่จะต่อสู้กับสิ่งนี้ผ่านค่าคงที่และรหัสเงื่อนไขที่รันไทม์หรือโดยใช้ระบบพิมพ์ (เช่นมีNonEmptyString
ประเภท) ส่วนหลังอาจไม่เหมาะสม (ประเภท "ดี" มักจะ "ปิด" เหนือชุดการดำเนินงานทั่วไปและเช่นNonEmptyString
ไม่ได้ปิด.SubString(0,0)
) แต่มันแสดงให้เห็นถึงจุดมากขึ้นในพื้นที่การออกแบบ ในตอนท้ายของวันในระบบประเภทใดก็ตามมีความซับซ้อนที่จะกำจัดได้ดีและความซับซ้อนอื่น ๆ ที่ยากยิ่งกว่าในการกำจัด กุญแจสำคัญสำหรับหัวข้อนี้คือในเกือบทุกระบบประเภทการเปลี่ยนจาก "การอ้างอิงแบบ nullable โดยค่าเริ่มต้น" เป็น "การอ้างอิงแบบไม่เป็นโมฆะโดยค่าเริ่มต้น" เป็นการเปลี่ยนแปลงที่เรียบง่ายซึ่งทำให้ระบบการพิมพ์มีความซับซ้อนมากขึ้น พิจารณาข้อผิดพลาดบางประเภทและสถานะที่ไม่มีความหมาย ดังนั้นมันจึงค่อนข้างบ้าที่หลายภาษาทำซ้ำข้อผิดพลาดนี้ซ้ำแล้วซ้ำอีก)