ทำไมฉันไม่ควรห่อทุกบล็อคใน“ ลอง” -“ จับ”?


430

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

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


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

คำตอบ:


340

วิธีการควรจับยกเว้นเมื่อมันสามารถจัดการได้ในทางที่เหมาะสม

มิฉะนั้นให้ส่งผ่านขึ้นโดยหวังว่าวิธีการที่สูงขึ้นของ call stack จะทำให้เกิดความรู้สึกของมัน

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


12
นอกจากนี้ยังเป็นที่น่าสังเกตว่ามีค่าใช้จ่าย (ในแง่ของรหัสที่สร้าง) ไปยังtryบล็อก มีการสนทนาที่ดีใน "More Effective C ++" ของ Scott Meyers
Nick Meyer

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

31
@Blindly: ตัวจัดการข้อยกเว้นด้านบนไม่ได้อยู่ที่นั่นเพื่อจัดการกับข้อยกเว้น แต่ในความเป็นจริงการตะโกนออกมาดัง ๆ ว่ามีข้อยกเว้นที่ไม่สามารถจัดการได้ให้ข้อความและสิ้นสุดโปรแกรมด้วยวิธีที่สง่างาม (คืน 1 แทนการโทรไปterminate) . มันเป็นกลไกด้านความปลอดภัย นอกจากนี้ยังtry/catchมีอิสระมากขึ้นหรือน้อยลงเมื่อไม่มีข้อยกเว้น เมื่อมีสิ่งหนึ่งแพร่กระจายมันจะกินเวลาในแต่ละครั้งที่มันถูกโยนและถูกจับดังนั้นสายโซ่ของการสร้างใหม่try/catchเพียงอย่างเดียวนั้นไม่เสียค่าใช้จ่าย
Matthieu M.

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

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

136

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

มีบางรหัสที่เกี่ยวข้องกับกลิ่นที่คุณต้องการหลีกเลี่ยงนอกเหนือจากกลิ่น"จับทุกอย่างทุกที่"

  1. "catch, log, rethrow" : หากคุณต้องการการบันทึกตามขอบเขตที่กำหนดไว้ให้เขียนคลาสที่ส่งคำสั่ง log ใน destructor เมื่อสแต็กไม่ได้เปิดเนื่องจากข้อยกเว้น (ala std::uncaught_exception()) สิ่งที่คุณต้องทำคือการประกาศอินสแตนซ์การบันทึกในขอบเขตที่คุณสนใจและใน voila คุณมีการบันทึกและไม่มีตรรกะtry/ ไม่จำเป็นcatch

  2. "catch, throw translation" : โดยปกติจะชี้ไปที่ปัญหาที่เป็นนามธรรม ถ้าคุณมีการดำเนินการแก้ปัญหาแบบ federated ที่คุณกำลังจะแปลเฉพาะข้อยกเว้นเป็นหนึ่งในหลายทั่วไปมากขึ้นหนึ่งคุณอาจจะมีชั้นที่ไม่จำเป็นของนามธรรม ... และไม่ได้บอกว่า "ฉันอาจจะจำเป็นต้องใช้มันในวันพรุ่งนี้"

  3. "catch, cleanup, rethrow" : นี่คือหนึ่งในสัตว์เลี้ยงของฉัน หากคุณเห็นสิ่งนี้จำนวนมากคุณควรใช้การได้มาซึ่งทรัพยากรคือเทคนิคการเริ่มต้นและวางส่วนการล้างข้อมูลในตัวทำลายของอินสแตนซ์วัตถุภารโรง

ฉันพิจารณาโค้ดที่เกลื่อนไปด้วยtry/ catchบล็อกเพื่อให้เป็นเป้าหมายที่ดีสำหรับการตรวจสอบโค้ดและการปรับโครงสร้างใหม่ มันบ่งชี้ว่าการจัดการข้อยกเว้นอย่างใดอย่างหนึ่งไม่เป็นที่เข้าใจดีหรือรหัสกลายเป็นamœbaและอยู่ในความต้องการอย่างจริงจังในการปรับโครงสร้างใหม่


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

