ออกแบบตามสัญญาโดยใช้คำยืนยันหรือข้อยกเว้น? [ปิด]


123

เมื่อเขียนโปรแกรมตามสัญญาฟังก์ชั่นหรือวิธีการก่อนอื่นให้ตรวจสอบว่ามีการปฏิบัติตามเงื่อนไขเบื้องต้นหรือไม่ก่อนที่จะเริ่มทำงานในความรับผิดชอบใช่ไหม? ทั้งสองมากที่สุดวิธีการประสบความสำเร็จในการทำตรวจสอบเหล่านี้โดยและassertexception

  1. ยืนยันล้มเหลวในโหมดดีบักเท่านั้น เพื่อให้แน่ใจว่าจำเป็นอย่างยิ่งที่จะต้อง (หน่วย) ทดสอบเงื่อนไขเบื้องต้นของสัญญาที่แยกจากกันทั้งหมดเพื่อดูว่าล้มเหลวจริงหรือไม่
  2. ข้อยกเว้นล้มเหลวในโหมดดีบักและรีลีส สิ่งนี้มีประโยชน์ที่พฤติกรรมการดีบักที่ทดสอบจะเหมือนกับพฤติกรรมการรีลีส แต่จะมีโทษประสิทธิภาพของรันไทม์

คุณคิดว่าอันไหนดีกว่ากัน?

ดูคำถามที่เกี่ยวข้องที่นี่


3
ประเด็นทั้งหมดที่อยู่เบื้องหลังการออกแบบตามสัญญาคือคุณไม่จำเป็นต้อง (และเนื้อหาไม่ควร) ตรวจสอบเงื่อนไขเบื้องต้นในรันไทม์ คุณตรวจสอบการป้อนข้อมูลก่อนที่จะผ่านมันกลายเป็นวิธีการที่มีปัจจัยพื้นฐานที่เป็นวิธีที่คุณเคารพของคุณสิ้นสุดสัญญา หากข้อมูลที่ป้อนไม่ถูกต้องหรือละเมิดการสิ้นสุดสัญญาของคุณโปรแกรมมักจะล้มเหลวอยู่ดีจากการดำเนินการตามปกติ (ที่คุณต้องการ)
void.pointer

เป็นคำถามที่ดี แต่ฉันคิดว่าคุณควรเปลี่ยนคำตอบที่ยอมรับจริงๆ (ตามที่แสดงผลโหวตด้วย)!
DaveFar

ฉันรู้ในภายหลังตลอดไป แต่คำถามนี้ควรมีแท็ก c ++ หรือไม่ ฉันกำลังมองหาคำตอบนี้เพื่อใช้ในภาษาอื่น (Delpih) และฉันนึกไม่ออกว่ามีภาษาใดบ้างที่มีข้อยกเว้นและการยืนยันที่ไม่เป็นไปตามกฎเดียวกัน (ยังคงเรียนรู้แนวทาง Stack Overflow)
Eric G

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

คำตอบ:


39

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

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


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

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

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

5
@jalf แม้ว่าคุณจะไม่สามารถใส่เบ็ดลงในดีบักเกอร์ในรุ่นรุ่นได้ แต่คุณสามารถใช้ประโยชน์จากการบันทึกเพื่อให้นักพัฒนาเห็นข้อมูลที่เกี่ยวข้องกับการยืนยันของคุณล้มเหลว ในเอกสารนี้ ( martinfowler.com/ieeeSoftware/failFast.pdf ) Jim Shore ชี้ให้เห็นว่า "โปรดจำไว้ว่าข้อผิดพลาดที่เกิดขึ้นที่ไซต์ของลูกค้าซึ่งเกิดจากกระบวนการทดสอบของคุณคุณอาจมีปัญหาในการสร้างซ้ำข้อผิดพลาดเหล่านี้คือ สิ่งที่หาได้ยากที่สุดและการยืนยันอย่างดีเพื่ออธิบายปัญหาอาจช่วยคุณประหยัดเวลาได้หลายวัน
StriplingWarrior

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

194

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


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

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

