คุณชอบข้อยกเว้นหรือรหัสส่งคืนข้อใดและเพราะเหตุใด


92

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

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

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


1
ปัญหาไก่หรือไข่จากโลกแห่งซอฟต์แวร์ ... เป็นที่ถกเถียงกันชั่วนิรันดร์ :)
Gishu

ขออภัยด้วย แต่หวังว่าการมีความคิดเห็นที่หลากหลายจะช่วยให้ผู้คน (รวมถึงตัวฉันเอง) เลือกได้อย่างเหมาะสม
Robert Gould

คำตอบ:


107

สำหรับบางภาษา (เช่น C ++) ทรัพยากรรั่วไหลไม่ควรมีเหตุผล

C ++ ขึ้นอยู่กับ RAII

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

รหัสส่งคืนมีรายละเอียดมากกว่า

มีลักษณะละเอียดและมีแนวโน้มที่จะพัฒนาเป็นสิ่งที่ชอบ:

if(doSomething())
{
   if(doSomethingElse())
   {
      if(doSomethingElseAgain())
      {
          // etc.
      }
      else
      {
         // react to failure of doSomethingElseAgain
      }
   }
   else
   {
      // react to failure of doSomethingElse
   }
}
else
{
   // react to failure of doSomething
}

ในท้ายที่สุดรหัสของคุณคือชุดคำสั่งที่ระบุ (ฉันเห็นรหัสประเภทนี้ในรหัสการผลิต)

รหัสนี้สามารถแปลเป็น:

try
{
   doSomething() ;
   doSomethingElse() ;
   doSomethingElseAgain() ;
}
catch(const SomethingException & e)
{
   // react to failure of doSomething
}
catch(const SomethingElseException & e)
{
   // react to failure of doSomethingElse
}
catch(const SomethingElseAgainException & e)
{
   // react to failure of doSomethingElseAgain
}

ซึ่งการประมวลผลรหัสและข้อผิดพลาดที่แยกจากกันอย่างหมดจดซึ่งอาจเป็นสิ่งที่ดี

รหัสส่งคืนมีความเปราะมากขึ้น

หากไม่มีคำเตือนที่คลุมเครือจากคอมไพเลอร์หนึ่งตัว (ดูความคิดเห็นของ "phjr") ก็สามารถเพิกเฉยได้อย่างง่ายดาย

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

ข้อผิดพลาดจะไม่ถูกละเว้น บางครั้งคุณต้องการให้มันไม่ระเบิด แต่ ... ดังนั้นคุณต้องเลือกอย่างระมัดระวัง

บางครั้งต้องแปลรหัสย้อนกลับ

