ทำไมไม่ใช้ข้อยกเว้นเป็นโฟลว์ควบคุมปกติ


193

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

C # และ Java (และอื่น ๆ อีกมากมายเกินไป) มีกับความอุดมสมบูรณ์ของประเภทบางส่วนของ 'ล้น' พฤติกรรมฉันไม่ชอบที่ทุกคน (เช่นtype.MaxValue + type.SmallestValue == type.MinValueตัวอย่างเช่น: int.MaxValue + 1 == int.MinValue)

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

Addวิธีการแทนที่:

/// <summary>
/// Increments this date with a timespan, but loops when
/// the maximum value for datetime is exceeded.
/// </summary>
/// <param name="ts">The timespan to (try to) add</param>
/// <returns>The Date, incremented with the given timespan. 
/// If DateTime.MaxValue is exceeded, the sum wil 'overflow' and 
/// continue from DateTime.MinValue. 
/// </returns>
public DateTime override Add(TimeSpan ts) 
{
    try
    {                
        return base.Add(ts);
    }
    catch (ArgumentOutOfRangeException nb)
    {
        // calculate how much the MaxValue is exceeded
        // regular program flow
        TimeSpan saldo = ts - (base.MaxValue - this);
        return DateTime.MinValue.Add(saldo)                         
    }
    catch(Exception anyOther) 
    {
        // 'real' exception handling.
    }
}

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

ฉันคิดว่าในหลาย ๆ กรณีพวกเขามีความชัดเจนมากกว่าถ้าโครงสร้างและไม่ทำลายสัญญาใด ๆ วิธีการทำ

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

หรือฉันเข้าใจผิด?

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

  1. ชัดเจน
  2. ทำตามสัญญาของวิธีการของคุณ

ยิงฉัน.


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

4
คือ: return -1 หากมีสิ่งใดเกิดขึ้นส่งคืน -2 หากมีสิ่งอื่น ฯลฯ ... สามารถอ่านได้มากขึ้นแล้วยกเว้นยกเว้น?
kender

1
เป็นเรื่องน่าเศร้าที่คน ๆ หนึ่งได้รับชื่อเสียงในทางลบจากการบอกความจริง: ตัวอย่างของคุณไม่สามารถเขียนได้หากมีข้อความ (นี่ไม่ได้เป็นการบอกว่ามันถูกต้อง / สมบูรณ์)
Ingo

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

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

คำตอบ:


171

คุณเคยลองดีบักโปรแกรมที่เพิ่มข้อยกเว้นห้าข้อต่อวินาทีในการดำเนินการปกติหรือไม่?

ฉันมี.

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

ฉันหวังว่าฉันจะสามารถเปิดตัวโปรแกรมและรอให้มีข้อยกเว้นเกิดขึ้น แต่มีข้อยกเว้นประมาณ 200 ข้อในระหว่างการเริ่มต้นในการดำเนินงานตามปกติ

ประเด็นของฉัน: หากคุณใช้ข้อยกเว้นสำหรับสถานการณ์ปกติคุณจะค้นหาสถานการณ์ที่ผิดปกติ (เช่นข้อยกเว้น al) ได้อย่างไร

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


13
ตัวอย่าง: เมื่อฉันดีบักโปรแกรม. net ฉันจะเปิดมันจาก visual studio และฉันขอให้ VS ทำลายข้อยกเว้นทั้งหมด หากคุณพึ่งพาข้อยกเว้นเป็นพฤติกรรมที่คาดหวังฉันไม่สามารถทำเช่นนั้นได้อีกต่อไป (เนื่องจากมันจะแบ่ง 5times / วินาที) และมันซับซ้อนกว่าการค้นหาส่วนที่เป็นปัญหาของรหัส
Brann

15
+1 สำหรับการชี้ให้เห็นว่าคุณไม่ต้องการสร้างข้อยกเว้นในกองหญ้าเพื่อหาเข็มพิเศษจริง ๆ
Grant Wagner

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

