หากโมฆะไม่ดีทำไมภาษาสมัยใหม่ถึงนำมาใช้? [ปิด]


82

ฉันแน่ใจว่าผู้ออกแบบภาษาเช่น Java หรือ C # รู้ปัญหาที่เกี่ยวข้องกับการมีอยู่ของการอ้างอิงที่เป็นโมฆะ (ดูที่การอ้างอิงโมฆะเป็นเรื่องเลวร้ายจริง ๆ หรือไม่ ) การใช้ประเภทตัวเลือกนั้นไม่ซับซ้อนกว่าการอ้างอิงแบบ null

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

มันเป็นเพียงเพราะการอนุรักษ์ - "ภาษาอื่นมีเราต้องได้เช่นกัน ... "?


99
เป็นสิ่งที่ดี ฉันรักมันและใช้มันทุกวัน
Pieter B

17
@PieterB แต่คุณใช้เพื่อการอ้างอิงส่วนใหญ่หรือคุณต้องการให้การอ้างอิงส่วนใหญ่ไม่เป็นโมฆะ? อาร์กิวเมนต์ไม่ควรมีข้อมูลที่ไม่เป็นโมฆะเท่านั้นควรชัดเจนและเลือกใช้

11
@PieterB แต่เมื่อคนส่วนใหญ่ไม่ควรเป็นโมฆะมันจะทำให้รู้สึกถึงความเป็นโมฆะยกเว้นมากกว่าค่าเริ่มต้นหรือไม่ โปรดทราบว่าในขณะที่การออกแบบประเภทตัวเลือกตามปกติคือการบังคับให้มีการตรวจสอบอย่างชัดเจนสำหรับการขาดและการเปิดออกเราสามารถมีซีแมนทิกส์ที่รู้จักกันดีของ Java / C # / ... สำหรับการอ้างอิงแบบ nullable opt-in ถ้าเป็นโมฆะ) อย่างน้อยก็จะป้องกันข้อบกพร่องบางอย่างและทำการวิเคราะห์แบบคงที่ที่บ่นเกี่ยวกับการตรวจสอบโมฆะที่ขาดหายไปมากขึ้นในทางปฏิบัติมากขึ้น

20
WTF ขึ้นอยู่กับพวกคุณเหรอ? ในทุกสิ่งที่สามารถทำได้และทำผิดพลาดกับซอฟท์แวร์การพยายามอ้างถึงโมฆะก็ไม่มีปัญหาอะไรเลย มันสร้าง AV / segfault อยู่เสมอและได้รับการแก้ไข มีปัญหาการขาดแคลนข้อบกพร่องมากมายที่คุณต้องกังวลเกี่ยวกับเรื่องนี้หรือไม่? ถ้าเป็นเช่นนั้นฉันมีอะไหล่เหลือเฟือและไม่มีใครแก้ปัญหาด้วยการอ้างอิง / พอยน์เตอร์ที่เป็นโมฆะ
Martin James

13
@MartinJames "มันสร้าง AV / segfault อยู่เสมอและได้รับการแก้ไข" - ไม่ไม่ไม่ได้
Detly

คำตอบ:


97

ข้อจำกัดความรับผิดชอบ: เนื่องจากฉันไม่รู้จักนักออกแบบภาษาใด ๆ เป็นการส่วนตัวคำตอบใด ๆ ที่ฉันให้คุณจะเป็นการเก็งกำไร

จากTony Hoareตัวเอง:

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

เน้นการขุด

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

"เราอยู่หลังโปรแกรมเมอร์ C ++ เราสามารถลากพวกเขาจำนวนมากประมาณครึ่งทางไปที่ Lisp" -Guy Steele ผู้เขียนร่วมของข้อกำหนด Java