3
โดยทั่วไปสิ่งที่คุณพูด: parashift.com/c++-faq-lite/exceptions.html#faq-17.13
Björn Pollex

1
# 2 ที่ไม่ได้กลิ่นรหัส แต่เหมาะสมสามารถปรับปรุงโดยทำให้ข้อยกเว้นเก่าเป็นซ้อนกัน
Deduplicator

1
เกี่ยวกับ # 1: std :: uncaught_exception () บอกคุณว่ามีข้อยกเว้นที่ไม่สามารถตรวจจับได้ในการบิน แต่ประโยค AFAIK เพียงประโยค catch () ช่วยให้คุณกำหนดว่าข้อยกเว้นนั้นคืออะไร ดังนั้นในขณะที่คุณสามารถบันทึกความจริงที่ว่าคุณกำลังออกจากขอบเขตเนื่องจากข้อยกเว้นที่ไม่ถูกตรวจจับได้ แต่การลอง / จับที่ล้อมรอบช่วยให้คุณสามารถบันทึกรายละเอียดใด ๆ ได้ แก้ไข?
Jeremy

@ Jeremy - คุณถูกต้อง ฉันมักจะบันทึกรายละเอียดข้อยกเว้นเมื่อฉันจัดการข้อยกเว้น การมีร่องรอยของเฟรมที่แทรกเข้ามานั้นมีประโยชน์มาก โดยทั่วไปคุณจะต้องเข้าสู่ระบบตัวระบุเธรดหรือบริบทบางอย่างที่ระบุเช่นกันเพื่อเชื่อมโยงสายบันทึก ฉันใช้Loggerคลาสที่คล้ายกับlog4j.Loggerที่มี ID เธรดในทุกบรรทัดบันทึกและส่งคำเตือนใน destructor เมื่อข้อยกเว้นแอ็คทีฟ
D.Shawley

48

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


29

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

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

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

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

int ret = SaveFirstSection();

if (ret == FAILED)
{
    /* some diagnostic */
    return;
}

ret = SaveSecondSection();

if (ret == FAILED)
{
    /* some diagnostic */
    return;
}

ret = SaveThirdSection();

if (ret == FAILED)
{
    /* some diagnostic */
    return;
}

นี่คือวิธีที่อาจเขียนด้วยข้อยกเว้น:

// these throw if failed, caught in SaveDocument's catch
SaveFirstSection();
SaveSecondSection();
SaveThirdSection();

ตอนนี้มันชัดเจนมากขึ้นว่าเกิดอะไรขึ้น

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


2
ตัวอย่างที่สวยงาม ใช่จับได้สูงที่สุดเท่าที่จะเป็นไปได้ในหน่วยทางลอจิคัลเช่นการดำเนินการ 'ธุรกรรม' เช่นการโหลด / บันทึก / ฯลฯ ไม่มีอะไรที่ดูแย่ไปกว่าโค้ดที่ใช้ซ้ำ ๆ ซ้ำซ้อนtry- catchบล็อกที่พยายามตั้งค่าสถานะการเรียงลำดับข้อผิดพลาดที่แตกต่างกันเล็กน้อยด้วยข้อความที่แตกต่างกันเล็กน้อย แต่ในความเป็นจริงแล้วพวกเขาควรจะจบสิ้นเหมือนกัน หากความล้มเหลวที่คุ้มค่าเกิดขึ้นฉันเดิมพันผู้ใช้ส่วนใหญ่แค่ต้องการกอบกู้สิ่งที่พวกเขาสามารถทำได้หรืออย่างน้อยก็ต้องอยู่คนเดียวโดยไม่ต้องจัดการกับข้อความ 10 ระดับเกี่ยวกับเรื่องนี้
underscore_d

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

27

