Clean Code และศิลปะการจัดการข้อยกเว้น

เผยแพร่แล้ว: 2022-03-11

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

สภาวะผิดปกติหรือพิเศษที่ต้องมีการประมวลผลพิเศษ – มักจะเปลี่ยนโฟลว์ปกติของการดำเนินการโปรแกรม...

และการจัดการนั้นต้องการ:

ภาษาโปรแกรมเฉพาะทางหรือกลไกฮาร์ดแวร์คอมพิวเตอร์

ดังนั้นข้อยกเว้นจำเป็นต้องได้รับการดูแลเป็นพิเศษ และข้อยกเว้นที่ไม่สามารถจัดการได้อาจทำให้เกิดพฤติกรรมที่ไม่คาดคิด ผลลัพธ์มักจะงดงาม ในปี พ.ศ. 2539 ความล้มเหลวในการยิงจรวด Ariane 5 อันโด่งดังเกิดจากข้อยกเว้นล้นที่ไม่สามารถจัดการได้ ข้อบกพร่องของซอฟต์แวร์ที่เลวร้ายที่สุดในประวัติศาสตร์มีจุดบกพร่องอื่นๆ ที่อาจเกิดจากข้อยกเว้นที่ไม่สามารถจัดการได้หรือจัดการพลาด

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

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

การจัดการข้อยกเว้น: เป็นสิ่งที่ดี

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

 begin do_something_that_might_not_work! rescue SpecificError => e do_some_specific_error_clean_up retry if some_condition_met? ensure this_will_always_be_executed end

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

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

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

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

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

"WTFs/m" โดย Thom Holwerda, OSNews

จากที่กล่าวมา เรามาเจาะลึกถึงแนวทางปฏิบัติเหล่านี้และดูว่าแต่ละข้อมีผลกระทบต่อมาตรการทั้งสามนี้อย่างไร

หมายเหตุ: เราจะนำเสนอตัวอย่างจาก Ruby แต่โครงสร้างทั้งหมดที่แสดงที่นี่มีความเทียบเท่าในภาษา OOP ทั่วไปส่วนใหญ่

สร้างลำดับชั้น ApplicationError ของคุณเองเสมอ

ภาษาส่วนใหญ่มาพร้อมกับคลาสข้อยกเว้นที่หลากหลาย ซึ่งจัดอยู่ในลำดับชั้นการสืบทอด เช่นเดียวกับคลาส OOP อื่นๆ เพื่อรักษาความสามารถในการอ่าน การบำรุงรักษา และความสามารถในการขยายของโค้ดของเรา เป็นความคิดที่ดีที่จะสร้างทรีย่อยของเราเองของข้อยกเว้นเฉพาะแอปพลิเคชันที่ขยายคลาสข้อยกเว้นพื้นฐาน การลงทุนในการจัดโครงสร้างลำดับชั้นนี้อย่างมีเหตุผลอาจเป็นประโยชน์อย่างยิ่ง ตัวอย่างเช่น:

 class ApplicationError < StandardError; end # Validation Errors class ValidationError < ApplicationError; end class RequiredFieldError < ValidationError; end class UniqueFieldError < ValidationError; end # HTTP 4XX Response Errors class ResponseError < ApplicationError; end class BadRequestError < ResponseError; end class UnauthorizedError < ResponseError; end # ... 

ตัวอย่างของลำดับชั้นข้อยกเว้นของแอปพลิเคชัน: StandardError อยู่ที่ด้านบนสุด ApplicationError สืบทอดมาจากมัน ValidationError และ ResponseError ทั้งคู่สืบทอดมาจากสิ่งนั้น RequiredFieldError และ UniqueFieldError สืบทอดมาจาก ValidationError ในขณะที่ BadRequestError และ UnauthorizedError สืบทอดมาจาก ResponseError

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

จากมุมมองของความสามารถในการอ่าน จะอ่านง่ายกว่ามาก:

 rescue ValidationError => e

กว่าจะอ่าน:

 rescue RequiredFieldError, UniqueFieldError, ... => e

จากมุมมองของความสามารถในการบำรุงรักษา ตัวอย่างเช่น เรากำลังใช้งาน JSON API และเราได้กำหนด ClientError ของเราเองด้วยประเภทย่อยต่างๆ เพื่อใช้เมื่อไคลเอนต์ส่งคำขอที่ไม่ถูกต้อง หากข้อใดข้อหนึ่งปรากฏขึ้น แอปพลิเคชันควรแสดงการแทนค่า JSON ของข้อผิดพลาดในการตอบกลับ จะแก้ไขหรือเพิ่มตรรกะได้ง่ายขึ้นในบล็อกเดียวที่จัดการ ClientError แทนที่จะวนรอบข้อผิดพลาดของไคลเอ็นต์ที่เป็นไปได้แต่ละรายการและใช้โค้ดตัวจัดการเดียวกันสำหรับแต่ละรายการ ในแง่ของการขยาย หากเราต้องใช้ข้อผิดพลาดของไคลเอ็นต์ประเภทอื่นในภายหลัง เราสามารถวางใจได้ว่าจะมีการจัดการอย่างถูกต้องที่นี่

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

 # app/controller/pseudo_controller.rb def authenticate_user! fail AuthenticationError if token_invalid? || token_expired? User.find_by(authentication_token: token) rescue AuthenticationError => e report_suspicious_activity if token_invalid? raise e end def show authenticate_user! show_private_stuff!(params[:id]) rescue ClientError => e render_error(e) end

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

