สำหรับบางภาษา (เช่น 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 ที่ตำแหน่งเชิงกลยุทธ์ที่เลือกไว้ในโค้ด แต่โดยปกติโค้ดของคุณจะเกี่ยวกับ "สิ่งที่ต้องทำ" แล้ว "สิ่งที่ฉันกลัวจะเกิดขึ้น"
แต่เมื่อคุณเขียนโค้ดเลยคุณต้องใช้เครื่องมือที่ดีที่สุดเท่าที่จะทำได้และบางครั้งก็คือ "อย่าซ่อนข้อผิดพลาดและแสดงโดยเร็วที่สุด" มาโครที่ฉันพูดข้างต้นเป็นไปตามปรัชญานี้