ชุมชนภาษาโปรแกรม Rust กำลังมีส่วนร่วมในการอภิปรายอย่างเข้มข้นเกี่ยวกับแนวทางการจัดการ error ซึ่งถูกจุดประกายโดยการวิเคราะห์โดยละเอียดของทีม iroh เกี่ยวกับการเดินทางของพวกเขาจากการจัดการ error แบบทั่วไปไปสู่แบบมีโครงสร้าง การถกเถียงนี้เน้นย้ำถึงความขัดแย้งพื้นฐานระหว่างผลิตภาพของนักพัฒนาและความแม่นยำของ API ที่ส่งผลต่อทุกโปรเจกต์ Rust
ความแตกแยกครั้งใหญ่: Anyhow vs Thiserror
ระบบนิเวศ Rust ได้แบ่งออกเป็นสองค่ายสำหรับการจัดการ error แนวทาง anyhow
ถือว่า error เป็นประเภททั่วไปขนาดใหญ่ที่สามารถห่อหุ้มอะไรก็ได้ ทำให้ดำเนินการได้อย่างรวดเร็วและยอดเยี่ยมสำหรับการ debug ด้วย backtrace แบบเต็ม ในอีกด้านหนึ่ง thiserror
สร้าง enum variant ที่ออกแบบอย่างพิถีพิถันสำหรับทุกกรณี error ที่เป็นไปได้ ให้ประเภท error ที่แม่นยำแก่ผู้ใช้ที่สามารถ match และจัดการแตกต่างกันได้
สมาชิกชุมชนกำลังตั้งคำถามว่าความซับซ้อนนี้คุ้มค่าหรือไม่ นักพัฒนาบางคนแสดงความคิดถึงแนวทางที่เรียบง่ายกว่า โดยมีคนหนึ่งกล่าวว่าพวกเขาจะเลือกการจัดการ error แบบตรงไปตรงมาของ Go มากกว่าระบบที่ซับซ้อนของ Rust อย่างไรก็ตาม คนอื่นๆ โต้แย้งว่าความซับซ้อนนี้มีจุดประสงค์ - ในภาษาที่มี exception การกำหนดว่าฟังก์ชันสามารถล้มเหลวได้อย่างไรต้องอาศัยการเชื่อถือเอกสารหรืออ่านผ่าน call chain ทั้งหมด
การเปรียบเทียบแนวทางการจัดการข้อผิดพลาด
แนวทาง | ข้อดี | ข้อเสีย | เหมาะสำหรับ |
---|---|---|---|
anyhow |
การใช้งานที่รวดเร็ว, backtrace แบบเต็มรูปแบบ, เพิ่ม context ได้ง่าย | ข้อผิดพลาดแบบทั่วไป, API ที่ไม่แม่นยำ | แอปพลิเคชัน, การสร้างต้นแบบอย่างรวดเร็ว |
thiserror |
ประเภทข้อผิดพลาดที่แม่นยำ, API ที่เสถียร, variant ที่สามารถ match ได้ | boilerplate มากขึ้น, ข้อจำกัดของ backtrace | ไลบรารี, API สาธารณะ |
snafu |
ข้อผิดพลาดแบบ enum + backtrace, context ที่หลากหลาย | ความซับซ้อนเพิ่มเติม, เส้นโค้งการเรียนรู้ | ความต้องการแบบผสมระหว่างไลบรารีและแอปพลิเคชัน |
ปัญหา Backtrace ที่ไม่หายไป
แหล่งที่มาหลักของความหงุดหงิดเกิดจากการเผยแพร่ backtrace ที่ไม่เสถียรของ Rust บน error ข้อจำกัดทางเทคนิคนี้บังคับให้นักพัฒนาต้องเลือกระหว่าง ergonomics ที่ดีกับ operator ?
หรือ backtrace ที่เหมาะสม ปัญหานี้เกิดจากระบบ trait ของ Rust - การ implement std::error::Error
สร้างความขัดแย้งระหว่าง blanket implementation ที่จำเป็นสำหรับ ergonomics และการจัดการ backtrace ที่ต้องการ concrete type
ข้อจำกัดพื้นฐานนี้หมายความว่าผู้เขียน library ต้องตัดสินใจอย่างยากลำบากโดยไม่มีวิธีแก้ไขที่สมบูรณ์แบบ ทีม iroh พบคำตอบของพวกเขาใน snafu
ซึ่งพวกเขาอธิบายว่าเป็น thiserror ที่แกร่งกว่า โดยให้ประเภท error แบบ enum พร้อมการแนบ context ที่อุดมไปด้วยข้อมูลและการจับ backtrace อัตโนมัติ
การต่อต้านของชุมชนต่อความซับซ้อน
การอภิปรายเผยให้เห็นความกังวลที่เพิ่มขึ้นเกี่ยวกับความซับซ้อนในการจัดการ error ของ Rust สมาชิกชุมชนบางคนโต้แย้งว่าระบบนิเวศได้ทำให้การจัดการ error ที่ควรจะตรงไปตรงมาซับซ้อนเกินไป พวกเขาชี้ให้เห็นว่าภาษาที่ประสบความสำเร็จอย่าง Python และ Java จัดการ exception ได้อย่างสง่างามในโปรเจกต์ในโลกจริง โดยตั้งคำถามว่าแนวทางของ Rust ให้ประโยชน์ที่คุ้มค่าจริงหรือไม่
ทุกฟังก์ชันสามารถล้มเหลวด้วย StackOverflowError และคุณไม่สามารถทำอะไรเกี่ยวกับมันได้ เกือบทุกฟังก์ชันสามารถล้มเหลวด้วย OutOfMemoryError และคุณไม่สามารถทำอะไรเกี่ยวกับมันได้ ฉันยอมรับว่าทุกอย่างสามารถล้มเหลวได้
คนอื่นๆ ปกป้อง structured error ว่าเป็นสิ่งจำเป็นสำหรับ API ของ library โดยโต้แย้งว่าการสร้างแบบจำลอง error ที่เหมาะสมช่วยลดความล้มเหลวที่ผู้ใช้เผชิญโดยแลกกับเวลาของนักพัฒนา การถกเถียงสัมผัสกับคำถามว่าอุตสาหกรรมได้นอนหลับกับการจัดการ error โดยไม่มี exception หรือไม่ โดยนักพัฒนา Rust เป็นคนแรกที่จริงจังในการแก้ไขปัญหาเหล่านี้
ช่องว่างในเอกสารและแนวปฏิบัติที่ดี
ข้อร้องเรียนที่เกิดขึ้นซ้ำๆ ในการอภิปรายของชุมชนเน้นไปที่เอกสารแนวปฏิบัติที่ดีที่กระจัดกระจายของ Rust ไม่เหมือนภาษาที่มี style guide ส่วนกลาง นักพัฒนา Rust ต้องค้นหาผ่าน blog post และการอภิปรายของชุมชนเพื่อหาคำแนะนำการจัดการ error ปัจจุบัน การกระจายตัวนี้ทำให้นักพัฒนาที่กลับมาใช้ Rust ยากที่จะทราบแนวทางและ pattern ล่าสุด
แนวทางโดยละเอียดของทีม iroh สำหรับการเขียน concrete error - เช่น การกำหนดขอบเขต error enum ให้กับฟังก์ชันแทนที่จะเป็น module และการรวม custom variant สำหรับ public trait - แสดงถึงภูมิปญญาเชิงปฏิบัติที่นักพัฒนาหวังว่าจะมีเอกสารส่วนกลางมากกว่านี้
แนวทางการจัดการข้อผิดพลาดของ Rust จากทีม iroh
- กำหนดขอบเขต error enums ให้กับฟังก์ชัน ไม่ใช่โมดูล - ทำให้หมวดหมู่ของข้อผิดพลาดเข้าใจได้ง่ายขึ้น
- ใช้ชื่อข้อผิดพลาดที่อธิบายได้ชัดเจน -
TicketParseError
เทียบกับTicketError
ช่วยชี้แจงขอบเขตของความล้มเหลว - รวม Custom variants สำหรับ public traits - อนุญาตให้ผู้ใช้งานสามารถส่งต่อข้อผิดพลาดของตนเองได้
- จัดเตรียมเมธอดช่วยเหลือ -
custom_err()
และcustom_err_box()
สำหรับการสร้างข้อผิดพลาดอย่างง่ายดาย
มองไปข้างหน้า: ความเป็นจริงเหนือลัทธิ
การอภิปรายของชุมชนแสดงให้เห็นการยอมรับที่เพิ่มขึ้นว่าโปรเจกต์ต่างๆ มีความต้องการการจัดการ error ที่แตกต่างกัน ในขณะที่มีแรงกดดันให้มั่นใจว่า library ทั้งหมดส่งคืน concrete error ความเป็นจริงเชิงปฏิบัติคือทีมต้องสร้างสมดุลระหว่างข้อกังวลหลายประการรวมถึงความเร็วในการพัฒนา ความเสถียรของ API และความสามารถในการ debug
แนวทางแบบผสมของทีม iroh - การใช้ structured error สำหรับ public API ในขณะที่รักษา rich context และ backtrace - อาจเป็นตัวแทนของจุดกึ่งกลางที่เป็นจริง อย่างไรก็ตาม ข้อจำกัดพื้นฐานในเรื่องราวการจัดการ error ของ Rust หมายความว่าจนกว่าการเผยแพร่ backtrace จะเสถียร ทีมจะยังคงต้องแลกเปลี่ยนแทนที่จะหาวิธีแก้ไขที่สมบูรณ์แบบ
อ้างอิง: Trying to get error backtraces in rust libraries right