สมมติว่าเรามีฟังก์ชันดังต่อไปนี้:

  • doSomething ซึ่งสามารถส่งคืน int ที่เรียกว่า NOT_FOUND_ERROR
  • doSomethingElse ซึ่งสามารถส่งคืนบูล "เท็จ" (สำหรับล้มเหลว)
  • doSomethingElseAgain ซึ่งสามารถส่งคืนวัตถุข้อผิดพลาด (มีทั้ง __LINE__, __FILE__ และตัวแปรสแต็กครึ่งหนึ่ง
  • doTryToDoSomethingWithAllThisMess ซึ่งดี ... ใช้ฟังก์ชันข้างต้นและส่งคืนรหัสข้อผิดพลาดประเภท ...

ประเภทของการกลับมาของ doTryToDoSomethingWithAllThisMess ถ้าฟังก์ชันที่เรียกว่าล้มเหลว

Return Codes ไม่ใช่โซลูชันสากล

ตัวดำเนินการไม่สามารถส่งคืนรหัสข้อผิดพลาด ตัวสร้าง C ++ ก็ทำไม่ได้เช่นกัน

Return Codes หมายความว่าคุณไม่สามารถเชื่อมโยงนิพจน์ได้

ข้อพิสูจน์ของจุดข้างต้น ถ้าฉันต้องการเขียน:

CMyType o = add(a, multiply(b, c)) ;

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

พิมพ์ข้อยกเว้น

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

จากนั้นการจับแต่ละครั้งสามารถเชี่ยวชาญได้

อย่าใช้การจับ (... ) โดยไม่ขว้างซ้ำ

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

ข้อยกเว้นคือ ... NUKE

ปัญหายกเว้นคือการใช้มากเกินไปจะสร้างรหัสที่เต็มไปด้วยการลอง / จับ แต่ปัญหาอยู่ที่อื่น: ใครลอง / จับรหัสของเขา / เธอโดยใช้ STL container? อย่างไรก็ตามคอนเทนเนอร์เหล่านั้นสามารถส่งข้อยกเว้นได้

แน่นอนว่าใน C ++ อย่าปล่อยให้ข้อยกเว้นออกจากตัวทำลาย

ข้อยกเว้นคือ ... ซิงโครนัส

อย่าลืมจับพวกมันก่อนที่จะดึงด้ายของคุณออกมาบนหัวเข่าหรือเผยแพร่ภายในลูปข้อความ Windows ของคุณ

วิธีแก้ปัญหาอาจผสมพวกเขา?

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

คำถามเดียวคือ "อะไรคือสิ่งที่ไม่ควรเกิดขึ้น"

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

อีกวิธีหนึ่งคือการแสดงข้อผิดพลาด

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

ในงานของฉันเราใช้คำว่า "Assert" ขึ้นอยู่กับค่าของไฟล์คอนฟิกูเรชันไม่ว่าอ็อพชันคอมไพล์ debug / release:

  • บันทึกข้อผิดพลาด
  • เปิดกล่องข้อความพร้อมกับข้อความ "เฮ้คุณมีปัญหา"
  • เปิดกล่องข้อความพร้อมกับข้อความ "เฮ้คุณมีปัญหาคุณต้องการแก้ไขข้อบกพร่องหรือไม่"

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

เพิ่มลงในรหัสเดิมได้อย่างง่ายดาย ตัวอย่างเช่น:

void doSomething(CMyObject * p, int iRandomData)
{
   // etc.
}

นำไปสู่ประเภทของรหัสที่คล้ายกับ:

void doSomething(CMyObject * p, int iRandomData)
{
   if(iRandomData < 32)
   {
      MY_RAISE_ERROR("Hey, iRandomData " << iRandomData << " is lesser than 32. Aborting processing") ;
      return ;
   }

   if(p == NULL)
   {
      MY_RAISE_ERROR("Hey, p is NULL !\niRandomData is equal to " << iRandomData << ". Will throw.") ;
      throw std::some_exception() ;
   }

   if(! p.is Ok())
   {
      MY_RAISE_ERROR("Hey, p is NOT Ok!\np is equal to " << p->toString() << ". Will try to continue anyway") ;
   }

   // etc.
}

(ฉันมีมาโครที่คล้ายกันซึ่งใช้งานได้เฉพาะในการดีบักเท่านั้น)

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

สรุป

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

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

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


3
ใช่ แต่เกี่ยวกับตัวอย่างแรกของคุณซึ่งสามารถเขียนได้ง่าย ๆ ว่า if( !doSomething() ) { puts( "ERROR - doSomething failed" ) ; return ; // or react to failure of doSomething } if( !doSomethingElse() ) { // react to failure of doSomethingElse() }
bobobobo

ทำไมไม่ ... แต่แล้วฉันยังพบว่าdoSomething(); doSomethingElse(); ...ดีกว่าเพราะถ้าฉันต้องการเพิ่ม if / while / etc คำสั่งสำหรับวัตถุประสงค์การดำเนินการปกติฉันไม่ต้องการให้ผสมกับ if / while / etc ข้อความที่เพิ่มเพื่อจุดประสงค์พิเศษ ... และตามกฎที่แท้จริงเกี่ยวกับการใช้ข้อยกเว้นคือการโยนไม่ใช่เพื่อจับคำสั่ง try / catch มักจะไม่รุกราน
paercebal

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

2
@Peter Weber: มันไม่แปลก แยกออกจากปัญหาจริงเนื่องจากไม่ได้เป็นส่วนหนึ่งของขั้นตอนการดำเนินการตามปกติ เป็นการดำเนินการที่ยอดเยี่ยม จากนั้นอีกครั้งประเด็นเกี่ยวกับข้อยกเว้นคือในกรณีที่มีข้อผิดพลาดพิเศษให้โยนบ่อยครั้งและไม่ค่อยจับถ้าเคย ดังนั้นบล็อก catch แทบจะไม่ปรากฏในโค้ดด้วยซ้ำ
paercebal

ตัวอย่างในการอภิปรายประเภทนี้ง่ายมาก โดยปกติแล้ว "doSomething ()" หรือ "doSomethingElse ()" จะทำหน้าที่บางอย่างเช่นการเปลี่ยนสถานะของวัตถุบางอย่าง รหัสข้อยกเว้นไม่รับประกันการส่งคืนวัตถุไปยังสถานะก่อนหน้าและแม้แต่น้อยกว่าเมื่อสิ่งที่จับได้อยู่ไกลจากการโยนมาก ... ตัวอย่างเช่นสมมติว่า doSomething ถูกเรียกสองครั้งและเพิ่มตัวนับก่อนที่จะโยน คุณจะรู้ได้อย่างไรว่าเมื่อจับข้อยกเว้นได้ว่าคุณควรลดลงครั้งหรือสองครั้ง? โดยทั่วไปการเขียนรหัสข้อยกเว้นที่ปลอดภัยสำหรับสิ่งที่ไม่ใช่ตัวอย่างของเล่นนั้นยากมาก (เป็นไปไม่ได้?)
xryl669

36

ฉันใช้ทั้งสองอย่างจริง

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

มีการใช้ข้อยกเว้นสำหรับสิ่งที่ฉันไม่คาดคิดเท่านั้น


+1 สำหรับวิธีง่ายๆ อย่างน้อยมันสั้นพอที่จะอ่านได้เร็วมาก :-)
Ashish Gupta

1
" มีการใช้ข้อยกเว้นสำหรับสิ่งที่ฉันไม่คาดคิดเท่านั้น " หากคุณไม่ได้คาดหวังสิ่งเหล่านี้แล้วทำไมคุณถึงใช้มันได้อย่างไร?
anar khalilov

5
เพียงเพราะฉันไม่ได้คาดหวังว่ามันจะเกิดขึ้นไม่ได้หมายความว่าฉันมองไม่เห็นว่ามันจะเป็นไปได้อย่างไร ฉันคาดว่า SQL Server ของฉันจะเปิดและตอบสนอง แต่ฉันยังคงตั้งรหัสความคาดหวังของฉันเพื่อที่ฉันจะได้ล้มเหลวอย่างสง่างามหากเกิดการหยุดทำงานที่ไม่คาดคิด
Stephen Wrighton

SQL Server ที่ไม่ตอบสนองจะไม่สามารถจัดหมวดหมู่ได้อย่างง่ายดายภายใต้ "ข้อผิดพลาดที่ทราบและเป็นไปได้" หรือไม่?
tkburbidge

21

ตามบทที่ 7 หัวข้อ "ข้อยกเว้น" ในแนวทางการออกแบบกรอบ: อนุสัญญาสำนวนและรูปแบบสำหรับไลบรารี. NET ที่นำกลับมาใช้ใหม่ได้มีการระบุเหตุผลจำนวนมากว่าเหตุใดการใช้ข้อยกเว้นมากกว่าค่าที่ส่งคืนจึงจำเป็นสำหรับกรอบ OO เช่น C #

บางทีนี่อาจเป็นเหตุผลที่น่าสนใจที่สุด (หน้า 179):

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


10

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

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


2
โปรดจำไว้ว่าการใช้ข้อยกเว้นทำให้การวิเคราะห์โปรแกรมยากขึ้น
Paweł Hajdan

7

ควรส่งคืนข้อยกเว้นในกรณีที่มีบางสิ่งเกิดขึ้นที่คุณไม่คาดคิดเท่านั้น

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

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

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


6

ใน Java ฉันใช้ (ตามลำดับต่อไปนี้):

  1. ออกแบบตามสัญญา (ตรวจสอบเงื่อนไขเบื้องต้นก่อนที่จะลองทำสิ่งใดที่อาจล้มเหลว) สิ่งนี้จับสิ่งต่างๆได้เกือบทั้งหมดและฉันส่งคืนรหัสข้อผิดพลาดสำหรับสิ่งนี้

  2. การส่งคืนรหัสข้อผิดพลาดในขณะที่กำลังประมวลผล (และดำเนินการย้อนกลับหากจำเป็น)

  3. ยกเว้น แต่เหล่านี้จะใช้เฉพาะสำหรับสิ่งที่ไม่คาดคิด


1
การใช้คำยืนยันสำหรับสัญญาจะไม่ถูกต้องกว่านี้หรือ? หากผิดสัญญาไม่มีอะไรจะช่วยคุณได้
Paweł Hajdan

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

และสิบสองปีในการตอบคำถามของคุณ ฉันควรเรียกใช้แผนกช่วยเหลือ :-)
paxdiablo

