ข้อยกเว้นเป็นการยืนยันหรือเป็นข้อผิดพลาด?


10

ฉันเป็นโปรแกรมเมอร์ C มืออาชีพและเป็นนักเขียน Obj-C โปรแกรมเมอร์ (OS X) เมื่อเร็ว ๆ นี้ฉันถูกล่อลวงให้ขยายไปสู่ ​​C ++ เนื่องจากมีไวยากรณ์ที่หลากหลายมาก

จนถึงตอนนี้ฉันไม่ได้จัดการกับข้อยกเว้นมากนัก Objective-C มี แต่นโยบายของ Apple ค่อนข้างเข้มงวด:

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

C ++ ดูเหมือนจะชอบการใช้ข้อยกเว้นบ่อยกว่า ตัวอย่างเช่นตัวอย่างวิกิพีเดียในRAIIส่งข้อยกเว้นหากไม่สามารถเปิดไฟล์ได้ Object--C จะreturn nilมีข้อผิดพลาดที่ส่งโดยพารามิเตอร์ออก โดยเฉพาะอย่างยิ่งดูเหมือนว่า std :: ofstream สามารถตั้งค่าได้ทั้งสองทาง

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

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

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

NSError *err = nil;
id obj = [NSFileHandle fileHandleForReadingFromURL:myurl error:&err];

[obj retain];

nilแม้ว่าสายแรกกลับ และอย่างที่คุณไม่เคยทำ*objใน Obj-C ไม่มีความเสี่ยงใด ๆ กับตัวชี้ NULL


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

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

ดูเหมือนว่าสำหรับ C ++ คุณต้องการความชอบธรรมมากกว่าที่จะไม่ใช้มัน ตามปฏิกิริยาที่นี่
Gangnus

2
บางที แต่ก็ยังไม่มีใครอธิบายว่าทำไมพวกเขาถึงดีกว่า (ยกเว้นเมื่อเปรียบเทียบกับรหัสข้อผิดพลาด) ฉันไม่ชอบแนวคิดของการใช้ข้อยกเว้นสำหรับสิ่งที่ไม่พิเศษ แต่มันมีสัญชาตญาณมากกว่าตามข้อเท็จจริง
ต่อ Johansson

"ฉันไม่ชอบ ... ใช้ข้อยกเว้นสำหรับสิ่งที่ไม่พิเศษ" - เห็นด้วย
นักเลง

คำตอบ:


1

C ++ ดูเหมือนจะชอบการใช้ข้อยกเว้นบ่อยกว่า

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

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

ซัทเทอร์ยังสนับสนุนให้หลีกเลี่ยงข้อยกเว้นในกรณีเช่นนี้ในมาตรฐานการเข้ารหัส C ++:

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

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

ข้อยกเว้นภายนอก

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

การทำธุรกรรม

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

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

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

ประสิทธิภาพ

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

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

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

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

ที่ไม่ใช่ข้อผิดพลาด

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

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

Set<int> set_intersection(const Set<int>& a, const Set<int>& b)
{
     Set<int> intersection;
     for (int key: a)
     {
          try
          {
              b.find(key);
              intersection.insert(other_key);
          }
          catch (const KeyNotFoundException&)
          {
              // Do nothing.
          }
     }
     return intersection;
}

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

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

คำถาม

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

การหลีกเลี่ยงข้อยกเว้นอย่างสมบูรณ์ใน C ++ ดูเหมือนว่าจะต่อต้านฉันได้มากยกเว้นว่าคุณกำลังทำงานในระบบฝังตัวบางตัวหรือบางประเภทที่ห้ามการใช้งานของพวกเขา (ในกรณีนี้คุณต้องหลีกทางให้พ้นด้วย ฟังก์ชั่นห้องสมุดและภาษาที่throwต้องการใช้อย่างเคร่งครัดnothrow new)

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

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

if ((err = ApiCall(...)) != success)
{
     // Handle error
}

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

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

สำหรับฉันแล้วดูเหมือนว่าเนื่องจากพอยน์เตอร์หายากฉันจะต้องดำเนินการกับข้อผิดพลาดภายในหากฉันเลือกที่จะหลีกเลี่ยงข้อยกเว้น

