มีข้อยกเว้นในฐานะโฟลว์คอนโทรลที่ถือว่าเป็น antipattern ที่ร้ายแรง ถ้าเป็นเช่นนั้นทำไม


129

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

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

ฉันคิดว่าความแตกต่างที่สำคัญคือวิธีการดำเนินการเป็นvoidหน้าที่ ผมจำไม่ได้ทุกสิ่งที่ฉันทำกับความเป็นจริงนี้ แต่ผมเคยลังเลที่จะได้ไปลงที่ถนนในการจดจำมากเพราะตั้งแต่งานที่เหมือน 15 ปีที่ผ่านมาผมไม่เคยเห็นนี้ในรหัสการผลิตที่งานอื่นฉันสันนิษฐานว่านี่เป็นสัญญาณว่าเป็นรูปแบบการต่อต้านที่เรียกว่า

เป็นกรณีนี้หรือไม่และถ้าเป็นเช่นนั้นทำไม

อัปเดต: เมื่อฉันถามคำถามฉันมีคำตอบในใจของ @ MasonWheeler ดังนั้นฉันจึงไปพร้อมกับคำตอบที่เพิ่มความรู้ให้มากที่สุด ฉันคิดว่าเขาเป็นคำตอบที่ดีเช่นกัน


2
คำถามที่เกี่ยวข้อง: programmers.stackexchange.com/questions/107723
Eric King

25
ไม่ใน Python การใช้ข้อยกเว้นเป็นการควบคุมโฟลว์ถือเป็น "pythonic"
user16764

4
ถ้าฉันจะทำสิ่งที่ฉัน Java ฉันจะไม่ทิ้งข้อยกเว้น ฉันจะได้รับจากบางอย่างที่ไม่ใช่ข้อยกเว้นไม่ใช่ข้อผิดพลาดลำดับชั้น Throwable
Thomas Eding

2
เพื่อเพิ่มคำตอบที่มีอยู่ต่อไปนี้เป็นแนวทางสั้น ๆ ที่ให้บริการฉันดี: - ห้ามใช้ข้อยกเว้นสำหรับ "เส้นทางแห่งความสุข" เส้นทางที่มีความสุขสามารถเป็นได้ทั้ง (สำหรับเว็บ) คำขอทั้งหมดหรือเพียงหนึ่งวัตถุ / วิธี แน่นอนว่ากฎอื่น ๆ ทั้งหมดยังคงมีผลบังคับใช้แน่นอน :)
Houen

8
ข้อยกเว้นไม่ใช่การควบคุมการไหลของแอปพลิเคชันเสมอหรือไม่

คำตอบ:


109

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

สรุปโดยย่อเกี่ยวกับสาเหตุโดยทั่วไปเป็นรูปแบบการต่อต้าน:

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

อ่านการสนทนาที่ wiki ของ Ward เพื่อดูข้อมูลเชิงลึกเพิ่มเติม


ดูซ้ำซ้อนของคำถามนี้ที่นี่


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

14
@MasonWheeler ความแตกต่างคือสำหรับ / ในขณะที่ลูปมีการควบคุมการไหลของพวกเขาเปลี่ยนแปลงอย่างชัดเจนและทำให้รหัสอ่านง่าย หากคุณเห็นคำสั่ง for ในรหัสของคุณคุณไม่ต้องลองคิดดูว่าไฟล์ใดมีจุดสิ้นสุดของลูป ไปที่ไม่เลวเพราะพระเจ้าบางคนบอกว่าพวกเขาจะไม่ดีเพียงเพราะพวกเขายากที่จะทำตามกว่าโครงสร้างวน ข้อยกเว้นคล้ายกันไม่ใช่เป็นไปไม่ได้ แต่ยากพอที่จะทำให้สับสน
Bill K

24
@BillK จากนั้นให้เหตุผลว่าและอย่าทำคำสั่งแบบง่าย ๆ เกี่ยวกับการยกเว้นเป็น gotos
Winston Ewert