หากฟังก์ชัน f (int * x) ของคุณมีบรรทัด x-> len อยู่ดังนั้น f (v) ที่ v ถูกพิสูจน์แล้วว่าเป็นโมฆะจะรับประกันว่าจะผิดพลาด นอกจากนี้หากก่อนหน้านี้บน v ถูกพิสูจน์แล้วว่าเป็นโมฆะ แต่ f (v) ได้รับการพิสูจน์แล้วว่าถูกเรียกแสดงว่าคุณมีความขัดแย้งทางตรรกะ เช่นเดียวกับการมี a / b โดยที่ b ถูกพิสูจน์แล้วว่าเป็น 0 ในที่สุดโค้ดดังกล่าวไม่ควรคอมไพล์ การปิดการตรวจสอบสมมติฐานถือเป็นเรื่องโง่เขลาอย่างสิ้นเชิงเว้นแต่ว่าปัญหาจะเป็นค่าใช้จ่ายของการตรวจสอบเนื่องจากจะปิดบังตำแหน่งที่มีการละเมิดข้อสันนิษฐาน อย่างน้อยต้องเข้าสู่ระบบ คุณควรมีการออกแบบใหม่เมื่อหยุดทำงาน
Rob

22

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

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

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

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


6

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

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

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


5

ไม่เป็นความจริงอย่างแน่นอนที่ว่า "การยืนยันล้มเหลวในโหมดดีบักเท่านั้น"

ในObject Oriented Software Construction, 2nd Editionโดย Bertrand Meyer ผู้เขียนเปิดประตูทิ้งไว้เพื่อตรวจสอบเงื่อนไขเบื้องต้นในโหมดเผยแพร่ ในกรณีนี้สิ่งที่จะเกิดขึ้นเมื่อการยืนยันล้มเหลวคือ ... มีการยกข้อยกเว้นการละเมิดการยืนยัน! ในกรณีนี้จะไม่มีการกู้คืนจากสถานการณ์: สิ่งที่มีประโยชน์สามารถทำได้และเป็นการสร้างรายงานข้อผิดพลาดโดยอัตโนมัติและในบางกรณีเพื่อรีสตาร์ทแอปพลิเคชัน

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

คุณควรเปิดใช้งานการตรวจสอบเงื่อนไขเบื้องต้นไว้เสมอหรือไม่? มันขึ้นอยู่กับ. มันขึ้นอยู่กับคุณ. ไม่มีคำตอบที่เป็นสากล หากคุณกำลังสร้างซอฟต์แวร์ให้กับธนาคารการขัดจังหวะการดำเนินการด้วยข้อความที่น่าตกใจอาจเป็นการดีกว่าที่จะโอนเงิน 1,000,000 ดอลลาร์แทนที่จะเป็น 1,000 ดอลลาร์ แต่ถ้าคุณกำลังเขียนโปรแกรมเกมล่ะ? บางทีคุณอาจต้องการความเร็วทั้งหมดที่คุณจะได้รับและถ้ามีคนได้รับ 1,000 คะแนนแทนที่จะเป็น 10 เนื่องจากข้อผิดพลาดที่เงื่อนไขเบื้องต้นไม่สามารถจับได้ (เนื่องจากไม่ได้เปิดใช้งาน) โชคไม่ดี

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

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

ดูเพิ่มเติม: การยืนยันควรอยู่ในรหัสการผลิตเมื่อใด


1
ประเด็นของคุณใช้ได้แน่นอน SO ไม่ได้ระบุภาษาใดภาษาหนึ่ง - ในกรณีของ C # การยืนยันมาตรฐานคือ System.Diagnostics.Debug.Assert ซึ่งจะล้มเหลวในการสร้าง Debug เท่านั้นและจะถูกลบออกในเวลาคอมไพล์ในรุ่น Release
yoyo

2

มีเธรดขนาดใหญ่เกี่ยวกับการเปิดใช้งาน / ปิดใช้งานการยืนยันในรีลีสสร้างบน comp.lang.c ++. ที่กลั่นกรองซึ่งหากคุณมีเวลาสองสามสัปดาห์คุณจะเห็นว่าความคิดเห็นแตกต่างกันอย่างไร :)

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

  1. ตายด้วยความล้มเหลวบางประเภทของระบบปฏิบัติการส่งผลให้มีการเรียกร้องให้ยกเลิก (โดยไม่ต้องยืนยัน)
  2. ตายด้วยการเรียกร้องให้ยกเลิกโดยตรง (พร้อมยืนยัน)

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

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

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


2

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

  • ปิดใช้การยืนยันการตรวจสอบราคาแพง (เช่นการตรวจสอบว่ามีการสั่งซื้อช่วงหรือไม่)
  • เปิดใช้งานการตรวจสอบเล็กน้อย (เช่นการตรวจหาตัวชี้ค่าว่างหรือค่าบูลีน)

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


1

คุณกำลังถามเกี่ยวกับความแตกต่างระหว่างข้อผิดพลาดเวลาออกแบบและเวลาทำงาน

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

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

