ปัญหา:
ตั้งแต่เวลานานฉันกังวลเกี่ยวกับexceptions
กลไกเพราะฉันรู้สึกว่ามันไม่ได้แก้ไขสิ่งที่ควร
เคลม: มีการถกเถียงกันนานเกี่ยวกับหัวข้อนี้และพวกเขาส่วนใหญ่มักจะพยายามเปรียบเทียบexceptions
และส่งกลับรหัสข้อผิดพลาด นี่ไม่ใช่หัวข้อที่แน่นอน
พยายามกำหนดข้อผิดพลาดฉันจะเห็นด้วยกับ CppCoreGuidelines จาก Bjarne Stroustrup & Herb Sutter
ข้อผิดพลาดหมายความว่าฟังก์ชันไม่สามารถบรรลุวัตถุประสงค์ที่โฆษณาไว้
การเรียกร้อง: exception
กลไกคือ semantic ภาษาสำหรับการจัดการข้อผิดพลาด
การเรียกร้อง: สำหรับฉันมี "ไม่มีข้อแก้ตัว" สำหรับฟังก์ชั่นที่ไม่ได้งาน: เพราะเรากำหนดเงื่อนไขล่วงหน้า / โพสต์ผิดดังนั้นฟังก์ชันไม่สามารถรับประกันผลลัพธ์ได้หรือกรณีพิเศษบางกรณีไม่ถือว่าสำคัญพอสำหรับการใช้เวลาในการพัฒนา ทางออก เมื่อพิจารณาแล้ว IMO ความแตกต่างระหว่างการจัดการรหัสปกติและรหัสข้อผิดพลาดคือ (ก่อนการติดตั้ง) บรรทัดที่เป็นอัตนัย
การเรียกร้อง: การใช้ข้อยกเว้นเพื่อระบุว่าเมื่อใดที่ไม่ได้รักษาเงื่อนไขก่อนหรือหลังการโพสต์ไว้เป็นอีกจุดประสงค์หนึ่งของexception
กลไก ฉันไม่ได้กำหนดเป้าหมายการใช้งานของexceptions
ที่นี่
ในหนังสือหลายบทช่วยสอนและแหล่งข้อมูลอื่น ๆ พวกเขามักจะแสดงการจัดการข้อผิดพลาดเป็นศาสตร์ที่ค่อนข้างมีวัตถุประสงค์ซึ่งแก้ไขได้ด้วยexceptions
และคุณเพียงแค่ต้องการให้catch
พวกเขามีซอฟต์แวร์ที่แข็งแกร่งสามารถกู้คืนจากสถานการณ์ใด ๆ แต่เป็นเวลาหลายปีในฐานะนักพัฒนาทำให้ฉันเห็นปัญหาจากแนวทางที่แตกต่าง:
- โปรแกรมเมอร์มีแนวโน้มที่จะทำให้งานของพวกเขาง่ายขึ้นโดยการโยนข้อยกเว้นเมื่อกรณีที่เฉพาะเจาะจงดูยากเกินกว่าที่จะดำเนินการอย่างระมัดระวัง กรณีทั่วไปนี้คือ: ปัญหาหน่วยความจำไม่เพียงพอ, ปัญหาเต็มรูปแบบดิสก์, ปัญหาไฟล์ที่เสียหายเป็นต้นซึ่งอาจเพียงพอ แต่ไม่สามารถตัดสินใจได้เสมอจากระดับสถาปัตยกรรม
- โปรแกรมเมอร์ไม่อ่านเอกสารอย่างละเอียดเกี่ยวกับข้อยกเว้นในไลบรารีและมักจะไม่ทราบว่าเมื่อใดและเมื่อใด นอกจากนี้แม้ว่าพวกเขารู้ว่าพวกเขาไม่ได้จัดการพวกเขาจริงๆ
- โปรแกรมเมอร์มักจะไม่ได้รับข้อยกเว้นเร็วพอและเมื่อพวกเขาทำมันส่วนใหญ่จะเข้าสู่ระบบและโยนต่อไป (อ้างถึงจุดแรก)
สิ่งนี้มีสองผล:
- ตรวจพบข้อผิดพลาดที่เกิดขึ้นบ่อยครั้งในช่วงต้นของการพัฒนาและดีบั๊ก (ซึ่งดี)
- ข้อยกเว้นที่หายากไม่ได้รับการจัดการและทำให้ระบบล่ม (ด้วยข้อความบันทึกที่ดี) ที่หน้าแรกของผู้ใช้ บางครั้งมีรายงานข้อผิดพลาดหรือไม่
เมื่อพิจารณาแล้ว IMO วัตถุประสงค์หลักของกลไกข้อผิดพลาดควรเป็น:
- ทำให้มองเห็นได้ในรหัสที่บางกรณีไม่ได้รับการจัดการ
- สื่อสารปัญหา runtime ให้กับรหัสที่เกี่ยวข้อง (อย่างน้อยผู้โทร) เมื่อสถานการณ์นี้เกิดขึ้น
- จัดเตรียมกลไกการกู้คืน
ข้อบกพร่องหลักของexception
ความหมายในฐานะกลไกการจัดการข้อผิดพลาดคือ IMO: มันง่ายที่จะดูว่า a อยู่ที่ไหนthrow
ในซอร์สโค้ด แต่ไม่ชัดเจนที่จะทราบว่าฟังก์ชันเฉพาะสามารถโยนได้โดยดูที่การประกาศ นี่ทำให้เกิดปัญหาทั้งหมดที่ฉันแนะนำไว้ข้างต้น
ภาษาไม่ได้บังคับใช้และตรวจสอบรหัสข้อผิดพลาดอย่างเคร่งครัดเท่าที่มันทำเพื่อด้านอื่น ๆ ของภาษา (เช่นชนิดของตัวแปรที่แข็งแกร่ง)
ลองวิธีแก้ปัญหา
ในความตั้งใจที่จะปรับปรุงสิ่งนี้ฉันได้พัฒนาระบบการจัดการข้อผิดพลาดที่ง่ายมากซึ่งพยายามที่จะทำให้การจัดการข้อผิดพลาดในระดับความสำคัญในระดับเดียวกันมากกว่ารหัสปกติ
ความคิดคือ:
- แต่ละฟังก์ชั่น (ที่เกี่ยวข้อง) ได้รับการอ้างอิงถึง
success
วัตถุที่มีน้ำหนักเบามากและอาจตั้งค่าเป็นสถานะข้อผิดพลาดในกรณี วัตถุเบามากจนกระทั่งบันทึกข้อผิดพลาดด้วยข้อความ - ฟังก์ชั่นได้รับการสนับสนุนให้ข้ามงานถ้าวัตถุที่ให้ไว้มีข้อผิดพลาด
- ข้อผิดพลาดจะต้องไม่ถูกแทนที่
เห็นได้ชัดว่าการออกแบบเต็มรูปแบบพิจารณาทุกแง่มุมอย่างละเอียด (ประมาณ 10 หน้า) รวมถึงวิธีการใช้กับ OOP
ตัวอย่างของSuccess
ชั้นเรียน:
class Success
{
public:
enum SuccessStatus
{
ok = 0, // All is fine
error = 1, // Any error has been reached
uninitialized = 2, // Initialization is required
finished = 3, // This object already performed its task and is not useful anymore
unimplemented = 4, // This feature is not implemented already
};
Success(){}
Success( const Success& v);
virtual ~Success() = default;
virtual Success& operator= (const Success& v);
// Comparators
virtual bool operator==( const Success& s)const { return (this->status==s.status && this->stateStr==s.stateStr);}
virtual bool operator!=( const Success& s)const { return (this->status!=s.status || this->stateStr==s.stateStr);}
// Retrieve if the status is not "ok"
virtual bool operator!() const { return status!=ok;}
// Retrieve if the status is "ok"
operator bool() const { return status==ok;}
// Set a new status
virtual Success& set( SuccessStatus status, std::string msg="");
virtual void reset();
virtual std::string toString() const{ return stateStr;}
virtual SuccessStatus getStatus() const { return status; }
virtual operator SuccessStatus() const { return status; }
private:
std::string stateStr;
SuccessStatus status = Success::ok;
};
การใช้งาน:
double mySqrt( Success& s, double v)
{
double result = 0.0;
if (!s) ; // do nothing
else if (v<0.0) s.set(Error, "Square root require non-negative input.");
else result = std::sqrt(v);
return result;
}
Success s;
mySqrt(s, 144.0);
otherStuff(s);
saveStuff(s);
if (s) /*All is good*/;
else cout << s << endl;
ฉันใช้มันในหลาย ๆ รหัส (ของตัวเอง) และมันบังคับให้โปรแกรมเมอร์ (ฉัน) คิดเพิ่มเติมเกี่ยวกับกรณีพิเศษที่เป็นไปได้และวิธีแก้ปัญหา (ดี) อย่างไรก็ตามมันมีช่วงการเรียนรู้และไม่ได้รวมเข้ากับโค้ดที่ใช้งานได้ในขณะนี้
คำถาม
ฉันอยากจะเข้าใจความหมายของการใช้กระบวนทัศน์ในโครงการให้ดีขึ้น
- หลักฐานของปัญหาถูกต้องหรือไม่? หรือฉันพลาดบางสิ่งที่เกี่ยวข้อง?
- การแก้ปัญหาเป็นแนวคิดทางสถาปัตยกรรมที่ดีหรือไม่? หรือราคาสูงเกินไป?
แก้ไข:
การเปรียบเทียบระหว่างวิธีการ:
//Exceptions:
// Incorrect
File f = open("text.txt"); // Could throw but nothing tell it! Will crash
save(f);
// Correct
File f;
try
{
f = open("text.txt");
save(f);
}
catch( ... )
{
// do something
}
//Error code (mixed):
// Incorrect
File f = open("text.txt"); //Nothing tell you it may fail! Will crash
save(f);
// Correct
File f = open("text.txt");
if (f) save(f);
//Error code (pure);
// Incorrect
File f;
open(f, "text.txt"); //Easy to forget the return value! will crash
save(f);
//Correct
File f;
Error er = open(f, "text.txt");
if (!er) save(f);
//Success mechanism:
Success s;
File f;
open(s, "text.txt");
save(s, f); //s cannot be avoided, will never crash.
if (s) ... //optional. If you created s, you probably don't forget it.