6

รหัสส่งคืนล้มเหลวในการทดสอบ " หลุมแห่งความสำเร็จ " สำหรับฉันเกือบทุกครั้ง

  • มันง่ายเกินไปที่จะลืมตรวจสอบรหัสส่งคืนจากนั้นจึงมีข้อผิดพลาดเรดแฮร์ริ่งในภายหลัง
  • รหัสส่งคืนไม่มีข้อมูลการดีบักที่ดีเช่น call stack ข้อยกเว้นภายใน
  • โค้ดส่งคืนไม่แพร่กระจายซึ่งพร้อมกับจุดข้างต้นมีแนวโน้มที่จะขับเคลื่อนการบันทึกการวินิจฉัยที่มากเกินไปและเชื่อมโยงกันแทนที่จะบันทึกในที่เดียวที่รวมศูนย์ (ตัวจัดการข้อยกเว้นระดับแอปพลิเคชันและเธรด)
  • รหัสส่งคืนมีแนวโน้มที่จะผลักดันโค้ดที่ยุ่งเหยิงในรูปแบบของบล็อก 'if' ที่ซ้อนกัน
  • เวลาที่นักพัฒนาใช้ในการแก้ไขข้อบกพร่องที่ไม่ทราบสาเหตุซึ่งอาจเป็นข้อยกเว้นที่ชัดเจน (หลุมแห่งความสำเร็จ) นั้นมีราคาแพง
  • หากทีมที่อยู่เบื้องหลัง C # ไม่ได้ตั้งใจให้มีข้อยกเว้นเพื่อควบคุมโฟลว์การควบคุมจะไม่มีการพิมพ์ execeptions จะไม่มีตัวกรอง "เมื่อ" ในคำสั่ง catch และไม่จำเป็นต้องมีคำสั่ง 'throw' ที่ไม่มีพารามิเตอร์ .

