ข้อยกเว้น -“ เกิดอะไรขึ้น” กับ“ จะทำอย่างไร”


19

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

เกิดอะไรขึ้นถ้ามีความเป็นไปได้ที่จะระบุพฤติกรรมที่คาดหวังของผู้บริโภค?

ตัวอย่างเช่นสมมติว่าเรามีfetchทรัพยากรซึ่งดำเนินการคำขอ HTTP และส่งคืนข้อมูลที่ดึงมา และแทนที่จะมีข้อผิดพลาดเช่นServiceTemporaryUnavailableหรือRateLimitExceededเราจะยกระดับการRetryableErrorแนะนำผู้บริโภคว่าควรลองใหม่อีกครั้งและไม่สนใจความล้มเหลวที่เฉพาะเจาะจง ดังนั้นเราจึงแนะนำการกระทำให้กับผู้โทรโดยทั่วไป - "สิ่งที่ต้องทำ"

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


3
HTTP ทำเช่นนี้ไม่ได้เหรอ? 503 เป็นความล้มเหลวชั่วคราวในการตอบดังนั้นผู้ร้องขอควรลองใหม่อีกครั้ง 404 เป็นการขาดพื้นฐานดังนั้นจึงไม่มีเหตุผลที่จะลองอีกครั้ง 301 หมายถึง "ย้ายอย่างถาวร" ดังนั้นคุณควรลองใหม่ แต่มีที่อยู่อื่น ฯลฯ
Kilian Foth

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

@Ixrec - มีแนวคิดเดียวกันด้วย อย่างไรก็ตามผู้บริโภคอาจไม่ต้องการรอคำขออื่นและไม่สนใจรายการหรือล้มเหลวอย่างสมบูรณ์
Roman Bodnarchuk

1
@ RomanBodnarchuk: ฉันไม่เห็นด้วย มันก็เหมือนกับการบอกว่าคน ๆ นั้นไม่จำเป็นต้องรู้ภาษาจีนเพื่อที่จะพูดภาษาจีนได้ HTTP เป็นโปรโตคอลและทั้งไคลเอนต์และเซิร์ฟเวอร์คาดว่าจะรู้และปฏิบัติตาม นั่นเป็นวิธีที่โปรโตคอลทำงาน หากมีเพียงด้านเดียวเท่านั้นที่รู้และปฏิบัติตามคุณจะไม่สามารถสื่อสารได้
Chris Pratt

1
ดูเหมือนว่าคุณกำลังพยายามแทนที่ข้อยกเว้นของคุณด้วยบล็อก catch นั่นเป็นเหตุผลที่เราย้ายไปที่ข้อยกเว้น - ไม่มากif( ! function() ) handle_something();แต่สามารถจัดการข้อผิดพลาดที่คุณรู้จักบริบทการโทร - เช่นบอกให้ลูกค้าโทรหาผู้ดูแลระบบ sys ถ้าเซิร์ฟเวอร์ของคุณล้มเหลวหรือโหลดซ้ำโดยอัตโนมัติหากการเชื่อมต่อลดลง ในกรณีที่ผู้โทรเป็นบริการอื่น ปล่อยให้บล็อก catch จัดการกับ catch
Sebb

คำตอบ:


47

แต่ลองนึกภาพว่าเป็นองค์ประกอบเฉพาะเรารู้วิธีการที่ดีที่สุดสำหรับผู้โทร

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

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

ให้พวกเขามีทางเลือก


4
ฉันเห็นด้วย. เซิร์ฟเวอร์ควรถ่ายทอดข้อมูลอย่างถูกต้องเท่านั้น เอกสารในทางกลับกันควรร่างแนวทางการดำเนินการที่เหมาะสมอย่างชัดเจนในกรณีดังกล่าว
Neil

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

3
@Pharap หากแฮกเกอร์ของคุณสามารถเข้าถึงข้อยกเว้นได้เองแทนที่จะเป็นข้อความแสดงข้อผิดพลาดแสดงว่าคุณสูญเสียไปแล้ว
corsiKa

2
ฉันชอบคำตอบนี้ แต่มันหายไปจากสิ่งที่ฉันพิจารณาถึงข้อยกเว้น ... ถ้าคุณรู้วิธีกู้คืนมันจะไม่เป็นข้อยกเว้น! ข้อยกเว้นควรเกิดขึ้นเมื่อคุณไม่สามารถทำอะไรได้บ้าง: อินพุตไม่ถูกต้อง, สถานะไม่ถูกต้อง, ความปลอดภัยที่ไม่ถูกต้อง - คุณไม่สามารถแก้ไขปัญหาเหล่านั้นได้
corsiKa

1
ตกลง หากคุณยืนยันRetryableErrorในการแสดงให้เห็นว่าลองใหม่อีกครั้งเป็นไปได้ที่คุณสามารถเสมอเพียงให้ข้อยกเว้นคอนกรีตที่เกี่ยวข้องสืบทอดมาจาก
sapi

18

คำเตือน! โปรแกรมเมอร์ C ++ ที่เข้ามาที่นี่พร้อมกับแนวคิดที่แตกต่างกันของวิธีการจัดการข้อยกเว้นควรพยายามตอบคำถามที่เกี่ยวกับภาษาอื่นอย่างแน่นอน!

รับความคิดนี้:

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

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

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

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

Thrower vs. Catcher

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

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

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

The Blind Thrower

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

Inverted Responsibilities และ Generalizing Catcher

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

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

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

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