สมุนไพร Sutter เขียนเกี่ยวกับปัญหานี้ที่นี่ เพื่อการอ่านที่คุ้มค่าแน่นอน
ทีเซอร์:

"การเขียนรหัสที่ปลอดภัยเป็นพิเศษคือการเขียน 'ลอง' และ 'จับ' ในตำแหน่งที่ถูกต้อง" สนทนา

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

ที่จริงแล้วปรากฎว่าความปลอดภัยยกเว้นนั้นไม่ค่อยเกี่ยวกับการเขียน 'ลอง' และ 'จับ' - และยิ่งดีกว่า นอกจากนี้อย่าลืมว่าข้อยกเว้นด้านความปลอดภัยส่งผลกระทบต่อการออกแบบของโค้ด มันไม่ได้เป็นเพียงแค่ความคิดในภายหลังที่สามารถดัดแปลงเพิ่มเติมด้วยการจับพิเศษบางอย่างราวกับว่าปรุงรส


15

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

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


12

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

จุดทั้งหมดของข้อยกเว้นคือพวกเขาไม่จำเป็นต้องจัดการในทุกวิธีในสายโซ่


9

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


2
@ KeithB: ฉันคิดว่ามันเป็นกลยุทธ์ที่ดีที่สุดอันดับสอง จะดีกว่าถ้าคุณสามารถเขียนบันทึกด้วยวิธีอื่น
David Thornley

1
@KeithB: เป็นกลยุทธ์ "ดีกว่าไม่มีอะไรในห้องสมุด" "จับบันทึกจัดการกับมันอย่างเหมาะสม" ดีกว่าที่เป็นไปได้ (ใช่ฉันรู้ว่ามันเป็นไปไม่ได้เสมอ)
Donal Fellows

6

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

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

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


5

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

ตัวอย่างเช่นเขาบอกเราว่าหากโปรแกรมพบปัญหาร้ายแรงบางอย่างในสถานที่ที่ไม่สามารถทำสิ่งต่อไปนี้ได้:

int f()
{
    // Do stuff

    if (condition == false)
        return -1;
    return 0;
}

int condition = f();

if (f != 0)
{
    // handle error
}

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


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

1
-1 @Sagelika คำตอบของคุณประกอบด้วยการหลีกเลี่ยงข้อยกเว้นจึงไม่จำเป็นต้องลองจับ
Vicente Botet Escriba

3
@ Kristopher: ข้อเสียที่สำคัญอื่น ๆ ของรหัสส่งคืนคือมันง่ายที่จะลืมตรวจสอบรหัสส่งคืนและหลังจากการโทรไม่จำเป็นต้องเป็นสถานที่ที่ดีที่สุดในการจัดการปัญหา
David Thornley

เอ๊ะมันขึ้นอยู่กับ แต่ในหลาย ๆ กรณี (ยกเว้นคนที่ขว้างเมื่อไม่ควรทำจริงๆ) ข้อยกเว้นนั้นดีกว่าการส่งคืนรหัสด้วยเหตุผลมากมาย ในที่สุดกรณีความคิดที่ว่าข้อยกเว้นที่เป็นอันตรายต่อผลการดำเนินงานเป็น OL ใหญ่'[อ้างจำเป็น]
underscore_d

3

หากคุณต้องการทดสอบผลลัพธ์ของทุกฟังก์ชั่นให้ใช้รหัสส่งคืน

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

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


2

ฉันต้องการเพิ่มการสนทนานี้ว่าตั้งแต่ C ++ 11มันสมเหตุสมผลมากตราบใดที่ทุก ๆcatchบล็อกrethrowมีข้อยกเว้นจนถึงจุดที่สามารถทำได้ / ควรได้รับการจัดการ วิธีนี้ติดตามย้อนหลังสามารถสร้าง ฉันเชื่อว่าความคิดเห็นก่อนหน้านี้ล้าสมัยไปแล้ว

ใช้std::nested_exceptionและstd::throw_with_nested

