การอ้างอิง Null เป็นสิ่งที่เลวร้ายจริง ๆ หรือไม่?


161

ฉันได้ยินมาว่าการรวมการอ้างอิงโมฆะในภาษาการเขียนโปรแกรมเป็น "ความผิดพลาดพันล้านดอลลาร์" แต่ทำไม แน่นอนว่าพวกเขาสามารถทำให้ NullReferenceExceptions ได้ แต่อย่างนั้น องค์ประกอบของภาษาใด ๆ สามารถเป็นแหล่งของข้อผิดพลาดหากใช้ไม่ถูกต้อง

และทางเลือกคืออะไร? ฉันคิดว่าแทนที่จะพูดสิ่งนี้:

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

คุณสามารถพูดสิ่งนี้:

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

แต่จะดีกว่าได้อย่างไร ไม่ว่าด้วยวิธีใดถ้าคุณลืมตรวจสอบว่ามีลูกค้าอยู่คุณจะได้รับการยกเว้น

ฉันสมมติว่า CustomerNotFoundException นั้นง่ายต่อการตรวจแก้จุดบกพร่องเล็กน้อยกว่า NullReferenceException โดยอาศัยการอธิบายเพิ่มเติม นั่นคือทั้งหมดที่มีไปหรือไม่


54
ฉันจะระวัง "ถ้าลูกค้ามีอยู่แล้วรับลูกค้า" ทันทีที่คุณเขียนโค้ดสองบรรทัดเช่นนั้นคุณกำลังเปิดประตูสำหรับสภาพการแข่งขัน รับแล้วตรวจสอบข้อยกเว้น null / catch ที่ปลอดภัยกว่า
Carson63000

26
หากเพียงเราสามารถกำจัดแหล่งที่มาของข้อผิดพลาดรันไทม์ทั้งหมดรหัสของเราจะรับประกันว่าจะสมบูรณ์แบบ! ถ้าการอ้างอิง NULL ใน C # ทำให้สมองของคุณผิดลองใช้ตัวชี้ที่ไม่ใช่ NULL แต่ไม่ใช่ตัวชี้ใน C;)
LnxPrgr3

มีการรวมกันเป็นโมฆะและตัวดำเนินการนำทางที่ปลอดภัยในบางภาษาว่า
jk

35
null ต่อ se ไม่เลว ระบบประเภทที่ไม่สร้างความแตกต่างระหว่างประเภท T และประเภท T + null นั้นไม่ดี
Ingo

27
กลับมาที่คำถามของตัวเองไม่กี่ปีต่อมาตอนนี้ฉันเปลี่ยนเป็นตัวเลือก / วิธีการทั้งหมด
ทิมกู๊ดแมน

คำตอบ:


91

null เป็นความชั่วร้าย

มีการนำเสนอใน InfoQ ในหัวข้อนี้: Null การอ้างอิง: ผิดพลาดพันล้านดอลลาร์โดยTony Hoare

ประเภทตัวเลือก

ทางเลือกจากการเขียนโปรแกรมการทำงานจะใช้ประเภทตัวเลือกที่สามารถมีหรือSOME valueNONE

บทความที่ดีรูปแบบ“ ตัวเลือก”ที่กล่าวถึงประเภทตัวเลือกและจัดให้มีการติดตั้งสำหรับ Java

ฉันยังได้พบข้อผิดพลาดรายงานสำหรับ Java เกี่ยวกับปัญหานี้: เพิ่มประเภทการเลือกที่ดีที่จะ Java เพื่อป้องกันไม่ให้ NullPointerExceptions คุณลักษณะที่ต้องการได้รับการแนะนำในชวา 8


5
Nullable <x> ใน C # เป็นแนวคิดที่คล้ายกัน แต่สั้นไปกว่าการใช้รูปแบบตัวเลือก สาเหตุหลักคือใน. NET System.Nullable ถูก จำกัด ประเภทของค่า
MattDavey

27
+1 สำหรับประเภทตัวเลือก หลังจากทำความคุ้นเคยกับ Haskell's บางทีโมฆะเริ่มดูแปลก ๆ ...
Andres F.

5
นักพัฒนาสามารถสร้างข้อผิดพลาดได้ทุกที่ดังนั้นแทนที่จะเป็นข้อยกเว้นตัวชี้โมฆะคุณจะได้รับข้อยกเว้นตัวเลือกที่ว่างเปล่า วิธีหลังดีกว่าอดีตหรือไม่
greenoldman

7
@greenoldman ไม่มีสิ่งเช่น "ข้อยกเว้นตัวเลือกที่ว่างเปล่า" ลองแทรกค่า NULL ในคอลัมน์ฐานข้อมูลที่กำหนดเป็น NOT NULL
Jonas

36
@greenoldman ปัญหาเกี่ยวกับ nulls คือทุกสิ่งสามารถเป็นโมฆะและในฐานะนักพัฒนาที่คุณต้องระมัดระวังเป็นพิเศษ Stringประเภทไม่ได้จริงๆสตริง มันเป็นString or Nullประเภท ภาษาที่ยึดตามแนวคิดของประเภทตัวเลือกไม่อนุญาตให้มีค่า Null ในระบบชนิดของพวกเขารับประกันว่าถ้าคุณกำหนดประเภทของลายเซ็นที่ต้องการString(หรืออย่างอื่น) มันจะต้องมีค่า Optionประเภทจะมีการให้การแสดงประเภทระดับของสิ่งที่อาจหรือไม่อาจมีค่าเพื่อให้คุณรู้ว่าคุณต้องชัดเจนจัดการกับสถานการณ์เหล่านั้น
KChaloux

127

ปัญหาคือเนื่องจากในทางทฤษฎีวัตถุใด ๆ สามารถเป็นโมฆะและโยนข้อยกเว้นเมื่อคุณพยายามใช้รหัสเชิงวัตถุของคุณนั้นเป็นชุดของระเบิดที่ไม่ได้ระเบิด

คุณพูดถูกว่าการจัดการข้อผิดพลาดที่สง่างามนั้นสามารถทำงานได้เหมือนกับifคำสั่งตรวจสอบค่าว่าง แต่สิ่งที่เกิดขึ้นเมื่อสิ่งที่คุณเชื่อว่าตัวเองไม่อาจจะเป็นโมฆะในความเป็นจริง null หรือไม่? Kerboom ไม่ว่าจะเกิดอะไรขึ้นต่อไปฉันยินดีที่จะเดิมพันว่า 1) มันจะไม่สง่างามและ 2) คุณจะไม่ชอบมัน

และอย่ายกเลิกค่าของ "ง่ายต่อการดีบัก" รหัสการผลิตสำหรับผู้ใหญ่เป็นสิ่งมีชีวิตที่บ้าคลั่งแผ่กิ่งก้านสาขา อะไรก็ตามที่ทำให้คุณเข้าใจมากขึ้นถึงสิ่งที่ผิดพลาดและสถานที่ที่อาจช่วยให้คุณประหยัดเวลาขุด


27
+1 โดยปกติแล้วข้อผิดพลาดในรหัสการผลิตจะต้องมีการระบุโดยเร็วที่สุดเพื่อให้สามารถแก้ไขได้ (โดยปกติจะเป็นข้อผิดพลาดข้อมูล) ยิ่งแก้ปัญหาได้ง่ายเท่าไหร่ก็ยิ่งดีเท่านั้น

4
คำตอบนี้เหมือนกันถ้านำไปใช้กับตัวอย่างของ OP อย่างใดอย่างหนึ่ง ไม่ว่าคุณจะทดสอบเงื่อนไขข้อผิดพลาดหรือไม่และคุณจัดการกับมันอย่างสง่างามหรือไม่ กล่าวอีกนัยหนึ่งการลบการอ้างอิงที่เป็นโมฆะจากภาษาจะไม่เปลี่ยนคลาสของข้อผิดพลาดที่คุณจะได้รับ แต่จะเปลี่ยนการแสดงข้อผิดพลาดเหล่านั้นอย่างละเอียด
dash-tom-bang

19
+1 สำหรับระเบิดที่ไม่ได้ระเบิด ด้วยการอ้างอิงที่เป็นโมฆะการพูดpublic Foo doStuff()ไม่ได้หมายความว่า "ทำสิ่งต่าง ๆ และส่งคืนผลลัพธ์ Foo" หมายความว่า "ทำสิ่งต่างๆและส่งกลับผลลัพธ์ Foo หรือส่งคืนผลลัพธ์เป็นโมฆะ แต่คุณไม่รู้ว่าจะเกิดขึ้นหรือไม่" ผลที่ได้คือคุณต้องตรวจสอบค่าว่างทุกที่เพื่อให้แน่ใจ อาจลบ nulls และใช้ชนิดหรือรูปแบบพิเศษ (เช่นชนิดของค่า) เพื่อระบุค่าที่ไม่มีความหมาย
Matt Olenik

