ข้อยกเว้นรหัสข้อผิดพลาดและสหภาพที่ถูกเลือกปฏิบัติ


80

ฉันเพิ่งเริ่มงานเขียนโปรแกรม C # แต่ฉันมีพื้นหลังค่อนข้างน้อยใน Haskell

แต่ฉันเข้าใจว่า C # เป็นภาษาที่เน้นวัตถุฉันไม่ต้องการบังคับให้หมุดกลมกลายเป็นช่องสี่เหลี่ยม

ฉันอ่านบทความException Throwingจาก Microsoft ซึ่งระบุว่า:

อย่าส่งคืนรหัสข้อผิดพลาด

แต่เมื่อใช้กับ Haskell ฉันใช้ชนิดข้อมูล C # OneOfคืนผลลัพธ์เป็นค่า "right" หรือข้อผิดพลาด (ส่วนใหญ่มักจะเป็น Enumeration) เป็นค่า "left"

นี่เป็นเหมือนการประชุมของEitherHaskell

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

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

แต่นี่ไม่ใช่วิธีการที่ Microsoft แนะนำ

ใช้OneOfแทนข้อยกเว้นในการจัดการข้อยกเว้น "สามัญ" (เช่น File Not Found ฯลฯ ) เป็นวิธีการที่สมเหตุสมผลหรือเป็นวิธีปฏิบัติที่เลวร้ายหรือไม่


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

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


ความคิดที่ตามมา:

จากการสนทนานี้สิ่งที่ฉันกำลังจะไปในขณะนี้มีดังนี้:

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

13
ใช้ F # Result;-)
Astrinus

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

9
บทความที่เกี่ยวข้อง: เอริค Lippert ในสี่ "บุ้งกี๋" ข้อยกเว้น ตัวอย่างเช่นที่เขาให้ในeceptions ภายนอกFileNotFoundExceptionแสดงให้เห็นว่ามีสถานการณ์ที่คุณเพียงแค่มีการจัดการกับข้อยกเว้นคือ
Søren D. Ptæus

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

9
มาได้อย่างไรไม่มีคำตอบชี้แจงว่าmonad ไม่ได้เป็นรหัสข้อผิดพลาดและไม่เป็น? พวกเขามีพื้นฐานที่แตกต่างกันและคำถามจึงดูเหมือนว่าจะอยู่บนพื้นฐานของความเข้าใจผิด (แม้ว่าจะอยู่ในรูปแบบที่ถูกปรับเปลี่ยน แต่ก็ยังเป็นคำถามที่ถูกต้อง)EitherOneOf
Konrad Rudolph

คำตอบ:


131

แต่การหยุดทำงานซอฟต์แวร์ไคลเอ็นต์ของคุณยังคงไม่ดี

แน่นอนที่สุดมันเป็นสิ่งที่ดี

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

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

เป็นเรื่องจริงอย่างแน่นอน แต่มักเข้าใจผิด เมื่อพวกเขาคิดค้นระบบยกเว้นพวกเขากลัวว่าพวกเขากำลังทำลายการเขียนโปรแกรมที่มีโครงสร้าง การเขียนโปรแกรมที่มีโครงสร้างเป็นเหตุผลที่เรามีfor, while, until, breakและเมื่อเราต้องการที่จะทำทั้งหมดที่เป็นcontinuegoto

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

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

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

ป้อนคำอธิบายรูปภาพที่นี่

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

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

อย่าส่งคืนรหัสข้อผิดพลาด

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

OneOf

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

ฉันชอบการประชุมด้วยตัวเอง หนึ่งในคำอธิบายที่ดีที่สุดของฉันพบที่นี่* :

ป้อนคำอธิบายรูปภาพที่นี่

แต่เท่าที่ฉันชอบฉันยังคงไม่ผสมกับอนุสัญญาอื่น ๆ เลือกอย่างใดอย่างหนึ่งและติดกับมัน 1