เกี่ยวกับประสิทธิภาพ:

  • ข้อยกเว้นอาจมีราคาแพงในเชิงคำนวณที่เกี่ยวข้องกับการไม่ขว้างเลย แต่พวกเขาเรียกว่าข้อยกเว้นด้วยเหตุผล การเปรียบเทียบความเร็วมักจะถือว่าอัตรายกเว้น 100% ซึ่งไม่ควรเป็นเช่นนั้น แม้ว่าข้อยกเว้นจะช้ากว่า 100 เท่า แต่สิ่งนั้นสำคัญแค่ไหนหากเกิดขึ้นเพียง 1% ของเวลา
  • เว้นแต่เราจะพูดถึงเลขคณิตลอยตัวสำหรับแอปพลิเคชันกราฟิกหรือสิ่งที่คล้ายกันรอบของ CPU จะถูกเมื่อเทียบกับเวลาของนักพัฒนา
  • ต้นทุนจากมุมมองของเวลามีอาร์กิวเมนต์เดียวกัน เมื่อเทียบกับการสืบค้นฐานข้อมูลหรือการเรียกใช้บริการเว็บหรือการโหลดไฟล์เวลาปกติของแอปพลิเคชันจะลดเวลายกเว้นลง ข้อยกเว้นอยู่ในระดับย่อยของไมโครวินาทีในปี 2549
    • ฉันกล้าทุกคนที่ทำงานใน. net เพื่อตั้งค่าดีบักเกอร์ของคุณให้ทำลายข้อยกเว้นทั้งหมดและปิดใช้งานเพียงรหัสของฉันและดูว่ามีข้อยกเว้นเกิดขึ้นมากมายที่คุณไม่รู้ด้วยซ้ำ

4

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