12
@greenoldman ตัวอย่างของคุณมีประสิทธิภาพเป็นสองเท่าในการแสดงให้เห็นถึงจุดของเราในการใช้ประเภทดั้งเดิม (ไม่ว่าจะเป็นโมฆะหรือจำนวนเต็ม) เนื่องจากแบบจำลองความหมายเป็นการปฏิบัติที่ไม่ดี หากคุณมีประเภทที่ตัวเลขติดลบไม่ใช่คำตอบที่ถูกต้องคุณควรสร้างคลาสประเภทใหม่เพื่อใช้โมเดลเชิงความหมายด้วยความหมายนั้น ตอนนี้รหัสทั้งหมดเพื่อจัดการกับการสร้างแบบจำลองของชนิดที่ไม่เป็นลบนั้นจะถูกแปลเป็นคลาสของมันแยกจากส่วนที่เหลือของระบบและความพยายามใด ๆ ในการสร้างค่าลบสำหรับมันสามารถถูกจับได้ทันที
Huperniketes

9
@greenoldman คุณยังคงพิสูจน์ให้เห็นว่าประเภทดั้งเดิมนั้นไม่เพียงพอสำหรับการสร้างแบบจำลอง ครั้งแรกมันเป็นจำนวนลบมากกว่า ตอนนี้มันอยู่เหนือตัวดำเนินการหารเมื่อศูนย์เป็นตัวหาร หากคุณรู้ว่าคุณไม่สามารถหารด้วยศูนย์ได้คุณสามารถทำนายผลลัพธ์ได้ว่าจะไม่สวยและมีความรับผิดชอบในการทำให้แน่ใจว่าคุณทดสอบและให้ค่า "ถูกต้อง" ที่เหมาะสมสำหรับกรณีนั้น นักพัฒนาซอฟต์แวร์มีหน้าที่รับผิดชอบในการรู้พารามิเตอร์ที่ใช้ในการทำงานของรหัสและการเขียนโค้ดอย่างเหมาะสม มันเป็นสิ่งที่แยกวิศวกรรมจากการซ่อม
Huperniketes

50

มีปัญหาหลายประการในการใช้การอ้างอิงแบบ null ในโค้ด

ครั้งแรกก็ใช้โดยทั่วไปเพื่อระบุสถานะพิเศษ แทนที่จะกำหนดชั้นเรียนใหม่หรือคงที่สำหรับแต่ละรัฐเป็นเฉพาะจะทำตามปกติโดยใช้อ้างอิงโมฆะใช้lossy ประเภททั่วไปอย่างหนาแน่น

ประการที่สองการดีบักรหัสกลายเป็นเรื่องยากมากขึ้นเมื่อมีการอ้างอิงที่เป็นโมฆะปรากฏขึ้นและคุณพยายามกำหนดสิ่งที่สร้างขึ้นซึ่งสถานะนั้นมีผลและสาเหตุของมันแม้ว่าคุณจะสามารถติดตามเส้นทางการดำเนินการอัปสตรีมได้

ประการที่สามการอ้างอิง null แนะนำเส้นทางรหัสเพิ่มเติมในการทดสอบ

ประการที่สี่เมื่อการอ้างอิงโมฆะถูกใช้เป็นสถานะที่ถูกต้องสำหรับพารามิเตอร์รวมถึงค่าส่งคืนการตั้งโปรแกรมการป้องกัน (สำหรับสถานะที่เกิดจากการออกแบบ) ต้องมีการตรวจสอบการอ้างอิงโมฆะเพิ่มเติมในสถานที่ต่าง ๆ ... ในกรณี

ประการที่ห้ารันไทม์ของภาษาทำการตรวจสอบชนิดแล้วเมื่อทำการเลือกตัวเลือกบนตารางวิธีการของวัตถุ ดังนั้นคุณพยายามทำซ้ำโดยการตรวจสอบว่าประเภทของวัตถุนั้นถูกต้อง / ไม่ถูกต้องหรือไม่จากนั้นให้รันไทม์ตรวจสอบประเภทของวัตถุที่ถูกต้องเพื่อเรียกใช้วิธีการ

ทำไมไม่ใช้รูปแบบ NullObjectเพื่อใช้ประโยชน์จากการตรวจสอบรันไทม์เพื่อเรียกใช้เมธอดNOPเฉพาะสำหรับสถานะนั้น (สอดคล้องกับอินเตอร์เฟสของสถานะปกติ) ในขณะเดียวกันก็กำจัดการตรวจสอบพิเศษทั้งหมดสำหรับการอ้างอิง null ตลอดทั้งโค้ดเบสของคุณด้วย?

มันเกี่ยวข้องกับการทำงานมากขึ้นโดยการสร้างคลาส NullObject สำหรับแต่ละอินเตอร์เฟสที่คุณต้องการแสดงสถานะพิเศษ แต่อย่างน้อยก็มีการแยกความเชี่ยวชาญเป็นพิเศษแต่ละรัฐมากกว่ารหัสที่รัฐอาจมีอยู่ IOW จำนวนการทดสอบจะลดลงเนื่องจากคุณมีเส้นทางการดำเนินการทางเลือกน้อยลงในวิธีการของคุณ


7
... เพราะการหาว่าใครเป็นผู้สร้าง (หรือไม่สนใจที่จะเปลี่ยนหรือเริ่มต้น) ข้อมูลขยะที่เติมวัตถุที่มีประโยชน์ของคุณควรจะง่ายกว่าการติดตามการอ้างอิง NULL? ฉันไม่ซื้อแม้ว่าฉันจะติดอยู่ในดินแดนที่เป็นแบบคงที่
dash-tom-bang

แม้ว่าข้อมูลขยะจะหายากกว่าการอ้างอิงแบบ null มาก โดยเฉพาะอย่างยิ่งในภาษาที่มีการจัดการ
Dean Harding

5
-1 สำหรับ Null Object
DeadMG

4
คะแนน (4) และ (5) นั้นแข็งแกร่ง แต่ NullObject ไม่ใช่วิธีการรักษา คุณจะตกอยู่ในกับดักที่แย่ยิ่งกว่า - ข้อผิดพลาดด้านลอจิก เมื่อคุณทราบสถานการณ์ปัจจุบันแน่นอนว่าสามารถช่วยได้ แต่การใช้อย่างสุ่มสี่สุ่มห้า (แทนที่จะเป็นโมฆะ) จะทำให้คุณเสียค่าใช้จ่ายมากขึ้น
greenoldman

1
@ gnasher729 ในขณะที่รันไทม์ของ Objective-C จะไม่ทำให้เกิดข้อยกเว้น null-pointer เช่น Java และระบบอื่น ๆ มันไม่ได้ใช้การอ้างอิงแบบ null ใด ๆ ที่ดีกว่าสำหรับเหตุผลที่ฉันอธิบายไว้ในคำตอบของฉัน ตัวอย่างเช่นหากไม่มี navigationController ใน [self.navigationController.presentingViewController dismissViewControllerAnimated: YES completion: nil];รันไทม์ถือว่าเป็นลำดับที่ไม่มี ops มุมมองจะไม่ถูกลบออกจากหน้าจอและคุณอาจนั่งอยู่ด้านหน้า Xcode เกาหัวเป็นเวลาหลายชั่วโมงเพื่อหาสาเหตุว่าทำไม
Huperniketes

48

Null ไม่ได้เลวร้ายนักนอกจากว่าคุณไม่ได้คาดหวัง คุณควรจะต้องระบุในรหัสที่คุณคาดหวังว่าเป็นโมฆะอย่างชัดเจนซึ่งเป็นปัญหาในการออกแบบภาษา พิจารณาสิ่งนี้:

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

หากคุณพยายามเรียกใช้เมธอดบน a Customer?คุณควรได้รับข้อผิดพลาดในการคอมไพล์ เหตุผลที่หลายภาษาไม่ทำเช่นนี้ (IMO) ก็คือพวกเขาไม่ได้ให้ประเภทของตัวแปรที่จะเปลี่ยนแปลงทั้งนี้ขึ้นอยู่กับขอบเขตที่มีอยู่หากภาษาสามารถจัดการกับปัญหานั้นได้ ระบบประเภท

นอกจากนี้ยังมีวิธีการทำงานในการจัดการปัญหานี้โดยใช้ตัวเลือกประเภทและMaybeแต่ฉันไม่คุ้นเคยกับมัน ฉันชอบวิธีนี้เพราะรหัสที่ถูกต้องในทางทฤษฎีควรจะต้องมีเพียงหนึ่งอักขระที่เพิ่มเข้ามาเพื่อคอมไพล์อย่างถูกต้อง


11
ว้าวมันเท่ห์มาก นอกเหนือจากการรวบรวมข้อผิดพลาดจำนวนมากในเวลารวบรวมแล้วมันทำให้ฉันไม่ต้องตรวจสอบเอกสารเพื่อหาว่าGetByLastNameผลตอบแทนเป็นโมฆะถ้าไม่พบ (ตรงข้ามกับการโยนข้อยกเว้น) - ฉันสามารถดูว่ามันส่งคืนCustomer?หรือCustomerไม่
ทิมกู๊ดแมน

14
@JBRWilkinson: นี่เป็นเพียงตัวอย่างที่ทำให้สุกเพื่อแสดงความคิดไม่ใช่ตัวอย่างของการนำไปปฏิบัติจริง จริง ๆ แล้วฉันคิดว่ามันเป็นความคิดที่ค่อนข้างประณีต
Dean Harding