สองสิ่งที่ควรทราบที่นี่:

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

ไม่เคย rescue Exception

นั่นคือ อย่าพยายามใช้ตัวจัดการ catch-all สำหรับประเภทข้อยกเว้นพื้นฐาน การช่วยเหลือหรือจับข้อยกเว้นทั้งหมดนั้น ไม่ใช่ ความคิดที่ดีในภาษาใดๆ ไม่ว่าจะเป็นในระดับสากลในระดับแอปพลิเคชันพื้นฐาน หรือในวิธีการฝังเล็กๆ ที่ใช้เพียงครั้งเดียว เราไม่ต้องการที่จะช่วยเหลือ Exception เพราะจะทำให้สิ่งใดก็ตามที่เกิดขึ้นสับสนสับสน ซึ่งสร้างความเสียหายทั้งการบำรุงรักษาและความสามารถในการขยาย เราสามารถเสียเวลาไปมากในการดีบักว่าปัญหาที่แท้จริงคืออะไร เมื่อมันง่ายเหมือนข้อผิดพลาดทางไวยากรณ์:

 # main.rb def bad_example i_might_raise_exception! rescue Exception nah_i_will_always_be_here_for_you end # elsewhere.rb def i_might_raise_exception! retrun do_a_lot_of_work! end

คุณอาจสังเกตเห็นข้อผิดพลาดในตัวอย่างก่อนหน้านี้ return พิมพ์ผิด แม้ว่าตัวแก้ไขสมัยใหม่จะให้การป้องกันข้อผิดพลาดทางไวยากรณ์ประเภทนี้ แต่ตัวอย่างนี้แสดงให้เห็นว่า rescue Exception เป็นอันตรายต่อโค้ดของเราอย่างไร ไม่มีการระบุประเภทที่แท้จริงของข้อยกเว้น (ในกรณีนี้คือ NoMethodError ) และไม่เคยเปิดเผยต่อนักพัฒนา ซึ่งอาจทำให้เราเสียเวลามากในการวิ่งวนเป็นวงกลม

อย่า rescue ข้อยกเว้นมากกว่าที่คุณต้องการ

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

หากเราพยายามจัดการกับข้อยกเว้นประเภทย่อยที่แตกต่างกันในตัวจัดการเดียวกัน เราจะแนะนำบล็อกโค้ดไขมันที่มีความรับผิดชอบมากเกินไป ตัวอย่างเช่น หากเรากำลังสร้างไลบรารีที่ใช้ API ระยะไกล การจัดการ MethodNotAllowedError (HTTP 405) มักจะแตกต่างจากการจัดการ UnauthorizedError (HTTP 401) แม้ว่าจะเป็น ResponseError ทั้งคู่ก็ตาม

อย่างที่เราจะได้เห็นกัน มักจะมีส่วนต่าง ๆ ของแอปพลิเคชันซึ่งเหมาะกว่าในการจัดการข้อยกเว้นเฉพาะในลักษณะที่แห้งกว่า

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

 def get_info begin response = HTTP.get(STOCKS_URL + "#{@symbol}/info") fail AuthenticationError if response.code == 401 fail StockNotFoundError, @symbol if response.code == 404 return JSON.parse response.body rescue JSON::ParserError retry end end

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

ต่อต้านการกระตุ้นให้จัดการกับข้อยกเว้นทันที

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

รูปแบบทั่วไปอย่างหนึ่งที่เราเห็นในแอปพลิเคชัน Rails (โดยเฉพาะรูปแบบที่แสดง JSON เท่านั้น API) คือวิธีการควบคุมต่อไปนี้:

 # app/controllers/client_controller.rb def create @client = Client.new(params[:client]) if @client.save render json: @client else render json: @client.errors end end

(โปรดทราบว่าแม้ว่าจะไม่ใช่ตัวจัดการข้อยกเว้นในทางเทคนิค แต่ตามการใช้งาน แต่ก็มีจุดประสงค์เดียวกัน เนื่องจาก @client.save จะคืนค่าเป็นเท็จเมื่อพบข้อยกเว้นเท่านั้น)