6
โอเค แต่จริงจังสิ่งที่เกิดขึ้นกับฝั่งเซิร์ฟเวอร์และแอป devs การฝังข้อผิดพลาดด้วยคำสั่ง catch ที่ว่างเปล่าใน JavaScript? มันเป็นฟีโนโมนที่น่ารังเกียจซึ่งทำให้ฉันต้องเสียเวลามากมายและฉันไม่รู้วิธีการถามโดยไม่ต้องคุยโว ข้อผิดพลาดคือเพื่อนของคุณ
Erik Reppen

12
@mattnz: ifและforeachยังมีความซับซ้อนGOTOs ฉันคิดว่าการเปรียบเทียบกับ goto นั้นไม่มีประโยชน์อะไรเลย มันเกือบจะเหมือนคำที่เสื่อมเสีย การใช้งาน GOTO นั้นไม่ได้มีความชั่วร้ายในตัวมันมีปัญหาจริงและข้อยกเว้นอาจแชร์สิ่งเหล่านั้น แต่อาจไม่ได้ การฟังปัญหาเหล่านั้นจะมีประโยชน์มากขึ้น
Eamon Nerbonne

110

กรณีการใช้งานที่ออกแบบมาเพื่อข้อยกเว้นคือ "ฉันเพิ่งเจอสถานการณ์ที่ฉันไม่สามารถจัดการกับมันได้อย่างถูกต้อง ณ จุดนี้เพราะฉันมีบริบทไม่เพียงพอที่จะจัดการมันได้ แต่กิจวัตรที่เรียกฉัน ควรรู้วิธีจัดการด้วย "

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

หากคุณไม่ได้ใช้การยกเว้นด้วยเหตุผลข้อใดข้อหนึ่งต่อไปนี้อาจเป็นวิธีที่ดีกว่า


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

6
@Krelp: นักออกแบบไม่เคยคาดหวังอะไรมากมายเช่นการจบลงด้วยระบบเทมเพลตที่ทัวริงสมบูรณ์แบบโดยไม่ได้ตั้งใจ! เทมเพลต C ++ เป็นตัวอย่างที่ดีสำหรับใช้ที่นี่
Mason Wheeler

15
@Krelp - เทมเพลต C ++ นั้นไม่ได้ 'สมบูรณ์แบบ' สำหรับ metaprogramming พวกมันคือฝันร้ายจนกว่าคุณจะเข้าใจถูกต้องแล้วพวกเขาก็มักจะเขียนโค้ดอย่างเดียวถ้าคุณไม่ใช่เทมเพลตอัจฉริยะ คุณอาจต้องการเลือกตัวอย่างที่ดีกว่า
Michael Kohne

ข้อยกเว้นเป็นอันตรายต่อองค์ประกอบของฟังก์ชั่นโดยเฉพาะและความกะทัดรัดของรหัสโดยทั่วไป เช่นในขณะที่ฉันไม่มีประสบการณ์ฉันใช้ข้อยกเว้นในโครงการและทำให้เกิดปัญหาเป็นเวลานานเนื่องจาก 1) ผู้คนต้องจำที่จะจับมัน 2) คุณไม่สามารถเขียนได้const myvar = theFunctionเพราะ myvar ต้องถูกสร้างขึ้นจากการลองจับ ดังนั้นจึงไม่คงที่อีกต่อไป ไม่ได้หมายความว่าฉันไม่ได้ใช้มันใน C # เพราะมันเป็นกระแสหลักที่นั่นไม่ว่าด้วยเหตุผลใดก็ตาม แต่ฉันก็พยายามลดมันลงไป
Hi-Angel

2
@AndreasBonini สิ่งที่พวกเขาได้รับการออกแบบ / มีไว้สำหรับเรื่องเพราะมักจะส่งผลในการตัดสินใจการใช้งานคอมไพเลอร์ / กรอบ / รันไทม์สอดคล้องกับการออกแบบ ยกตัวอย่างเช่นการขว้างปายกเว้นสามารถมากขึ้น“แพง” กว่าผลตอบแทนที่ง่ายเพราะมันจะเก็บรวบรวมข้อมูลหมายถึงการช่วยให้คนแก้จุดบกพร่องรหัสเช่นร่องรอยสแต็ค ฯลฯ
Binki

29