1
คุณพูดถูกว่าไม่ใช่รูปแบบวัตถุว่างเปล่า
Dean Harding

13
นี้ดูเหมือนว่าสิ่งที่ Haskell เรียกMaybe- เป็นMaybe Customerอย่างใดอย่างหนึ่งJust c(ที่cเป็นCustomer) Nothingหรือ ในภาษาอื่น ๆ มีการเรียกOption- ดูคำตอบอื่น ๆ ของคำถามนี้
MatrixFrog

2
@SimonBarker: คุณสามารถมั่นใจได้ว่าCustomer.FirstNameเป็นประเภทString(ตรงข้ามกับString?) นั่นเป็นผลประโยชน์ที่แท้จริง - เพียงตัวแปร / คุณสมบัติที่มีค่าอะไรที่มีความหมายเท่านั้นที่ควรประกาศว่าไร้ค่า
Yogu

20

มีคำตอบที่ยอดเยี่ยมมากมายที่ครอบคลุมถึงอาการที่โชคร้ายnullดังนั้นฉันจึงขอเสนอการโต้แย้งทางเลือก: Null เป็นข้อบกพร่องในระบบการพิมพ์

วัตถุประสงค์ของระบบประเภทคือเพื่อให้แน่ใจว่าส่วนประกอบที่แตกต่างกันของโปรแกรม "เข้าด้วยกัน" เหมาะสม; โปรแกรมที่พิมพ์ได้ดีไม่สามารถ "ปิดราง" เข้ากับพฤติกรรมที่ไม่ได้กำหนด

พิจารณาภาษาถิ่นสมมุติของ Java หรือภาษาที่คุณต้องการพิมพ์แบบคงที่ซึ่งคุณสามารถกำหนดสตริง"Hello, world!"ให้กับตัวแปรประเภทใดก็ได้:

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

และคุณสามารถตรวจสอบตัวแปรดังนี้:

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

ไม่มีอะไรที่เป็นไปไม่ได้เกี่ยวกับเรื่องนี้ - บางคนสามารถออกแบบภาษาได้ถ้าพวกเขาต้องการ ค่าพิเศษที่ไม่จำเป็นต้องเป็น"Hello, world!"- มันอาจจะได้รับหมายเลข 42, tuple หรือพูด(1, 4, 9) nullแต่ทำไมคุณถึงทำเช่นนี้? ตัวแปรประเภทFooควรเก็บFoos เท่านั้น- นั่นคือจุดรวมของระบบประเภท! nullไม่ได้เป็นFooมากกว่าที่"Hello, world!"เป็นอยู่ ที่เลวร้ายยิ่งกว่าnullนั้นไม่ได้มีค่าสำหรับประเภทใดและไม่มีอะไรที่คุณสามารถทำได้!

โปรแกรมเมอร์ไม่สามารถมั่นใจได้ว่าตัวแปรมีอยู่จริงFooและไม่สามารถโปรแกรมได้ เพื่อหลีกเลี่ยงพฤติกรรมที่ไม่ได้กำหนดจะต้องตรวจสอบตัวแปร"Hello, world!"ก่อนใช้เป็นFoos โปรดทราบว่าการดำเนินการตรวจสอบสตริงในข้อมูลโค้ดก่อนหน้านั้นไม่ได้เผยแพร่ข้อเท็จจริงที่ว่า foo1 เป็นจริงFoo- barน่าจะมีเช็คของตัวเองเช่นกันเพื่อความปลอดภัย

เปรียบเทียบกับการใช้ a Maybe/ Optionกับการจับคู่รูปแบบ:

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

ภายในJust fooประโยคทั้งคุณและโปรแกรมรู้ว่าMaybe Fooตัวแปรของเรามีค่าอย่างแท้จริงFoo- ข้อมูลนั้นแพร่กระจายลงในห่วงโซ่การโทรและbarไม่จำเป็นต้องทำการตรวจสอบใด ๆ เพราะMaybe Fooเป็นชนิดที่แตกต่างจากFooที่คุณกำลังบังคับให้จัดการกับความเป็นไปได้ว่ามันอาจจะมีเพื่อให้คุณไม่สามารถโดยซีกโดยNothing NullPointerExceptionคุณสามารถให้เหตุผลเกี่ยวกับโปรแกรมของคุณได้ง่ายขึ้นและคอมไพเลอร์สามารถละเว้นการตรวจสอบโมฆะโดยที่รู้ว่าตัวแปรทุกชนิดFooมีFoos จริงๆ ทุกคนชนะ


สิ่งที่เกี่ยวกับค่าเหมือนโมฆะใน Perl 6? พวกเขายังคงเป็นโมฆะยกเว้นพวกเขาจะพิมพ์เสมอ ก็ยังคงเป็นปัญหาที่คุณสามารถเขียนmy Int $variable = Intที่Intคุ้มค่า null ประเภทInt?
Konrad Borowski

@xfix ฉันไม่คุ้นเคยกับ Perl แต่ตราบใดที่คุณไม่ได้มีวิธีการบังคับใช้this type only contains integersเมื่อเทียบกับthis type contains integers and this other valueคุณจะมีปัญหาเวลาที่คุณทุกเพียงต้องการจำนวนเต็ม
Doval

1
+1 สำหรับการจับคู่รูปแบบ ด้วยจำนวนคำตอบข้างต้นที่กล่าวถึงประเภทตัวเลือกคุณคิดว่ามีคนพูดถึงความจริงที่ว่าคุณลักษณะภาษาอย่างง่ายเช่นตัวจับคู่รูปแบบสามารถทำให้พวกเขาใช้งานได้ง่ายกว่า null
จูลส์

1
ฉันคิดว่าการเชื่อมแบบ monadic นั้นมีประโยชน์มากกว่าการจับคู่รูปแบบ (แม้ว่าแน่นอนว่าการเชื่อมโยงสำหรับบางทีใช้กับการจับคู่รูปแบบ) ฉันคิดว่ามันเป็นชนิดของสนุกเห็นภาษาเช่น C # วางไวยากรณ์พิเศษสำหรับจำนวนมากของสิ่งที่ ( ??, ?.และอื่น ๆ ) สำหรับสิ่งที่ภาษาเช่น Haskell ใช้ฟังก์ชั่นเป็นห้องสมุด ฉันคิดว่ามันประณีตกว่านี้ null/ Nothingการเผยแพร่ไม่ได้ "พิเศษ" ในทางใดทางหนึ่งดังนั้นทำไมเราต้องเรียนรู้ไวยากรณ์เพิ่มเติมเพื่อให้ได้?
ร่า

14

(ขว้างหมวกของฉันใส่แหวนเพื่อตั้งคำถาม))

ปัญหาเฉพาะกับ null คือมันหยุดการพิมพ์แบบคงที่

หากฉันมีThing tคอมไพเลอร์สามารถรับประกันได้ว่าฉันสามารถโทรt.doSomething()ได้ ดี UNLESS tเป็นโมฆะในขณะทำงาน ตอนนี้การเดิมพันทั้งหมดจะปิด คอมไพเลอร์กล่าวว่ามันเป็น OK แต่ผมพบว่าออกมากในภายหลังว่าไม่ได้ในความเป็นจริงt doSomething()ดังนั้นแทนที่จะสามารถไว้วางใจคอมไพเลอร์ให้ตรวจจับข้อผิดพลาดประเภทฉันต้องรอจนกระทั่งรันไทม์เพื่อตรวจจับพวกเขา ฉันก็อาจใช้ Python!

ดังนั้นในแง่หนึ่ง null แนะนำการพิมพ์แบบไดนามิกในระบบที่พิมพ์แบบคงที่พร้อมผลลัพธ์ที่คาดหวัง

ความแตกต่างระหว่างการหารด้วยศูนย์หรือบันทึกการลบ ฯลฯ คือเมื่อ i = 0 มันยังคงเป็น int คอมไพเลอร์ยังคงสามารถรับประกันประเภทของมัน ปัญหาคือตรรกะนั้นนำค่านั้นไปใช้ในทางที่ไม่ได้รับอนุญาตจากตรรกะ ... แต่ถ้าตรรกะทำเช่นนั้นนั่นเป็นคำจำกัดความของข้อบกพร่อง

(คอมไพเลอร์สามารถตรวจจับปัญหาเหล่านั้นได้เช่น BTW เช่นเดียวกับการตั้งค่าสถานะนิพจน์เช่นi = 1 / 0เวลารวบรวม แต่คุณไม่สามารถคาดหวังให้คอมไพเลอร์ติดตามฟังก์ชันและมั่นใจว่าพารามิเตอร์ทั้งหมดสอดคล้องกับตรรกะของฟังก์ชัน)

ปัญหาที่เกิดขึ้นจริงคือคุณต้องทำงานเพิ่มมากขึ้นและเพิ่มการตรวจสอบค่า Null เพื่อป้องกันตัวเองในขณะทำงาน แต่ถ้าคุณลืมมันไป คอมไพเลอร์หยุดคุณจากการกำหนด:

