ตัวชี้ null กับรูปแบบวัตถุ Null


27

การแสดงที่มา: สิ่งนี้เกิดจากคำถาม P.SEที่เกี่ยวข้อง

พื้นหลังของฉันอยู่ใน C / C ++ แต่ฉันได้ทำงานในจำนวนที่เป็นธรรมใน Java และฉันกำลังเข้ารหัส C # เนื่องจากพื้นหลัง C ของฉันการตรวจสอบพอยน์เตอร์ที่ผ่านและส่งคืนนั้นเป็นของมือสอง แต่ฉันยอมรับว่ามันมีอคติต่อมุมมองของฉัน

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

  • ดังนั้นข้อดี / ข้อเสียของการตรวจสอบค่า null เมื่อเปรียบเทียบกับการใช้รูปแบบวัตถุ Null คืออะไร

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

  • รูปแบบวัตถุ Null ไม่สามารถมีปัญหาที่คล้ายกันกับที่ไม่ทำการตรวจสอบเป็นโมฆะ?

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


ไม่ใช่ "ข้อผิดพลาดกรณี" แต่ "ในทุกกรณีเป็นวัตถุที่ถูกต้อง"

@ ThorbjørnRavnAndersen - จุดดีและฉันแก้ไขคำถามเพื่อสะท้อนให้เห็นว่า โปรดแจ้งให้เราทราบหากฉันยังคงตั้งสมมติฐานของ NOP ผิด

6
คำตอบสั้น ๆ คือ "รูปแบบวัตถุ null" เป็นชื่อเรียกที่ผิด มันถูกต้องมากกว่าที่เรียกว่า "null object anti-pattern" คุณมักจะดีกว่าด้วย "วัตถุผิดพลาด" - วัตถุที่ถูกต้องซึ่งใช้อินเทอร์เฟซทั้งหมดของคลาส แต่การใช้งานใด ๆ ของมันจะทำให้เกิดเสียงดังและเสียชีวิตอย่างกะทันหัน
Jerry Coffin

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

1
ฉันไม่ชอบ "รูปแบบ" นี้ แต่อาจเป็นไปได้ว่ามีการรูทในฐานข้อมูลเชิงสัมพันธ์แบบ overconstrained ซึ่งง่ายต่อการอ้างถึง "null แถว" พิเศษในคีย์ต่างประเทศในระหว่าง staging ของข้อมูลที่แก้ไขได้ก่อนการปรับปรุงขั้นสุดท้ายเมื่อคีย์ต่างประเทศทั้งหมดไม่สมบูรณ์

คำตอบ:


24

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

รูปแบบวัตถุ Null นั้นเหมาะสำหรับสถานที่ที่มีพฤติกรรมเริ่มต้นที่สามารถนำมาใช้ในกรณีที่ไม่พบวัตถุ ตัวอย่างเช่นพิจารณาดังต่อไปนี้:

User* pUser = GetUser( "Bob" );

if( pUser )
{
    pUser->SetAddress( "123 Fake St." );
}

ถ้าคุณใช้ NOP คุณจะเขียน:

GetUser( "Bob" )->SetAddress( "123 Fake St." );

โปรดทราบว่าพฤติกรรมของรหัสนี้คือ "ถ้า Bob มีอยู่ฉันต้องการอัปเดตที่อยู่ของเขา" เห็นได้ชัดว่าหากใบสมัครของคุณต้องการให้ Bob ปรากฏตัวคุณไม่ต้องการประสบความสำเร็จอย่างเงียบ ๆ แต่มีบางกรณีที่พฤติกรรมประเภทนี้เหมาะสม และในกรณีเหล่านั้น NOP ไม่ได้สร้างรหัสที่กระชับและกระชับกว่านี้ใช่ไหม

ในสถานที่ที่คุณไม่สามารถอยู่ได้โดยปราศจากบ๊อบฉันจะให้ GetUser () โยนข้อยกเว้นแอปพลิเคชัน (เช่นไม่ใช่การละเมิดการเข้าถึงหรืออะไรทำนองนั้น) ที่จะได้รับการจัดการในระดับที่สูงขึ้นและจะรายงานความล้มเหลวในการดำเนินงานทั่วไป ในกรณีนี้ไม่จำเป็นต้องใช้ NOP แต่ไม่จำเป็นต้องตรวจสอบ NULL อย่างชัดเจน IMO การตรวจสอบค่า NULL จะทำให้โค้ดใหญ่ขึ้นและไม่สามารถอ่านได้ ตรวจสอบ NULL ยังคงเป็นตัวเลือกการออกแบบที่เหมาะสมสำหรับอินเทอร์เฟซบางตัว แต่ไม่มากเท่าที่บางคนมักจะคิด


