ฉันได้รับแจ้งว่าควรใช้ข้อยกเว้นในกรณีพิเศษเท่านั้น ฉันจะรู้ได้อย่างไรว่ากรณีของฉันยอดเยี่ยม


99

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

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

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

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

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


3
ซ้ำกันเป็นไปได้? เมื่อใดจะโยนข้อยกเว้น แม้ว่ามันจะปิดที่นั่น แต่ฉันคิดว่ามันเหมาะกับที่นี่ มันยังคงเป็นปรัชญาอยู่บ้างผู้คนและชุมชนบางคนมักจะเห็นข้อยกเว้นว่าเป็นการควบคุมการไหล
thorsten müller

8
เมื่อผู้ใช้เป็นใบ้พวกเขาให้การป้อนข้อมูลที่ไม่ถูกต้อง เมื่อผู้ใช้ฉลาดพวกเขาเล่นด้วยการป้อนข้อมูลที่ไม่ถูกต้อง ดังนั้นการป้อนข้อมูลผู้ใช้ที่ไม่ถูกต้องจึงไม่ใช่ข้อยกเว้น
mouviciel

7
นอกจากนี้อย่าสับสนยกเว้นซึ่งเป็นกลไกที่เฉพาะเจาะจงมากใน Java และ. NET กับข้อผิดพลาดซึ่งเป็นคำทั่วไปมากขึ้น มีข้อผิดพลาดมากกว่าการจัดการข้อผิดพลาด การสนทนานี้สัมผัสกับความแตกต่างระหว่างข้อยกเว้นและข้อผิดพลาด
Eric King เมื่อ

4
"เจ๋ง"! = "ไม่ค่อยเกิดขึ้น"
ConditionRacer

3
ฉันพบว่าข้อยกเว้น Vexingของ Eric Lippert เป็นคำแนะนำที่เหมาะสม
Brian

คำตอบ:


87

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

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

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


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

17
ฉันไม่ได้บอกว่ายังคงมีกรณีที่การเข้าชมการทำงานเป็นเหตุผลที่ถูกต้อง แต่กรณีเหล่านั้นเป็นข้อยกเว้น (ตั้งใจให้เล่นสำนวน) มากกว่ากฎ
Karl Bielefeldt

11
@RobertHarvey เคล็ดลับใน Java throw new ...คือการโยนวัตถุยกเว้นก่อนประดิษฐ์มากกว่า หรือโยนข้อยกเว้นที่กำหนดเองโดยที่ fillInStackTrace () ถูกเขียนทับ แล้วคุณไม่ควรแจ้งให้ทราบ degradations การใด ๆ ไม่ได้ที่จะพูดถึงความนิยม
Ingo

3
+1: สิ่งที่ฉันจะตอบ ใช้มันเมื่อมันทำให้รหัสง่ายขึ้น ข้อยกเว้นสามารถให้รหัสที่ชัดเจนมากขึ้นโดยที่คุณไม่ต้องกังวลกับการตรวจสอบค่าตอบแทนในทุกระดับใน call-stack (เช่นเดียวกับทุกสิ่งทุกอย่างหากใช้วิธีที่ผิดจะทำให้โค้ดของคุณยุ่งเหยิงน่ากลัวมาก)
Leo