11
@Peter: การดีบักโดยไม่มีข้อยกเว้นเป็นเรื่องยากและการจับข้อยกเว้นทั้งหมดนั้นเป็นเรื่องที่เจ็บปวดหากมีการออกแบบจำนวนมาก ผมคิดว่าการออกแบบซึ่งจะทำให้การแก้จุดบกพร่องที่ยากคือการเสียเกือบบางส่วน (ในคำอื่น ๆ การออกแบบมีสิ่งที่จะทำอย่างไรกับการแก้จุดบกพร่อง, IMO)
บราน

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

158

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

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

อย่างไรก็ตามบางภาษา (โดยเฉพาะ Python) ใช้ข้อยกเว้นเป็นโครงสร้างการควบคุมการไหล ตัวอย่างเช่นตัววนซ้ำยกStopIterationข้อยกเว้นหากไม่มีรายการเพิ่มเติม แม้แต่โครงสร้างภาษามาตรฐาน (เช่นfor) ก็ใช้สิ่งนี้


11
เฮ้ข้อยกเว้นไม่น่าประหลาดใจ! และคุณขัดแย้งกับตัวเองเมื่อคุณพูดว่า "มันเป็นความคิดที่ไม่ดี" จากนั้นก็พูดต่อไป "แต่มันก็เป็นความคิดที่ดีในงูหลาม"
hasen

6
ฉันยังไม่มั่นใจเลย: 1) ความง่ายอยู่นอกเหนือจากคำถามโปรแกรมที่ไม่ใช่เรือยอชท์จำนวนมากไม่สามารถดูแลน้อยลง (เช่นส่วนต่อประสานผู้ใช้) 2) ที่น่าอัศจรรย์: อย่างที่ฉันบอกว่ามันเป็นเพียงสาเหตุที่ไม่น่าใช้ คำถามยังคงอยู่: ทำไมไม่ใช้รหัสในตอนแรก? แต่เนื่องจากนี่เป็นคำตอบ
ปีเตอร์

4
+1 จริง ๆ แล้วฉันดีใจที่คุณชี้ให้เห็นความแตกต่างระหว่าง Python และ C # ฉันไม่คิดว่ามันขัดแย้งกัน Python มีพลวัตมากขึ้นและความคาดหวังของการใช้ข้อยกเว้นในลักษณะนี้จะถูกทำให้เป็นภาษา เป็นส่วนหนึ่งของวัฒนธรรม EAFP ของ Python ฉันไม่ทราบว่าวิธีการใดที่มีแนวคิดที่บริสุทธิ์กว่าหรือสอดคล้องกับตนเองมากกว่า แต่ฉันชอบความคิดในการเขียนโค้ดที่ทำในสิ่งที่คนอื่นคาดหวังว่าจะทำได้ซึ่งหมายถึงสไตล์ที่แตกต่างในภาษาต่างๆ
Mark E. Haase

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

4
ที่จริงแล้วผู้ขาย VM ส่วนใหญ่คาดว่าจะมีข้อยกเว้นและจัดการได้อย่างมีประสิทธิภาพ ในฐานะที่เป็น @LukasEder ข้อยกเว้นมีความแตกต่างอย่างสิ้นเชิงเหมือนข้ามไปที่พวกเขามีโครงสร้าง
Marcin

30

กฎง่ายๆของฉันคือ:

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

ปัญหาที่ฉันเห็นด้วยข้อยกเว้นมาจากมุมมองของไวยากรณ์อย่างหมดจด (ฉันค่อนข้างมั่นใจว่าค่าใช้จ่ายในการทำงานมีน้อยมาก) ฉันไม่ชอบบล็อกลองทั่วทุกที่

ใช้ตัวอย่างนี้:

try
{
   DoSomeMethod();  //Can throw Exception1
   DoSomeOtherMethod();  //Can throw Exception1 and Exception2
}
catch(Exception1)
{
   //Okay something messed up, but is it SomeMethod or SomeOtherMethod?
}

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

Class1 myInstance;
try
{
   myInstance = Class1Factory.Build();
}
catch(SomeException)
{
   // Couldn't instantiate class, do something else..
}
myInstance.BestMethodEver();   // Will throw a compile-time error, saying that myInstance is uninitalized, which it potentially is.. :(