มันอธิบายไว้ใน StackOverflow ที่นี่และที่นี่วิธีการบรรลุสิ่งนี้

เนื่องจากคุณสามารถทำได้ด้วยคลาสยกเว้นที่ได้รับคุณสามารถเพิ่มข้อมูลจำนวนมากลงใน backtrace! คุณอาจดูMWEของฉันบน GitHubด้วยที่ backtrace จะมีหน้าตาดังนี้:

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"

2

ฉันได้รับ "โอกาส" ในการกอบกู้โครงการหลายโครงการและผู้บริหารแทนที่ทีมพัฒนาทั้งหมดเนื่องจากแอปมีข้อผิดพลาดมากเกินไปและผู้ใช้รู้สึกเบื่อกับปัญหาและวิ่งไปรอบ ๆ รหัสเหล่านี้ล้วน แต่มีการจัดการข้อผิดพลาดจากส่วนกลางที่ระดับแอพเหมือนกับคำตอบที่ได้รับการโหวตสูงสุด หากคำตอบนั้นเป็นวิธีปฏิบัติที่ดีที่สุดทำไมมันไม่ทำงานและอนุญาตให้ทีม dev ก่อนหน้านี้แก้ไขปัญหาได้ บางทีบางครั้งมันไม่ทำงาน? คำตอบข้างต้นไม่ได้พูดถึงว่านักพัฒนาใช้เวลาในการแก้ไขปัญหาเดียวนานแค่ไหน หากเวลาในการแก้ไขปัญหาคือการวัดที่สำคัญรหัสการใช้เครื่องมือกับ try..catch blocks คือแนวปฏิบัติที่ดีกว่า

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

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

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

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

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

บางครั้งนักพัฒนาเลือกที่จะโยนข้อยกเว้นสแต็กจาก catch catch แต่วิธีนี้ช้ากว่าโค้ดปกติ 100 เท่าที่ไม่ได้โยน แนะนำให้ใช้ Catch and release พร้อมการบันทึก

เทคนิคนี้ใช้เพื่อสร้างความเสถียรให้แอพที่ล้มเหลวทุกชั่วโมงสำหรับผู้ใช้ส่วนใหญ่ใน บริษัท Fortune 500 ที่พัฒนาโดย 12 Devs ในระยะเวลา 2 ปี การใช้ข้อยกเว้นที่แตกต่างกัน 3000 ข้อนี้ได้รับการระบุแก้ไขทดสอบและปรับใช้ใน 4 เดือน ค่าเฉลี่ยนี้จะแก้ไขโดยเฉลี่ยทุก 15 นาทีเป็นเวลา 4 เดือน

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


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

1
"ข้อยกเว้นนั้นช้ากว่าโค้ดปกติ 100 ถึง 1,000 เท่าและไม่ควรทำการซ้ำอีกครั้ง" - ข้อความนั้นไม่เป็นความจริงสำหรับคอมไพเลอร์และฮาร์ดแวร์ที่ทันสมัยที่สุด
มิทช์ข้าวสาลี

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

ไม่ข้อยกเว้นช้ามาก ทางเลือกคือรหัสส่งคืนวัตถุหรือตัวแปร ดูโพสต์ล้นสแต็กนี้ ... "ข้อยกเว้นช้ากว่ารหัสส่งคืนอย่างน้อย 30,000 เท่า" stackoverflow.com/questions/891217/…
2502917

1

