← Ruby EnglishChapter 09 of 13

Error Handling

## Learning Objectives - Handle exceptions with begin-rescue - Use ensure for cleanup - Raise custom exceptions - Create custom exception classes ## Exception Hierarchy ```text Exception ├── StandardError │ ├── ArgumentError │ ├── NameError │ ├── NoMethodError │ ├── TypeError │ ├── RuntimeError │ └── ... └── SystemExit ``` ## Basic Exception Handling ### begin-rescue ```ruby begin result = 10 / 0 rescue puts "An error occurred" end ``` ### With Error Variable ```ruby begin result = 10 / 0 rescue => e puts "Error: #{e.class} - #{e.message}" end ``` ### Multiple rescue Clauses ```ruby begin # Code that might fail result = risky_operation rescue ArgumentError => e puts "Invalid argument: #{e.message}" rescue NoMethodError => e puts "Method not found: #{e.message}" rescue => e puts "Unexpected error: #{e.message}" end ``` ### rescue as Modifier ```ruby result = risky_operation rescue default_value ``` ## Ensure `ensure` runs whether an exception occurs or not: ```ruby begin file = File.open("example.txt", "r") content = file.read rescue Errno::ENOENT => e puts "File not found: #{e.message}" ensure file.close if file end ``` ## Retry `retry` re-executes the begin block: ```ruby attempts = 0 begin attempts += 1 connect_to_service rescue ConnectionError if attempts < 3 puts "Retrying... (attempt #{attempts})" retry else puts "Failed after #{attempts} attempts" end end ``` ## Raise ### Raising Standard Errors ```ruby def divide(a, b) raise ArgumentError, "Divisor cannot be zero" if b == 0 a / b end ``` ### Raising Custom Exceptions ```ruby def withdraw(amount) raise "Insufficient funds" if amount > @balance @balance -= amount end # Better: use specific exception class class InsufficientFundsError < StandardError def initialize(balance, amount) super("Cannot withdraw #{amount}, balance is #{balance}") end end def withdraw(amount) raise InsufficientFundsError.new(@balance, amount) if amount > @balance @balance -= amount end ``` ## Custom Exception Classes ```ruby class ValidationError < StandardError attr_reader :field def initialize(message, field = nil) super(message) @field = field end end def validate_age(age) unless age.is_a?(Integer) raise ValidationError.new("Age must be an integer", :age) end unless age >= 0 raise ValidationError.new("Age cannot be negative", :age) end end ``` ## Catch and Throw Not exceptions - used for flow control: ```ruby result = catch(:found) do [1, 2, 3, 4, 5].each do |n| throw(:found, n * 2) if n == 3 end nil end puts result # 6 ``` ## Exception Methods ```ruby begin 10 / 0 rescue => e puts e.class # ZeroDivisionError puts e.message # divided by 0 puts e.backtrace # Array of location strings puts e.backtrace.first # First line of trace end ``` ## Best Practices ### Do Not Suppress Exceptions ```ruby # Bad begin do_something rescue # Do nothing - silently ignore end # Good begin do_something rescue SomeError => e handle_error(e) end ``` ### Raise Early, Catch Late ```ruby # Raise exceptions early (fail fast) def process_user(data) raise ArgumentError, "User data required" if data.nil? # ... rest of processing end # Catch exceptions at appropriate level def import_users users.each do |user_data| begin process_user(user_data) rescue ValidationError => e log_warning("Skipping invalid user: #{e.message}") end end end ``` ### Use Specific Exceptions ```ruby # Bad - too generic begin File.open(data_file) rescue # Handle error end # Good - specific exception begin File.open(data_file) rescue Errno::ENOENT puts "Data file not found" rescue Errno::EACCES puts "Permission denied" end ``` ## Exception Handling Patterns ### nil Pattern ```ruby def find_user(id) begin User.find(id) rescue ActiveRecord::RecordNotFound nil end end # Or with rescue modifier def find_user(id) User.find(id) rescue nil end ``` ### Default Value Pattern ```ruby config = begin YAML.load_file("config.yml") rescue Errno::ENOENT {} end ``` ### Logging Pattern ```ruby require 'logger' logger = Logger.new(STDOUT) begin risky_operation rescue => e logger.error("#{e.class}: #{e.message}") logger.debug(e.backtrace.join("\n")) raise # Re-raise after logging end ``` ## Summary - `begin...rescue...ensure` handles exceptions - `rescue ErrorType => e` captures specific errors - `raise` throws an exception - `ensure` runs cleanup code always - `retry` re-executes begin block - `catch/throw` for non-exception flow control - Create custom exceptions by subclassing `StandardError` - Always handle specific exceptions, not all exceptions - Use `ensure` for cleanup (file close, etc.)

Comments

Comments powered by Giscus

To enable comments, add your Giscus embed code here.

Learn more about Giscus →