Soo ส่วนตัวฉันคิดว่าคุณควรเก็บข้อยกเว้นสำหรับข้อผิดพลาดที่หายาก (หน่วยความจำไม่เพียงพอ ฯลฯ ) และใช้ค่าส่งคืน (คลาสที่มีค่า, struct หรือ enums) เพื่อทำการตรวจสอบข้อผิดพลาดแทน

หวังว่าฉันเข้าใจคำถามของคุณถูกต้อง :)


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

2
ใช่นั่นอาจเป็นสิ่งที่คุณจะจบลง แต่ลองพิจารณาตัวอย่างที่ซับซ้อนมากขึ้นซึ่งตัวประเภท myInstance สามารถโยนข้อยกเว้น .. และ isntances อื่น ๆ ในขอบเขตวิธีก็สามารถทำได้เช่นกัน คุณจะพบกับบล็อก try / catch ที่ซ้อนกันมากมาย :(
cwap

คุณควรทำการแปล Exception (เป็น Exception type ที่เหมาะสมกับระดับ Abstraction) ใน catch block ของคุณ FYI: "Multi-catch" คาดว่าจะเข้าสู่ Java 7
jasonnerothin

FYI: ใน C ++ คุณสามารถใส่หลาย ๆ ตัวหลังจากพยายามจับข้อยกเว้นที่แตกต่างกัน
RobH

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

25

ปฏิกิริยาแรกต่อคำตอบมากมาย:

คุณกำลังเขียนให้โปรแกรมเมอร์และหลักการของความประหลาดใจน้อยที่สุด

แน่นอน! แต่ถ้าไม่ชัดเจนกว่าตลอดเวลา

มันไม่น่าประหลาดใจเช่น: แบ่ง (1 / x) จับ (DivisionByZero) ชัดเจนกว่าถ้าฉัน (ที่ Conrad และอื่น ๆ ) ความจริงที่ว่าการเขียนโปรแกรมประเภทนี้ไม่ได้คาดหวังว่าจะเป็นเรื่องธรรมดา แต่ก็ยังมีความเกี่ยวข้อง บางทีในตัวอย่างของฉันถ้าจะชัดเจนกว่า

แต่ DivisionByZero และ FileNotFound สำหรับเรื่องนั้นชัดเจนกว่า ifs

แน่นอนว่าถ้ามันมีประสิทธิภาพน้อยกว่าและต้องการเวลา zillion ต่อวินาทีแน่นอนว่าคุณควรหลีกเลี่ยง แต่ฉันก็ยังไม่ได้อ่านเหตุผลที่ดีเพื่อหลีกเลี่ยงการออกแบบโดยรวม

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

ข้อยกเว้นสำหรับสถานการณ์ปกติคุณจะค้นหาสถานการณ์ที่ผิดปกติ (เช่นพิเศษ) ได้อย่างไร

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

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


1
1) คุณตรวจสอบxก่อนโทรเสมอ1/xหรือไม่? 2) คุณห่อทุกส่วนของการดำเนินการไว้ในบล็อก try-catch เพื่อดักจับDivideByZeroExceptionหรือไม่? 3) สิ่งที่ตรรกะใดที่คุณใส่ลงไปในการป้องกันการจับการกู้คืนจากDivideByZeroException?
Lightman

1
ยกเว้น DivisionByZero และ FileNotFound เป็นตัวอย่างที่ไม่ดีเนื่องจากเป็นกรณีพิเศษที่ควรถือว่าเป็นข้อยกเว้น
0x6C38

ไม่มีอะไร "พิเศษสุด ๆ " เกี่ยวกับไฟล์ที่ไม่พบในลักษณะที่มากกว่าคน "ต่อต้านข้อยกเว้น" ที่นี่กำลังโน้มน้าว openConfigFile();สามารถตามด้วย FileNotFound ที่ถูกดักจับด้วย{ createDefaultConfigFile(); setFirstAppRun(); }FileNotFound ไม่ผิดพลาดมาทำให้ประสบการณ์ของผู้ใช้ดีขึ้นไม่แย่ลง คุณอาจพูดว่า "แต่ถ้านี่ไม่ใช่การวิ่งครั้งแรกจริง ๆ และพวกเขาได้สิ่งนั้นทุกครั้ง" อย่างน้อยแอพจะทำงานทุกครั้งและไม่หยุดทำงานทุกครั้งที่เริ่มต้น! ใน 1 ถึง 10 "นี่แย่มาก": "First-run" ทุกการเริ่มต้น = 3 หรือ 4 ผิดพลาดทุกการเริ่มต้น = 10
Loduwijk

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