สิ่งนี้จะใช้เฉพาะถ้าคุณสามารถออกแบบฟังก์ชั่นทั่วไปบางอย่างที่การทำธุรกรรมด้านนอกทั้งหมดเหล่านี้ใช้ (เช่น: ฟังก์ชั่นที่ป้อนฟังก์ชั่นอื่นในการโทรหรือคลาสฐานธุรกรรมนามธรรมที่มีพฤติกรรม overridable )

ทว่าผู้รับผิดชอบในการรวมศูนย์ผู้ใช้ในการดำเนินการเพื่อตอบสนองต่อข้อผิดพลาดต่าง ๆ ที่เป็นไปได้และยังอยู่ในบริบทของการจับแทนที่จะโยน ตัวอย่างง่าย ๆ (Python-ish pseudocode และฉันไม่ได้เป็นนักพัฒนา Python ที่มีประสบการณ์เพียงเล็กน้อยดังนั้นอาจมีวิธีที่เป็นไปได้มากกว่าในเรื่องนี้):

def general_catcher(task):
    try:
       task()
    except SomeError1:
       # do some uniformly-designed recovery stuff here
    except SomeError2:
       # do some other uniformly-designed recovery stuff here
    ...

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

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


2
+1 ฉันไม่เคยได้ยินแนวคิดเรื่อง "Blind Thrower" มาก่อน แต่มันสอดคล้องกับวิธีที่ฉันคิดเกี่ยวกับการจัดการข้อยกเว้น: ระบุข้อผิดพลาดที่เกิดขึ้นอย่าคิดว่าควรจัดการอย่างไร เมื่อคุณต้องรับผิดชอบสแต็คเต็มรูปแบบมันยาก (แต่สำคัญ!) เพื่อแยกความรับผิดชอบออกจากกันอย่างชัดเจนและผู้ที่มีหน้าที่รับผิดชอบในการแจ้งเตือนเกี่ยวกับปัญหาและผู้โทรเพื่อจัดการปัญหา ผู้ที่รู้เพียงรู้ว่ามันขอให้ทำอะไรทำไม การจัดการข้อผิดพลาดควรทำในบริบทของ 'ทำไม' จึง: ในผู้โทร
Sjoerd Job Postmus

1
(เช่น: ฉันไม่คิดว่าคำตอบของคุณเฉพาะ C ++ แต่ใช้ได้กับการจัดการข้อยกเว้นโดยทั่วไป)
Sjoerd Job Postmus

1
@SjoerdJobPostmus Yay ขอบคุณ! "Blind Thrower" เป็นเพียงการเปรียบเทียบที่ฉันมาด้วย - ฉันไม่ฉลาดหรือรวดเร็วในการย่อยแนวคิดทางเทคนิคที่ซับซ้อนดังนั้นฉันมักจะต้องการค้นหาภาพและอุปมาอุปมัยเพื่อพยายามอธิบายและพัฒนาความเข้าใจของตัวเอง ของสิ่งที่. บางทีวันหนึ่งฉันสามารถลองวิธีการเขียนหนังสือการเขียนโปรแกรมเล็ก ๆ น้อย ๆ ที่เต็มไปด้วยภาพวาดการ์ตูนมากมาย :-D

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

1
@DunkunkCoder: โปรดอย่าทำลายโพสต์ของคุณ เรามีสิ่งที่ borken เพียงพอบนอินเทอร์เน็ตแล้ว หากคุณมีเหตุผลที่ดีสำหรับการลบให้ตั้งค่าสถานะการโพสต์ของคุณเพื่อให้ผู้ดูแลสนใจและทำให้เป็นกรณีของคุณ
Robert Harvey

2

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

ตัวอย่างเช่นพิจารณาฟังก์ชั่น:

Response fetchUrl(URL url, RetryPolicy retryPolicy);

ฉันสามารถผ่าน RetryPolicy.noRetries () หรือ RetryPolicy.retries (3) หรืออะไรก็ตาม ในกรณีที่เกิดความล้มเหลวซ้ำอีกครั้งจะปรึกษานโยบายเพื่อตัดสินใจว่าควรลองใหม่หรือไม่


แต่นั่นไม่ได้เกี่ยวข้องอะไรมากนักกับการโยนข้อยกเว้นกลับไปที่ไซต์การโทร คุณกำลังพูดถึงบางสิ่งบางอย่างที่แตกต่างออกไปเล็กน้อยซึ่งก็ดี แต่ก็ไม่ได้เป็นส่วนหนึ่งของคำถามเลย
Lightness Races with Monica

@LightnessRacesinOrbit ไปในทางตรงกันข้าม ฉันกำลังนำเสนอเป็นทางเลือกให้กับแนวคิดของการโยนข้อยกเว้นกลับไปที่ไซต์การโทร ในตัวอย่างของ OP fetchUrl จะโยน RetryableException และฉันพูดแทนคุณควรบอก fetchUrl เมื่อมันควรลองใหม่
Winston Ewert

2
@ WinstonEwert: แม้ว่าฉันจะเห็นด้วยกับ LightnessRacesinOrbit ฉันก็เห็นจุดของคุณ แต่คิดว่ามันเป็นวิธีที่แตกต่างในการเป็นตัวแทนของการควบคุมเดียวกัน แต่ไม่พิจารณาว่าคุณอาจจะต้องการที่จะผ่านnew RetryPolicy().onRateLimitExceeded(STOP).onServiceTemporaryUnavailable(RETRY, 3)หรืออะไรเพราะความจำเป็นอาจจะต้องมีการจัดการที่แตกต่างจากRateLimitExceeded ServiceTemporaryUnavailableหลังจากที่เขียนออกมาความคิดของฉันคือ: ทิ้งข้อยกเว้นได้ดีกว่าเพราะให้การควบคุมที่ยืดหยุ่นมาก
Sjoerd Job Postmus

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