String s = new จำนวนเต็ม (1234);

ดังนั้นทำไมมันควรอนุญาตให้มีการมอบหมายให้ค่า (null) ที่จะทำลาย de-อ้างอิงถึงs?

โดยการผสม "ไม่มีค่า" กับการอ้างอิง "พิมพ์" ในรหัสของคุณคุณจะเป็นภาระเพิ่มเติมในโปรแกรมเมอร์ และเมื่อ NullPointerExceptions เกิดขึ้นการติดตามพวกเขาอาจทำให้เสียเวลามากขึ้น มากกว่าอาศัยการพิมพ์แบบคงที่จะพูดว่า "นี่คือการอ้างอิงถึงบางสิ่งบางอย่างที่คาดไว้" คุณปล่อยให้ภาษาที่พูดว่า "นี้อาจจะดีมีการอ้างอิงถึงสิ่งที่คาดว่า."


3
ใน C เป็นตัวแปรของชนิด*intทั้งระบุหรือint NULLในทำนองเดียวกันใน C ++ ตัวแปรประเภท*Widgetทั้งระบุWidgetสิ่งที่มีสาเหตุมาจากประเภทหรือWidget NULLปัญหากับ Java และสัญญาซื้อขายล่วงหน้าเช่น C # คือว่าพวกเขาใช้สถานที่เก็บจะหมายถึงWidget *Widgetวิธีการแก้ปัญหาคือไม่ต้องแกล้งทำเป็นว่า*Widgetไม่ควรถือNULLแต่ควรมีประเภทที่Widgetเก็บข้อมูลที่เหมาะสม
supercat

14

nullชี้เป็นเครื่องมือ

ไม่ใช่ศัตรู คุณควรใช้พวกมันถูกต้อง

คิดเพียงนาทีเกี่ยวกับระยะเวลาในการค้นหาและแก้ไขข้อผิดพลาดตัวชี้ที่ไม่ถูกต้องทั่วไปเปรียบเทียบกับการค้นหาและแก้ไขข้อผิดพลาดตัวชี้แบบ null nullมันเป็นเรื่องง่ายที่จะตรวจสอบตัวชี้กับ มันเป็น PITA ที่จะตรวจสอบว่าตัวชี้ที่ไม่ใช่นัลชี้ไปยังข้อมูลที่ถูกต้องหรือไม่

หากยังไม่มั่นใจให้เพิ่ม multithreading ในสถานการณ์ของคุณให้ดีลองคิดอีกครั้ง

คุณธรรมของเรื่องราว: อย่าโยนเด็ก ๆ ลงไปในน้ำ และอย่าตำหนิเครื่องมือสำหรับอุบัติเหตุ ชายผู้คิดค้นหมายเลขศูนย์มานานแล้วค่อนข้างฉลาดตั้งแต่วันนั้นคุณสามารถตั้งชื่อ "ไม่มีอะไร" nullชี้ไม่เป็นเช่นนั้นห่างไกลจากที่


แก้ไข:แม้ว่าNullObjectรูปแบบดูเหมือนจะเป็นทางออกที่ดีกว่าการอ้างอิงที่อาจเป็นโมฆะ แต่ก็มีการแนะนำปัญหาด้วยตนเอง:

  • อ้างอิงถือNullObjectควร (ตามทฤษฎี) ทำอะไรเมื่อมีการเรียกวิธีการ ดังนั้นข้อผิดพลาดเล็ก ๆ น้อย ๆ สามารถนำมาใช้เนื่องจากการอ้างอิงที่ไม่ได้กำหนดที่ไม่ได้รับมอบหมายซึ่งตอนนี้รับประกันว่าจะไม่เป็นโมฆะ (yehaa!) แต่ทำการกระทำที่ไม่ต้องการ: ไม่มีอะไร ด้วย NPE มันอยู่ถัดจากที่ชัดเจนว่าปัญหาอยู่ที่ใด ด้วยสิ่งNullObjectที่ทำงานอย่างใดอย่างหนึ่ง (แต่ผิด) เราแนะนำความเสี่ยงของข้อผิดพลาดที่ตรวจพบ (เกินไป) ล่าช้า สิ่งนี้ไม่ได้เกิดขึ้นโดยบังเอิญคล้ายกับปัญหาตัวชี้ที่ไม่ถูกต้องและมีผลที่คล้ายกัน: จุดอ้างอิงของบางสิ่งที่ดูเหมือนว่าข้อมูลที่ถูกต้อง แต่ไม่แนะนำข้อผิดพลาดของข้อมูลและอาจติดตามได้ยาก ความซื่อสัตย์ในกรณีเหล่านี้ฉันจะไม่มีการกระพริบตาชอบ NPE ที่ล้มเหลวทันทีตอนนี้มีข้อผิดพลาดแบบลอจิคัล / ข้อมูลซึ่งส่งผลให้ฉันชนกับฉันในภายหลังบนถนน โปรดจำไว้ว่า IBM ศึกษาเกี่ยวกับค่าใช้จ่ายของข้อผิดพลาดซึ่งเป็นหน้าที่ของการตรวจจับระยะที่พวกเขาอยู่

  • ความคิดของการทำอะไรเมื่อวิธีการที่เรียกว่าในไม่ถือเมื่อทะเยอทะยานทรัพย์สินหรือฟังก์ชั่นที่เรียกว่าหรือเมื่อวิธีการส่งกลับค่าผ่านทางNullObject outสมมติว่าค่าส่งคืนเป็นintและเราตัดสินใจว่า "ไม่ทำอะไรเลย" หมายถึง "คืนค่า 0" แต่เราจะมั่นใจได้อย่างไรว่า 0 นั้นคือ "ไม่มีอะไร" ที่ถูกต้องที่จะส่งคืน (ไม่ใช่) ท้ายที่สุดแล้วNullObjectไม่ควรทำอะไร แต่เมื่อถามถึงคุณค่ามันต้องตอบสนองอย่างใด ความล้มเหลวไม่ใช่ตัวเลือก: เราใช้NullObjectเพื่อป้องกัน NPE และแน่นอนว่าเราจะไม่ค้าขายกับข้อยกเว้นอื่น (ไม่ได้ใช้งานหารด้วยศูนย์, ... ) ใช่ไหม ดังนั้นคุณจะไม่คืนสิ่งใดอย่างถูกต้องเมื่อคุณต้องส่งคืนบางสิ่งได้อย่างไร

ฉันไม่สามารถช่วยได้ แต่เมื่อมีคนพยายามใช้NullObjectรูปแบบกับปัญหาแต่ละปัญหาดูเหมือนว่าจะพยายามแก้ไขข้อผิดพลาดหนึ่งโดยทำอย่างอื่น ไม่ต้องสงสัยเลยว่ามันเป็นวิธีแก้ปัญหาที่ดีและมีประโยชน์ในบางกรณี แต่มันก็ไม่ได้เป็นเวทย์มนตร์สำหรับทุกกรณี


คุณสามารถนำเสนอเหตุผลว่าทำไมจึงnullควรมีอยู่? เป็นไปได้ที่จะจัดการข้อบกพร่องเกี่ยวกับภาษาใด ๆ ด้วย "มันไม่ใช่ปัญหา แต่อย่าทำผิดพลาด"
Doval

คุณจะตั้งค่าตัวชี้ที่ไม่ถูกต้องเป็นค่าใด
JensG

คุณไม่ได้ตั้งค่าเป็นเพราะคุณไม่ควรอนุญาต แต่คุณใช้ตัวแปรประเภทที่แตกต่างกันซึ่งอาจเรียกได้ว่าMaybe Tอาจมีอย่างใดอย่างหนึ่งNothingหรือเป็นTค่า สิ่งสำคัญที่นี่คือคุณถูกบังคับให้ตรวจสอบว่ามีMaybeจริง ๆTก่อนที่คุณได้รับอนุญาตให้ใช้และให้ดำเนินการในกรณีที่ไม่มี Maybeและเนื่องจากคุณไม่ได้รับอนุญาตชี้ไม่ถูกต้องตอนนี้คุณไม่ต้องตรวจสอบอะไรที่ไม่ได้เป็น
Doval

1
@JensG ในตัวอย่างล่าสุดของคุณคอมไพเลอร์ทำให้คุณตรวจสอบค่าว่างก่อนการโทรทุกครั้งt.Something()หรือไม่ หรือมีวิธีรู้ว่าคุณได้ตรวจสอบแล้วและไม่ทำให้คุณทำอีกครั้ง? ถ้าคุณตรวจสอบก่อนที่คุณจะผ่านtวิธีนี้ tกับประเภทตัวเลือกคอมไพเลอร์รู้ว่าคุณได้ทำการตรวจสอบเลยหรือไม่โดยดูที่ประเภทของ หากเป็นตัวเลือกคุณไม่ได้ตรวจสอบในขณะที่ถ้าเป็นค่าที่ยังไม่ได้เปิดคุณก็มี
ทิมกู๊ดแมน

1
สิ่งใดที่คุณเป็นโมฆะกลับมาเมื่อถามถึงค่า?
JensG

8