1: โดยที่ฉันหมายถึงอย่าทำให้ฉันคิดถึงการประชุมมากกว่าหนึ่งครั้งในเวลาเดียวกัน


ความคิดที่ตามมา:

จากการสนทนานี้สิ่งที่ฉันกำลังจะไปในขณะนี้มีดังนี้:

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

มันไม่ง่ายอย่างนี้จริงๆ หนึ่งในสิ่งพื้นฐานที่คุณต้องเข้าใจคือสิ่งที่เป็นศูนย์

เหลือกี่วันในเดือนพฤษภาคม 0 (เพราะไม่ใช่พฤษภาคมมันเป็นเดือนมิถุนายนแล้ว)

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

ตัวอย่าง2 :

IList<int> ParseAllTheInts(String s) { ... }

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

นั่นเป็นสัญลักษณ์ของชื่อที่ดี ขออภัยTryParseไม่ใช่ชื่อที่ดีของฉัน

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

IList<Point> Intersection(Line a, Line b) { ... }

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

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

Maybe<Point> Intersection(Line a, Line b) { ... }

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

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


หากคุณจะตามใจฉันฉันต้องการพูดถึงความคิดเห็นนี้:

ทำไมไม่มีคำตอบที่ชัดเจนว่าทั้งสอง monad ไม่ใช่รหัสข้อผิดพลาดและไม่ใช่ OneOf พวกเขามีพื้นฐานที่แตกต่างกันและคำถามจึงดูเหมือนว่าจะอยู่บนพื้นฐานของความเข้าใจผิด (แม้ว่าจะอยู่ในรูปแบบที่ถูกปรับเปลี่ยน แต่มันก็ยังเป็นคำถามที่ถูกต้อง) - Konrad Rudolph 4 มิ.ย. 18 ที่ 14:08

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


9
มันจะไม่เหมาะสมกว่านี้หากแทร็กสีแดงอยู่ใต้กล่องดังนั้นจึงไม่วิ่งไปมา?
Flater

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

7
ฉันคิดว่าข้อยกเว้นเพิ่มเติมเช่นคำสั่งCOMEFROMมากกว่า GOTO เนื่องจากthrowไม่ทราบ / พูดจริง ๆ ว่าเราจะข้ามไปที่ใด)
Warbo

3
ไม่มีอะไรผิดปกติกับการประชุมหากคุณสามารถกำหนดขอบเขตซึ่งเป็นสิ่งที่เกี่ยวกับการห่อหุ้ม
Robert Harvey

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

35

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

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

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

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


9
การเผยแพร่พระไม่ต้องการอะไรเลย
Basilevs

23
@Basilevs มันต้องการการสนับสนุนสำหรับการเผยแพร่พระสงฆ์ในขณะที่รักษารหัสที่สามารถอ่านได้ซึ่ง C # ไม่ได้มี คุณอาจได้รับซ้ำคำแนะนำ if-return หรือ chains of lambdas
เซบาสเตียนเรดล

4
@SebastianRedl lambdas ดูโอเคใน C #
Basilevs

@Basilevs จากมุมมองไวยากรณ์ใช่ แต่ฉันสงสัยว่าจากมุมมองประสิทธิภาพพวกเขาอาจจะน่าสนใจน้อยกว่า
Pharap

5
ใน C # ที่ทันสมัยคุณสามารถใช้ฟังก์ชั่นในเครื่องเพื่อรับความเร็ว ไม่ว่าในกรณีใดซอฟต์แวร์ธุรกิจส่วนใหญ่จะชะลอตัวลงอย่างมีนัยสำคัญโดย IO มากกว่าซอฟต์แวร์อื่น
Turksarama

26

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