GOTOจะมีการยกเว้นเป็นพลังเป็นตและ พวกเขาเป็นโครงสร้างการไหลของการควบคุมสากล

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

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

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


7
ถ้าเพียง แต่เรามีการเรียกซ้ำแบบหางเรียกที่เหมาะสมบางทีเราอาจมีอิทธิพลต่อการพัฒนาเว็บฝั่งไคลเอ็นต์ด้วย JavaScript แล้วตามด้วยการแพร่กระจายไปยังฝั่งเซิร์ฟเวอร์และมือถือเป็นโซลูชันแพลตฟอร์มแพนขั้นสูงสุดเช่นไฟป่า อนิจจา แต่มันก็ไม่เป็นเช่นนั้น ประณามฟังก์ชั่นชั้นหนึ่งของเราไม่เพียงพอการปิดและกระบวนทัศน์ที่ขับเคลื่อนด้วยเหตุการณ์ สิ่งที่เราต้องการจริงๆคือของจริง
Erik Reppen

20
@ErikReppen: ฉันรู้ว่าคุณแค่เหน็บแนม แต่ . . ฉันจริงๆไม่คิดว่าความจริงที่ว่าเรา "มีความโดดเด่นในการพัฒนาเว็บฝั่งไคลเอ็นต์ด้วย JavaScript" มีอะไรจะทำอย่างไรกับคุณลักษณะของภาษา มีการผูกขาดในตลาดนั้นดังนั้นจึงสามารถแก้ไขปัญหามากมายที่ไม่สามารถเขียนออกมาด้วยการเสียดสี
ruakh

1
การเรียกซ้ำแบบหางจะเป็นโบนัสที่ดี (พวกมันกำลังลดค่าฟีเจอร์ของฟังก์ชั่นที่จะมีในอนาคต) แต่ใช่ฉันอยากจะบอกว่ามันชนะกับ VB, Flash, Applets และอื่น ๆ ... ด้วยเหตุผลที่เกี่ยวข้องกับคุณลักษณะ ถ้ามันไม่ลดความซับซ้อนและกลับสู่สภาวะปกติอย่างคล่องแคล่วเหมือนกับว่ามันจะต้องมีการแข่งขันจริงในบางจุด ขณะนี้ฉันกำลังใช้ Node.js เพื่อจัดการการเขียนใหม่ของไฟล์กำหนดค่า 21 รายการสำหรับสแต็ก C # + Java ที่น่ากลัวและฉันรู้ว่าฉันไม่ใช่คนเดียวที่ทำเช่นนั้น เป็นสิ่งที่ดีมากในสิ่งที่ดี
Erik Reppen

1
หลายภาษาไม่มี gotos (Goto ถือว่าเป็นอันตราย!), coroutines หรือ continuations และใช้การควบคุมการไหลที่ถูกต้องสมบูรณ์แบบเพียงแค่ใช้ ifs, whiles และ function call (หรือ gosubs!) ในความเป็นจริงเหล่านี้ส่วนใหญ่สามารถนำมาใช้เพื่อเลียนแบบซึ่งเป็นความต่อเนื่องที่สามารถใช้เพื่อเป็นตัวแทนของทั้งหมดข้างต้น (ตัวอย่างเช่นคุณสามารถใช้ในขณะที่ดำเนินการถ้าแม้ว่ามันเป็นวิธีที่แปลกรหัส) ดังนั้นไม่จำเป็นต้องมีข้อยกเว้นใด ๆ ในการควบคุมการไหลขั้นสูง
เชน

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

19

ตามที่คนอื่น ๆ ได้กล่าวไว้เป็นจำนวนมาก ( เช่นในคำถาม Stack Overflow ) หลักการของความประหลาดใจอย่างน้อยจะไม่อนุญาตให้คุณใช้ข้อยกเว้นมากเกินไปสำหรับจุดประสงค์การควบคุมการไหลเท่านั้น ในทางกลับกันไม่มีกฎใดถูกต้อง 100% และมีกรณีเหล่านั้นเสมอที่ข้อยกเว้นคือ "เครื่องมือที่เหมาะสม" - เหมือนgotoตัวของมันเองโดยวิธีการที่จัดส่งในรูปแบบbreakและcontinueในภาษาเช่น Java ซึ่ง มักเป็นวิธีที่สมบูรณ์แบบในการกระโดดออกจากลูปซ้อนกันอย่างหนักซึ่งไม่สามารถหลีกเลี่ยงได้เสมอ