(ที่มา: http://www.paulgraham.com/icad.html )

และแน่นอน C ++ มีค่าว่างเนื่องจาก C มีค่าเป็นศูนย์และไม่จำเป็นต้องมีผลกระทบทางประวัติศาสตร์ของ C ภาษา C ++ เป็นภาษา J ++ ซึ่งเป็นการนำ Java มาใช้ของ Microsoft และ C ++ นั้นเป็นภาษาที่ถูกเลือกสำหรับการพัฒนา Windows ดังนั้นมันจึงได้มาจากภาษาใดภาษาหนึ่ง

แก้ไขนี่คือข้อความที่ยกมาจาก Hoare ที่ควรพิจารณา:

ภาษาการเขียนโปรแกรมโดยรวมมีความซับซ้อนมากกว่าที่เคยเป็นมา: การวางแนวของวัตถุการสืบทอดและคุณสมบัติอื่น ๆ ยังไม่ได้รับการพิจารณาอย่างแท้จริงจากมุมมองของวินัยที่สอดคล้องกันและทางวิทยาศาสตร์หรือทฤษฎีความถูกต้อง . สัจพจน์ดั้งเดิมของฉันซึ่งฉันได้ใฝ่หาเป็นนักวิทยาศาสตร์มาตลอดชีวิตของฉันคือว่าหนึ่งใช้เกณฑ์ของความถูกต้องเป็นวิธีการบรรจบในการออกแบบภาษาการเขียนโปรแกรมที่ดี - หนึ่งที่ไม่ได้วางกับดักสำหรับผู้ใช้และใน ซึ่งองค์ประกอบที่แตกต่างกันของโปรแกรมสอดคล้องอย่างชัดเจนกับองค์ประกอบที่แตกต่างกันของข้อกำหนดเพื่อให้คุณสามารถให้เหตุผลเกี่ยวกับองค์ประกอบ [... ] เครื่องมือรวมถึงคอมไพเลอร์จะต้องอยู่บนพื้นฐานของทฤษฎีว่ามันหมายถึงอะไรในการเขียนโปรแกรมที่ถูกต้อง - บทสัมภาษณ์ประวัติศาสตร์โดย Philip L. Frana วันที่ 17 กรกฎาคม 2545 เมืองเคมบริดจ์ประเทศอังกฤษ Charles Babbage Institute, มหาวิทยาลัยมินนิโซตา [ http://www.cbi.umn.edu/oh/display.phtml?id=343]

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

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

(ที่มา: http://www.artima.com/intv/bloch13.html )


32
เรียนผู้ลงคะแนน: ฉันจะปรับปรุงคำตอบของฉันได้อย่างไร
Doval

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

29
@DougM คุณคาดหวังว่าฉันจะทำอะไรตีนักออกแบบภาษาทุกคนในช่วง 50 ปีที่ผ่านมาและถามเขาว่าทำไมเขาถึงใช้nullภาษาของเขา คำตอบสำหรับคำถามนี้จะเป็นการเก็งกำไรเว้นแต่จะมาจากนักออกแบบภาษา ฉันไม่รู้เกี่ยวกับเว็บไซต์นี้บ่อยครั้งนอกเหนือจาก Eric Lippert ส่วนสุดท้ายคือปลาเฮอริ่งแดงด้วยเหตุผลหลายประการ จำนวนรหัสของบุคคลที่สามที่เขียนอยู่ด้านบนของ MS และ Java ของ API นั้นมีค่ามากกว่าจำนวนของรหัสใน API อย่างชัดเจน ดังนั้นถ้าลูกค้าของคุณต้องการคุณให้พวกเขาnull nullคุณคิดว่าพวกเขาได้รับการยอมรับnullก็คือการคิดเงิน
Doval

3
หากคำตอบเดียวที่คุณสามารถให้คือการเก็งกำไรระบุไว้อย่างชัดเจนในวรรคเปิดของคุณ (คุณถามว่าคุณจะปรับปรุงคำตอบของคุณได้อย่างไรและฉันก็ตอบกลับการสอดแทรกใด ๆ เป็นเพียงความเห็นที่คุณสามารถเพิกเฉยได้นั่นคือสิ่งที่วงเล็บเป็นภาษาอังกฤษหลังจากนั้นทั้งหมด)
DougM

7
คำตอบนี้สมเหตุสมผล ฉันได้เพิ่มข้อควรพิจารณาเพิ่มเติมในเหมืองแล้ว ฉันทราบว่าICloneableมีการแตกในทำนองเดียวกันใน. NET; น่าเสียดายที่นี่เป็นที่เดียวที่ไม่ได้เรียนรู้ข้อบกพร่องของ Java
Eric Lippert

121

ฉันแน่ใจว่าผู้ออกแบบภาษาเช่น Java หรือ C # รู้ปัญหาที่เกี่ยวข้องกับการมีอยู่ของการอ้างอิงที่เป็นโมฆะ

แน่นอน.

การใช้ประเภทตัวเลือกนั้นไม่ซับซ้อนกว่าการอ้างอิงแบบ null

ฉันขอแตกต่าง! ข้อควรพิจารณาในการออกแบบที่เปลี่ยนเป็นประเภทค่าที่ไม่มีค่าใน C # 2 มีความซับซ้อนขัดแย้งและยาก พวกเขานำทีมออกแบบของทั้งสองภาษาและการถกเถียงกันมาหลายเดือนการใช้งานต้นแบบและอื่น ๆ และในความเป็นจริงความหมายของการชกมวย nullable ถูกเปลี่ยนอย่างใกล้ชิดกับการขนส่ง C # 2.0 ซึ่งขัดแย้งกันมาก

เหตุใดพวกเขาจึงตัดสินใจรวมไว้ด้วย

การออกแบบทั้งหมดเป็นกระบวนการของการเลือกเป้าหมายที่เข้ากันไม่ได้อย่างละเอียดและไม่เข้าท่า ฉันสามารถให้ภาพร่างสั้น ๆ ของปัจจัยเพียงไม่กี่ข้อที่จะพิจารณา:

  • ความโดดเด่นของคุณสมบัติภาษานั้นโดยทั่วไปถือว่าเป็นสิ่งที่ดี C # มีประเภทค่าที่เป็นโมฆะประเภทค่าที่ไม่เป็นโมฆะและประเภทการอ้างอิงที่ไม่มีค่า ประเภทการอ้างอิงที่ไม่เป็นโมฆะไม่มีอยู่ซึ่งทำให้ระบบประเภทไม่เป็นแบบมุมฉาก

  • ความคุ้นเคยกับผู้ใช้ปัจจุบันของ C, C ++ และ Java เป็นสิ่งสำคัญ

  • การทำงานร่วมกันได้ง่ายกับ COM เป็นสิ่งสำคัญ

  • การทำงานร่วมกันได้ง่ายกับภาษา. NET อื่น ๆ ทั้งหมดเป็นสิ่งสำคัญ

  • การทำงานร่วมกันอย่างง่ายดายกับฐานข้อมูลเป็นสิ่งสำคัญ

  • ความสอดคล้องของความหมายเป็นสิ่งสำคัญ ถ้าเรามีการอ้างอิง TheKingOfFrance เท่ากับ null นั่นแปลว่า "ตอนนี้ไม่มีกษัตริย์ของฝรั่งเศส" หรือมันอาจหมายถึง "มีราชาแห่งฝรั่งเศสแน่นอนฉันไม่รู้ว่าใครเป็นใครในตอนนี้"? หรืออาจหมายถึง "ความคิดที่ว่าการมีกษัตริย์ในฝรั่งเศสนั้นไร้สาระดังนั้นอย่าถามด้วยซ้ำ!"? Null อาจหมายถึงสิ่งเหล่านี้ทั้งหมดและอื่น ๆ ใน C # และแนวคิดเหล่านี้ทั้งหมดมีประโยชน์

  • ต้นทุนประสิทธิภาพเป็นสิ่งสำคัญ

  • การคล้อยตามการวิเคราะห์เชิงสถิตเป็นสิ่งสำคัญ

  • ความสอดคล้องของระบบพิมพ์เป็นสิ่งสำคัญ เราจะรู้ได้อย่างไรว่าการอ้างอิงที่ไม่เป็นโมฆะไม่เคยภายใต้สถานการณ์ใด ๆ ที่พบว่าไม่ถูกต้อง? สิ่งที่เกี่ยวกับในตัวสร้างของวัตถุที่มีเขตข้อมูลที่ไม่เป็นโมฆะประเภทการอ้างอิง? สิ่งที่เกี่ยวกับใน finalizer ของวัตถุดังกล่าวที่วัตถุจะเสร็จสมบูรณ์เพราะรหัสที่ควรจะกรอกในการอ้างอิงโยนข้อยกเว้น ? ระบบการพิมพ์ที่อยู่กับคุณเกี่ยวกับการรับประกันเป็นสิ่งที่อันตราย

  • และความหมายที่สอดคล้องกันของความหมายคืออะไร? Null ค่าเผยแพร่เมื่อใช้ แต่ null อ้างอิงโยนข้อยกเว้นเมื่อนำมาใช้ มันไม่สอดคล้องกัน ความไม่สอดคล้องนั้นมีความชอบธรรมโดยผลประโยชน์

  • เราสามารถใช้คุณสมบัติโดยไม่ทำลายคุณสมบัติอื่น ๆ ได้หรือไม่? คุณลักษณะอื่น ๆ ที่เป็นไปได้ในอนาคตมีคุณสมบัติที่ห้ามหรือไม่

  • คุณไปทำสงครามกับกองทัพที่คุณมีไม่ใช่คนที่คุณต้องการ โปรดจำไว้ว่า C # 1.0 ไม่ได้มีชื่อสามัญดังนั้นการพูดถึงMaybe<T>ทางเลือกจึงไม่ใช่ตัวเริ่มต้นที่สมบูรณ์ . NET ควรลดลงเป็นเวลาสองปีในขณะที่ทีมรันไทม์เพิ่มชื่อทั่วไปเพื่อกำจัดการอ้างอิงที่เป็นโมฆะ?

  • สิ่งที่เกี่ยวกับความสอดคล้องของระบบประเภท? คุณสามารถพูดNullable<T>สำหรับประเภทค่าใด ๆ - ไม่รอนั่นเป็นเรื่องโกหก คุณไม่สามารถพูดNullable<Nullable<T>>ได้ คุณควรจะทำอย่างไร ถ้ามีความหมายที่ต้องการคืออะไร? มันคุ้มค่าไหมที่ทำให้ระบบทั้งระบบมีตัวพิมพ์พิเศษสำหรับคุณสมบัตินี้?

และอื่น ๆ การตัดสินใจเหล่านี้มีความซับซ้อน


12
+1 สำหรับทุกสิ่งทุกอย่าง มันง่ายที่จะลืมว่ามีช่วงเวลาหนึ่งในประวัติศาสตร์ของ Java และ C # ที่ไม่มีชื่อสามัญ
Doval

2
อาจเป็นคำถามโง่ ๆ (ฉันเป็นแค่นักศึกษาระดับปริญญาตรีด้านไอที) - แต่ประเภทตัวเลือกที่ไม่สามารถใช้งานได้ในระดับไวยากรณ์ (กับ CLR ไม่ทราบอะไรเกี่ยวกับเรื่องนี้) เป็นการอ้างอิง nullable ปกติที่ต้องมีการตรวจสอบ "มีค่า" ก่อนใช้งาน รหัส? ฉันเชื่อว่าประเภทตัวเลือกไม่จำเป็นต้องมีการตรวจสอบที่รันไทม์
mrpyo

2
@mrpyo: แน่นอนว่านี่เป็นตัวเลือกการใช้งานที่เป็นไปได้ ไม่มีตัวเลือกการออกแบบอื่นหายไปและตัวเลือกการนำไปใช้นั้นมีข้อดีและข้อเสียมากมาย
Eric Lippert

1
@mrpyo ฉันคิดว่าการบังคับให้ใช้การตรวจสอบ "มีค่า" ไม่ใช่ความคิดที่ดี ในทางทฤษฎีมันเป็นความคิดที่ดีมาก แต่ในทางปฏิบัติ IMO มันจะนำเช็คว่างเปล่าทุกประเภทมาเติมเต็มคอมไพเลอร์ - เหมือนกับข้อยกเว้นที่ถูกตรวจสอบใน Java และผู้คนหลอกมันโดยcatchesไม่ทำอะไรเลย ฉันคิดว่าเป็นการดีกว่าที่จะปล่อยให้ระบบระเบิดแทนการทำงานต่อเนื่องในสถานะที่ไม่ถูกต้อง
NothingsImossible

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

28

Null ให้บริการวัตถุประสงค์ที่ถูกต้องมากในการแสดงถึงการขาดคุณค่า

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

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

ตัวอย่างการพิสูจน์โมฆะ:

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

Date of Death เป็นฟิลด์ DateTime และไม่มีค่า "ไม่ทราบ" หรือ "ว่าง" มันมีวันที่เริ่มต้นที่เกิดขึ้นเมื่อคุณสร้างวันที่และเวลาใหม่ซึ่งแตกต่างกันไปตามภาษาที่ใช้ แต่มีโอกาสทางเทคนิคที่บุคคลนั้นในความเป็นจริงตายในเวลานั้นและจะตั้งค่าสถานะเป็น "ค่าว่าง" ถ้าคุณ ใช้วันที่เริ่มต้น

ข้อมูลจะต้องแสดงถึงสถานการณ์อย่างถูกต้อง

บุคคลคือวันที่ตายซึ่งเป็นที่รู้จัก (3/9/1984)

ง่าย '3/9/1984'

บุคคลคือวันที่ตายซึ่งไม่ทราบ

ดังนั้นสิ่งที่ดีที่สุด เป็นศูนย์ '0/0/0000' หรือ '01 / 01/1869 '(หรือค่าเริ่มต้นของคุณคืออะไร)

คนไม่ตายวันตายไม่เกี่ยวข้อง

ดังนั้นสิ่งที่ดีที่สุด เป็นศูนย์ '0/0/0000' หรือ '01 / 01/1869 '(หรือค่าเริ่มต้นของคุณคืออะไร)

งั้นลองคิดค่าแต่ละอย่างกัน ...

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

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

หากฉันไม่มีแอปเปิ้ลฉันมี 0 แอปเปิ้ล แต่ถ้าฉันไม่รู้แอปเปิ้ลที่ฉันมี

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


37
Null serves a very valid purpose of representing a lack of value.OptionหรือMaybeประเภทมีจุดมุ่งหมายที่ถูกต้องอย่างนี้โดยไม่ต้องผ่านระบบการพิมพ์
Doval

34
ไม่มีใครโต้แย้งว่าไม่ควรมีค่าขาดมูลค่าพวกเขาโต้เถียงว่าค่าที่อาจหายไปควรทำเครื่องหมายอย่างชัดเจนเช่นนั้นแทนที่จะเป็นทุกค่าที่อาจหายไป

2
ฉันเดา RualStorge ได้พูดคุยในความสัมพันธ์กับ SQL NOT NULLเพราะมีค่ายที่รัฐคอลัมน์ทุกคนควรจะทำเครื่องหมายว่าเป็น คำถามของฉันไม่เกี่ยวข้องกับ RDBMS แม้ว่า ...
mrpyo

5
+1 สำหรับการแยกแยะระหว่าง "ไม่มีค่า" กับ "ค่าที่ไม่รู้จัก"
David

2
มันจะไม่สมเหตุสมผลกว่าหรือที่จะแยกรัฐออกจากบุคคล? เช่นPersonชนิดมีstateสาขาของประเภทStateซึ่งเป็นสหภาพการเลือกปฏิบัติของและAlive Dead(dateOfDeath : Date)
jon-hanson

10

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

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

แต่ (โดยเฉพาะอย่างยิ่งสำหรับ Java และ C # เนื่องจากเวลาของการแนะนำและกลุ่มเป้าหมายของพวกเขา) โดยใช้ประเภทตัวเลือกสำหรับชั้นการทำงานร่วมกันนี้อาจได้รับอันตรายหากไม่ได้ฉลองชัยการนำไปใช้ ไม่ว่าชนิดของตัวเลือกจะถูกส่งไปจนหมดทำให้เกิดความรำคาญกับโปรแกรมเมอร์ C ++ ในช่วงกลางถึงปลายยุค 90 หรือเลเยอร์การทำงานร่วมกันอาจทำให้เกิดข้อยกเว้นเมื่อพบโมฆะ ..


3
ย่อหน้าแรกไม่สมเหตุสมผลสำหรับฉัน Java ไม่มี C interop ในรูปร่างที่คุณแนะนำ (มี JNI แต่กระโดดข้ามห่วงสิบสำหรับทุกสิ่งที่เกี่ยวข้องกับการอ้างอิง; และมันไม่ค่อยใช้ในการปฏิบัติ) เช่นเดียวกับภาษา "ทันสมัย" อื่น ๆ

@delnan - ขอโทษฉันคุ้นเคยกับ C # มากกว่าซึ่งมี interop ประเภทนี้ ฉันคิดว่าห้องสมุด Java พื้นฐานหลายแห่งใช้ JNI ที่ด้านล่างเช่นกัน
Telastyn

6
คุณทำให้อาร์กิวเมนต์ที่ดีสำหรับการอนุญาตให้เป็นโมฆะ แต่คุณยังสามารถช่วยให้ null โดยไม่ต้องให้กำลังใจมัน สกาล่าเป็นตัวอย่างที่ดีของเรื่องนี้ มันต่อเนื่องสามารถทำงานร่วมกับ APIs Java ที่ใช้ null แต่คุณได้รับการสนับสนุนในการห่อไว้ในสำหรับการใช้งานภายในสกาล่าซึ่งเป็นเรื่องง่ายเหมือนOption ในทางปฏิบัติมันไม่ได้ใช้เวลานานมากสำหรับคนที่จะเห็นประโยชน์ของการนั้นval x = Option(possiblyNullReference) Option
Karl Bielefeldt

1
ประเภทของตัวเลือกจะไปจับคู่กับการจับคู่รูปแบบ (ตรวจสอบแบบคงที่) ซึ่ง C # น่าเสียดายที่ไม่มี แม้ว่า F # จะเป็นเช่นนั้นและมันวิเศษมาก
Steven Evers

1
@SteveEvers เป็นไปได้ที่จะทำการปลอมโดยใช้คลาสฐานที่เป็นนามธรรมพร้อมตัวสร้างส่วนตัวคลาสภายในที่ปิดผนึกและMatchวิธีการที่ใช้ผู้รับมอบสิทธิ์เป็นอาร์กิวเมนต์ จากนั้นคุณส่งแลมบ์ดานิพจน์ไปที่Match(คะแนนโบนัสสำหรับการใช้อาร์กิวเมนต์ที่มีชื่อ) และMatchเรียกสิ่งที่ถูกต้อง
Doval

7

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

ช่วยให้nullการอ้างอิง (และตัวชี้) เป็นเพียงหนึ่งในการดำเนินการตามแนวคิดนี้และอาจจะเป็นที่นิยมมากที่สุดแม้ว่ามันจะรู้ว่าจะมีปัญหา: C, Java, Python, Ruby, PHP, JavaScript, ... nullทั้งหมดใช้ที่คล้ายกัน

ทำไม ทางเลือกคืออะไร

ในภาษาที่ใช้งานได้เช่น Haskell คุณมีOptionหรือMaybeพิมพ์; อย่างไรก็ตามสิ่งเหล่านี้ถูกสร้างขึ้นเมื่อ:

  • ประเภทพารามิเตอร์
  • ชนิดข้อมูลเชิงพีชคณิต

ตอนนี้ต้นฉบับ C, Java, Python, Ruby หรือ PHP รองรับฟีเจอร์เหล่านั้นหรือไม่? ไม่ข้อมูลทั่วไปของข้อบกพร่องของจาวาเป็นเรื่องล่าสุดในประวัติศาสตร์ของภาษาและฉันก็สงสัยว่าคนอื่น ๆ จะนำไปใช้จริง

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


+1 สำหรับ "null นั้นง่ายประเภทข้อมูลพีชคณิตเกี่ยวกับตัวแปรนั้นยากกว่า" แต่ฉันคิดว่ามันไม่ได้เป็นปัญหาของการพิมพ์พารามิเตอร์และ ADTs มากขึ้น เป็นเพียงว่าพวกเขาไม่ได้รับรู้ตามความจำเป็น ถ้า Java ถูกส่งโดยไม่มีระบบวัตถุในทางกลับกันมันจะล้มลง OOP เป็นคุณสมบัติ "showstopping" ซึ่งถ้าคุณไม่มีมันก็ไม่มีใครสนใจ
Doval

@Doval: OOP อาจเป็นสิ่งจำเป็นสำหรับ Java แต่ไม่ใช่สำหรับ C :) แต่เป็นความจริงที่ Java มุ่งเน้นที่ความเรียบง่าย น่าเสียดายที่ผู้คนคิดว่าภาษาง่าย ๆ นำไปสู่โปรแกรมที่เรียบง่ายซึ่งค่อนข้างแปลก (Brainfuck เป็นภาษาที่ง่ายมาก ... ) แต่เราเห็นด้วยอย่างแน่นอนว่าภาษาที่ซับซ้อน (C ++ ... ) ไม่ใช่ยาครอบจักรวาล พวกมันมีประโยชน์อย่างเหลือเชื่อ
Matthieu M.

1
@MatthieuM: ระบบจริงมีความซับซ้อน ภาษาที่ได้รับการออกแบบมาอย่างดีซึ่งมีความซับซ้อนตรงกับระบบในโลกแห่งความจริงที่ถูกสร้างแบบจำลองสามารถอนุญาตให้ระบบที่ซับซ้อนถูกสร้างแบบจำลองด้วยรหัสง่าย ๆ ความพยายามที่จะทำให้ภาษามีขนาดใหญ่เกินไปเพียงแค่ส่งความซับซ้อนไปยังโปรแกรมเมอร์ที่ใช้งานอยู่
supercat

@supercat: ฉันไม่สามารถตกลงกันได้อีก หรืออย่างที่ไอน์สไตน์ถอดความ: "ทำให้ทุกอย่างง่ายที่สุดเท่าที่จะทำได้ แต่ไม่ง่ายกว่านี้"
Matthieu M.

@ MatthieuM: Einstein มีความฉลาดในหลาย ๆ ด้าน ภาษาที่พยายามสมมติว่า "ทุกอย่างเป็นวัตถุการอ้างอิงถึงที่อาจถูกเก็บไว้ในObject" ล้มเหลวในการรับรู้ว่าแอปพลิเคชันที่ใช้งานจริงต้องใช้วัตถุที่ไม่สามารถแบ่งปันที่ไม่ได้ใช้ร่วมกันและวัตถุที่ไม่เปลี่ยนรูปแบบร่วมกันได้ หน่วยงาน การใช้Objectประเภทเดียวสำหรับทุกสิ่งไม่ได้ขจัดความต้องการความแตกต่างดังกล่าว มันทำให้ยากต่อการใช้อย่างถูกต้อง
supercat

5

Null / nil / none ไม่มีความชั่วร้าย

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

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

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


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

ทีนี้, รากที่ไม่ใช่แบบดั้งเดิมหนึ่งชนิดในอัตราใด ๆ เหตุใดจึงเป็นจุดอ่อนจากมุมมองของภาษา ฉันไม่เข้าใจว่าทำไมความจริงข้อนี้กำหนดให้ทุกประเภทมีค่าเริ่มต้น (หรือในทางกลับกันสาเหตุที่หลายประเภทของรูทจะอนุญาตให้ประเภทไม่มีค่าเริ่มต้น) หรือสาเหตุที่เป็นจุดอ่อน
BT

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

... ระบบชนิด Java ไม่มีวิธีการแสดงว่า หากfooถือการอ้างอิงเท่านั้นกับผู้int[]ที่มี{1,2,3}รหัสและต้องการfooที่จะถือการอ้างอิงไปยังint[]มีวิธีที่เร็วที่สุดเพื่อให้บรรลุว่าจะเพิ่มขึ้น{2,2,3} foo[0]หากโค้ดต้องการให้เมธอดทราบว่าfooมี{1,2,3}อยู่เมธอดอื่นจะไม่แก้ไขอาร์เรย์หรือคงอยู่การอ้างอิงเกินกว่าจุดที่fooต้องการจะแก้ไขวิธีที่เร็วที่สุดในการบรรลุซึ่งจะเป็นการส่งผ่านการอ้างอิงไปยังอาร์เรย์ ถ้า Java มีประเภท "การอ้างอิงแบบอ่านอย่างเดียว" ดังนั้น ...
supercat

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

4

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

ตัวอย่างเช่นถ้าฉันต้องการเขียนสคริปต์อย่างง่ายที่ทำงานกับไฟล์ฉันสามารถเขียนรหัสเทียมเช่น:

file = openfile("joebloggs.txt")

for line in file
{
  print(line)
}

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


13
และที่นี่คุณให้ตัวอย่างของสิ่งที่ผิดกับ nulls ฟังก์ชัน "openfile" ที่นำมาใช้อย่างถูกต้องควรมีข้อยกเว้น (สำหรับไฟล์ที่หายไป) ซึ่งจะหยุดการทำงานที่นั่นพร้อมคำอธิบายที่ถูกต้องเกี่ยวกับสิ่งที่เกิดขึ้น แต่ถ้ามันคืนค่า null มันจะแพร่กระจายต่อไป (ถึงfor line in file) และส่งข้อยกเว้นการอ้างอิง null ที่ไม่มีความหมายซึ่งก็โอเคสำหรับโปรแกรมอย่างง่าย แต่ทำให้เกิดปัญหาการดีบักจริงในระบบที่ซับซ้อนมากขึ้น หากไม่มีค่า null ผู้ออกแบบ "openfile" จะไม่สามารถทำผิดพลาดได้
mrpyo

2
+1 สำหรับ "เนื่องจากภาษาการเขียนโปรแกรมได้รับการออกแบบโดยทั่วไปว่ามีประโยชน์มากกว่าการแก้ไขในทางเทคนิค"
Martin Ba

2
ประเภทตัวเลือกที่ทุกคนฉันรู้ช่วยให้คุณทำล้มเหลว-on-null กับสั้นเรียกวิธีเดียวพิเศษ (สนิมเช่น: let file = something(...).unwrap()) ขึ้นอยู่กับ POV ของคุณเป็นวิธีที่ง่ายที่จะไม่จัดการกับข้อผิดพลาดหรือยืนยันรวบรัดว่า null ไม่สามารถเกิดขึ้นได้ และเวลาในการสูญเสียน้อยที่สุดและให้คุณประหยัดเวลาในสถานที่อื่น ๆ เพราะคุณจะได้ไม่ต้องคิดออกว่าบางสิ่งบางอย่างสามารถเป็นโมฆะ ข้อดีอีกประการ (ซึ่งอาจคุ้มค่ากับการโทรเพิ่ม) คือคุณไม่ต้องสนใจกรณีข้อผิดพลาดอย่างชัดเจน เมื่อล้มเหลวมีข้อสงสัยเล็กน้อยว่าเกิดข้อผิดพลาดอะไรและการแก้ไขต้องไปที่ใด

4
@mrpyo ไม่ใช่ทุกภาษาที่รองรับข้อยกเว้นและ / หรือการจัดการข้อยกเว้น (ลอง / จับ) และสามารถใช้ข้อยกเว้นได้เช่นกัน - "exception as flow control" เป็นรูปแบบการต่อต้านทั่วไป สถานการณ์นี้ - ไฟล์ไม่มีอยู่ - เป็น AFAIK ตัวอย่างที่อ้างถึงบ่อยที่สุดของรูปแบบการต่อต้าน ดูเหมือนว่าคุณกำลังแทนที่การฝึกฝนที่ไม่ดีอย่างหนึ่ง
David

8
@mrpyo if file exists { open file }ทนทุกข์ทรมานจากสภาพการแข่งขัน วิธีที่เชื่อถือได้เพียงวิธีเดียวที่จะทราบได้ว่าการเปิดไฟล์จะสำเร็จหรือไม่คือการลองเปิดไฟล์

4

มีการใช้ตัวชี้NULL(หรือnilหรือNilหรือnullหรือNothingหรือสิ่งที่เรียกว่าเป็นภาษาที่คุณต้องการ) อย่างชัดเจนและเป็นประโยชน์

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

char *buf = malloc(20);
if (!buf)
{
    perror("memory allocation failed");
    exit(1);
}

ที่นี่NULLส่งคืนจากmalloc(3)ใช้เป็นเครื่องหมายของความล้มเหลว

เมื่อใช้ในการโต้แย้งวิธี / ฟังก์ชั่นก็สามารถระบุการใช้เริ่มต้นสำหรับการโต้แย้งหรือละเว้นอาร์กิวเมนต์เอาท์พุท ตัวอย่างด้านล่าง

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

NSError *err = nil;
NSString *content = [NSString stringWithContentsOfURL:sourceFile
                                         usedEncoding:NULL // This output is ignored
                                                error:&err];
if (!content) // If the object is null, we have a soft error to recover from
{
    fprintf(stderr, "error: %s\n", [[err localizedDescription] UTF8String]);
    if (!error) // Check if the parent method ignored the error argument
        *error = err;
    return nil; // Go back to parent layer, with another soft error.
}

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


5
ปัญหาคือไม่มีวิธีแยกความแตกต่างของตัวแปรที่ไม่ควรมีnullจากที่ควร ตัวอย่างเช่นถ้าฉันต้องการประเภทใหม่ที่มี 5 ค่าใน Java ฉันสามารถใช้ enum ได้ แต่สิ่งที่ฉันได้รับคือประเภทที่สามารถเก็บค่าได้ 6 ค่า (5 ตัวที่ฉันต้องการ + null) มันเป็นข้อบกพร่องในระบบประเภท
Doval

@Doval ถ้านั่นเป็นสถานการณ์ที่เพิ่งกำหนดค่า NULL ให้กับความหมาย (หรือถ้าคุณมีค่าเริ่มต้นให้ถือว่ามันเป็นคำพ้องความหมายของค่าเริ่มต้น) หรือใช้ NULL (ซึ่งไม่ควรปรากฏในตอนแรก) เป็นเครื่องหมายของข้อผิดพลาดอ่อน (เช่นข้อผิดพลาด แต่อย่างน้อยก็ยังไม่พัง)
Maxthon Chan

1
@ MaxtonChan Nullสามารถกำหนดความหมายได้เฉพาะเมื่อค่าประเภทไม่มีข้อมูล (เช่นค่า enum) ทันทีที่ค่าของคุณมีความซับซ้อนมากขึ้น (เช่นโครงสร้าง) nullไม่สามารถกำหนดความหมายที่เหมาะสมสำหรับประเภทนั้นได้ ไม่มีวิธีใช้nullเป็นโครงสร้างหรือรายการ และอีกครั้งปัญหาของการใช้nullเป็นสัญญาณข้อผิดพลาดก็คือเราไม่สามารถบอกได้ว่าสิ่งใดอาจส่งคืนโมฆะหรือยอมรับโมฆะ ตัวแปรใด ๆ ในโปรแกรมของคุณอาจเป็นไปได้nullเว้นแต่คุณจะพิถีพิถันอย่างมากในการตรวจสอบทุกรายการnullก่อนใช้งานครั้งเดียวซึ่งไม่มีใครทำ
Doval

1
@Doval: ไม่มีความยากโดยธรรมชาติในการมีชนิดการอ้างอิงที่ไม่เปลี่ยนรูปซึ่งถือว่าnullเป็นค่าเริ่มต้นที่ใช้งานได้ (เช่นมีค่าเริ่มต้นของการstringทำงานเป็นสตริงว่างเปล่าวิธีที่มันทำภายใต้ Common Object Model ก่อนหน้า) สิ่งที่จำเป็นทั้งหมดนั้นมีไว้เพื่อให้ภาษาใช้callแทนที่จะcallvirtเรียกใช้สมาชิกที่ไม่เสมือน
supercat

@supercat เป็นจุดที่ดี แต่ตอนนี้คุณไม่จำเป็นต้องเพิ่มการสนับสนุนสำหรับการแยกแยะระหว่างประเภทที่ไม่เปลี่ยนรูปแบบและไม่เปลี่ยนรูปแบบ? ฉันไม่แน่ใจว่าจะเพิ่มเรื่องเล็กน้อยในภาษาได้อย่างไร
Doval

4

มีสองประเด็นที่เกี่ยวข้อง แต่แตกต่างกันเล็กน้อย:

  1. ควรnullมีอยู่ทั้งหมดหรือไม่ หรือคุณควรใช้ในMaybe<T>กรณีที่ null มีประโยชน์เสมอ
  2. ควรอ้างอิงทั้งหมดเป็นโมฆะ? ถ้าไม่ควรจะเป็นค่าเริ่มต้น?

    การประกาศประเภทอ้างอิงที่ไม่สามารถระบุได้อย่างชัดเจนว่าเป็นสาเหตุที่ทำให้เกิดstring?ปัญหาส่วนใหญ่ (แต่ไม่ทั้งหมด) ของปัญหาnullโดยไม่แตกต่างจากโปรแกรมเมอร์ที่ใช้

อย่างน้อยฉันก็เห็นด้วยกับคุณที่ไม่อ้างอิงทั้งหมดควรเป็นโมฆะ แต่การหลีกเลี่ยงค่า null นั้นไม่ได้เกิดจากความซับซ้อน:

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

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

  • สำหรับฟิลด์แบบสแตติกทำให้มั่นใจได้ว่านี่จะยากกว่าเว้นแต่คุณจะมีข้อ จำกัด ที่เข้มงวดเกี่ยวกับชนิดของรหัสที่อาจทำงานในฟิลด์ initializer เนื่องจากคุณไม่สามารถซ่อนthisตัวชี้ได้

  • วิธีการเริ่มต้นอาร์เรย์ของประเภทอ้างอิง? พิจารณาหนึ่งList<T>ซึ่งได้รับการสนับสนุนโดยอาร์เรย์ที่มีความจุมากกว่าความยาว องค์ประกอบที่เหลือต้องมีค่าบางอย่าง

ปัญหาอีกประการหนึ่งคือมันไม่อนุญาตให้วิธีการbool TryGetValue<T>(key, out T value)ที่กลับมาdefault(T)ราวกับvalueว่าพวกเขาไม่พบอะไรเลย แม้ว่าในกรณีนี้มันง่ายที่จะยืนยันว่าพารามิเตอร์ out เป็นการออกแบบที่ไม่ดีในตอนแรกและวิธีนี้ควรคืนค่าการแยกแยะสหภาพหรืออาจจะแทน

ปัญหาทั้งหมดเหล่านี้สามารถแก้ไขได้ แต่มันก็ไม่ง่ายเหมือน "การห้ามให้เป็นโมฆะและทั้งหมดเป็นอย่างดี"


List<T>เป็น IMHO ตัวอย่างที่ดีที่สุดเพราะมันจะต้องมีทั้งที่ทุกคนTมีค่าเริ่มต้นที่รายการในการจัดเก็บสำรองข้อมูลทุกจะเป็นMaybe<T>กับเพิ่มช่อง "IsValid" แม้เมื่อTเป็นMaybe<U>หรือว่ารหัสสำหรับการList<T>ทำงานแตกต่างกันขึ้นอยู่กับ ขึ้นอยู่กับว่าTตัวเองเป็นประเภท nullable ฉันจะพิจารณาการเริ่มต้นของT[]องค์ประกอบเป็นค่าเริ่มต้นที่จะเป็นความชั่วร้ายอย่างน้อยที่สุดของตัวเลือกเหล่านั้น แต่แน่นอนว่าองค์ประกอบนั้นต้องมีค่าเริ่มต้น
supercat

สนิมตามจุดที่ 1 - ไม่มีค่าว่างเลย Ceylon ตามหลังจุดที่ 2 - ไม่ใช่ค่าเริ่มต้น การอ้างอิงที่สามารถเป็นโมฆะได้มีการประกาศอย่างชัดเจนพร้อมกับประเภทยูเนี่ยนที่รวมการอ้างอิงหรือโมฆะ แต่ null ไม่สามารถเป็นค่าของการอ้างอิงธรรมดาได้ เป็นผลให้ภาษามีความปลอดภัยอย่างสมบูรณ์และไม่มี NullPointerException เพราะมันเป็นไปไม่ได้ทางความหมาย
Jim Balter

2

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

ภาษาและระบบที่ต่างกันใช้แนวทางที่แตกต่างกัน

  • วิธีหนึ่งที่จะกล่าวได้ว่าการพยายามอ่านสิ่งที่ไม่ได้เขียนจะก่อให้เกิดข้อผิดพลาดทันที

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

  • วิธีที่สามคือการเพิกเฉยต่อปัญหาและปล่อยให้สิ่งที่จะเกิดขึ้น "ตามธรรมชาติ" ก็เกิดขึ้น

  • วิธีที่สี่คือการบอกว่าทุกประเภทจะต้องมีค่าเริ่มต้นและช่องใด ๆ ที่ไม่ได้เขียนด้วยอะไรจะเริ่มต้นกับค่านั้น

วิธีการ # 4 มีความปลอดภัยกว่าวิธีที่ # 3 อย่างมากและโดยทั่วไปราคาถูกกว่าวิธีที่ # 1 และ # 2 จากนั้นจึงทิ้งคำถามว่าค่าเริ่มต้นควรเป็นประเภทการอ้างอิงใด สำหรับประเภทการอ้างอิงที่ไม่เปลี่ยนรูปแบบในหลายกรณีมันจะสมเหตุสมผลที่จะกำหนดอินสแตนซ์เริ่มต้นและบอกว่าค่าเริ่มต้นสำหรับตัวแปรใด ๆ ของประเภทนั้นควรเป็นการอ้างอิงไปยังอินสแตนซ์นั้น อย่างไรก็ตามสำหรับประเภทการอ้างอิงที่ไม่แน่นอนนั้นจะไม่เป็นประโยชน์ หากมีความพยายามในการใช้ประเภทการอ้างอิงที่ไม่แน่นอนก่อนที่จะถูกเขียนขึ้นโดยทั่วไปจะไม่มีวิธีการที่ปลอดภัยใด ๆ ยกเว้นกับดัก ณ จุดที่พยายามใช้งาน

การพูดเชิงความหมายถ้ามีหลายcustomersประเภทCustomer[20]และหนึ่งความพยายามCustomer[4].GiveMoney(23)โดยไม่เก็บอะไรไว้Customer[4]การดำเนินการจะต้องดัก เราอาจโต้เถียงว่าความพยายามในการอ่านCustomer[4]ควรดักทันทีแทนที่จะรอจนกว่าจะพยายามใช้รหัสGiveMoneyแต่มีหลายกรณีที่มีประโยชน์ในการอ่านสล็อตพบว่ามันไม่ได้มีคุณค่าและใช้ประโยชน์จากสิ่งนั้น ข้อมูลที่มีความพยายามในการอ่านตัวเองล้มเหลวมักจะเป็นปัญหาใหญ่

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


จะไม่ได้เป็นMaybe/ Optionประเภทแก้ปัญหาด้วย # 2 เพราะถ้าคุณไม่ได้มีค่าสำหรับการอ้างอิงของคุณเลยแต่จะมีหนึ่งในอนาคตคุณก็สามารถจัดเก็บNothingในMaybe <Ref type>?
Doval

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

@Doval: ประเภทการสนับสนุนของควรList<T>จะเป็นT[]หรือMaybe<T>? สิ่งที่เกี่ยวกับประเภทการสนับสนุนของList<Maybe<T>>?
supercat

@supercat ผมไม่แน่ใจว่าวิธีการประเภทการสนับสนุนของMaybeทำให้รู้สึกListตั้งแต่Maybeถือเป็นค่าเดียว คุณหมายถึงMaybe<T>[]อะไร
Doval

@cHao Nothingเท่านั้นที่สามารถได้รับมอบหมายให้ค่าชนิดดังนั้นจึงไม่มากเช่นการกำหนดMaybe และมีสองประเภทที่แตกต่างกัน nullMaybe<T>T
Doval
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.