16

ก่อนหน้าข้อยกเว้นใน C มีอยู่setjmpและlongjmpสามารถใช้เพื่อให้ได้สแต็กเฟรมที่คล้ายกัน

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

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

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

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

การใช้งานที่ชื่นชอบคือลำดับของthrow new Success()รหัสที่มีความยาวซึ่งพยายามทำสิ่งหนึ่งหลังจากที่อื่น ๆ จนกว่าจะพบสิ่งที่มันกำลังมองหา แต่ละสิ่ง - ตรรกะแต่ละชิ้น - อาจมีการทำรังแบบอาร์บิทรัลดังนั้นจึงbreakออกมาเหมือนกับการทดสอบเงื่อนไขใด ๆ if-elseรูปแบบเปราะ หากฉันแก้ไขelseหรือทำให้ไวยากรณ์ผิดพลาดอย่างอื่นแสดงว่ามีข้อผิดพลาดแบบขน

การใช้การไหลของรหัสเป็นthrow new Success() เชิงเส้น ฉันใช้Successคลาสที่กำหนดไว้ในท้องถิ่น- ตรวจสอบแน่นอน - ดังนั้นถ้าฉันลืมที่จะจับมันรหัสจะไม่รวบรวม และฉันไม่จับวิธีการอื่นSuccessES

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

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

JVM หรือคอมไพเลอร์ใดที่เกี่ยวข้องกับฉันน้อยกว่าเว้นแต่มีฮอตสปอต ฉันไม่สามารถเชื่อได้ว่ามีเหตุผลพื้นฐานใด ๆ ที่คอมไพเลอร์จะไม่ตรวจพบการโยนทิ้งในพื้นที่และจับข้อยกเว้นและเพียงแค่ปฏิบัติต่อพวกมันอย่างมีประสิทธิภาพgotoในระดับรหัสเครื่อง

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

โดยส่วนตัวฉันไม่ได้ใช้รูปแบบข้ามเฟรมสแต็คและฉันสามารถดูได้ว่ามันต้องใช้ความซับซ้อนในการออกแบบอย่างไร แต่ใช้เท่าที่ควรก็จะดี

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


3
การใช้รูปแบบนี้ฟังก์ชั่นหยาบของฉันส่วนใหญ่มีการจับเล็กน้อยสองครั้งที่สิ้นสุด - หนึ่งสำหรับความสำเร็จและอีกหนึ่งสำหรับความล้มเหลวและนั่นเป็นที่ที่ฟังก์ชันสรุปสิ่งต่าง ๆ เช่นการเตรียมการตอบสนอง servlet ที่ถูกต้องหรือเตรียมค่าตอบแทน มีสถานที่เดียวที่จะทำห่อเป็นสิ่งที่ดี returnทางเลือก -pattern จะต้องมีสองฟังก์ชั่นสำหรับทุกฟังก์ชั่นดังกล่าว ตัวนอกเพื่อเตรียมการตอบสนองของ servlet หรือการกระทำอื่น ๆ และตัวที่อยู่ภายในเพื่อทำการคำนวณ PS: อาจารย์ภาษาอังกฤษอาจจะแนะนำให้ฉันใช้ "น่าพิศวง" มากกว่า "น่าประหลาดใจ" ใน para ล่าสุด :-)
หมอผี

11

anwser มาตรฐานคือข้อยกเว้นไม่ปกติและควรใช้ในกรณีพิเศษ

เหตุผลหนึ่งที่สำคัญสำหรับฉันคือเมื่อฉันอ่านtry-catchโครงสร้างการควบคุมในซอฟต์แวร์ที่ฉันบำรุงรักษาหรือดีบั๊กฉันพยายามค้นหาสาเหตุที่ coder ดั้งเดิมใช้การจัดการข้อยกเว้นแทนif-elseโครงสร้าง และฉันคาดหวังว่าจะได้รับคำตอบที่ดี

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


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

9

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


8