โพสต์บล็อกต่อไปนี้อธิบายกรณีการใช้งานที่ค่อนข้างซับซ้อน แต่ก็ค่อนข้างน่าสนใจสำหรับผู้ที่ไม่ใช่คนท้องถิ่น ControlFlowException :

มันอธิบายวิธีที่อยู่ภายในของjOOQ (ไลบรารีนามธรรมของ SQL สำหรับ Java) (ข้อจำกัดความรับผิดชอบ: ฉันทำงานกับผู้ขาย), ข้อยกเว้นดังกล่าวจะถูกใช้เป็นครั้งคราวเพื่อยกเลิกกระบวนการเรนเดอร์ SQL ก่อนเมื่อเงื่อนไขบางอย่าง "หายาก"

ตัวอย่างของเงื่อนไขดังกล่าวคือ:

  • พบค่าการผูกมากเกินไป ฐานข้อมูลบางตัวไม่สนับสนุนจำนวนการผูกค่าในงบ SQL ของตนเอง (SQLite: 999, 10.1.1: 1024, Sybase ASE 15.5: 2000, SQL Server 2008: 2100) ในกรณีดังกล่าว jOOQ จะยกเลิกเฟสการเรนเดอร์ SQL และแสดงคำสั่ง SQL อีกครั้งด้วยค่าการโยงแบบอินไลน์ ตัวอย่าง:

    // Pseudo-code attaching a "handler" that will
    // abort query rendering once the maximum number
    // of bind values was exceeded:
    context.attachBindValueCounter();
    String sql;
    try {
    
      // In most cases, this will succeed:
      sql = query.render();
    }
    catch (ReRenderWithInlinedVariables e) {
      sql = query.renderWithInlinedBindValues();
    }

    หากเราแยกค่าการเชื่อมโยงออกจากแบบสอบถาม AST อย่างชัดเจนเพื่อนับพวกเขาทุกครั้งเราจะสูญเสียรอบการทำงานของ CPU ที่มีค่าสำหรับ 99.9% ของการสืบค้นที่ไม่ประสบปัญหานี้

  • ตรรกะบางอย่างใช้ได้เฉพาะทางอ้อมผ่าน API ที่เราต้องการดำเนินการ "บางส่วน" เท่านั้น UpdatableRecord.store()วิธีการสร้างINSERTหรือUPDATEคำสั่งขึ้นอยู่กับRecord's ธงภายใน จาก "ข้างนอก" เราไม่ทราบว่ามีตรรกะประเภทใดstore()(เช่นการล็อกในแง่ดีการจัดการฟังเหตุการณ์ ฯลฯ ) ดังนั้นเราจึงไม่ต้องการทำซ้ำตรรกะนั้นเมื่อเราเก็บบันทึกหลายชุดในชุดคำสั่ง โดยที่เราต้องการstore()สร้างคำสั่ง SQL เพียงอย่างเดียวไม่ใช่เรียกใช้งานจริง ตัวอย่าง:

    // Pseudo-code attaching a "handler" that will
    // prevent query execution and throw exceptions
    // instead:
    context.attachQueryCollector();
    
    // Collect the SQL for every store operation
    for (int i = 0; i < records.length; i++) {
      try {
        records[i].store();
      }
    
      // The attached handler will result in this
      // exception being thrown rather than actually
      // storing records to the database
      catch (QueryCollectorException e) {
    
        // The exception is thrown after the rendered
        // SQL statement is available
        queries.add(e.query());                
      }
    }

    หากเรามีstore()ตรรกะภายนอกในAPI "ที่ใช้ซ้ำได้" ซึ่งสามารถปรับแต่งให้เป็นทางเลือกที่ไม่ได้เรียกใช้งาน SQL เราจะมองหาการสร้าง API ที่ค่อนข้างยากที่จะรักษาและไม่สามารถใช้งานได้อีก

ข้อสรุป