4
เห็นด้วยกับกรณีการใช้งานสำหรับรูปแบบวัตถุ null แต่ทำไมผลตอบแทนเป็นโมฆะเมื่อประสบกับความล้มเหลวอย่างรุนแรง? ทำไมไม่โยนข้อยกเว้น?
Apoorv Khurasia

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

2
@ MonsterTruck: โดย "ย้อนกลับว่า" ฉันหมายถึงอย่าส่งคืน NULL อย่างชัดเจนเมื่อคุณระบุข้อผิดพลาดที่ร้ายแรง แต่คาดว่า NULL จะเกิดขึ้นเพียงเพราะข้อผิดพลาดร้ายแรงที่ไม่คาดคิด
DXM

ตอนนี้ฉันเข้าใจสิ่งที่คุณหมายถึงโดยข้อผิดพลาดรุนแรง - บางสิ่งที่ทำให้การจัดสรรวัตถุล้มเหลว ขวา? แก้ไข: ไม่สามารถทำ @ DXM ได้เนื่องจากชื่อเล่นของคุณมีความยาวเพียง 3 ตัวอักษร
Apoorv Khurasia

@ MonsterTruck: แปลกเกี่ยวกับสิ่ง "@" ฉันรู้ว่าคนอื่นเคยทำมาก่อน ฉันสงสัยว่าพวกเขา tweaked เว็บไซต์เมื่อเร็ว ๆ นี้ ไม่จำเป็นต้องจัดสรร ถ้าฉันเขียนคลาสและความตั้งใจของ API คือการส่งคืนวัตถุที่ถูกต้องเสมอฉันจะไม่ให้ผู้โทรทำการตรวจสอบค่าว่าง แต่ข้อผิดพลาดที่เกิดจากความหายนะอาจเป็นอะไรก็ได้เช่นข้อผิดพลาดของโปรแกรมเมอร์ (ตัวพิมพ์ที่ทำให้ NULL ถูกส่งคืน) หรืออาจจะมีเงื่อนไขการแข่งขันบางอย่างที่ลบวัตถุก่อนเวลาหรืออาจมีความเสียหายของสแต็ก เป็นหลักเส้นทางรหัสใด ๆ ที่ไม่คาดคิดที่นำไปสู่การ NULL จะถูกส่งกลับ เมื่อสิ่งนั้นเกิดขึ้นคุณต้องการ ...
DXM

7

ดังนั้นข้อดี / ข้อเสียของการตรวจสอบค่า null เมื่อเปรียบเทียบกับการใช้รูปแบบวัตถุ Null คืออะไร

ข้อดี

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

จุดด้อย

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

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

เช่นเดียวกับการตัดสินใจออกแบบอื่น ๆ มีคว่ำและลงตามความต้องการของคุณ


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

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

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

@shawnhcorey - อะไรนะ
Telastyn

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

6