Josh Bloch เกี่ยวข้องกับหัวข้อนี้อย่างกว้างขวางใน Java ที่มีประสิทธิภาพ ข้อเสนอแนะของเขากำลังส่องสว่างและควรใช้กับ. Net เช่นกัน (ยกเว้นรายละเอียด)

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

ตัวอย่างเช่นวิธีที่สองนั้นใช้ง่ายกว่าวิธีแรก:

/**
 * Adds two positive numbers.
 *
 * @param addend1 greater than zero
 * @param addend2 greater than zero
 * @throws AdditionException if addend1 or addend2 is less than or equal to zero
 */
int addPositiveNumbers(int addend1, int addend2) throws AdditionException{
  if( addend1 <= 0 ){
     throw new AdditionException("addend1 is <= 0");
  }
  else if( addend2 <= 0 ){
     throw new AdditionException("addend2 is <= 0");
  }
  return addend1 + addend2;
}

/**
 * Adds two positive numbers.
 *
 * @param addend1 greater than zero
 * @param addend2 greater than zero
 */
public int addPositiveNumbers(int addend1, int addend2) {
  if( addend1 <= 0 ){
     throw new IllegalArgumentException("addend1 is <= 0");
  }
  else if( addend2 <= 0 ){
     throw new IllegalArgumentException("addend2 is <= 0");
  }
  return addend1 + addend2;
}

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

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

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

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


7

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

วิธีที่ดีที่สุดในการลดค่าใช้จ่ายในการสร้างข้อยกเว้นคือการแทนที่เมธอด fillInStackTrace () ดังนี้:

public Throwable fillInStackTrace() { return this; }

ข้อยกเว้นดังกล่าวจะไม่มีการเติมสแต็ค


Stacktrace ยังต้องการให้ผู้เรียก "รู้เกี่ยวกับ" (เช่นมีการพึ่งพา) Throwables ทั้งหมดในสแต็ก นี่คือสิ่งที่ไม่ดี โยนข้อยกเว้นที่เหมาะสมกับระดับของสิ่งที่เป็นนามธรรม (ServiceExceptions ในบริการ, DaoExceptions จากเมธอด Dao เป็นต้น) เพียงแปลถ้าจำเป็น
jasonnerothin

5

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

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


5

นี่คือแนวทางปฏิบัติที่ดีที่สุดที่ฉันอธิบายไว้ในโพสต์บล็อกของฉัน:

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

4

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


4

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

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


1
ฉันจัดการแล้วว่าหนึ่งที่สูงขึ้น: การดีบั๊กในข้อยกเว้นทั้งหมด: เหมือนกันนั่นเพิ่งเสร็จสิ้นเพราะการออกแบบที่จะไม่ใช้ข้อยกเว้นเป็นเรื่องปกติ คำถามของฉันคือ: ทำไมมันเป็นเรื่องธรรมดาในสถานที่แรก?
Peter

โดยทั่วไปแล้วคำตอบของคุณคือ "ไม่ดีเพราะ Visual Studio มีคุณลักษณะนี้ ... "? ฉันเขียนโปรแกรมมาประมาณ 20 ปีแล้วและฉันก็ไม่ได้สังเกตด้วยซ้ำว่ามีตัวเลือก "หยุดข้อยกเว้นทั้งหมด" ยัง "เพราะคุณสมบัตินี้!" ฟังดูเหมือนเหตุผลที่อ่อนแอ เพียงติดตามข้อยกเว้นไปยังแหล่งที่มา หวังว่าคุณกำลังใช้ภาษาที่ทำให้ง่าย - มิฉะนั้นปัญหาของคุณคือคุณสมบัติภาษาไม่ใช่การใช้ข้อยกเว้นทั่วไป
Loduwijk

3

สมมติว่าคุณมีวิธีที่ใช้ในการคำนวณ มีพารามิเตอร์อินพุตจำนวนมากที่ต้องตรวจสอบความถูกต้องจากนั้นส่งคืนตัวเลขที่มากกว่า 0

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

ฉันจำได้จาก C วันของฉันฟังก์ชั่นจำนวนมากส่งกลับรหัสข้อผิดพลาดเช่นนี้:

-1 - x lesser then MinX
-2 - x greater then MaxX
-3 - y lesser then MinY

