Computer >> Máy Tính >  >> Lập trình >> Ruby

Xử lý mộc mạc trong Ruby

Trong khoảng sáu tháng qua, tôi đã làm việc với trình giả lập NES trong Rust. Như bạn có thể mong đợi, tôi đã học được rất nhiều về rỉ sét và thậm chí nhiều hơn nữa về các bộ phận bên trong NES. Nhưng trải nghiệm cũng đã thay đổi cách tôi nhìn nhận về Ruby.

Cụ thể, nó khiến tôi hơi hoang tưởng về các phương thức trả về nil .

Nếu bạn không ủng hộ điều gì đó, bạn sẽ gục ngã vì bất cứ điều gì

Cái gì nil nghĩa là trong Ruby? Gần như mọi thứ. Khi một phương thức trả về nil, nó có thể có nghĩa là:

  • Phương thức không có giá trị trả về
  • Thường có một giá trị trả lại nhưng không phải lúc này
  • Nó trả về một giá trị từ cơ sở dữ liệu, là NULL
  • Có điều gì đó không mong muốn đã xảy ra

Điều này làm cho mã khó đọc và là nguyên nhân chính của phổ biến nhất Ngoại lệ Ruby trong Ruby:NoMethodError . Là chủ sở hữu một phần của dịch vụ giám sát ngoại lệ, NoMethodError đang đưa con tôi đi học.

Nhìn vào đoạn mã sau. Nó trả về nil hầu hết thời gian vì if các câu lệnh đánh giá thành nil khi điều kiện không khớp và không có else .

def color_name(rgb)
  if rgb == 0x000000
    "black"
  end
end

color_name("#FFFFFF").titleize
=> NoMethodError: undefined method `titleize' for nil:NilClass

Nếu bạn là một nhà phát triển Ruby có kinh nghiệm, bạn biết cái lỗ thỏ này còn sâu hơn nhiều. Đôi khi những ý nghĩa khác nhau này của nil trùng lặp theo những cách kỳ lạ, khiến chúng ta không thể biết liệu - ví dụ - một giá trị trong cơ sở dữ liệu có phải là NULL hay không hoặc không có giá trị nào trong cơ sở dữ liệu.

Một cách tốt hơn

Trong Rust không có thứ gọi là nil . Thay vào đó, khi chúng ta muốn biểu thị rằng một hàm đôi khi trả về một giá trị và đôi khi trả về "không có gì", chúng ta sử dụng Option .

Options là một kiểu chứa một số giá trị cụ thể hoặc không chứa giá trị nào. Đây là những gì chúng trông giống như trong mã:

Option::Some(42); // Wraps the number 42 in an option
Option::None;     // Indicates "no result"

Điều này đã trông đẹp hơn so với nil đặc biệt của chúng tôi sử dụng, nhưng nó thậm chí còn tốt hơn. Trình biên dịch Rust buộc bạn phải xem xét None trường hợp. Bạn không thể vô tình bỏ qua nó.

match my_option {
  Some(x) => do_something_with_x(x),
  // If you remove the `None` match below, this code
  // won't compile.
  None => do_the_default_thing()  
}

Vì vậy, chúng tôi có thể viết ví dụ đặt tên màu của chúng tôi bằng gỉ như sau:

fn color_name(rgb: u32) -> Option<String> {
    if rgb == 0x000000 {
      Some("black".to_owned())
    } else {
      None
    }
}

Bây giờ chúng tôi buộc phải xử lý cả SomeNone điều kiện:

let name = color_name(0xFFFFFF);

let name = match color_name(0xFFFFFF) {
  Some(value) => value,
  None => "unknown".to_owned(),
}

Chắc chắn điều này hơi dài dòng và trông kỳ lạ, nhưng nó khiến bạn không thể bỏ qua trường hợp một hàm không trả về giá trị hữu ích. Điều đó có nghĩa là mã dễ hiểu và dễ bảo trì hơn.

Triển khai tùy chọn trong Ruby

Điều mà Ruby thiếu ở tính nghiêm ngặt, nó bù đắp cho sự linh hoạt. Tôi nghĩ sẽ rất thú vị nếu cố gắng triển khai một cái gì đó như Option trong Ruby.

Chúng tôi không thể tạo lỗi thời gian biên dịch trong Ruby, vì nó là một ngôn ngữ thông dịch. Nhưng chúng tôi có thể khiến mã không chính xác luôn luôn nêu ra một ngoại lệ, thay vì chỉ nâng một ngoại lệ khi bạn gặp một trường hợp cạnh.

Đầu tiên, hãy tạo hai lớp. Some giữ một giá trị chỉ đọc. None trống rỗng. Chúng đơn giản như chúng có vẻ.

  class Some
    attr_reader :value
    def initialize(value)
      @value = value
    end
  end

  class None
  end

Tiếp theo, chúng tôi sẽ tạo Option lớp chứa Some hoặc None và chỉ cho phép chúng tôi truy cập chúng khi chúng tôi cung cấp trình xử lý cho cả hai.

class Option
  def initialize(value)
    @value = value
  end

  def self.some(value)
    self.new(Some.new(value))
  end

  def self.none()
    self.new(None.new)
  end

  def match(some_lambda, none_lambda)
    if @value.is_a?(Some)
      some_lambda.call(@value.value)
    elsif @value.is_a?(None)
      none_lambda.call()
    else
      raise "Option value must be either Some or None"
    end
 end
end

Cuối cùng, chúng ta có thể viết lại ví dụ về màu sắc của mình để sử dụng Option mới lớp:

def color_name(rgb)
  if rgb == 0x000000
    Option.some("black")
  else
    Option.none()
  end
end

puts color_name(0x000000).match(
  -> value { value },
  -> { "no match" })

# Prints "black"

Kết luận

Tôi vẫn chưa thử kỹ thuật này trong một dự án thực tế. Tôi nghĩ rằng nó chắc chắn có thể ngăn chặn rất nhiều NoMethodErrors luôn luôn được đưa vào sản xuất. Nó trông hơi rườm rà và không giống Rubyish lắm nhưng tôi tưởng tượng rằng với một số cải tiến, một cú pháp dễ chịu hơn sẽ xuất hiện.