ฉันไม่ได้เป็นแหล่งข้อมูลที่ดี แต่เป็นแบบอัตนัย:

  • ฉันไม่ต้องการให้ระบบของฉันตายไปตลอดกาล สำหรับฉันมันเป็นสัญญาณของการออกแบบระบบที่ไม่ดีหรือไม่สมบูรณ์; ระบบที่สมบูรณ์ควรจัดการกับทุกกรณีที่เป็นไปได้และไม่เคยเข้าสู่สภาวะที่ไม่คาดคิด เพื่อให้บรรลุสิ่งนี้ฉันต้องมีความชัดเจนในการสร้างแบบจำลองการไหลและสถานการณ์ทั้งหมด ใช้ nulls ไม่ชัดเจนและกลุ่มต่าง ๆ มากมายกรณีภายใต้ umrella; ฉันชอบวัตถุ NonExistingUser เป็นโมฆะฉันชอบ NaN เป็นโมฆะ ... วิธีการดังกล่าวช่วยให้ฉันมีความชัดเจนเกี่ยวกับสถานการณ์เหล่านั้นและการจัดการของพวกเขาในรายละเอียดมากที่สุดเท่าที่ฉันต้องการในขณะที่ null ทำให้ฉันมีตัวเลือก null เท่านั้น ในสภาพแวดล้อมเช่น Java วัตถุใด ๆ ที่อาจเป็นโมฆะดังนั้นทำไมคุณต้องการที่จะซ่อนในกลุ่มทั่วไปของกรณีและกรณีเฉพาะของคุณ?
  • การใช้งานแบบ null มีพฤติกรรมที่แตกต่างอย่างมากจากวัตถุและส่วนใหญ่จะติดกับชุดของวัตถุทั้งหมด (เพราะเหตุใดเพราะอะไร) สำหรับฉันแล้วความแตกต่างที่รุนแรงนี้ดูเหมือนว่าเป็นผลมาจากการออกแบบโมฆะเพื่อบ่งบอกถึงข้อผิดพลาดซึ่งในกรณีที่ระบบส่วนใหญ่จะตาย (ฉันขอร้องให้คนจำนวนมากชอบที่ระบบของพวกเขาจะตายในกระทู้ด้านบน) สำหรับฉันนั่นเป็นวิธีการที่มีข้อบกพร่องในรูท - มันอนุญาตให้ระบบตายตามค่าเริ่มต้นเว้นแต่คุณจะได้รับการดูแลอย่างชัดเจนนั่นไม่ปลอดภัยใช่มั้ย แต่ในกรณีใด ๆ มันเป็นไปไม่ได้ที่จะเขียนโค้ดที่ถือว่าเป็นโมฆะค่าและวัตถุในลักษณะเดียวกัน - เพียงไม่กี่ตัวสร้างพื้นฐานในตัวดำเนินการ (มอบหมายเท่ากับเท่าเทียมพารามิเตอร์ ฯลฯ ) จะทำงานรหัสอื่น ๆ ทั้งหมดจะล้มเหลวและคุณต้องการ สองเส้นทางที่แตกต่างอย่างสิ้นเชิง
  • การใช้ Null Object ปรับขนาดได้ดีขึ้น - คุณสามารถใส่ข้อมูลและโครงสร้างได้มากเท่าที่คุณต้องการ NonExistingUser เป็นตัวอย่างที่ดีมาก - สามารถมีอีเมลของผู้ใช้ที่คุณพยายามรับและแนะนำให้สร้างผู้ใช้ใหม่ตามข้อมูลนั้นในขณะที่โซลูชัน null ฉันต้องคิดวิธีเก็บอีเมลที่ผู้ใช้พยายามเข้าถึง ใกล้กับการจัดการผลที่เป็นโมฆะ;

ในสภาพแวดล้อมเช่น Java หรือ C # มันจะไม่ให้ประโยชน์หลักของ explicitness - ความปลอดภัยของการแก้ปัญหาทำให้คุณยังสามารถรับ null ในตำแหน่งของวัตถุใด ๆ ในระบบและ Java ไม่มีวิธี (ยกเว้นหมายเหตุประกอบแบบกำหนดเองสำหรับถั่ว และอื่น ๆ ) เพื่อป้องกันคุณจากสิ่งนั้น แต่ในระบบที่ไม่มี null implementaitons การแก้ปัญหาด้วยวิธีดังกล่าว (ด้วยวัตถุที่แสดงถึงกรณีพิเศษ) ให้ประโยชน์ทั้งหมดที่กล่าวถึง ดังนั้นลองอ่านเรื่องอื่นที่ไม่ใช่ภาษากระแสหลักและคุณจะพบว่าตัวเองเปลี่ยนวิธีการเขียนโค้ด

โดยทั่วไปแล้ววัตถุ Null / Error / ประเภทการคืนจะถือว่าเป็นกลยุทธ์การจัดการข้อผิดพลาดที่มั่นคงมากและค่อนข้างมีเสียงทางคณิตศาสตร์ในขณะที่ข้อยกเว้นการจัดการกลยุทธ์ของ Java หรือ C ไปเหมือนขั้นตอนเดียวข้างต้นกลยุทธ์ "ตายเร็วที่สุด" - โดยทั่วไป ความไม่แน่นอนและความไม่แน่นอนของระบบในผู้พัฒนาหรือผู้ดูแล

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

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