แม้ว่าจะมีข้อยกเว้นอยู่เสมอในรูปแบบของTryXXXวิธีการซึ่งจะส่งคืนbooleanผลสำเร็จและให้ผลลัพธ์ค่าที่สองผ่านoutพารามิเตอร์ สิ่งเหล่านี้คล้ายกับรูปแบบ "ลอง" ในการเขียนโปรแกรมการทำงานบันทึกว่าผลลัพธ์นั้นมาจากพารามิเตอร์ออกแทนที่จะMaybe<T>ส่งคืนค่า

แต่สิ่งต่าง ๆ กำลังเปลี่ยนแปลง ฟังก์ชั่นการเขียนโปรแกรมกำลังเป็นที่นิยมมากขึ้นและภาษาอย่าง C # กำลังตอบสนอง C # 7.0 แนะนำคุณสมบัติการจับคู่รูปแบบพื้นฐานบางอย่างกับภาษา C # 8 จะแนะนำมากขึ้นรวมถึงการแสดงออกของสวิทช์รูปแบบการเรียกซ้ำและอื่น ๆ นอกจากนี้ยังมีการเจริญเติบโตใน"ห้องสมุดที่ใช้งานได้" สำหรับ C # เช่นห้องสมุด Succinc <T> ของฉันเองซึ่งให้การสนับสนุนสหภาพที่แบ่งแยก .

ทั้งสองสิ่งที่รวมกันหมายถึงรหัสเช่นนี้มีการเติบโตอย่างช้าๆในความนิยม

static Maybe<int> TryParseInt(string source) =>
    int.TryParse(source, out var result) ? new Some<int>(result) : none; 

public int GetNumberFromUser()
{
    Console.WriteLine("Please enter a number");
    while (true)
    {
        var userInput = Console.ReadLine();
        if (TryParseInt(userInput) is int value)
        {
            return value;
        }
        Console.WriteLine("That's not a valid number. Please try again");
    }
}

ยังคงค่อนข้างเฉพาะในขณะนี้แม้ว่าในช่วงสองปีที่ผ่านมาฉันได้สังเกตเห็นการเปลี่ยนแปลงที่ชัดเจนจากความเป็นศัตรูทั่วไปในหมู่นักพัฒนา C # เป็นรหัสดังกล่าวเป็นการเพิ่มความสนใจในการใช้เทคนิคดังกล่าว

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


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

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

7
@AleksandrDubinsky คุณไม่ได้เกลียดเมื่อพวกเขาเพิ่มคุณสมบัติตอนนี้จากปี 1960
ctrl-alt-delor

6
@AleksandrDubinsky ใช่ เนื่องจาก Java พยายามที่จะเพิ่มสิ่งหนึ่งครั้ง (generics) พวกเขาล้มเหลวตอนนี้เราต้องอยู่กับความยุ่งเหยิงที่น่ากลัวซึ่งทำให้พวกเขาเรียนรู้บทเรียนและเลิกเพิ่มอะไรเลย
:)

3
@Agent_L: คุณรู้หรือไม่ว่า Java เพิ่มjava.util.Streamแล้ว :)
cHao

5

ฉันคิดว่ามันเหมาะสมอย่างยิ่งที่จะใช้ DU เพื่อส่งคืนข้อผิดพลาด แต่ฉันจะบอกว่าเมื่อฉันเขียน OneOf :) แต่ฉันจะบอกว่าต่อไปเนื่องจากเป็นวิธีปฏิบัติทั่วไปใน F # etc (เช่นประเภทผลลัพธ์ตามที่ผู้อื่นกล่าวถึง )

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

นี่คือตัวอย่างจากหน้าโครงการ

public OneOf<User, InvalidName, NameTaken> CreateUser(string username)
{
    if (!IsValid(username)) return new InvalidName();
    var user = _repo.FindByUsername(username);
    if(user != null) return new NameTaken();
    var user = new User(username);
    _repo.Save(user);
    return user;
}

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

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

ฉันจะบันทึกข้อยกเว้นสำหรับสิ่งต่าง ๆ ที่บ่งบอกถึงบางสิ่งบางอย่างที่เสียหายถ้าทำได้