ในสาระสำคัญการใช้งานของเราที่ไม่ใช่ในท้องถิ่นเหล่านี้gotoเป็นไปตามสิ่งที่Mason Wheelerกล่าวในคำตอบของเขา:

"ฉันเพิ่งเจอสถานการณ์ที่ฉันไม่สามารถจัดการกับมันได้อย่างถูกต้อง ณ จุดนี้เพราะฉันมีบริบทไม่เพียงพอที่จะจัดการกับมัน แต่กิจวัตรที่เรียกฉัน ."

การใช้งานทั้งสองแบบControlFlowExceptionsนั้นค่อนข้างง่ายต่อการใช้งานเมื่อเทียบกับทางเลือกของพวกเขาทำให้เราสามารถใช้ตรรกะที่หลากหลายได้

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


11

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

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

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

ด้วยการใช้ข้อยกเว้นสำหรับบางสิ่งที่ไม่เป็นพิเศษคุณกำลังใช้ abstractions ที่ไม่เหมาะสมสำหรับปัญหาที่คุณพยายามแก้ไข

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

แต่ภาษาอื่นเช่นทับทิมมีไวยากรณ์คล้ายข้อยกเว้นสำหรับโฟลว์การควบคุม สถานการณ์พิเศษได้รับการจัดการโดยraise/ rescueโอเปอเรเตอร์ แต่คุณสามารถใช้throw/ catchสำหรับการสร้างโฟลว์การควบคุมที่เหมือนข้อยกเว้น **

ดังนั้นแม้จะมีข้อยกเว้นทั่วไปไม่ได้ใช้สำหรับการควบคุมการไหล, ภาษาของคุณเลือกอาจมีสำนวนอื่น ๆ

* ตัวอย่างของการใช้งานข้อยกเว้นมีค่าใช้จ่ายสูง: ฉันเคยถูกตั้งค่าให้ปรับใช้แอปพลิเคชัน ASP.NET Web Form ที่มีประสิทธิภาพต่ำ มันกลับกลายเป็นว่าการแสดงผลของตารางที่มีขนาดใหญ่ได้รับการเรียกร้องint.Parse()ในการประมาณ สตริงว่างหนึ่งพันรายการบนหน้าเฉลี่ย มีข้อยกเว้นนับพันที่จัดการ โดยการแทนที่รหัสด้วยint.TryParse()ฉันโกนออกหนึ่งวินาที! สำหรับทุก ๆ หน้าขอ!

** สิ่งนี้อาจสร้างความสับสนอย่างมากสำหรับโปรแกรมเมอร์ที่มาจาก Ruby ในภาษาอื่น ๆ เนื่องจากทั้งคู่throwและcatchเป็นคำหลักที่เกี่ยวข้องกับข้อยกเว้นในภาษาอื่น ๆ อีกมากมาย


1
+1 สำหรับ "ด้วยการใช้ข้อยกเว้นสำหรับบางสิ่งที่ไม่เป็นพิเศษคุณกำลังใช้ abstractions ที่ไม่เหมาะสมสำหรับปัญหาที่คุณพยายามแก้ไข"
Giorgio

8

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

status = getValue(&inout);
if (status < 0)
{
    logError("message");
    return status;
}

doSomething(*inout);

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

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

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


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

5

ใน Python มีการใช้ข้อยกเว้นสำหรับตัวสร้างและการยกเลิกซ้ำ Python มีลอง / ยกเว้นบล็อกที่มีประสิทธิภาพมาก แต่จริงๆแล้วการเพิ่มข้อยกเว้นมีค่าใช้จ่ายบางอย่าง

เนื่องจากขาดการแบ่งหลายระดับหรือคำสั่ง goto ใน Python ฉันมีบางครั้งใช้ข้อยกเว้น:

class GOTO(Exception):
  pass

try:
  # Do lots of stuff
  # in here with multiple exit points
  # each exit point does a "raise GOTO()"
except GOTO:
  pass
except Exception as e:
  #display error

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

3
@ 9000 ผมกำลังจะแสดงความคิดเห็นว่าสิ่งเดียวกัน ... เก็บtry:บล็อก 1 หรือ 2 try: # Do lots of stuffสายโปรดไม่เคย
wim

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

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