4
คุณกำลังตีความผิด สิ่งที่พวกเขาหมายถึงคือ "ถ้าโปรแกรมของคุณมีข้อยกเว้นในขั้นตอนปกติแสดงว่าผิด" กล่าวอีกนัยหนึ่ง "ใช้เฉพาะข้อยกเว้นสำหรับสิ่งพิเศษเท่านั้น"
Paweł Hajdan

4

ฉันเขียนบล็อกโพสต์เกี่ยวกับเรื่องนี้เมื่อไม่นานมานี้

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


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

ฉันเห็นด้วยอย่างยิ่ง ดูเหมือนว่าฉันได้เรียนรู้สิ่งหนึ่งหรือสองสิ่งในเก้าปีที่ผ่านมา;)
โทมัส

4

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

CRetType obReturn = CODE_SUCCESS;
obReturn = CallMyFunctionWhichReturnsCodes();
if (obReturn == CODE_BLOW_UP)
{
  // bail out
  goto FunctionExit;
}

ในไม่ช้าการเรียกเมธอดซึ่งประกอบด้วยการเรียกฟังก์ชัน 4 สายจะขยายตัวขึ้นพร้อมกับการจัดการข้อผิดพลาด 12 บรรทัด .. ซึ่งบางอย่างจะไม่เกิดขึ้น ถ้าและสลับกรณีมาก

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

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

ฉันได้ตามล่าเล็กน้อยเกี่ยวกับการตอบโต้ 'การลงโทษประสิทธิภาพ' .. เพิ่มเติมในโลก C ++ / COM แต่ในภาษาที่ใหม่กว่าฉันคิดว่าความแตกต่างไม่มากนัก ไม่ว่าในกรณีใดเมื่อมีบางสิ่งบางอย่างเกิดขึ้นความกังวลด้านประสิทธิภาพจะลดลงไปที่ backburner :)


3

ด้วยข้อยกเว้นของคอมไพเลอร์หรือสภาพแวดล้อมรันไทม์ที่เหมาะสมจะไม่ก่อให้เกิดโทษอย่างมีนัยสำคัญ มันมากหรือน้อยเหมือนคำสั่ง GOTO ที่ข้ามไปที่ตัวจัดการข้อยกเว้น นอกจากนี้การมีข้อยกเว้นที่ถูกจับโดยสภาพแวดล้อมรันไทม์ (เช่น JVM) ช่วยแยกและแก้ไขข้อบกพร่องได้ง่ายขึ้นมาก ฉันจะใช้ NullPointerException ใน Java เหนือ segfault ใน C ทุกวัน


2
ข้อยกเว้นมีราคาแพงมาก พวกเขาต้องเดินไปตามสแต็กเพื่อค้นหาตัวจัดการข้อยกเว้นที่อาจเกิดขึ้น กองนี้เดินไม่ถูก หากมีการสร้างสแต็กแทร็กจะมีราคาแพงกว่าเนื่องจากจะต้องแยกวิเคราะห์สแต็กทั้งหมด
Derek Park

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

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

ฉันยอมรับว่าประโยชน์ในการแก้ไขข้อบกพร่องของข้อยกเว้นมักจะชดเชยต้นทุนด้านประสิทธิภาพ
Derek Park

1
Derak Park ข้อยกเว้นมีราคาแพงเมื่อเกิดขึ้น นี่คือเหตุผลที่พวกเขาไม่ควรใช้มากเกินไป แต่เมื่อไม่เกิดขึ้นก็แทบไม่มีค่าใช้จ่ายใด ๆ
paercebal

3

ฉันมีกฎง่ายๆ:

1) ใช้รหัสส่งคืนสำหรับสิ่งที่คุณคาดหวังว่าผู้โทรของคุณจะตอบสนองทันที

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

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


3

ฉันใช้ข้อยกเว้นใน python ทั้งในสถานการณ์พิเศษและไม่พิเศษ

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

try:
    dataobj = datastore.fetch(obj_id)
except LookupError:
    # could not find object, create it.
    dataobj = datastore.create(....)

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

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

# wrong way:
if os.path.exists(directory_to_remove):
    # race condition is here.
    os.path.rmdir(directory_to_remove)

# right way:
try: 
    os.path.rmdir(directory_to_remove)