สิ่งที่คุณพูดคือOneOfเกี่ยวกับการทำให้ค่าส่งคืนสมบูรณ์ยิ่งขึ้นเช่นคืนค่า Nullable มันแทนที่ข้อยกเว้นสำหรับสถานการณ์ที่ไม่ได้ยอดเยี่ยมในการเริ่มต้น
Aleksandr Dubinsky

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

4

OneOfใกล้เคียงกับข้อยกเว้นที่ตรวจสอบใน Java มีความแตกต่างบางประการ:

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

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

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

  • พวกเขาทั้งสองบังคับให้คุณจัดการพวกเขาทุกที่ในสายสแตก

สิ่งนี้จะป้องกันความกลัวของคุณ

ใน C # การละเว้นข้อยกเว้นจะไม่ทำให้เกิดข้อผิดพลาดในการคอมไพล์และหากพวกมันไม่ถูกจับพวกมันก็จะทำให้โปรแกรมของคุณพัง

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

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


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

1
คุณสามารถส่งคืนข้อมูลข้อผิดพลาดด้วย OneOf โดยการทำให้วัตถุข้อผิดพลาดที่ส่งคืนมีข้อมูลที่เป็นประโยชน์เช่นOneOf<SomeSuccess, SomeErrorInfo>ที่SomeErrorInfoให้รายละเอียดข้อผิดพลาด
mcintyre321

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

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

4

การใช้ OneOf แทนที่จะเป็นข้อยกเว้นสำหรับจัดการข้อยกเว้น "สามัญ" (เช่น File Not Found และอื่น ๆ ) เป็นวิธีการที่สมเหตุสมผลหรือเป็นการปฏิบัติที่แย่มาก?

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

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

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

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

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

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

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


2

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

OneOf ไม่ใช่รหัสข้อผิดพลาดมันเป็นเหมือน monad

วิธีการใช้OneOf<Value, Error1, Error2>เป็นคอนเทนเนอร์ที่แสดงถึงผลลัพธ์ที่แท้จริงหรือสถานะข้อผิดพลาดบางอย่าง มันเป็นเหมือนOptionalยกเว้นว่าเมื่อไม่มีค่าอยู่มันสามารถให้รายละเอียดเพิ่มเติมว่าทำไมถึงเป็นเช่นนั้น

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

ปัญหาเดียวคือเมื่อคุณไม่สนใจผลลัพธ์ ตัวอย่างจากเอกสารของ OneOf ให้:

public OneOf<User, InvalidName, NameTaken> CreateUser(string username) { ... }

... และจากนั้นพวกเขาสร้างการตอบสนอง HTTP ที่แตกต่างกันสำหรับแต่ละผลลัพธ์ซึ่งดีกว่าการใช้ข้อยกเว้น แต่ถ้าคุณเรียกวิธีการเช่นนี้

public void CreateUserButtonClick(String username) {
    UserManager.CreateUser(string username)
}

แล้วคุณจะมีปัญหาและข้อยกเว้นจะเป็นทางเลือกที่ดีกว่า เปรียบเทียบรถไฟของVSsavesave!


1

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

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

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

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


0

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

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

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