4

มาร่างภาพการใช้งานข้อยกเว้น:

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

นอกจากนี้ต้องใช้บูลีนพิเศษfound(เพื่อบรรจุในชั้นเรียนมิฉะนั้นอาจจะมีเพียง int เท่านั้นที่ถูกส่งคืน) และสำหรับความลึกในการเรียกซ้ำจะมี postlude เดียวกันเกิดขึ้น

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


หืมมคือ downvoting สำหรับตำแหน่งที่ตรงกันข้ามแน่นอนหรือไม่เพียงพอหรือการโต้แย้งสั้น ๆ ? ฉันอยากรู้จริงๆ
Joop Eggen

ฉันกำลังเผชิญกับสถานการณ์นี้ฉันหวังว่าคุณจะเห็นสิ่งที่คุณคิดว่าคำถามต่อไปนี้ของฉัน? ใช้ข้อยกเว้นซ้ำ ๆ เพื่อสะสมเหตุผล (ข้อความ) สำหรับข้อยกเว้นระดับบนสุดcodereview.stackexchange.com/questions/107862/…
CL22

@Jodes ฉันเพิ่งอ่านเทคนิคที่น่าสนใจของคุณ แต่ตอนนี้ฉันค่อนข้างยุ่งอยู่ หวังว่าคนอื่นจะหลั่งเธอ / แสงของเขาในปัญหา
Joop Eggen

4

การเขียนโปรแกรมเป็นเรื่องเกี่ยวกับงาน

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

ทุกครั้งที่มีการเรียกเมธอดผู้เรียกจะพูดว่า"ฉันไม่รู้วิธีทำงานนี้ แต่คุณรู้วิธีคุณจึงทำเพื่อฉัน"

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

วิธีการเริ่มต้นในการสื่อสารนี้คือการคืนค่า "ขยะ" เพียง บางทีคุณอาจคาดหวังว่าจำนวนเต็มบวกดังนั้นเมธอดที่เรียกคืนค่าจำนวนลบ อีกวิธีหนึ่งในการบรรลุเป้าหมายนี้คือการตั้งค่าความผิดพลาดที่ใดที่หนึ่ง แต่น่าเสียดายที่ทั้งสองวิธีมีผลในต้นแบบให้ฉันตรวจสอบ-over-ที่นี่เพื่อทำให้แน่ใจว่า everything's เพียวรหัส เมื่อสิ่งต่าง ๆ ซับซ้อนมากขึ้นระบบนี้ก็จะแยกจากกัน

การเปรียบเทียบที่ยอดเยี่ยม

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

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

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

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

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

ดังนั้น ... การต่อต้านแบบ?

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

จึงจะมีคุณสมบัติเป็นรูปแบบการต่อต้านมันต้อง

  • แก้ปัญหา
  • มีผลกระทบเชิงลบอย่างชัดเจน

จุดแรกพบได้ง่าย - ระบบใช้งานใช่ไหม

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

แต่ที่ไม่ชัดเจนอันตราย มันเป็นวิธีที่ไม่ดีที่จะทำสิ่งต่าง ๆ และแปลก แต่มีรูปแบบต่อต้าน? เลขที่ ... แปลก


มีหนึ่งผลลัพธ์ที่ไม่ดีอย่างน้อยในภาษาที่มีการติดตามสแต็กเต็มรูปแบบ เนื่องจากรหัสนั้นได้รับการปรับให้เหมาะสมอย่างมากพร้อมกับการฝังอินไลน์จำนวนมากการติดตามสแต็กจริงและผู้พัฒนาต้องการเห็นความแตกต่างกันมากมายดังนั้นการสร้างสแต็กการติดตามจะมีค่าใช้จ่ายสูง ข้อยกเว้นที่มากเกินไปนั้นไม่ดีสำหรับประสิทธิภาพในภาษาดังกล่าว (Java, C #)
maaartinus

"มันเป็นวิธีที่ไม่ดีในการทำสิ่งต่าง ๆ " - ไม่ควรที่จะจัดว่าเป็นรูปแบบต่อต้านหรือไม่
อาจจะ_Factor

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