การอ้างอิง Null เป็นความผิดพลาดเพราะอนุญาตให้ใช้โค้ดที่ไม่ได้เป็นความลับ:

foo = null
foo.bar()

มีทางเลือกถ้าคุณใช้ประโยชน์จากระบบประเภท:

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

ความคิดโดยทั่วไปคือการวางตัวแปรลงในกล่องและสิ่งเดียวที่คุณทำได้คือการแกะกล่องออกโดยควรขอความช่วยเหลือจากคอมไพเลอร์อย่างที่เสนอให้หอไอเฟล

Haskell มีตั้งแต่เริ่มต้น ( Maybe) ใน C ++ คุณสามารถใช้ประโยชน์ได้boost::optional<T>แต่คุณยังสามารถรับพฤติกรรมที่ไม่ได้กำหนด ...


6
-1 สำหรับ "เลเวอเรจ"!
ทำเครื่องหมาย C

8
แต่แน่นอนว่าการสร้างโปรแกรมทั่วไปจำนวนมากอนุญาตให้ใช้โค้ดที่ไม่มีความหมายได้หรือไม่? การหารด้วยศูนย์คือ (เนื้อหา) ไร้สาระ แต่เรายังคงอนุญาตให้มีการหารและหวังว่าโปรแกรมเมอร์จำได้ว่าตรวจสอบว่าตัวหารนั้นไม่ใช่ศูนย์ (หรือจัดการกับข้อยกเว้น)
ทิมกู๊ดแมน

3
ฉันไม่แน่ใจว่าสิ่งที่คุณหมายถึงโดย "ตั้งแต่เริ่มต้น" แต่ไม่ได้สร้างขึ้นเป็นภาษาหลักหมายของมันก็เกิดขึ้นจะอยู่ในมาตรฐานโหมโรง:Maybe data Maybe t = Nothing | Just t
fredoverflow

4
@FredOverflow: เนื่องจากการโหลดโหมโรงเป็นค่าเริ่มต้นฉันจะพิจารณาว่า "ตั้งแต่เริ่มต้น" Haskell มีระบบพิมพ์ที่น่าประหลาดใจพอที่จะอนุญาตให้ผู้ที่ถูกกำหนดด้วยกฎทั่วไปของภาษาเป็นเพียงโบนัส :)
Matthieu M.

2
@TimGoodman ในขณะที่การหารด้วยศูนย์อาจไม่ใช่ตัวอย่างที่ดีที่สุดสำหรับทุกประเภทค่าตัวเลข (IEEE 754 กำหนดผลลัพธ์สำหรับการหารด้วยศูนย์) ในกรณีที่มันจะโยนข้อยกเว้นแทนการมีค่าของประเภทTหารด้วยค่าประเภทอื่นTคุณจะ จำกัด การดำเนินการเพื่อค่าประเภทหารด้วยค่าประเภทT NonZero<T>
kbolino

8

และทางเลือกคืออะไร?

การเลือกประเภทและรูปแบบการจับคู่ เนื่องจากฉันไม่รู้ C # นี่คือส่วนหนึ่งของรหัสในภาษาสมมติที่เรียกว่า 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

ปัญหาเกี่ยวกับ nulls คือภาษาที่อนุญาตให้พวกเขาบังคับให้คุณเขียนโปรแกรมเชิงป้องกัน ต้องใช้ความพยายามอย่างมาก (ยิ่งกว่าการพยายามใช้การป้องกันแบบบล็อกต่อ) เพื่อให้แน่ใจ

  1. วัตถุที่คุณคาดหวังว่าวัตถุเหล่านั้นจะไม่เป็นโมฆะจะไม่มีวันว่างเปล่าและ
  2. กลไกการป้องกันของคุณจะจัดการกับ NPE ที่มีศักยภาพทั้งหมดได้อย่างมีประสิทธิภาพ

ดังนั้น nulls จึงเป็นสิ่งที่มีค่าใช้จ่ายสูง


1
อาจเป็นประโยชน์หากคุณรวมตัวอย่างที่ต้องใช้ความพยายามมากขึ้นในการจัดการกับค่า null ในตัวอย่างที่เรียบง่ายเกินความคาดหมายของฉันความพยายามนั้นก็เหมือนกัน ฉันไม่ได้ไม่เห็นด้วยกับคุณฉันเพิ่งพบตัวอย่างรหัสเฉพาะ (หลอก) วาดภาพที่ชัดเจนกว่างบทั่วไป
ทิมกู๊ดแมน

2
อาฉันไม่ได้อธิบายตัวเองชัดเจน ไม่ใช่ว่าจะจัดการกับโมฆะได้ยากขึ้น มันก็คือว่ามันเป็นเรื่องยากมาก (ถ้าไม่เป็นไปไม่ได้) เพื่อรับประกันว่าทุกคนสามารถเข้าถึงการอ้างอิงอาจ null อยู่แล้วปลอดภัยรักษา นั่นคือในภาษาที่อนุญาตให้มีการอ้างอิงเป็นโมฆะมันเป็นไปไม่ได้ที่จะรับประกันได้ว่าชิ้นส่วนของรหัสที่มีขนาดตามอำเภอใจนั้นเป็นอิสระจากข้อยกเว้นของตัวชี้โมฆะ นั่นคือสิ่งที่ทำให้การอ้างอิงเป็นโมฆะเป็นสิ่งที่มีราคาแพง มันแพงมากที่จะรับประกันว่าไม่มีข้อยกเว้นตัวชี้โมฆะทั้งหมด
luis.espinal

4

ปัญหาระดับภาษาการเขียนโปรแกรมของคุณพยายามที่จะพิสูจน์ความถูกต้องของโปรแกรมก่อนที่จะรัน ในภาษาที่พิมพ์แบบคงที่คุณพิสูจน์ได้ว่าคุณมีประเภทที่ถูกต้อง ด้วยการย้ายไปใช้ค่าเริ่มต้นของการอ้างอิงที่ไม่เป็นโมฆะ (ด้วยการอ้างอิงโมฆะทางเลือก) คุณสามารถกำจัดหลาย ๆ กรณีที่มีการส่งผ่านค่าว่างและไม่ควรเป็นเช่นนั้น คำถามคือความพยายามพิเศษในการจัดการการอ้างอิงที่ไม่เป็นโมฆะนั้นคุ้มค่ากับผลประโยชน์ในแง่ของความถูกต้องของโปรแกรมหรือไม่


4

ปัญหาไม่มากนักเพราะคุณไม่สามารถระบุประเภทการอ้างอิงที่ไม่เป็นโมฆะในภาษาที่ทันสมัยจำนวนมาก

ตัวอย่างเช่นรหัสของคุณอาจดูเหมือน

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
}

เมื่อการอ้างอิงในคลาสผ่านไปแล้วการตั้งโปรแกรมการป้องกันจะกระตุ้นให้คุณตรวจสอบพารามิเตอร์ทั้งหมดสำหรับ null อีกครั้งแม้ว่าคุณเพิ่งตรวจสอบว่ามีค่าเป็นโมฆะก่อนโดยเฉพาะเมื่อสร้างหน่วยที่ใช้ซ้ำได้ภายใต้การทดสอบ การอ้างอิงเดียวกันสามารถตรวจสอบได้อย่างง่ายดายเป็นโมฆะ 10 ครั้งทั่วโค้ดเบส!

จะดีกว่าไหมถ้าคุณมีประเภท nullable ปกติเมื่อคุณได้รับสิ่งนั้นจัดการกับค่า null แล้วและจากนั้นส่งผ่านเป็นประเภทที่ไม่สามารถลบล้างได้สำหรับฟังก์ชันตัวช่วยและคลาสทั้งหมดโดยไม่ตรวจสอบค่า null ทั้งหมด เวลา?

จุดนั้นของตัวเลือกโซลูชัน - ไม่อนุญาตให้คุณเปิดสถานะข้อผิดพลาด แต่เพื่ออนุญาตการออกแบบฟังก์ชันที่โดยนัยไม่สามารถยอมรับอาร์กิวเมนต์ที่เป็นโมฆะได้ การใช้งานชนิดเริ่มต้นเป็นโมฆะหรือไม่เป็นโมฆะมีความสำคัญน้อยกว่าความยืดหยุ่นของการมีเครื่องมือทั้งสองพร้อมใช้งานหรือไม่

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

นี่คือรายการยังเป็นคุณลักษณะ # 2 โหวตมากที่สุดสำหรับ C # -> https://visualstudio.uservoice.com/forums/121579-visual-studio/category/30931-languages-c


ฉันคิดว่าอีกจุดสำคัญเกี่ยวกับตัวเลือก <T> ก็คือมันเป็น monad (และดังนั้นจึงเป็น endofunctor) ดังนั้นคุณสามารถเขียนการคำนวณที่ซับซ้อนเหนือสตรีมค่าที่มีประเภทที่เป็นตัวเลือกโดยไม่ต้องตรวจสอบอย่างชัดเจนสำหรับNoneแต่ละขั้นตอน
sara

3

Null เป็นสิ่งชั่วร้าย อย่างไรก็ตามการขาดโมฆะอาจเป็นความชั่วร้ายที่ยิ่งใหญ่กว่า