(โปรดทราบว่าฉันไม่ได้มีประสบการณ์มากใน C # แต่คำตอบของฉันควรเก็บไว้ในกรณีทั่วไปตามระดับแนวคิดของข้อยกเว้นใด ๆ )


7
ฉันไม่เห็นด้วยกับสถานที่ตั้งของคำตอบนี้ หากนักออกแบบภาษาไม่ได้ตั้งใจที่จะใช้ข้อยกเว้นสำหรับข้อผิดพลาดที่สามารถกู้คืนได้catchคำหลักนั้นจะไม่มีอยู่ และเนื่องจากเรากำลังพูดถึงในบริบทของ C # จึงเป็นที่น่าสังเกตว่าปรัชญาที่ดำเนินการในที่นี้ไม่ได้ถูกตามด้วยกรอบงาน. NET บางทีตัวอย่างที่ง่ายที่สุดที่เป็นไปได้ของ"อินพุตที่ไม่ถูกต้องที่คุณสามารถจัดการได้"คือผู้ใช้ที่พิมพ์ในจำนวนที่ไม่ใช่ในฟิลด์ที่คาดว่าจะมีหมายเลข ... และint.Parseส่งข้อยกเว้น
Mark Amery

@ MarkAmery สำหรับ int.Parse มันไม่มีอะไรที่มันสามารถกู้คืนจากอะไรก็ได้ที่มันคาดหวัง โดยทั่วไป catch clause จะถูกใช้เพื่อหลีกเลี่ยงความล้มเหลวที่สมบูรณ์จากมุมมองภายนอกโดยจะไม่ถามผู้ใช้สำหรับข้อมูลประจำตัวฐานข้อมูลใหม่หรือสิ่งที่คล้ายกันมันจะหลีกเลี่ยงการหยุดทำงานของโปรแกรม มันเป็นความล้มเหลวทางเทคนิคไม่ใช่ขั้นตอนการควบคุมแอพพลิเคชัน
Frank Hopkins

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

@Darkwing "คุณสามารถจัดการกับกรณีเหล่านั้นด้วยรหัสข้อผิดพลาดหรือสถาปัตยกรรมอื่น ๆ " ใช่คุณมีอิสระที่จะทำเช่นนั้น อย่างไรก็ตามคุณไม่ได้รับการสนับสนุนจาก. NET ในการทำเช่นนั้นเนื่องจาก. NET CL เองใช้ข้อยกเว้นแทนรหัสข้อผิดพลาด ดังนั้นไม่เพียง แต่คุณในหลายกรณีที่ถูกบังคับให้จับข้อยกเว้น. NET และส่งคืนรหัสข้อผิดพลาดที่เกี่ยวข้องแทนการปล่อยให้เกิดฟองข้อยกเว้นขึ้นคุณยังบังคับให้ผู้อื่นในทีมของคุณเข้าสู่แนวคิดการจัดการข้อผิดพลาดอื่น ๆ ควบคู่ไปกับข้อยกเว้นแนวคิดการจัดการข้อผิดพลาดมาตรฐาน
Aleksander

-2

มันเป็น 'ความคิดที่น่ากลัว'

มันแย่มากเพราะอย่างน้อยใน. net คุณไม่มีรายการยกเว้นที่ครบถ้วน รหัสใด ๆ สามารถโยนข้อยกเว้นใด ๆ โดยเฉพาะอย่างยิ่งถ้าคุณกำลังทำ OOP และอาจจะเรียกวิธีการแทนที่ในประเภทย่อยมากกว่าประเภทที่ประกาศ

ดังนั้นคุณจะต้องเปลี่ยนวิธีการและฟังก์ชั่นทั้งหมดเป็นOneOf<Exception, ReturnType>ถ้าคุณต้องการจัดการกับชนิดของข้อยกเว้นต่าง ๆ คุณจะต้องตรวจสอบประเภทIf(exception is FileNotFoundException)อื่น ๆ

try catch เท่ากับ OneOf<Exception, ReturnType>

แก้ไข ----

ฉันรู้ว่าคุณเสนอให้กลับมาOneOf<ErrorEnum, ReturnType>แต่ฉันรู้สึกว่านี่เป็นความแตกต่างโดยไม่มีความแตกต่าง

คุณกำลังรวมรหัสส่งคืนข้อยกเว้นที่คาดหวังและการแยกสาขาด้วยข้อยกเว้น แต่ผลโดยรวมก็เหมือนกัน


2
ข้อยกเว้น 'ปกติ' นั้นเป็นแนวคิดที่แย่เช่นกัน เบาะแสอยู่ในชื่อ
Ewan

4
ผมไม่ได้เสนอให้มีการกลับมาExceptionของ
Clinton

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