9
เหตุผลที่ฉันต้องการให้แอปพลิเคชันของฉันล้มเหลวเมื่อสิ่งที่ไม่คาดคิดเกิดขึ้นคือสิ่งที่ไม่คาดคิดเกิดขึ้น มันเกิดขึ้นได้อย่างไร? ทำไม? ฉันได้รับการถ่ายโอนข้อมูลผิดพลาดและฉันสามารถตรวจสอบและแก้ไขปัญหาที่อาจเป็นอันตรายแทนที่จะปล่อยให้มันถูกซ่อนอยู่ในรหัส ใน C # แน่นอนว่าฉันสามารถตรวจจับข้อยกเว้นที่ไม่คาดคิดทั้งหมดเพื่อลองและกู้คืนแอปพลิเคชัน แต่ถึงอย่างนั้นฉันก็ต้องการบันทึกสถานที่ที่เกิดปัญหาและดูว่าเป็นสิ่งที่ต้องจัดการแยกกันหรือไม่ บันทึกข้อผิดพลาดนี้คาดว่าจะสามารถกู้คืนได้จริง ")
Luaan

3

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

Nilแทนที่จะได้รับกลับชี้ไปยังหน่วยความจำเตรียมเป็นภาษาที่จะกลับวัตถุ นี่คือความสะดวกโดยความจริงที่ว่าภาษาที่พิมพ์แบบไดนามิก ฟังก์ชันไม่รับประกันว่าจะส่งคืนintทุกครั้ง สามารถส่งคืนได้Nilถ้านั่นคือสิ่งที่ต้องทำ มันซับซ้อนมากขึ้นและยากที่จะทำสิ่งนี้ในภาษาที่พิมพ์แบบสแตติกเนื่องจากคุณคาดหวังว่า null จะใช้กับวัตถุใด ๆ ที่สามารถเรียกได้โดยอ้างอิง คุณจะต้องเริ่มใช้งานเวอร์ชันที่เป็นโมฆะของวัตถุใด ๆ ที่อาจเป็นโมฆะ (หรือไปที่เส้นทาง Scala / Haskell และมีกระดาษห่อ "บางที" บางชนิด)

MrLister นำเสนอข้อเท็จจริงที่ว่าใน C ++ คุณไม่ได้รับประกันว่าจะมีการอ้างอิง / ตัวชี้เริ่มต้นเมื่อคุณสร้างมันขึ้นมาครั้งแรก โดยมีวัตถุ null เฉพาะแทนตัวชี้ raw raw คุณสามารถได้รับคุณสมบัติเพิ่มเติมที่ดี Ruby Nilประกอบด้วยto_sฟังก์ชัน (toString) ที่ให้ผลลัพธ์เป็นข้อความว่างเปล่า นี่เป็นสิ่งที่ดีถ้าคุณไม่สนใจที่จะได้รับผลตอบแทนที่เป็นโมฆะในสตริงและต้องการข้ามการรายงานโดยไม่หยุดทำงาน Open Classes ยังอนุญาตให้คุณแทนที่ผลลัพธ์ด้วยตนเองNilหากจำเป็น

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


1

สำหรับบางมุมมองเพิ่มเติมให้ดูที่ Objective-C โดยที่แทนที่จะให้วัตถุ Null แทนค่า nil kind-of work เหมือนกับวัตถุ ข้อความใด ๆ ที่ส่งไปยังวัตถุไม่มีผลใด ๆ และส่งกลับค่า 0 / 0.0 / NO (false) / NULL / nil ไม่ว่าสิ่งที่ประเภทค่าตอบแทนของวิธีการคืออะไร สิ่งนี้ใช้เป็นรูปแบบที่แพร่หลายมากในการเขียนโปรแกรม MacOS X และ iOS และโดยปกติวิธีการจะได้รับการออกแบบเพื่อให้วัตถุศูนย์คืน 0 เป็นผลลัพธ์ที่สมเหตุสมผล

ตัวอย่างเช่นหากคุณต้องการตรวจสอบว่าสตริงมีอักขระอย่างน้อยหนึ่งตัวคุณสามารถเขียนได้

aString.length > 0