ดังนั้นหากคุณคิดว่าจะกำจัดจุดบกพร่องทั้งหมดได้ให้ใช้ข้อยกเว้นเท่านั้น ถ้าคิดว่าทำไม่ได้ ..... ให้ใช้ข้อยกเว้น. คุณยังคงสามารถใช้การยืนยันการแก้ไขข้อบกพร่องเพื่อทำให้จำนวนข้อยกเว้นน้อยลงได้

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

สุดท้ายนี้ฉันจะไม่ใช้ - รหัสข้อผิดพลาดนั้นดีกว่ามากสำหรับข้อผิดพลาดที่คุณคิดว่าจะเกิดขึ้นเป็นประจำ :)


0

ฉันชอบอันที่สองมากกว่า แม้ว่าการทดสอบของคุณอาจทำงานได้ดี แต่Murphyกล่าวว่าสิ่งที่ไม่คาดคิดจะผิดพลาด ดังนั้นแทนที่จะได้รับข้อยกเว้นในการเรียกเมธอดที่ผิดพลาดจริงคุณจะต้องติดตาม NullPointerException (หรือเทียบเท่า) 10 สแต็กเฟรมที่ลึกกว่า


0

คำตอบก่อนหน้านี้ถูกต้อง: ใช้ข้อยกเว้นสำหรับฟังก์ชัน API สาธารณะ ครั้งเดียวที่คุณอาจต้องการปรับเปลี่ยนกฎนี้คือเมื่อเช็คมีราคาแพงในการคำนวณ ในกรณีนี้คุณสามารถยืนยันได้

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


0

คุณควรใช้ทั้งสองอย่าง การยืนยันมีไว้เพื่อความสะดวกของคุณในฐานะผู้พัฒนา ข้อยกเว้นจับสิ่งที่คุณพลาดหรือไม่คาดคิดระหว่างรันไทม์

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

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

#ifdef DEBUG
#define RETURN_IF_FAIL(expr)      do {                      \
 if (!(expr))                                           \
 {                                                      \
     fprintf(stderr,                                        \
        "file %s: line %d (%s): precondition `%s' failed.", \
        __FILE__,                                           \
        __LINE__,                                           \
        __PRETTY_FUNCTION__,                                \
        #expr);                                             \
     ::print_stack_trace(2);                                \
     return;                                                \
 };               } while(0)
#define RETURN_VAL_IF_FAIL(expr, val)  do {                         \
 if (!(expr))                                                   \
 {                                                              \
    fprintf(stderr,                                             \
        "file %s: line %d (%s): precondition `%s' failed.",     \
        __FILE__,                                               \
        __LINE__,                                               \
        __PRETTY_FUNCTION__,                                    \
        #expr);                                                 \
     ::print_stack_trace(2);                                    \
     return val;                                                \
 };               } while(0)
#else
#define RETURN_IF_FAIL(expr)
#define RETURN_VAL_IF_FAIL(expr, val)
#endif

หากฉันต้องการการตรวจสอบรันไทม์ของอาร์กิวเมนต์ฉันจะทำสิ่งนี้:

char *doSomething(char *ptr)
{
    RETURN_VAL_IF_FAIL(ptr != NULL, NULL);  // same as assert(ptr != NULL), but returns NULL if it fails.
                                            // Goes away when debug off.

    if( ptr != NULL )
    {
       ...
    }

    return ptr;
}

ฉันไม่คิดว่าฉันเคยเห็นในคำถาม OP เกี่ยวกับ C ++ ฉันเชื่อว่าไม่ควรรวมอยู่ในคำตอบของคุณ
ForceMagic

@ForceMagic: คำถามมีแท็ก C ++ ในปี 2008 เมื่อฉันโพสต์คำตอบนี้และในความเป็นจริงแท็ก C ++ ถูกลบเมื่อ 5 ชั่วโมงที่แล้ว ไม่ว่าโค้ดจะแสดงแนวคิดที่ไม่ขึ้นกับภาษา
indiv

0

ฉันพยายามสังเคราะห์คำตอบอื่น ๆ ที่นี่ด้วยมุมมองของฉันเอง

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

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

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


0

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

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

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

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

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

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

PS: คุณอาจต้องการที่จะตรวจสอบคำถามที่คล้ายกัน: ยกเว้น Vs ยืนยัน


-1

ดูคำถามนี้ด้วย :

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

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

ฉันมักจะโยนข้อยกเว้นใน if-statement เพื่อรับหน้าที่แทนในกรณีที่พวกเขาถูกปิดใช้งาน

assert(value>0);
if(value<=0) throw new ArgumentOutOfRangeException("value");
//do stuff
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.