ฉันไม่จำเป็นต้องพูดว่าพอยน์เตอร์นั้นหายาก แม้จะมีวิธีการใน C ++ 11 ขึ้นไปเพื่อรับข้อมูลพอยน์เตอร์ของคอนเทนเนอร์และnullptrคำสำคัญใหม่ โดยทั่วไปถือว่าไม่ฉลาดที่จะใช้ตัวชี้แบบ raw เพื่อเป็นเจ้าของ / จัดการหน่วยความจำหากคุณสามารถใช้บางอย่างเช่นunique_ptrแทนที่จะให้ความสำคัญกับการเป็น RAII ที่สอดคล้องกับสถานะของข้อยกเว้น แต่พอยน์เตอร์ดิบที่ไม่ได้เป็นเจ้าของ / จัดการหน่วยความจำก็ไม่ได้ถือว่าแย่มาก (แม้แต่จากคนอย่างซัทเทอร์และสตราวสตรัป) และบางครั้งก็ใช้งานได้จริงเพื่อชี้ไปยังสิ่งต่าง ๆ

เนื้อหาเหล่านั้นปลอดภัยไม่น้อยไปกว่าตัวทำซ้ำคอนเทนเนอร์มาตรฐาน (อย่างน้อยในรุ่นที่วางจำหน่ายขาดตัวตรวจสอบตัวทำซ้ำ) ซึ่งจะไม่ตรวจพบว่าคุณพยายามตรวจสอบซ้ำหลังจากที่พวกเขาถูกทำให้เป็นโมฆะ ภาษา C ++ ยังคงเป็นภาษาที่อันตรายอยู่บ้างผมจะบอกว่าถ้าคุณใช้มันโดยเฉพาะเพื่อต้องการห่อทุกอย่างและซ่อนแม้กระทั่งตัวชี้ดิบที่ไม่ได้เป็นเจ้าของ เกือบเป็นเรื่องสำคัญยิ่งที่มีข้อยกเว้นว่าทรัพยากรเป็นไปตาม RAII (ซึ่งโดยทั่วไปจะไม่มีค่าใช้จ่ายในการดำเนินการ) แต่นอกเหนือจากนั้นไม่จำเป็นต้องพยายามใช้ภาษาที่ปลอดภัยที่สุดเพื่อหลีกเลี่ยงค่าใช้จ่ายที่นักพัฒนาไม่ต้องการ แลกกับสิ่งอื่น การใช้ที่แนะนำไม่ได้พยายามปกป้องคุณจากสิ่งต่าง ๆ เช่นตัวชี้ห้อยและตัววนซ้ำที่ใช้งานไม่ได้ดังนั้นจึงควรพูด (ไม่เช่นนั้นเราควรสนับสนุนให้ใช้shared_ptrทั่วทุกสถานที่ซึ่ง Stroustrup ร้อนแรงคัดค้าน) มันพยายามที่จะปกป้องคุณจากความล้มเหลวที่จะปล่อย / ถูกต้องฟรี / ทำลาย / ปลด / throwsทำความสะอาดทรัพยากรเมื่อบางสิ่งบางอย่าง


14

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

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

แต่นี่คือความจริงง่ายๆ ข้อยกเว้นทำงานโดยอัตโนมัติ เมื่อคุณส่งข้อยกเว้น RAII จะเข้าควบคุมและคอมไพเลอร์จะจัดการทุกอย่าง เมื่อคุณใช้รหัสข้อผิดพลาดคุณต้องจำไว้เพื่อตรวจสอบพวกเขา สิ่งนี้จะทำให้ข้อยกเว้นปลอดภัยกว่ารหัสข้อผิดพลาดอย่างมีนัยสำคัญ - ตรวจสอบตัวเองเหมือนเดิม นอกจากนี้พวกเขาแสดงออกได้มากขึ้น โยนข้อยกเว้นและคุณจะได้รับสตริงที่บอกคุณว่าข้อผิดพลาดคืออะไรและสามารถมีข้อมูลที่เฉพาะเจาะจงเช่น "พารามิเตอร์แย่ X ซึ่งมีค่า Y แทน Z" รับรหัสข้อผิดพลาด 0xDEADBEEF แล้วเกิดอะไรขึ้น ฉันหวังว่าเอกสารจะสมบูรณ์และทันสมัยและแม้ว่าคุณจะได้รับ "พารามิเตอร์ที่ไม่ดี" ก็จะไม่บอกคุณว่าพารามิเตอร์สิ่งที่มีค่าและสิ่งที่ควรมี ถ้าคุณจับโดยการอ้างอิงเช่นกันพวกเขาสามารถ polymorphic ในที่สุดข้อยกเว้นสามารถถูกโยนจากสถานที่ที่รหัสข้อผิดพลาดไม่สามารถทำได้เช่นตัวสร้าง แล้วอัลกอริทึมทั่วไปล่ะ? เป็นวิธีการที่std::for_eachจะจัดการกับรหัสข้อผิดพลาดของคุณหรือไม่ เคล็ดลับ: มันไม่ใช่

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

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

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

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

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

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