และรับผลลัพธ์ที่สมเหตุสมผลถ้า aString == nil เนื่องจากสตริงที่ไม่มีอยู่ไม่มีอักขระใด ๆ คนมักจะใช้เวลาสักครู่เพื่อทำความคุ้นเคยกับมัน แต่มันอาจจะใช้เวลาสักครู่ก่อนที่ฉันจะคุ้นเคยกับการตรวจสอบค่าศูนย์ในทุกที่ในภาษาอื่น ๆ

(นอกจากนี้ยังมีวัตถุซิงเกิลตันของคลาส NSNull ซึ่งทำงานแตกต่างกันมากและไม่ได้ใช้ในทางปฏิบัติมากนัก)


Objective-C ทำให้สิ่ง Null Object / Null Pointer พร่ามัวจริงๆ nilรับ#define'NULL' และทำตัวเหมือนวัตถุ null นั่นคือฟีเจอร์รันไทม์ขณะที่พอยน์เตอร์ C # / Java null ปล่อยข้อผิดพลาด
Maxthon Chan

0

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

อันดับแรกให้เขียนรายการ desiderata จากนั้นคิดว่าเราจะแสดงสิ่งนี้ในสองสามภาษา (C ++, OCaml, Python) ได้อย่างไร

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

ฉันคิดว่าความตึงเครียดระหว่าง Null Object Pattern และตัวชี้โมฆะมาจาก (5) หากเราสามารถตรวจพบข้อผิดพลาดเร็วพอแม้ว่า (5) จะกลายเป็นที่สงสัย

ลองพิจารณาภาษานี้ตามภาษา:

C ++

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

อย่างไรก็ตามหมายความว่าคุณไม่สามารถทราบได้อย่างแน่นอนว่าค่าประเภทMyClassอยู่ใน "สถานะเริ่มต้น" หรือไม่ นอกจากนี้ยังวางอยู่ในbool nonemptyสนามหรือคล้ายกันกับพื้นผิวเริ่มต้น-Ness ที่รันไทม์ที่ดีที่สุดที่คุณสามารถทำได้คือการผลิตอินสแตนซ์ใหม่MyClassในทางที่บังคับให้ผู้ใช้ตรวจสอบมัน

ผมขอแนะนำให้ใช้ฟังก์ชั่นโรงงานที่ส่งกลับstd::optional<MyClass>, std::pair<bool, MyClass>หรือการอ้างอิง R-value ไปstd::unique_ptr<MyClass>ถ้าเป็นไปได้

หากคุณต้องการให้ฟังก์ชั่นจากโรงงานของคุณคืนสถานะ "ตัวยึดตำแหน่ง" บางอย่างที่แตกต่างจากค่าเริ่มต้นที่สร้างMyClassให้ใช้std::pairและให้แน่ใจว่าเอกสารที่ฟังก์ชั่นของคุณทำ

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

OCaml

หากคุณกำลังใช้ภาษาอย่าง OCaml คุณสามารถใช้optionประเภท (ในไลบรารีมาตรฐานของ OCaml) หรือeitherประเภท (ไม่ใช่ในไลบรารีมาตรฐาน แต่ง่ายต่อการม้วนของคุณเอง) หรือdefaultableประเภท (ฉันกำลังทำคำศัพท์defaultable)

type 'a option =
    | None
    | Some of 'a

type ('a, 'b) either =
    | Left of 'a
    | Right of 'b

type 'a defaultable =
    | Default of 'a
    | Real of 'a

A defaultableดังที่แสดงไว้ข้างต้นดีกว่าคู่เนื่องจากผู้ใช้ต้องจับคู่รูปแบบเพื่อแยก'aและไม่สามารถละเลยองค์ประกอบแรกของคู่ได้

ชนิดที่เทียบเท่าdefaultableดังที่แสดงด้านบนสามารถใช้ใน C ++ โดยใช้ a ที่std::variantมีอินสแตนซ์สองประเภทเดียวกัน แต่บางส่วนของstd::variantAPI ไม่สามารถใช้ได้หากทั้งสองประเภทเหมือนกัน นอกจากนี้ยังเป็นการใช้งานที่แปลกstd::variantเนื่องจากตัวสร้างชนิดไม่มีชื่อ

หลาม

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

ฉันอยากจะแนะนำเพียงแค่โยนข้อยกเว้นเมื่อคุณจะถูกบังคับให้สร้างอินสแตนซ์เริ่มต้น

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

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