อย่างไรก็ตาม ในกรณีนี้ การทำซ้ำตัวจัดการข้อผิดพลาดเดียวกันในทุกการกระทำของตัวควบคุมนั้นตรงกันข้ามกับ DRY และทำให้การบำรุงรักษาและการขยายเสียหายเสียหาย แต่เราสามารถใช้ลักษณะพิเศษของการเผยแพร่ข้อยกเว้น และจัดการได้เพียงครั้งเดียวในคลาสตัวควบคุมหลัก ApplicationController :

 # app/controllers/client_controller.rb def create @client = Client.create!(params[:client]) render json: @client end
 # app/controller/application_controller.rb rescue_from ActiveRecord::RecordInvalid, with: :render_unprocessable_entity def render_unprocessable_entity(e) render \ json: { errors: e.record.errors }, status: 422 end

ด้วยวิธีนี้ เราจึงมั่นใจได้ว่าข้อผิดพลาด ActiveRecord::RecordInvalid ทั้งหมดได้รับการจัดการอย่างเหมาะสมและจัดการแบบ DRY ในที่เดียว ในระดับ ApplicationController พื้นฐาน สิ่งนี้ทำให้เรามีอิสระที่จะเล่นซอกับพวกเขา หากเราต้องการจัดการกรณีเฉพาะในระดับล่าง หรือเพียงแค่ปล่อยให้พวกเขาเผยแพร่อย่างงดงาม

ไม่จำเป็นต้องจัดการข้อยกเว้นทั้งหมด

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

ลองใช้ ActiveRecord เป็นตัวอย่างของโซลูชันในอุดมคติ ห้องสมุดมีแนวทางให้นักพัฒนาสองแนวทางเพื่อความสมบูรณ์ วิธีการ save จะจัดการกับข้อยกเว้นโดยไม่ต้องเผยแพร่ เพียงแค่คืนค่า false ในขณะที่ save! ทำให้เกิดข้อยกเว้นเมื่อล้มเหลว สิ่งนี้ทำให้นักพัฒนามีตัวเลือกในการจัดการกรณีข้อผิดพลาดเฉพาะอย่างแตกต่างออกไป หรือเพียงแค่จัดการกับความล้มเหลวในลักษณะทั่วไป

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

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

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

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

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

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

ปฏิบัติตามอนุสัญญา

การนำ Ruby ไปใช้งาน และที่ยิ่งกว่านั้นคือ Rails นั้นเป็นไปตามหลักการตั้งชื่อบางประการ เช่น การแยกความแตกต่างระหว่าง method_names และ method_names! กับ "ปัง" ใน Ruby คำว่า bang บ่งชี้ว่าเมธอดจะเปลี่ยนออบเจกต์ที่เรียกใช้ และใน Rails หมายความว่าเมธอดจะเพิ่มข้อยกเว้นหากไม่สามารถดำเนินการตามลักษณะการทำงานที่คาดไว้ได้ พยายามเคารพหลักการเดียวกัน โดยเฉพาะอย่างยิ่งถ้าคุณกำลังจะเปิดห้องสมุดของคุณ

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

อนุสัญญา Ruby อีกประการหนึ่งซึ่งมาจาก Jim Weirich คือการใช้ fail เพื่อระบุความล้มเหลวของวิธีการ และใช้เฉพาะการ raise หากคุณกำลังเพิ่มข้อยกเว้นอีกครั้ง

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

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

Logger.log(ทุกอย่าง)

แนวทางปฏิบัตินี้ใช้ไม่ได้กับข้อยกเว้นเพียงอย่างเดียว แต่หากมีสิ่งหนึ่งที่ควรบันทึกไว้ เสมอ ก็เป็นข้อยกเว้น

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

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

ความมั่นใจในรหัสที่สะอาด

การจัดการข้อยกเว้นที่สะอาดจะส่งคุณภาพโค้ดของคุณไปยังดวงจันทร์!
ทวีต

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

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

เราเห็นว่าการ "จับให้หมด" นั้นไม่ดี และปล่อยให้พวกมันลอยไปรอบๆ และพองตัวได้ก็ไม่เป็นไร

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

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

สุดท้าย เราได้พูดถึงประเด็นอื่นๆ ที่สามารถช่วยเพิ่มประโยชน์สูงสุดของข้อยกเว้น เช่น การปฏิบัติตามอนุสัญญาและการบันทึกทุกอย่าง

ด้วยหลักเกณฑ์พื้นฐานเหล่านี้ เรารู้สึกสบายใจและมั่นใจมากขึ้นในการจัดการกับกรณีข้อผิดพลาดในโค้ดของเรา และทำให้ข้อยกเว้นของเรามีความพิเศษอย่างแท้จริง!

ขอขอบคุณเป็นพิเศษสำหรับ Avdi Grimm และ Exceptional Ruby ที่ยอดเยี่ยมของเขา ซึ่งช่วยอย่างมากในการสร้างบทความนี้

ที่เกี่ยวข้อง: เคล็ดลับและแนวทางปฏิบัติที่ดีที่สุดสำหรับนักพัฒนา Ruby