ปัญหาคือในโลกแห่งความเป็นจริงคุณมักจะมีสถานการณ์ที่คุณไม่มีข้อมูล ตัวอย่างของรุ่นที่ไม่มีค่า null ยังสามารถระเบิดได้ - ไม่ว่าคุณจะทำผิดตรรกะและลืมตรวจสอบสามีหรือบางทีสามีแต่งงานระหว่างที่คุณตรวจสอบและเมื่อคุณเงยหน้าขึ้นมอง (มันช่วยในการประเมินตรรกะถ้าคุณคิดว่า Moriarty กำลังเฝ้าดูโค้ดของคุณทุกบิตและพยายามที่จะทำให้คุณตื่นขึ้น)

การค้นหาของกู๊ดแมนทำอะไรเมื่อเธอไม่อยู่ที่นั่น? คุณจะต้องส่งคืนลูกค้าเริ่มต้นบางประเภทและตอนนี้คุณกำลังขายสินค้าให้เป็นค่าเริ่มต้น

โดยพื้นฐานแล้วมันมีความสำคัญมากกว่าที่รหัสจะทำงานไม่ว่าจะทำงานอย่างถูกต้องหรือไม่ หากพฤติกรรมที่ไม่เหมาะสมนั้นเป็นสิ่งที่ไม่มีพฤติกรรมคุณก็ไม่ต้องการเป็นโมฆะ (สำหรับตัวอย่างว่านี่อาจเป็นทางเลือกที่เหมาะสมหรือไม่พิจารณาการเปิดตัวครั้งแรกของ Ariane V. ข้อผิดพลาดที่ไม่ได้ถูกจับ / 0 ทำให้จรวดหมุนอย่างหนักและการทำลายตัวเองถูกยิงเมื่อมันแยกออกจากกัน มันพยายามที่จะคำนวณจริง ๆ แล้วไม่ได้ตอบสนองวัตถุประสงค์ใด ๆ ทันทีที่บูสเตอร์สว่าง - มันจะทำให้วงโคจรแม้จะมีขยะกลับประจำ)

อย่างน้อย 99 ครั้งจาก 100 ฉันจะเลือกใช้โมฆะ ฉันจะกระโดดด้วยความดีใจเมื่อทราบรุ่นของตนเอง


1
นี่เป็นคำตอบที่ดี โครงสร้างแบบ null ในการเขียนโปรแกรมไม่ใช่ปัญหา แต่เป็นอินสแตนซ์ทั้งหมดของค่า Null ที่เป็น หากคุณสร้างวัตถุเริ่มต้นในที่สุดโมฆะทั้งหมดของคุณจะถูกแทนที่ด้วยค่าเริ่มต้นเหล่านั้นและ UI, DB ของคุณและทุกที่ในระหว่างนั้นจะถูกเติมเต็มด้วยสิ่งเหล่านั้นแทนซึ่งอาจไม่มีประโยชน์มากขึ้น แต่คุณจะนอนในเวลากลางคืนเพราะอย่างน้อยโปรแกรมของคุณก็ไม่พังฉันเดา
Chris McCall

2
คุณคิดว่าnullเป็นวิธีเดียวในการสร้างแบบจำลองที่ไม่มีค่า
sara

1

ปรับให้เหมาะสมสำหรับกรณีที่พบบ่อยที่สุด

ต้องตรวจสอบค่าว่างตลอดเวลาเป็นที่น่าเบื่อ - คุณต้องการที่จะได้รับวัตถุลูกค้าและทำงานกับมัน

ในกรณีปกติสิ่งนี้ควรใช้งานได้ดี - คุณทำการค้นหารับวัตถุและใช้มัน

ในกรณีพิเศษที่คุณ (โดยการสุ่ม) ค้นหาลูกค้าตามชื่อโดยไม่รู้ว่าบันทึก / วัตถุนั้นมีอยู่คุณต้องมีข้อบ่งชี้ว่าสิ่งนี้ล้มเหลว ในสถานการณ์นี้คำตอบคือการโยนข้อยกเว้น RecordNotFound (หรือให้ผู้ให้บริการ SQL ที่อยู่ด้านล่างทำสิ่งนี้ให้คุณ)

หากคุณอยู่ในสถานการณ์ที่คุณไม่ทราบว่าคุณสามารถเชื่อถือข้อมูลที่เข้ามา (พารามิเตอร์) อาจเป็นเพราะผู้ใช้ป้อนข้อมูลหรือไม่คุณสามารถระบุ 'TryGetCustomer (ชื่อลูกค้าออก)' แบบแผน การอ้างอิงโยงกับ int.Parse และ int.TryParse


ฉันขอยืนยันว่าคุณไม่มีทางรู้ว่าคุณสามารถเชื่อถือข้อมูลที่เข้ามาได้หรือไม่เพราะมันเข้ามาในฟังก์ชั่นและเป็นประเภทที่ไม่มีค่า รหัสสามารถ refactored เพื่อให้การป้อนข้อมูลที่แตกต่างกันโดยสิ้นเชิง ดังนั้นหากมีความเป็นไปได้ที่เป็นโมฆะคุณต้องตรวจสอบทุกครั้ง ดังนั้นคุณจะจบลงด้วยระบบที่ตัวแปรทุกตัวถูกตรวจสอบว่าเป็นโมฆะทุกครั้งก่อนที่จะเข้าถึง กรณีที่ไม่ได้ตรวจสอบจะกลายเป็นเปอร์เซ็นต์ความล้มเหลวของโปรแกรมของคุณ
Chris McCall

0

ข้อยกเว้นไม่ได้เป็นหลักฐาน!

พวกเขาอยู่ที่นั่นด้วยเหตุผล หากรหัสของคุณไม่ดีแสดงว่ามีรูปแบบอัจฉริยะเรียกว่าข้อยกเว้นและจะบอกคุณว่ามีบางอย่างผิดปกติ

โดยการหลีกเลี่ยงการใช้วัตถุ null คุณกำลังซ่อนส่วนหนึ่งของข้อยกเว้นเหล่านั้น ฉันไม่ได้สนใจเกี่ยวกับตัวอย่าง OP ที่เขาแปลงข้อยกเว้นตัวชี้โมฆะเป็นข้อยกเว้นที่พิมพ์ได้ดีนี่อาจเป็นสิ่งที่ดีเพราะมันเพิ่มความสามารถในการอ่าน เมื่อใดก็ตามที่คุณใช้ตัวเลือกประเภทโซลูชันตามที่ @Jonas ชี้ให้เห็นคุณกำลังซ่อนข้อยกเว้น

มากกว่าในแอปพลิเคชั่นการผลิตของคุณแทนที่จะถูกโยนทิ้งเมื่อคุณคลิกปุ่มเพื่อเลือกตัวเลือกที่ว่างเปล่าไม่มีอะไรเกิดขึ้น แทนที่จะปล่อยให้ตัวชี้โมฆะว่างเปล่าและเราอาจได้รับรายงานข้อยกเว้นนั้น (ในแอปพลิเคชันการผลิตจำนวนมากที่เรามีกลไกดังกล่าว) และเกินกว่าที่เราจะสามารถแก้ไขได้

การทำให้รหัสของคุณกันกระสุนโดยการหลีกเลี่ยงข้อยกเว้นนั้นเป็นความคิดที่ไม่ดีการทำให้รหัสของคุณสามารถป้องกันได้ด้วยการแก้ไขข้อยกเว้นนี่คือเส้นทางที่ฉันจะเลือก


1
ฉันรู้เพิ่มเติมเกี่ยวกับประเภทตัวเลือกมากกว่าตอนที่ฉันโพสต์คำถามเมื่อไม่กี่ปีที่ผ่านมาเนื่องจากตอนนี้ฉันใช้พวกเขาเป็นประจำใน Scala จุดของตัวเลือกคือทำให้การกำหนดNoneสิ่งที่ไม่ใช่ตัวเลือกลงในข้อผิดพลาดการคอมไพล์เวลาและในทำนองเดียวกันการใช้Optionราวกับว่ามันมีค่าโดยไม่ต้องตรวจสอบก่อนว่ามันเป็นข้อผิดพลาดเวลารวบรวม ข้อผิดพลาดในการคอมไพล์นั้นดีกว่าข้อยกเว้นในเวลาทำงาน
ทิมกู๊ดแมน

ตัวอย่างเช่น: คุณไม่สามารถพูดได้ว่าคุณจะต้องบอกว่าs: String = None s: Option[String] = Noneและถ้าsเป็นตัวเลือกที่คุณไม่สามารถพูดได้s.lengthแต่คุณสามารถตรวจสอบว่ามันมีค่าและดึงค่าในเวลาเดียวกันกับการจับคู่แบบ:s match { case Some(str) => str.length; case None => throw RuntimeException("Where's my value?") }
ทิมสามี

น่าเสียดายที่ Scala คุณยังสามารถพูดs: String = nullได้ แต่นั่นเป็นราคาของการทำงานร่วมกันกับ Java โดยทั่วไปเราไม่อนุญาตสิ่งนั้นในรหัสสกาล่าของเรา
ทิมกู๊ดแมน