except OSError:
    # directory didn't exist, good.
    pass

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


ตัวอย่างที่สองทำให้เข้าใจผิด คิดว่าผิดทางคือผิดเนื่องจากรหัส os.path.rmdir ถูกออกแบบมาเพื่อทิ้งข้อยกเว้น การใช้งานที่ถูกต้องในโฟลว์โค้ดส่งคืนจะเป็น 'if rmdir (... ) == FAILED: pass'
MaR

3

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

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


1
COM wad ออกแบบมาให้ใช้งานได้ในภาษาที่ไม่รองรับข้อยกเว้น
Kevin

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

ฉันไม่เห็นด้วย: VB6 จะบันทึกเฉพาะข้อผิดพลาดสุดท้ายเท่านั้น เมื่อรวมกับ "on error resume next" ที่น่าอับอายคุณจะพลาดต้นตอของปัญหาไปโดยสิ้นเชิงเมื่อคุณเห็น โปรดทราบว่านี่เป็นพื้นฐานของการจัดการข้อผิดพลาดใน Win32 APII (ดูฟังก์ชัน GetLastError)
paercebal

2

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


2

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

รหัสส่งคืนข้อผิดพลาดหากใช้งานหนักอาจทำให้เกิดรหัสที่น่าเกลียดมากหากมีการทดสอบที่คล้ายกับแบบฟอร์มนี้:

if(function(call) != ERROR_CODE) {
    do_right_thing();
}
else {
    handle_error();
}

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


อย่างน้อยใน C / C ++ และ gcc คุณสามารถให้ฟังก์ชันเป็นแอตทริบิวต์ที่จะสร้างคำเตือนเมื่อค่าที่ส่งคืนถูกละเว้น
Paweł Hajdan

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

2

มีเหตุผลหลายประการที่จะชอบข้อยกเว้นมากกว่ารหัสส่งคืน:

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

2

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

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

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

try{
    db.UpdateAll(somevalue);
}
catch (Exception ex) {
    logger.Exception(ex, "UpdateAll method failed");
    throw;
}

ตราบใดที่คุณปล่อยให้ข้อยกเว้นยังคงเป็นฟองก็โอเค อีกตัวอย่างหนึ่งคือ:

try{
    dbHasBeenUpdated = db.UpdateAll(somevalue); // true/false
}
catch (ConnectionException ex) {
    logger.Exception(ex, "Connection failed");
    dbHasBeenUpdated = false;
}

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

เหตุใดการพยายามจับ - ในที่สุดจึงเป็นรูปแบบการต่อต้าน? นี่คือเหตุผล:

try{
    db.UpdateAll(somevalue);
}
catch (Exception ex) {
    logger.Exception(ex, "UpdateAll method failed");
    throw;
}
finally {
    db.Close();
}

จะเกิดอะไรขึ้นถ้าวัตถุ db ถูกปิดไปแล้ว? ข้อยกเว้นใหม่ถูกโยนทิ้งและต้องจัดการ! ดีกว่า:

try{
    using(IDatabase db = DatabaseFactory.CreateDatabase()) {
        db.UpdateAll(somevalue);
    }
}
catch (Exception ex) {
    logger.Exception(ex, "UpdateAll method failed");
    throw;
}

หรือถ้าออบเจ็กต์ db ไม่ใช้ IDisposable ให้ทำสิ่งนี้:

try{
    try {
        IDatabase db = DatabaseFactory.CreateDatabase();
        db.UpdateAll(somevalue);
    }
    finally{
        db.Close();
    }
}
catch (DatabaseAlreadyClosedException dbClosedEx) {
    logger.Exception(dbClosedEx, "Database connection was closed already.");
}
catch (Exception ex) {
    logger.Exception(ex, "UpdateAll method failed");
    throw;
}

นั่นคือ 2 เซ็นต์ของฉันอยู่ดี! :)


มันจะแปลกถ้าวัตถุมี. Close () แต่ไม่มี. Dispose ()
abatishchev

ฉันใช้เพียง Close () เป็นตัวอย่างเท่านั้น อย่าลังเลที่จะคิดว่าเป็นอย่างอื่น ตามที่ฉันระบุ; ควรใช้รูปแบบการใช้ (doh!) ถ้ามี แน่นอนว่านี่หมายความว่าชั้นเรียนใช้ IDisposable และด้วยเหตุนี้คุณจึงสามารถเรียก Dispose ได้
noocyte