4
@Brendan สมมติว่ามีข้อยกเว้นเกิดขึ้นและรหัสการจัดการข้อผิดพลาดคือ 4 ระดับด้านล่างใน call stack หากคุณใช้รหัสข้อผิดพลาดฟังก์ชั่นทั้ง 4 ตัวด้านบนตัวจัดการจะต้องมีประเภทของรหัสข้อผิดพลาดเป็นค่าส่งคืนและคุณต้องทำเชนif (foo() == ERROR) { return ERROR; } else { // continue }ในทุกระดับ หากคุณโยนข้อยกเว้นที่ไม่ได้ตรวจสอบจะไม่มีเสียงดังและซ้ำซ้อน "ถ้าข้อผิดพลาดการส่งคืนข้อผิดพลาด" นอกจากนี้หากคุณส่งผ่านฟังก์ชั่นเป็นอาร์กิวเมนต์การใช้รหัสข้อผิดพลาดอาจเปลี่ยนลายเซ็นฟังก์ชั่นเป็นประเภทที่เข้ากันไม่ได้แม้ว่าข้อผิดพลาดอาจไม่เกิดขึ้น
Doval

72

เมื่อใดควรโยนข้อยกเว้น เมื่อพูดถึงโค้ดฉันคิดว่าคำอธิบายต่อไปนี้มีประโยชน์มาก:

ข้อยกเว้นคือเมื่อสมาชิกล้มเหลวในการทำงานให้เสร็จสมบูรณ์ก็ควรที่จะดำเนินการตามที่ระบุโดยชื่อของมัน (Jeffry Richter, CLR ผ่าน C #)

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

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

public void Save(PersonData personData) {  }

ชื่อเมธอดแนะนำว่าทำการตรวจสอบความถูกต้องเสร็จสิ้นหรือไม่? ไม่ในกรณีนี้ PersonData ที่ไม่ถูกต้องควรมีข้อยกเว้น

สมมติว่าคลาสมีเมธอดอื่นซึ่งมีลักษณะดังนี้:

public ValidationResult Validate(PersonData personData) {  }

ชื่อเมธอดแนะนำว่าทำการตรวจสอบความถูกต้องเสร็จสิ้นหรือไม่? ใช่. ในกรณีนี้ PersonData ที่ไม่ถูกต้องไม่ควรโยนข้อยกเว้น

เมื่อต้องการรวมสิ่งต่าง ๆ เข้าด้วยกันทั้งสองวิธีแนะนำว่ารหัสลูกค้าควรมีลักษณะดังนี้:

ValidationResult validationResult = personRegister.Validate(personData);
if (validationResult.IsValid())
{
    personRegister.Save(personData)
}
else
{
    // Throw an exception? To answer this look at the context!
    // That is: (a) Method name, (b) signature and
    // (c) where this method is (expected) to be used.
}

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


เมื่อวานฉันเพิ่งstructเรียกว่า "ValidationResult" และจัดโครงสร้างโค้ดตามวิธีที่คุณอธิบาย
พอล

4
มันไม่ได้ช่วยตอบคำถามของคุณ แต่ฉันชอบที่จะชี้ให้เห็นว่าคุณทำตามหลักการแยกคำสั่งหรือแบบสอบถามโดยปริยาย ( en.wikipedia.org/wiki/Command-query_separation ) ;-)
Theo Lenndorff

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

@ Heinzi ฉันเห็นด้วย มันอาจจะ refactored ดังนั้นที่Validate()เรียกว่าภายในSave()วิธีการและรายละเอียดที่เฉพาะเจาะจงจากValidationResultสามารถนำมาใช้ในการสร้างข้อความที่เหมาะสมสำหรับข้อยกเว้น
Phil

3
นี่เป็นคำตอบที่ดีกว่าที่ฉันคิด โยนเมื่อสายไม่สามารถทำสิ่งที่มันควรจะทำ
Andy

31

ข้อยกเว้นควรพิเศษ: คาดว่าผู้ใช้อาจป้อนข้อมูลที่ไม่ถูกต้องดังนั้นนี่ไม่ใช่กรณีพิเศษ

ในอาร์กิวเมนต์นั้น

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

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

ดังนั้นฉันคิดว่า "ข้อยกเว้นควรพิเศษ" เป็นกฎง่ายๆ

สิ่งที่คุณควรทำขึ้นอยู่กับภาษา ภาษาที่แตกต่างกันมีการประชุมที่แตกต่างกันเกี่ยวกับเมื่อข้อยกเว้นควรถูกโยน ตัวอย่างเช่น Python โยนข้อยกเว้นสำหรับทุกสิ่งและเมื่ออยู่ใน Python ฉันตามหลังชุดสูท ในอีกทางหนึ่ง C ++ ก็มีข้อยกเว้นค่อนข้างน้อยและฉันก็ตามหลังชุดสูท คุณสามารถปฏิบัติต่อ C ++ หรือ Java เช่น Python และโยนข้อยกเว้นสำหรับทุกสิ่ง แต่การทำงานของคุณขัดแย้งกับวิธีการใช้ภาษาที่คาดหวัง

ฉันชอบวิธีการของไพ ธ อน แต่ฉันคิดว่ามันเป็นความคิดที่ดีที่จะใช้ภาษาอื่น


1
@gnat ฉันรู้ ประเด็นของฉันคือคุณควรปฏิบัติตามอนุสัญญาของภาษา (ในกรณีนี้ Java) แม้ว่าพวกเขาจะไม่ได้โปรดของคุณ
Winston Ewert

6
+1 ก็"exceptions should be exceptional" is a terrible rule of thumb.บอกว่า! นั่นเป็นหนึ่งในสิ่งที่ผู้คนทำซ้ำโดยไม่คิดถึงพวกเขา
Andres F.

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

1
@ Mikera ใช่ฟังก์ชั่นควร (เท่านั้น) โยนข้อยกเว้นที่กำหนดไว้ในสัญญา แต่นั่นไม่ใช่คำถาม คำถามคือคุณตัดสินใจได้อย่างไรว่าสัญญาควรเป็นอย่างไร ฉันยืนยันว่ากฎง่ายๆ "ยกเว้นควรจะยอดเยี่ยม" ไม่เป็นประโยชน์ในการตัดสินใจ
Winston Ewert

1
@supercat ฉันไม่คิดว่ามันเป็นเรื่องสำคัญที่กลายเป็นเรื่องธรรมดามากขึ้น ฉันคิดว่าคำถามที่สำคัญคือการมีสติเริ่มต้น หากฉันไม่ได้จัดการกับเงื่อนไขข้อผิดพลาดอย่างชัดเจนรหัสของฉันจะแกล้งทำเป็นไม่มีอะไรเกิดขึ้นหรือฉันจะได้รับข้อความแสดงข้อผิดพลาดที่เป็นประโยชน์หรือไม่?
Winston Ewert

30

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

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

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

แก้ไข (อย่างอื่นที่ต้องพิจารณา):

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

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


8
แต่ทำไม เหตุใดข้อยกเว้นมีประโยชน์น้อยกว่าในการจัดการปัญหาที่คาดหวังมากกว่า
Winston Ewert

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

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

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

4
File.ReadAllBytes จะส่ง a FileNotFoundExceptionเมื่อได้รับอินพุตที่ไม่ถูกต้อง (เช่นชื่อไฟล์ที่ไม่มีอยู่) นั่นเป็นวิธีเดียวที่ถูกต้องในการคุกคามข้อผิดพลาดนี้คุณสามารถทำอะไรได้อีกโดยไม่ส่งคืนรหัสข้อผิดพลาด
oɔɯǝɹ

16

การอ้างอิง

จากโปรแกรมเมอร์ในทางปฏิบัติ:

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

พวกเขาจะตรวจสอบตัวอย่างของการเปิดไฟล์เพื่ออ่านและไฟล์นั้นไม่มีอยู่ - นั่นควรยกข้อยกเว้นหรือไม่

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

ต่อมาพวกเขาคุยกันว่าทำไมพวกเขาถึงเลือกวิธีนี้:

[A] ยกเว้น n แทนทันทีโอนต่างแดนของการควบคุม - gotoมันเป็นชนิดของซ้อน โปรแกรมที่ใช้ข้อยกเว้นเป็นส่วนหนึ่งของการประมวลผลปกติของพวกเขาประสบปัญหาการอ่านและการบำรุงรักษาของรหัสสปาเก็ตตี้คลาสสิกทั้งหมด โปรแกรมเหล่านี้แบ่งการห่อหุ้ม: รูทีนและผู้โทรจะถูกผนวกเข้าด้วยกันอย่างแน่นหนามากขึ้นผ่านการจัดการข้อยกเว้น

เกี่ยวกับสถานการณ์ของคุณ

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

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


11

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

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


1
+1 ใกล้กับสิ่งที่ฉันจะพูดมาก ฉันจะบอกว่ามันเกี่ยวกับขอบเขตและไม่มีอะไรเกี่ยวข้องกับผู้ใช้ ตัวอย่างที่ดีของสิ่งนี้คือความแตกต่างระหว่างทั้งสองฟังก์ชั่น. net int.Parse และ int.TryParse อดีตไม่มีทางเลือกนอกจากโยนข้อยกเว้นของอินพุตที่ไม่ดีในภายหลังไม่ควรโยนข้อยกเว้น
jmoreno

1
@jmoreno: เออร์โกคุณจะใช้ TryParse เมื่อโค้ดสามารถทำอะไรบางอย่างเกี่ยวกับเงื่อนไขที่ไม่สามารถหาได้และแยกวิเคราะห์เมื่อไม่สามารถทำได้
Robert Harvey

7

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

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

  2. ดีนำมาใช้ส่วนติดต่อผู้ใช้มีความกังวลเพิ่มเติม: การตรวจสอบของผู้ใช้ป้อนข้อมูลและข้อเสนอแนะที่สร้างสรรค์เกี่ยวกับข้อผิดพลาด (ขอเรียกส่วนนี้Validator)

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

จากมุมมองของประสบการณ์ผู้ใช้ผู้ใช้ไม่ควรพูดคุยกับสิ่งนี้โดยตรงAssignerตั้งแต่แรก พวกเขาควรจะพูดคุยกับมันผ่านValidator

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

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


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

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

ฉันเดาว่าจะทำให้คำตอบโดยตรง

... ฉันจะรู้ได้อย่างไรว่ากรณีของฉันยอดเยี่ยม

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


-1 ใช่มีข้อกังวลสองข้อ แต่นี่ไม่ได้ตอบคำถาม "ฉันจะรู้ได้อย่างไรว่ากรณีของฉันยอดเยี่ยม"
RMalke

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

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

4

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


3

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

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

ดังนั้นตามกฎทั่วไปถ้า:

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

จากนั้นโค้ดของคุณไม่ควรมีข้อยกเว้นแม้แต่อย่างเดียวที่ถูกดักจับในภายหลัง ในการดักจับข้อมูลที่ไม่ถูกต้องคุณสามารถใช้เครื่องมือตรวจสอบความถูกต้องได้ที่ระดับ UI หรือรหัสเช่นInt32.TryParse()ในเลเยอร์การนำเสนอ

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


2

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

หากรูทีนการอ่านข้อมูลไม่ได้ใช้ข้อยกเว้น แต่เพียงรายงานว่าการอ่านสำเร็จหรือไม่รหัสการโทรจะต้องมีลักษณะดังนี้:

temp = dataSource.readInteger();
if (temp == null) return null;
field1 = (int)temp;
temp = dataSource.readInteger();
if (temp == null) return null;
field2 = (int)temp;
temp = dataSource.readString();
if (temp == null) return null;
field3 = temp;

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

field1 = dataSource.readInteger();
field2 = dataSource.readInteger();
field3 = dataSource.readString();

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

do
{
  temp = dataSource.tryReadInteger();
  if (temp == null) break;
  total += (int)temp;
} while(true);

กับ

try
{
  do
  {
    total += (int)dataSource.readInteger();
  }
  while(true);
}
catch endOfDataSourceException ex
{ // Don't do anything, since this is an expected condition (eventually)
}

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

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


2

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

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

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

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


2

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

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

ตัวอย่างที่ไม่ดี:

วิธีการตรวจสอบสำหรับการDogเรียนโดยใช้ข้อยกเว้น:

void validate(Set<DogValidationException> previousExceptions) {
    if (!DOG_NAME_PATTERN.matcher(this.name).matches()) {
        DogValidationException disallowedName = new DogValidationException(Problem.DISALLOWED_DOG_NAME);
        if (!previousExceptions.contains(disallowedName)){
            throw disallowedName;
        }
    }
    if (this.legs < 4) {
        DogValidationException invalidDog = new DogValidationException(Problem.LITERALLY_INVALID_DOG);
        if (!previousExceptions.contains(invalidDog)){
            throw invalidDog;
        }
    }
    // etc.
}

วิธีการโทร:

Set<DogValidationException> exceptions = new HashSet<DogValidationException>();
boolean retry;
do {
    retry = false;
    try {
        dog.validate(exceptions);
    } catch (DogValidationException e) {
        exceptions.add(e);
        retry = true;
    }
} while (retry);

if(exceptions.isEmpty()) {
    dogDAO.beginTransaction();
    dogDAO.save(dog);
    dogDAO.commitAndCloseTransaction();
} else {
    // notify user to fix the problems
}

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

วิธีที่ดีกว่าคือ:

วิธีการโทร:

Set<Problem> validationResults = dog.validate();
if(validationResults.isEmpty()) {
    dogDAO.beginTransaction();
    dogDAO.save(dog);
    dogDAO.commitAndCloseTransaction();
} else {
    // notify user to fix the problems
}

วิธีการตรวจสอบ:

Set<Problem> validate() {
    Set<Problem> result = new HashSet<Problem>();
    if(!DOG_NAME_PATTERN.matcher(this.name).matches()) {
        result.add(Problem.DISALLOWED_DOG_NAME);
    }
    if(this.legs < 4) {
        result.add(Problem.LITERALLY_INVALID_DOG);
    }
    // etc.
    return result;
}

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

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

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