2
อย่างไรก็ตามฉันเห็นด้วยกับความคิดเห็นทั่วไปว่าข้อยกเว้น (ใช้อย่างเหมาะสม) ไม่ใช่สิ่งที่ไม่ดีและ "แก้ไข" ข้อยกเว้นโดยการกลืนมันเป็นความคิดที่แย่มาก แต่ด้วยประเภทตัวเลือกเป้าหมายคือเปลี่ยนข้อยกเว้นให้เป็นข้อผิดพลาดในการรวบรวมเวลาซึ่งเป็นสิ่งที่ดีกว่า
ทิมกู๊ดแมน

@TimGoodman มันน่ากลัวเพียงชั้นเชิงหรือสามารถใช้ในภาษาอื่นเช่น Java และ ActionScript 3 มันจะดีถ้าคุณสามารถแก้ไขคำตอบของคุณด้วยตัวอย่างเต็มรูปแบบ
Ilya Gazman

0

Nullsเป็นปัญหาเนื่องจากต้องตรวจสอบอย่างชัดเจน แต่คอมไพเลอร์ไม่สามารถเตือนคุณว่าคุณลืมตรวจสอบ การวิเคราะห์แบบคงที่ที่ใช้เวลานานเท่านั้นที่สามารถบอกคุณได้ว่า โชคดีที่มีทางเลือกที่ดีหลายอย่าง

นำตัวแปรออกจากขอบเขต บ่อยครั้งที่nullใช้เป็นตัวยึดตำแหน่งเมื่อโปรแกรมเมอร์ประกาศตัวแปรเร็วเกินไปหรือทำให้มันยาวเกินไป วิธีที่ดีที่สุดคือกำจัดตัวแปร อย่าประกาศจนกว่าคุณจะมีค่าที่ถูกต้องที่จะใส่ในนั้น นี่ไม่ใช่ข้อ จำกัด ที่ยากอย่างที่คุณคิดและมันทำให้โค้ดของคุณสะอาดขึ้นมาก

ใช้รูปแบบวัตถุ null บางครั้งค่าที่ขาดหายไปเป็นสถานะที่ถูกต้องสำหรับระบบของคุณ รูปแบบวัตถุ null เป็นวิธีแก้ปัญหาที่ดีที่นี่ มันหลีกเลี่ยงความจำเป็นในการตรวจสอบอย่างชัดเจนอย่างต่อเนื่อง แต่ยังช่วยให้คุณสามารถแสดงสถานะที่ว่างเปล่า ไม่ใช่ "papering ผ่านเงื่อนไขข้อผิดพลาด" อย่างที่บางคนอ้างเพราะในกรณีนี้สถานะที่ว่างเปล่าเป็นสถานะ semantic ที่ถูกต้อง ที่ถูกกล่าวว่าคุณไม่ควรใช้รูปแบบนี้เมื่อรัฐเป็นโมฆะไม่ใช่ความหมายที่ถูกต้อง คุณควรถอดตัวแปรออกจากขอบเขต

ใช้บางที / ตัวเลือก ก่อนอื่นสิ่งนี้จะทำให้คอมไพเลอร์เตือนคุณว่าคุณต้องตรวจสอบค่าที่หายไป แต่จะทำมากกว่าการแทนที่การตรวจสอบที่ชัดเจนประเภทหนึ่งด้วยอีกประเภทหนึ่ง ใช้Optionsคุณสามารถโยงรหัสของคุณและดำเนินการต่อราวกับว่ามีค่าของคุณอยู่โดยไม่จำเป็นต้องตรวจสอบจริง ๆ จนกว่าจะถึงนาทีสุดท้าย ใน Scala โค้ดตัวอย่างของคุณจะมีลักษณะดังนี้:

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)

ในบรรทัดที่สองที่เรากำลังสร้างข้อความน่ากลัวเราจะไม่ทำให้การตรวจสอบอย่างชัดเจนหากลูกค้าพบและความงามเป็นสิ่งที่เราไม่จำเป็นต้อง มันไม่ได้จนกว่าจะถึงบรรทัดสุดท้ายมากซึ่งอาจจะมีหลายสายในภายหลังหรือแม้กระทั่งในโมดูลที่แตกต่างกันอย่างชัดเจนที่เรากังวลตัวเองกับสิ่งที่เกิดขึ้นถ้าเป็นOption Noneไม่เพียงแค่นั้นหากเราลืมที่จะทำมันจะไม่ได้รับการตรวจสอบประเภท ถึงอย่างนั้นมันก็ทำในลักษณะที่เป็นธรรมชาติมากโดยไม่ต้องมีifคำสั่งที่ชัดเจน

เปรียบเทียบกับ a nullที่ซึ่งมันตรวจสอบได้ดี แต่ที่คุณต้องทำการตรวจสอบอย่างชัดเจนในทุกขั้นตอนหรือแอปพลิเคชันทั้งหมดของคุณจะระเบิดที่รันไทม์หากคุณโชคดีระหว่างการใช้งานหน่วยทดสอบการออกกำลังกาย มันไม่คุ้มค่ากับความยุ่งยาก


0

ปัญหาหลักของ NULL คือทำให้ระบบไม่น่าเชื่อถือ ใน 1,980 Tony Hoare ในกระดาษทุ่มเทเพื่อรางวัลทัวริงของเขาเขียนว่า:

ดังนั้นคำแนะนำที่ดีที่สุดของฉันที่มีต่อผู้สร้างและนักออกแบบของ ADA นั้นก็ถูกเพิกเฉย ... ไม่อนุญาตให้ใช้ภาษานี้ในสถานะปัจจุบันในแอปพลิเคชั่นที่ความน่าเชื่อถือมีความสำคัญเช่นสถานีพลังงานนิวเคลียร์ขีปนาวุธล่องเรือระบบเตือนภัยล่วงหน้าระบบป้องกันขีปนาวุธต่อต้านเชื้อแบคทีเรีย จรวดต่อไปที่จะหลงทางเนื่องจากข้อผิดพลาดของภาษาการเขียนโปรแกรมอาจไม่ใช่จรวดอวกาศเชิงสำรวจในการเดินทางไปดาวศุกร์โดยไม่เป็นอันตราย: มันอาจเป็นหัวรบนิวเคลียร์ที่ระเบิดในเมืองใดเมืองหนึ่งของเรา ภาษาโปรแกรมที่ไม่น่าเชื่อถือซึ่งสร้างโปรแกรมที่ไม่น่าเชื่อถือนั้นมีความเสี่ยงต่อสภาพแวดล้อมและสังคมของเรามากกว่ารถยนต์ที่ไม่ปลอดภัยยาฆ่าแมลงที่เป็นพิษหรืออุบัติเหตุที่สถานีพลังงานนิวเคลียร์ ระมัดระวังเพื่อลดความเสี่ยงไม่เพิ่มขึ้น

ภาษา ADA มีการเปลี่ยนแปลงมากมายตั้งแต่นั้นอย่างไรก็ตามปัญหาดังกล่าวยังคงมีอยู่ใน Java, C # และภาษายอดนิยมอื่น ๆ อีกมากมาย

มันเป็นหน้าที่ของนักพัฒนาในการสร้างสัญญาระหว่างลูกค้าและซัพพลายเออร์ ตัวอย่างเช่นใน C # เช่นเดียวกับใน Java คุณสามารถใช้Genericsเพื่อลดผลกระทบของNullการอ้างอิงโดยการสร้างแบบอ่านอย่างเดียวNullableClass<T>(สองตัวเลือก):

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

แล้วใช้มันเป็น

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

หรือใช้สองสไตล์ตัวเลือกด้วยวิธีการขยาย C #:

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

ความแตกต่างมีความสำคัญ ในฐานะลูกค้าของวิธีการที่คุณรู้ว่าสิ่งที่คาดหวัง ทีมสามารถมีกฎ:

ถ้าชั้นไม่ได้เป็นประเภท NullableClass แล้วก็เช่นจะต้องไม่เป็นโมฆะ

ทีมสามารถเสริมความคิดนี้โดยใช้การออกแบบตามสัญญาและการตรวจสอบแบบคงที่ในเวลารวบรวมเช่นด้วยเงื่อนไขก่อนหน้านี้:

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

หรือสำหรับสตริง

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
}

วิธีการเหล่านี้สามารถเพิ่มความน่าเชื่อถือของแอปพลิเคชั่นและคุณภาพซอฟต์แวร์ได้อย่างมาก ออกแบบโดยสัญญาเป็นกรณีของตรรกะ Hoareซึ่งมีประชากรโดย Bertrand Meyer ในหนังสือ Object-Oriented Software Construction และภาษาไอเฟลที่มีชื่อเสียงของเขาในปี 1988 แต่มันไม่ได้ใช้อย่างไม่ถูกต้องในการประดิษฐ์ซอฟต์แวร์ที่ทันสมัย


0

เลขที่