1

ฉันใช้เฉพาะข้อยกเว้นไม่มีรหัสส่งคืน ฉันกำลังพูดถึง Java ที่นี่

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


1

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

void foo()
{
  MyPointer* p = NULL;
  try{
    p = new PointedStuff();
    //I'm a module user and  I'm doing stuff that might throw or not

  }
  catch(...)
  {
    //should I delete the pointer?
  }
}

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


นี่คือคำสั่ง <code> สุดท้าย </code> สำหรับ แต่อนิจจามันไม่อยู่ในมาตรฐาน C ++ ...
โทมัส

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

โทมัสคุณคิดผิด C ++ ยังไม่มีในที่สุดเพราะไม่จำเป็นต้องใช้ มันมี RAII แทน วิธีแก้ปัญหาของ Serge เป็นวิธีแก้ปัญหาหนึ่งโดยใช้ RAII
paercebal

Robert ใช้วิธีแก้ปัญหาของ Serge แล้วคุณจะพบว่าปัญหาของคุณหมดไป ตอนนี้ถ้าคุณเขียน try / catches มากกว่าพ่น (โดยการตัดสินความคิดเห็นของคุณ) บางทีคุณอาจมีปัญหาในรหัสของคุณ แน่นอนว่าการใช้ catch (... ) โดยไม่มีการโยนซ้ำมักจะไม่ดีเนื่องจากจะซ่อนข้อผิดพลาดเพื่อให้เพิกเฉยได้ดีกว่า
paercebal

1

กฎทั่วไปของฉันในข้อยกเว้นกับอาร์กิวเมนต์รหัสส่งคืน:

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

ไม่มีเหตุผลในขณะที่คุณไม่สามารถใช้ข้อยกเว้นเมื่อคุณทำ l10n / i18n ข้อยกเว้นอาจมีข้อมูลที่แปลเป็นภาษาท้องถิ่นเช่นกัน
gizmo

1

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

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

ฉันชอบแนวคิดในการส่งคืนค่า (การแจงนับ?) สำหรับผลลัพธ์และข้อยกเว้นสำหรับกรณีพิเศษ


0

สำหรับภาษาเช่น Java ฉันจะใช้ Exception เพราะคอมไพเลอร์ให้ข้อผิดพลาดเวลาคอมไพล์หากไม่มีการจัดการข้อยกเว้นสิ่งนี้บังคับให้ฟังก์ชันการโทรจัดการ / โยนข้อยกเว้น

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


0

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

แนวทางนี้เป็นเรื่องปกติในภาษา Elixir

# I care whether this succeeds. If it doesn't return :ok, raise an exception.
:ok = File.write(path, content)

# I don't care whether this succeeds. Don't check the return value.
File.write(path, content)

# This had better not succeed - the path should be read-only to me.
# If I get anything other than this error, raise an exception.
{:error, :erofs} = File.write(path, content)

# I want this to succeed but I can handle its failure
case File.write(path, content) do
  :ok => handle_success()
  error => handle_error(error)
end

ผู้คนกล่าวว่าโค้ดส่งคืนอาจทำให้คุณมีifคำสั่งซ้อนกันจำนวนมากแต่สามารถจัดการได้ด้วยไวยากรณ์ที่ดีกว่า ใน Elixir withคำสั่งนี้ช่วยให้เราสามารถแยกชุดของค่าการส่งคืนความสุขจากความล้มเหลวใด ๆ ได้อย่างง่ายดาย

with {:ok, content} <- get_content(),
  :ok <- File.write(path, content) do
    IO.puts "everything worked, happy path code goes here"
else
  # Here we can use a single catch-all failure clause
  # or match every kind of failure individually
  # or match subsets of them however we like
  _some_error => IO.puts "one of those steps failed"
  _other_error => IO.puts "one of those steps failed"
end

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

# Raises a generic MatchError because the return value isn't :ok
:ok = File.write(path, content)

# Raises a File.Error with a descriptive error message - eg, saying
# that the file is read-only
File.write!(path, content)

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

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

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