เป็นต้น

มันอ่านได้น้อยลงแล้วขว้างและจับข้อยกเว้น?


นั่นเป็นเหตุผลที่พวกเขาคิดค้น enums :) แต่ตัวเลขที่มีมนต์ขลังเป็นเรื่องที่แตกต่างอย่างสิ้นเชิง .. en.wikipedia.org/wiki/...
Isak โว

ตัวอย่างที่ดี ฉันกำลังจะเขียนสิ่งเดียวกัน @IsakSavo: Enums ไม่เป็นประโยชน์ในสถานการณ์นี้หากคาดว่าวิธีการคืนค่าความหมายหรือวัตถุ เช่น getAccountBalance () ควรส่งคืนออบเจกต์ Money ไม่ใช่ออบเจกต์ AccountBalanceResultEnum โปรแกรม C จำนวนมากมีรูปแบบที่คล้ายคลึงกันโดยที่หนึ่งค่า Sentinel (0 หรือ null) แสดงถึงข้อผิดพลาดและจากนั้นคุณต้องเรียกใช้ฟังก์ชันอื่นเพื่อรับรหัสข้อผิดพลาดแยกต่างหากเพื่อระบุสาเหตุที่เกิดข้อผิดพลาด (MySQL C API เป็นเช่นนี้)
Mark E. Haase

3

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

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

กล่าวว่าวิธีอื่น: เพียงเพราะคุณสามารถไม่ได้หมายความว่าคุณควร


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

1
@Val: ข้อยกเว้นสำหรับสถานการณ์พิเศษ - หากเราตรวจพบมากพอที่จะโยนข้อยกเว้นและจัดการกับมันเรามีข้อมูลเพียงพอที่จะไม่โยนมันและยังคงจัดการมัน เราสามารถตรงไปที่ตรรกะการจัดการและข้ามลอง / จับที่ฟุ่มเฟือยราคาแพง
ไบรอันวัตส์

โดยตรรกะนั้นคุณอาจไม่มีข้อยกเว้นและออกจากระบบแทนการทิ้งข้อยกเว้น หากคุณต้องการทำอะไรก่อนออกจากนั้นให้ทำเสื้อคลุมและเรียกสิ่งนั้น ตัวอย่าง Java: public class ExitHelper{ public static void cleanExit() { cleanup(); System.exit(1); } }จากนั้นเรียกสิ่งนั้นว่าแทนที่จะขว้าง: ExitHelper.cleanExit();ถ้าการโต้เถียงของคุณดีขึ้นสิ่งนี้จะเป็นแนวทางที่ต้องการและจะไม่มีข้อยกเว้น คุณมักจะพูดว่า "เหตุผลเดียวสำหรับข้อยกเว้นคือการชนในลักษณะที่ต่างออกไป"
Loduwijk

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

@BryanWatts ตอบรับแล้ว อื่น ๆ อีกมากมายได้บอกว่าคุณควรจะใช้ข้อยกเว้นสำหรับสิ่งที่คุณไม่สามารถกู้คืนจากและโดยการขยายควรจะ crashing ในข้อยกเว้น นี่คือเหตุผลที่มันยากที่จะพูดคุยเกี่ยวกับสิ่งเหล่านี้; มีไม่เพียง 2 ความคิดเห็น แต่มีหลายคน ฉันยังคงไม่เห็นด้วยกับคุณ แต่ไม่รุนแรง มีบางครั้งที่การขว้างปา / จับเข้าด้วยกันเป็นโค้ดที่อ่านง่ายและสามารถบำรุงรักษาได้ดีที่สุด นี้มักจะเกิดขึ้นถ้าคุณมีอยู่แล้วจับข้อยกเว้นอื่น ๆ เพื่อให้คุณมีอยู่แล้วลอง / จับและการเพิ่ม 1 หรือ 2 ifจับมากขึ้นคือสะอาดกว่าการตรวจสอบข้อผิดพลาดที่แยกจากกันโดย
Loduwijk

3

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

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

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

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

  • พบค่าการผูกมากเกินไป ฐานข้อมูลบางตัวไม่สนับสนุนจำนวนการผูกค่าในงบ SQL ของตน (SQLite: 999, Ingres 10.1.0: 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] [5] กล่าวไว้ในคำตอบของเขา:

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

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

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