นอกจากคำแนะนำข้างต้นโดยส่วนตัวแล้วฉันลองใช้ + catch + throw; ด้วยเหตุผลดังต่อไปนี้:

  1. ที่ขอบเขตของ coder ที่แตกต่างกันฉันใช้ try + catch + throw ในโค้ดที่เขียนด้วยตัวเองก่อนที่จะมีข้อยกเว้นถูกส่งไปยังผู้โทรที่เขียนโดยคนอื่นนี่ทำให้ฉันมีโอกาสที่จะทราบเงื่อนไขข้อผิดพลาดบางอย่างที่เกิดขึ้นในรหัสของฉัน สถานที่แห่งนี้อยู่ใกล้กับรหัสมากขึ้นซึ่งในขั้นต้นจะทำให้เกิดข้อยกเว้นยิ่งใกล้ชิดและง่ายต่อการค้นหาเหตุผล
  2. ที่ขอบเขตของโมดูลแม้ว่าโมดูลที่แตกต่างกันอาจจะเขียนคนเดียวกันของฉัน
  3. วัตถุประสงค์การเรียนรู้ + การดีบักในกรณีนี้ฉันใช้ catch (... ) ใน C ++ และ catch (Exception ex) ใน C #, สำหรับ C ++, ไลบรารีมาตรฐานไม่ส่งข้อยกเว้นมากเกินไปดังนั้นกรณีนี้หายากใน C ++ แต่สถานที่ทั่วไปใน C #, C # มีห้องสมุดขนาดใหญ่และลำดับชั้นของข้อยกเว้นที่เป็นผู้ใหญ่รหัสห้องสมุด C # จะมีข้อยกเว้นมากมายในทางทฤษฎี I (และคุณ) ควรรู้ข้อยกเว้นทุกประการจากฟังก์ชันที่คุณเรียกใช้และทราบเหตุผล / เหตุผลว่าทำไม ข้อยกเว้นเหล่านี้ถูกโยนและรู้วิธีจัดการกับมัน (ผ่านหรือจับและจัดการกับมันในสถานที่) อย่างสง่างาม น่าเสียดายที่ในความเป็นจริงมันยากมากที่จะรู้ทุกอย่างเกี่ยวกับข้อยกเว้นที่อาจเกิดขึ้นก่อนที่ฉันจะเขียนโค้ดหนึ่งบรรทัด ดังนั้นฉันจึงจับได้ทั้งหมดและให้โค้ดของฉันพูดออกเสียงโดยการบันทึก (ในสภาพแวดล้อมของผลิตภัณฑ์) / โต้ตอบการยืนยัน (ในสภาพแวดล้อมการพัฒนา) เมื่อมีข้อยกเว้นเกิดขึ้นจริง ด้วยวิธีนี้ฉันเพิ่มรหัสการจัดการข้อยกเว้นอย่างต่อเนื่อง ฉันรู้ว่ามันสับสนกับคำแนะนำที่ดี แต่ในความเป็นจริงมันใช้งานได้สำหรับฉันและฉันก็ไม่รู้วิธีที่ดีกว่าสำหรับปัญหานี้

1

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

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

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

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

ดังนั้นในระยะสั้นลอง / จับทำให้รหัสยากมากที่จะเข้าใจ


-2

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

  1. คุณสามารถใช้บล็อกนี้ที่คุณต้องการจัดการกับข้อยกเว้นหรือเพียงแค่คุณบอกว่าบล็อกของโค้ดที่เขียนอาจทำให้เกิดข้อยกเว้น
  2. หากคุณต้องการกำจัดวัตถุของคุณทันทีหลังการใช้งานคุณสามารถใช้try-catchบล็อก

1
"หากคุณต้องการกำจัดวัตถุของคุณทันทีหลังการใช้งานคุณสามารถใช้บล็อกลองดู" คุณตั้งใจทำสิ่งนี้เพื่อส่งเสริม RAII / อายุการใช้งานของวัตถุน้อยที่สุด ถ้าเป็นเช่นนั้นดีtry/ catchเป็นที่สมบูรณ์แยก / orthogonal จากที่ หากคุณต้องการกำจัดวัตถุในขอบเขตที่เล็กลงคุณสามารถเปิดวัตถุใหม่ได้โดย{ Block likeThis; /* <- that object is destroyed here -> */ }ไม่จำเป็นต้องห่อสิ่งนี้ไว้ในtry/ catchนอกเสียจากว่าคุณต้องการcatchอะไรก็ตาม
underscore_d

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