ฉันอาจเรียกใช้ความโกรธเกรี้ยวของ Pythonistas (ไม่รู้ว่าฉันไม่ได้ใช้ Python มาก) หรือโปรแกรมเมอร์จากภาษาอื่นด้วยคำตอบนี้ แต่ในความเห็นของฉันฟังก์ชั่นส่วนใหญ่ไม่ควรมีcatch
บล็อก เพื่อแสดงว่าทำไมให้ฉันเปรียบเทียบสิ่งนี้กับการเผยแพร่รหัสข้อผิดพลาดด้วยตนเองของชนิดที่ฉันต้องทำเมื่อทำงานกับ Turbo C ในช่วงปลายยุค 80 และต้นยุค 90
ดังนั้นสมมติว่าเรามีฟังก์ชั่นในการโหลดภาพหรืออะไรทำนองนั้นในการตอบสนองต่อผู้ใช้ที่เลือกไฟล์ภาพที่จะโหลดและนี่คือการเขียนใน C และการชุมนุม:
ฉันตัดการทำงานระดับต่ำบางส่วนออกไป แต่เราสามารถเห็นได้ว่าฉันได้ระบุประเภทของฟังก์ชั่นต่าง ๆ รหัสสีตามหน้าที่ความรับผิดชอบที่พวกเขามีเกี่ยวกับการจัดการข้อผิดพลาด
จุดล้มเหลวและการกู้คืน
ตอนนี้มันก็ไม่เคยยากที่จะเขียนประเภทของฟังก์ชั่นที่ผมเรียกว่า "จุดที่เป็นไปได้ของความล้มเหลว" (ที่คนที่throw
, IE) และ "การกู้คืนข้อผิดพลาดและรายงาน" ฟังก์ชั่น (คนที่catch
เช่น)
ฟังก์ชั่นเหล่านั้นมักจะน่ารำคาญที่จะเขียนอย่างถูกต้องก่อนการจัดการข้อยกเว้นที่สามารถใช้ได้ตั้งแต่ฟังก์ชั่นที่สามารถใช้เป็นความล้มเหลวภายนอกเช่นความล้มเหลวในการจัดสรรหน่วยความจำก็สามารถกลับมาNULL
หรือ0
หรือ-1
หรือตั้งค่ารหัสข้อผิดพลาดในระดับโลกหรือสิ่งดังต่อไปนี้ และการกู้คืนข้อผิดพลาด / การรายงานเป็นเรื่องง่ายเสมอตั้งแต่เมื่อคุณทำงานลง call stack ไปยังจุดที่เหมาะสมในการกู้คืนและรายงานความล้มเหลวคุณเพียงแค่นำรหัสข้อผิดพลาดและ / หรือข้อความและรายงานไปยังผู้ใช้ และโดยธรรมชาติแล้วฟังก์ชั่นที่อยู่ในลำดับชั้นของสิ่งนี้ซึ่งไม่เคยล้มเหลวไม่ว่าจะมีการเปลี่ยนแปลงอย่างไรในอนาคต ( Convert Pixel
) จะตายง่ายต่อการเขียนอย่างถูกต้อง (อย่างน้อยก็เกี่ยวกับการจัดการข้อผิดพลาด)
การแพร่กระจายข้อผิดพลาด
อย่างไรก็ตามฟังก์ชั่นที่น่าเบื่อนั้นมักจะเกิดจากความผิดพลาดของมนุษย์คือตัวกระจายข้อผิดพลาดซึ่งไม่ได้ทำงานโดยตรงกับความล้มเหลว แต่เรียกว่าฟังก์ชั่นที่อาจล้มเหลวได้ลึกลงไปในลำดับชั้น ณ จุดนั้นAllocate Scanline
อาจต้องจัดการกับความล้มเหลวจากmalloc
นั้นส่งคืนข้อผิดพลาดลงไปConvert Scanlines
จากนั้นConvert Scanlines
จะต้องตรวจสอบข้อผิดพลาดนั้นและส่งมันลงไปDecompress Image
แล้วจากนั้นDecompress Image->Parse Image
และParse Image->Load Image
และLoad Image
ไปยังคำสั่งผู้ใช้ปลายทางที่มีการรายงานข้อผิดพลาด .
นี่คือที่มนุษย์จำนวนมากทำผิดเพราะใช้เวลาเพียงหนึ่งข้อผิดพลาดในการตรวจสอบและส่งผ่านข้อผิดพลาดสำหรับลำดับชั้นของฟังก์ชั่นทั้งหมดที่จะล้มลงเมื่อมันมาถึงการจัดการข้อผิดพลาดอย่างถูกต้อง
ต่อไปหากรหัสข้อผิดพลาดจะถูกส่งกลับโดยฟังก์ชั่นเราสวยมากสูญเสียความสามารถในการพูด 90% ของ codebase ของเราจะกลับค่าดอกเบี้ยที่ประสบความสำเร็จตั้งแต่ฟังก์ชั่นจำนวนมากดังนั้นจะต้องมีการสำรองค่าตอบแทนของพวกเขาสำหรับส่งคืนรหัสข้อผิดพลาดใน ความล้มเหลว
การลดข้อผิดพลาดของมนุษย์: รหัสข้อผิดพลาดทั่วโลก
ดังนั้นเราจะลดความเป็นไปได้ของความผิดพลาดของมนุษย์ได้อย่างไร นี่ฉันยังอาจก่อให้เกิดความโกรธเกรี้ยวของบางโปรแกรมเมอร์ C แต่การปรับปรุงทันทีในความคิดของฉันคือการใช้ทั่วโลกรหัสข้อผิดพลาดเช่น OpenGL glGetError
กับ อย่างน้อยก็ทำให้ฟังก์ชั่นนี้คืนค่านิยมที่มีความหมายต่อความสำเร็จ มีหลายวิธีในการทำให้เธรดนี้ปลอดภัยและมีประสิทธิภาพโดยที่โค้ดระบุความผิดพลาดถูกแปลเป็นเธรด
นอกจากนี้ยังมีบางกรณีที่ฟังก์ชั่นอาจพบข้อผิดพลาด แต่ก็ค่อนข้างไม่เป็นอันตรายสำหรับมันที่จะทำงานต่อไปอีกสักหน่อยก่อนที่มันจะกลับมาก่อนกำหนดอันเป็นผลมาจากการค้นพบข้อผิดพลาดก่อนหน้านี้ สิ่งนี้ทำให้เกิดสิ่งดังกล่าวโดยไม่ต้องตรวจสอบข้อผิดพลาดกับ 90% ของการเรียกใช้ฟังก์ชันที่เกิดขึ้นในทุกฟังก์ชั่นเดียวดังนั้นจึงยังคงสามารถจัดการข้อผิดพลาดได้อย่างเหมาะสมโดยไม่ต้องพิถีพิถันมากนัก
การลดข้อผิดพลาดของมนุษย์: การจัดการข้อยกเว้น
อย่างไรก็ตามวิธีการแก้ปัญหาข้างต้นยังคงต้องใช้ฟังก์ชั่นมากมายในการจัดการด้านการควบคุมการแพร่กระจายข้อผิดพลาดด้วยตนเองแม้ว่ามันอาจจะลดจำนวนบรรทัดif error happened, return error
ของรหัสประเภทคู่มือ มันจะไม่กำจัดมันอย่างสมบูรณ์เพราะมักจะต้องมีการตรวจสอบข้อผิดพลาดอย่างน้อยหนึ่งที่และส่งคืนเกือบทุกฟังก์ชันการเผยแพร่ข้อผิดพลาดเดียว ดังนั้นนี่คือเมื่อการจัดการข้อยกเว้นเข้ามาในรูปภาพเพื่อบันทึกวัน (sorta)
แต่คุณค่าของการจัดการข้อยกเว้นที่นี่คือการเพิ่มความจำเป็นในการจัดการกับลักษณะการไหลของการควบคุมของการเผยแพร่ข้อผิดพลาดด้วยตนเอง นั่นหมายความว่าคุณค่าของมันนั้นเชื่อมโยงกับความสามารถในการหลีกเลี่ยงการเขียนจำนวนcatch
บล็อกในฐานโค้ดของคุณ ในแผนภาพด้านบนสถานที่เดียวที่ควรมีcatch
บล็อกคือLoad Image User Command
ที่ซึ่งรายงานข้อผิดพลาด ไม่มีอะไรที่ควรจะต้องทำcatch
อย่างอื่นเพราะอย่างนั้นมันเริ่มที่จะได้รับเป็นที่น่าเบื่อและเป็นข้อผิดพลาดได้ง่ายเช่นเดียวกับการจัดการรหัสข้อผิดพลาด
ดังนั้นถ้าคุณถามฉันถ้าคุณมี codebase ที่จริงๆได้รับประโยชน์จากการจัดการข้อยกเว้นในวิธีที่สง่างามก็ควรจะมีขั้นต่ำจำนวนcatch
บล็อก (โดยต่ำสุดที่ผมไม่ได้หมายถึงศูนย์ แต่มากขึ้นเช่นหนึ่งสำหรับทุกสูงที่ไม่ซ้ำกัน การดำเนินการของผู้ใช้ที่อาจล้มเหลวและอาจน้อยลงหากการดำเนินการของผู้ใช้ระดับสูงทั้งหมดถูกเรียกใช้ผ่านระบบคำสั่งกลาง)
การล้างทรัพยากร
อย่างไรก็ตามการจัดการข้อยกเว้นแก้ปัญหาความจำเป็นในการหลีกเลี่ยงการจัดการกับลักษณะการไหลของการควบคุมการแพร่กระจายข้อผิดพลาดด้วยตนเองในเส้นทางที่แยกต่างหากจากการไหลปกติของการดำเนินการ บ่อยครั้งที่ฟังก์ชั่นซึ่งทำหน้าที่เป็นผู้เผยแพร่ข้อผิดพลาดแม้ว่าจะทำสิ่งนี้โดยอัตโนมัติด้วย EH แต่ก็อาจได้รับทรัพยากรบางอย่างที่จำเป็นต้องทำลาย ตัวอย่างเช่นฟังก์ชั่นดังกล่าวอาจเปิดไฟล์ชั่วคราวที่จำเป็นต้องปิดก่อนที่จะกลับจากฟังก์ชั่นไม่ว่าจะเกิดอะไรขึ้นหรือล็อค mutex ที่ต้องการปลดล็อคไม่ว่าจะเกิดอะไรขึ้น
สำหรับเรื่องนี้ฉันอาจจะเรียกความโกรธเคืองของโปรแกรมเมอร์จำนวนมากจากทุกประเภทของภาษา แต่ฉันคิดว่าวิธี C ++ สำหรับสิ่งนี้เป็นอุดมคติ ภาษานี้แนะนำdestructorsที่ถูกเรียกใช้ในรูปแบบที่กำหนดขึ้นมาทันทีที่วัตถุออกนอกขอบเขต ด้วยเหตุนี้รหัส C ++ ที่กล่าวว่าการล็อก mutex ผ่านวัตถุ mutex ที่ถูกกำหนดขอบเขตด้วย destructor ไม่จำเป็นต้องปลดล็อกด้วยตนเองเนื่องจากมันจะถูกปลดล็อกโดยอัตโนมัติเมื่อวัตถุออกจากขอบเขตไม่ว่าจะเกิดอะไรขึ้น (แม้ว่าข้อยกเว้นคือ พบ) ดังนั้นจึงไม่จำเป็นต้องใช้รหัส C ++ ที่เขียนดีเพื่อจัดการกับการล้างทรัพยากรในท้องถิ่น
ในภาษาที่ไม่มี destructors พวกเขาอาจจำเป็นต้องใช้finally
บล็อกเพื่อล้างทรัพยากรภายในเครื่องด้วยตนเอง ที่กล่าวว่ายังคงมีการทิ้งขยะรหัสของคุณด้วยการเผยแพร่ข้อผิดพลาดด้วยตนเองหากคุณไม่ต้องมีcatch
ข้อยกเว้นทั่วสถานที่เลว
ย้อนกลับผลข้างเคียงภายนอก
นี่คือปัญหาความคิดที่ยากที่สุดในการแก้ปัญหา หากฟังก์ชั่นใด ๆ ไม่ว่าจะเป็นตัวเผยแพร่ข้อผิดพลาดหรือจุดของความล้มเหลวทำให้เกิดผลข้างเคียงภายนอกก็จำเป็นต้องย้อนกลับหรือ "เลิกทำ" ผลข้างเคียงเหล่านั้นเพื่อส่งคืนระบบกลับสู่สถานะราวกับว่าการดำเนินการไม่เคยเกิดขึ้นแทน " สถานะที่ถูกต้องครึ่ง "ที่การดำเนินการครึ่งทางประสบความสำเร็จ ฉันรู้ว่าไม่มีภาษาใดที่ทำให้เกิดปัญหาเกี่ยวกับแนวคิดนี้ได้ง่ายกว่ามากยกเว้นภาษาที่ลดความต้องการฟังก์ชั่นส่วนใหญ่ในการทำให้เกิดผลข้างเคียงจากภายนอกในตอนแรกเช่นภาษาที่ใช้งานได้ซึ่งหมุนรอบ
นี่finally
คือวิธีการแก้ปัญหาที่หรูหราที่สุดในภาษาที่หมุนรอบความแปรปรวนและผลข้างเคียงเพราะบ่อยครั้งที่ตรรกะประเภทนี้มีความเฉพาะเจาะจงกับฟังก์ชั่นหนึ่ง ๆ และไม่ได้แมปกับแนวคิดของ "การล้างทรัพยากร" " และฉันขอแนะนำให้ใช้finally
อย่างเสรีในกรณีเหล่านี้เพื่อให้แน่ใจว่าฟังก์ชั่นของคุณย้อนกลับผลข้างเคียงในภาษาที่สนับสนุนไม่ว่าคุณจะต้องการcatch
บล็อกหรือไม่(และอีกครั้งหากคุณถามฉันรหัสที่เขียนดีควรมีจำนวนขั้นต่ำcatch
บล็อกและcatch
บล็อกทั้งหมดควรอยู่ในสถานที่ที่เหมาะสมที่สุดตามแผนภาพด้านบนLoad Image User Command
)
ภาษาดรีม
อย่างไรก็ตาม IMO finally
นั้นใกล้เคียงกับอุดมคติสำหรับการกลับรายการผลข้างเคียง แต่ไม่มากนัก เราจำเป็นต้องแนะนำboolean
ตัวแปรหนึ่งตัวเพื่อย้อนกลับผลข้างเคียงอย่างมีประสิทธิภาพในกรณีของการออกก่อนกำหนด (จากข้อยกเว้นที่ส่งออกไปหรืออย่างอื่น) เช่น:
bool finished = false;
try
{
// Cause external side effects.
...
// Indicate that all the external side effects were
// made successfully.
finished = true;
}
finally
{
// If the function prematurely exited before finishing
// causing all of its side effects, whether as a result of
// an early 'return' statement or an exception, undo the
// side effects.
if (!finished)
{
// Undo side effects.
...
}
}
ถ้าฉันสามารถออกแบบภาษาได้วิธีการในฝันของฉันในการแก้ปัญหานี้จะเป็นเช่นนี้เพื่อทำให้รหัสข้างบนเป็นไปโดยอัตโนมัติ:
transaction
{
// Cause external side effects.
...
}
rollback
{
// This block is only executed if the above 'transaction'
// block didn't reach its end, either as a result of a premature
// 'return' or an exception.
// Undo side effects.
...
}
... กับ destructors เพื่อทำให้การทำความสะอาดของทรัพยากรในท้องถิ่นทำให้มันดังนั้นเราจะต้องtransaction
, rollback
และcatch
(แม้ว่าฉันอาจยังคงต้องการที่จะเพิ่มfinally
สำหรับการพูด, การทำงานร่วมกับทรัพยากร C ที่ไม่ได้ทำความสะอาดตัวเองขึ้นไป) อย่างไรก็ตามfinally
ด้วยboolean
ตัวแปรเป็นสิ่งที่ใกล้เคียงที่สุดที่จะทำให้สิ่งนี้ตรงไปตรงมาที่ฉันพบว่ายังขาดภาษาในฝันของฉัน วิธีแก้ปัญหาที่ตรงไปตรงมามากที่สุดอันดับที่สองที่ฉันพบคือนี้เป็นขอบเขตของภาษาเช่น C ++ และ D แต่ฉันมักจะพบว่าขอบเขตของตัวป้องกันนั้นค่อนข้างน่าอึดอัดใจในทางแนวคิดเนื่องจากมันทำให้แนวคิดของ ในความคิดของฉันเหล่านั้นเป็นแนวคิดที่แตกต่างกันมากที่จะจัดการในวิธีที่แตกต่าง
ความฝันเล็ก ๆ น้อย ๆ ของฉันเกี่ยวกับภาษาจะหมุนรอบอย่างหนักเกี่ยวกับความไม่สามารถเปลี่ยนแปลงได้และโครงสร้างข้อมูลแบบถาวรเพื่อให้ง่ายขึ้นมากแม้ว่าจะไม่จำเป็นต้องเขียนฟังก์ชันที่มีประสิทธิภาพซึ่งไม่จำเป็นต้องคัดลอกโครงสร้างข้อมูลขนาดใหญ่ทั้งหมด ไม่มีผลข้างเคียง
ข้อสรุป
ฉันคิดว่าtry/finally
รหัสของคุณสำหรับการปิดซ็อกเก็ตนั้นดีและดีมากเพราะ Python ไม่มีตัวทำลาย C ++ เทียบเท่าและโดยส่วนตัวฉันคิดว่าคุณควรใช้สิ่งนี้อย่างอิสระสำหรับสถานที่ที่ต้องการผลข้างเคียง และลดจำนวนสถานที่ที่คุณต้องไปcatch
ยังสถานที่ที่เหมาะสมที่สุด