ทางเลือกไม่จำเป็นต้องเป็น int อย่างง่าย ฉันมีแนวโน้มที่จะใช้สิ่งที่คล้ายกับ NSError ซึ่งฉันคุ้นเคยกับจาก Obj-C
ต่อ Johansson

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

ใครก็ตามที่ใช้ Objective-C แน่นอนจะบอกคุณว่าคุณผิดอย่างสมบูรณ์
gnasher729

5

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

bool success = function1(&result1, &err);
if (!success) {
    error_handler(err);
    return;
}

success = function2(&result2, &err);
if (!success) {
    error_handler(err);
    return;
}

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

result1 = function1();
result2 = function2();

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

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


1
สิ่งนี้จะสมเหตุสมผลมากกว่าหากโค้ดที่ไม่มีข้อยกเว้นจริง ๆ มีลักษณะเช่นนั้น แต่โดยปกติจะไม่ รูปแบบนี้ปรากฏขึ้นเมื่อ error_handler ไม่กลับมา แต่ไม่ค่อยเป็นเช่นนั้น
ต่อ Johansson

1

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

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

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

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

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


ฉันเห็นด้วยกับคุณ แต่มันก็เป็นเหตุผลที่ทำให้ฉันไม่ถามคำถาม
ต่อ Johansson

0

มีบทน่ารักอยู่สองสามบทในClean Codeซึ่งพูดถึงเรื่องนี้มาก - ลบมุมมองของ Apple

แนวคิดพื้นฐานคือคุณไม่เคยคืนค่าศูนย์จากฟังก์ชันของคุณเพราะ:

  • เพิ่มรหัสซ้ำจำนวนมาก
  • ทำให้รหัสของคุณยุ่งเหยิง - Ie: อ่านยากขึ้น
  • มักจะส่งผลให้เกิดข้อผิดพลาดศูนย์ - หรือการละเมิดการเข้าถึงขึ้นอยู่กับการเขียนโปรแกรม "ศาสนา" โดยเฉพาะของคุณ

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

ย้อนกลับไปในวันที่พัฒนา COM ของฉันฉันมีโครงการที่ทุกความล้มเหลวยกข้อยกเว้นและแต่ละข้อยกเว้นจำเป็นต้องใช้รหัสข้อผิดพลาดที่ไม่ซ้ำกันตามการขยายข้อยกเว้น COM มาตรฐานของ windows มันเป็นการระงับโครงการก่อนหน้านี้ซึ่งมีการใช้รหัสข้อผิดพลาดมาก่อนหน้านี้ แต่ บริษัท ต้องการที่จะไปที่วัตถุทั้งหมดและกระโดดขึ้นไปบน COM-bandwagon โดยไม่เปลี่ยนวิธีที่พวกเขาทำสิ่งต่าง ๆ มากเกินไป ใช้เวลาเพียง 4 เดือนกว่าพวกเขาจะหมดรหัสข้อผิดพลาดและตกสู่ฉันเพื่อ refactor ผืนที่ใหญ่ของรหัสที่จะใช้ข้อยกเว้นแทน ดีกว่าที่จะบันทึกความพยายามล่วงหน้าและใช้ข้อยกเว้นอย่างรอบคอบ


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