2

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

โดยทั่วไปถ้าคุณรู้ว่ามีความน่าจะเป็นสูงที่คุณสามารถตรวจสอบได้ ... คุณควรตรวจสอบ ... เช่นถ้า (obj! = null) obj.method ()

ในกรณีของคุณฉันไม่คุ้นเคยกับห้องสมุด C # มากพอที่จะทราบว่าเวลาของวันที่มีวิธีที่ง่ายในการตรวจสอบว่าการประทับเวลานั้นเกินขอบเขตหรือไม่ ถ้าเป็นเช่นนั้นเพียงแค่เรียกถ้า (.isvalid (ts)) มิฉะนั้นโค้ดของคุณก็ใช้ได้

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


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

2

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

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

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

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

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

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


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

2

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

นี่ไม่ได้มีอะไรเกี่ยวข้องกับการปฏิบัติที่ดีที่สุดในภาษาอื่น ๆ แต่มันแสดงให้คุณเห็นว่าคุณสามารถทำอะไรได้บ้างกับแนวคิดการออกแบบในทิศทางที่คุณกำลังคิด

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


2

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

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

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


2

เหตุผลหนึ่งที่สวยงาม:

การลองมาพร้อมกับการจับเสมอในขณะที่ถ้าไม่จำเป็นต้องมาพร้อมกับสิ่งอื่น

if (PerformCheckSucceeded())
   DoSomething();

ด้วยความพยายาม / จับมันจะกลายเป็น verbose มากขึ้น

try
{
   PerformCheckSucceeded();
   DoSomething();
}
catch
{
}

มีรหัส 6 บรรทัดมากเกินไป


1

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


1

ฉันรู้สึกว่าไม่มีอะไรผิดปกติกับตัวอย่างของคุณ ในทางตรงกันข้ามมันจะเป็นบาปที่จะเพิกเฉยต่อข้อยกเว้นที่เกิดจากฟังก์ชั่นที่เรียก

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


ขออภัยนี่ผิดธรรมดา Brann มันขึ้นอยู่กับสภาพ ไม่ใช่กรณีที่เงื่อนไขเล็กน้อย ดังนั้นคำสั่ง if อาจใช้เวลาหลายชั่วโมงหรือหลายวันหรือนานกว่านั้น
Ingo

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

1
Ingo: สถานการณ์พิเศษคือสถานการณ์ที่คุณไม่คาดคิด คือคนที่คุณไม่ได้คิดว่าเป็นโปรแกรมเมอร์ ดังนั้นกฎของฉันคือ "เขียนโค้ดที่ไม่โยนข้อยกเว้น" :)
บราน

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

1
ฉันเห็นด้วยกับคุณที่จะไม่โยนข้อยกเว้นอย่างดุเดือด แต่แน่นอนว่า "พิเศษ" คือคำถามของคำนิยาม String.parseDouble นั้นจะส่งข้อยกเว้นหากไม่สามารถส่งผลลัพธ์ที่เป็นประโยชน์ตัวอย่างเช่นอยู่ในลำดับ มันควรทำอะไรอีก? กลับมาที่ NaN? ฮาร์ดแวร์ที่ไม่ใช่ของ IEEE ล่ะ
Ingo

1

มีกลไกทั่วไปบางอย่างที่ภาษาสามารถอนุญาตให้วิธีการออกโดยไม่คืนค่าและคลายไปที่บล็อก "catch" ถัดไป:

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

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

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

        adr lr,next_instr
        b subroutine
        b handle_exception
    next_instr:
    

จะออกจากปกติ, ย่อยก็จะทำbx lrหรือpop {pc}; ในกรณีที่มีทางออกผิดปกติรูทีนย่อยจะลบ 4 จาก LR ก่อนที่จะดำเนินการส่งคืนหรือใช้sub lr,#4,pc (ขึ้นอยู่กับรูปแบบ ARM โหมดการดำเนินการ ฯลฯ ) วิธีการนี้จะทำงานผิดปกติอย่างมากหากผู้โทรไม่ได้ออกแบบมาเพื่อรองรับ

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

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