ความล้มเหลวของภาษาในการบัญชีสำหรับค่าพิเศษเช่นnullเป็นปัญหาของภาษา หากคุณดูภาษาอย่างKotlinหรือSwiftซึ่งบังคับให้นักเขียนจัดการกับความเป็นไปได้ที่จะมีค่าว่างในทุก ๆ การเผชิญหน้าก็ไม่มีอันตรายใด ๆ

ในภาษาเช่นหนึ่งในคำถามภาษาช่วยให้nullเพื่อเป็นค่าใด ๆ-ishประเภท แต่ไม่ได้ให้การใด ๆ สำหรับการสร้างการยอมรับในภายหลังว่าซึ่งนำไปสู่สิ่งที่เป็นnullโดยไม่คาดคิด (โดยเฉพาะเมื่อส่งผ่านไปยังคุณจากที่อื่น) และ ดังนั้นคุณจะได้รับความผิดพลาด นั่นคือความชั่วร้าย: ให้คุณใช้nullโดยไม่ทำให้คุณจัดการกับมัน


-1

โดยทั่วไปแนวคิดหลักคือประเด็นการอ้างอิงตัวชี้โมฆะควรได้รับการรวบรวมในเวลารวบรวมแทนเวลาทำงาน ดังนั้นหากคุณกำลังเขียนฟังก์ชั่นที่ใช้วัตถุบางอย่างและคุณไม่ต้องการให้ใครเรียกมันด้วยการอ้างอิงเป็นโมฆะแล้วระบบพิมพ์ควรอนุญาตให้คุณระบุความต้องการนั้นเพื่อให้คอมไพเลอร์สามารถใช้มันเพื่อให้การรับประกันว่า รวบรวมถ้าผู้โทรไม่ตอบสนองความต้องการของคุณ สิ่งนี้สามารถทำได้หลายวิธีเช่นการตกแต่งประเภทของคุณหรือการใช้ประเภทตัวเลือก (หรือที่เรียกว่าประเภทที่ไม่สามารถใช้ได้ในบางภาษา) แต่เห็นได้ชัดว่ามันทำงานได้มากขึ้นสำหรับนักออกแบบคอมไพเลอร์

ฉันคิดว่ามันเป็นที่เข้าใจว่าทำไมในทศวรรษ 1960 และ 70 ผู้ออกแบบคอมไพเลอร์ไม่ได้ใช้ความคิดนี้เพราะเครื่องมีทรัพยากร จำกัด ผู้รวบรวมต้องเรียบง่ายและไม่มีใครรู้ว่ามันจะเลว 40 ปีลงบรรทัด


-1

ฉันมีการคัดค้านอย่างง่าย ๆ ต่อnull:

จะแบ่งความหมายของรหัสของคุณอย่างสมบูรณ์โดยการแนะนำความคลุมเครือ

บ่อยครั้งที่คุณคาดหวังว่าผลลัพธ์จะเป็นประเภทที่แน่นอน นี่คือความหมายของเสียง ถ้าคุณถามฐานข้อมูลให้ผู้ใช้idทราบคุณคาดว่า resut จะเป็นประเภทที่แน่นอน (= user) แต่จะเกิดอะไรขึ้นถ้าไม่มีผู้ใช้ id นั้น ทางออกหนึ่งที่ไม่ดีคือการเพิ่มnullมูลค่า ดังนั้นผลที่ได้คือambigous : ทั้งจะเป็นหรือจะเป็นuser nullแต่nullไม่ใช่ประเภทที่คาดหวัง และนี่คือจุดเริ่มต้นของกลิ่นรหัส

ในการหลีกเลี่ยงnullคุณทำให้โค้ดของคุณชัดเจน

มีวิธีการที่รอบ ๆ อยู่เสมอnull: refactoring คอลเลกชัน, Null-objects, optionalsฯลฯ


-2

หากเป็นไปได้ในเชิงความหมายเพื่อให้สามารถเข้าถึงตำแหน่งที่เก็บข้อมูลของการอ้างอิงหรือประเภทตัวชี้ก่อนที่โค้ดจะถูกคำนวณเพื่อคำนวณมูลค่า การมีที่ตั้งดังกล่าวเป็นค่าเริ่มต้นจากการอ้างอิงตัวชี้โมฆะซึ่งอาจอ่านและคัดลอกได้อย่างอิสระ แต่จะเกิดความผิดพลาดหากมีการอ้างอิงหรือทำดัชนีเป็นตัวเลือกที่เป็นไปได้ ตัวเลือกอื่น ๆ ได้แก่ :

  1. มีตำแหน่งเริ่มต้นเป็นตัวชี้โมฆะ แต่ไม่อนุญาตให้ใช้รหัสเพื่อดูพวกเขา (หรือแม้กระทั่งกำหนดว่าพวกเขาเป็นโมฆะ) โดยไม่ต้องล้มเหลว อาจมีวิธีการที่ชัดเจนในการตั้งค่าตัวชี้เป็น null
  2. ให้สถานที่ตั้งเป็นค่าเริ่มต้นสำหรับตัวชี้ที่เป็นโมฆะและหยุดทำงานหากมีการพยายามอ่านหนึ่งครั้ง แต่ให้วิธีการที่ไม่ล้มเหลวในการทดสอบว่าตัวชี้ถือเป็นโมฆะหรือไม่ นอกจากนี้ยังมีวิธีการอธิบายการตั้งค่าตัวชี้เป็นโมฆะ
  3. มีสถานที่ตั้งเป็นค่าเริ่มต้นสำหรับตัวชี้ไปยังอินสแตนซ์เริ่มต้นของคอมไพเลอร์ที่ระบุประเภทที่ระบุ
  4. มีสถานที่ตั้งเป็นค่าเริ่มต้นสำหรับตัวชี้ไปยังอินสแตนซ์ค่าเริ่มต้นของโปรแกรมประเภทที่ระบุ
  5. มีการเข้าถึงตัวชี้ null ใด ๆ (ไม่ใช่เพียงแค่การดำเนินการยกเลิกการลงทะเบียน) เรียกรูทีนที่มาจากโปรแกรมเพื่อให้อินสแตนซ์
  6. การออกแบบซีแมนทิกส์ภาษาที่คอลเลกชันของที่เก็บจะไม่มีอยู่ยกเว้นคอมไพเลอร์ที่ไม่สามารถเข้าถึงได้ชั่วคราวจนกว่าจะถึงเวลาที่รูทีนการเริ่มต้นถูกเรียกใช้บนสมาชิกทุกคน (บางอย่างเช่นตัวสร้างอาร์เรย์ ฟังก์ชั่นที่จะส่งคืนอินสแตนซ์หรืออาจเป็นคู่ของฟังก์ชั่น - หนึ่งที่จะส่งกลับอินสแตนซ์และเป็นหนึ่งที่จะเรียกว่าในอินสแตนซ์ที่สร้างไว้ก่อนหน้านี้หากมีข้อยกเว้นเกิดขึ้นระหว่างการสร้างอาร์เรย์)

ตัวเลือกสุดท้ายอาจมีการอุทธรณ์โดยเฉพาะอย่างยิ่งหากภาษานั้นมีทั้งประเภท nullable และ non-nullable (หนึ่งสามารถเรียกตัวสร้างอาร์เรย์แบบพิเศษสำหรับประเภทใดก็ได้ แต่จะต้องเรียกพวกเขาเพียงอย่างเดียวเมื่อสร้างอาร์เรย์ประเภท non-nullable) แต่อาจจะไม่เป็นไปได้ในช่วงเวลาที่ตัวชี้โมฆะถูกประดิษฐ์ขึ้น จากตัวเลือกอื่น ๆ ดูเหมือนจะไม่มีอะไรน่าดึงดูดยิ่งไปกว่าการอนุญาตให้คัดลอกพอยน์เตอร์พอยน์เตอร์ แต่ไม่ถูกอ้างถึงหรือจัดทำดัชนี วิธีการ # 4 อาจสะดวกที่จะมีเป็นตัวเลือกและควรมีราคาถูกพอสมควรที่จะใช้ แต่มันไม่ควรจะเป็นตัวเลือกเท่านั้น การขอให้พอยน์เตอร์ต้องชี้ไปที่ออบเจ็กต์ที่ถูกต้องบางอันนั้นแย่กว่าการพอยน์เตอร์พอยน์เตอร์เป็นค่า Null ซึ่งสามารถอ่านหรือคัดลอกได้ แต่ไม่ถูกอ้างถึงหรือจัดทำดัชนี


-2

ใช่NULL คือการออกแบบที่แย่มากในโลกแห่งวัตถุ สรุปการใช้งาน NULL นำไปสู่:

  • การจัดการข้อผิดพลาดแบบเฉพาะกิจ (แทนข้อยกเว้น)
  • ความหมายคลุมเครือ
  • ช้าแทนที่จะล้มเหลวอย่างรวดเร็ว
  • การคิดคอมพิวเตอร์กับการคิดเชิงวัตถุ
  • วัตถุที่ไม่แน่นอนและไม่สมบูรณ์

ตรวจสอบโพสต์บล็อกนี้สำหรับคำอธิบายโดยละเอียด: http://www.yegor256.com/2014/05/13/why-null-is-bad.html

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.