5

我正在用 Ruby 编写一个带有 Product 类的程序。每当使用错误类型的参数初始化产品时,我都会引发一些异常。有什么方法可以消除我提出的异常(我是否正确地指代了这些异常?)我感谢您的帮助。代码如下:

class Product
  attr_accessor :quantity, :type, :price, :imported

  def initialize(quantity, type, price, imported)
    raise ArgumentError.new("Type must be a string") if type.class != String
    raise ArgumentError.new("Quantity must be greater than zero") if quantity <= 0
    raise ArgumentError.new("Price must be a float") if price.class != Float

    @quantity = quantity
    @type     = type
    @price    = price.round(2)
    @imported = imported
  end
end
4

3 回答 3

9

惯用的方法是根本不进行类型检查,而是强制传递的对象(使用to_s,to_f等):

class Product
  attr_accessor :quantity, :type, :price, :imported

  def initialize(quantity, type, price, imported)
    raise ArgumentError.new("Quantity must be greater than zero") unless quantity > 0

    @quantity = quantity
    @type     = type.to_s
    @price    = price.to_f.round(2)
    @imported = imported
  end
end

然后您将获得适当的字符串/浮点数/等。传递的对象的表示形式,并且如果他们不知道如何强制转换为这些类型(因为他们不响应该方法),那么您将适当地获得 NoMethodError。

至于数量检查,这看起来很像验证,您可能希望将其提取到单独的方法中(特别是如果它们很多的话):

class Product
  attr_accessor :quantity, :type, :price, :imported

  def initialize(quantity, type, price, imported)
    @quantity = quantity
    @type     = type.to_s
    @price    = price.to_f.round(2)
    @imported = imported

    validate!
  end

  private

  def validate!
    raise ArgumentError.new("Quantity must be greater than zero") unless @quantity > 0
  end
end
于 2013-10-27T17:00:38.930 回答
1
class Product
  attr_accessor :quantity, :type, :price, :imported

  def initialize(quantity, type, price, imported)
    raise ArgumentError.new "Type must be a string" unless type.is_a?(String)
    raise ArgumentError.new "Quantity must be greater than zero" if quantity.zero?
    raise ArgumentError.new "Price must be a float" unless price.is_a?(Float)

    @quantity, @type, @price, @imported = quantity, type, price.round(2), imported
  end
end
于 2013-10-27T16:49:47.980 回答
1

您可以执行以下操作,尽管我希望有一些宝石可以做到这一点,并且做得更好:

module ArgCheck
  def type_check(label, arg, klass)
    raise_arg_err label + \
      " (= #{arg}) is a #{arg.class} object, but should be be a #{klass} object" unless arg.is_a? klass
  end

  def range_check(label, val, min, max)
    raise_arg_err label + " (= #{val}) must be between #{min} and #{max}" unless val >= min && val <= max
  end

  def min_check(label, val, min)
  puts "val = #{val}, min = #{min}"
    raise_arg_err label + " (= #{val})  must be >= #{min}" unless val >= min
  end

  def max_check(val, min)
    raise_arg_err label + " (= #{val})  must be <= #{max}" unless val <= max
  end    

  # Possibly other checks here  

  private

  def raise_arg_err(msg)
    raise ArgumentError, msg + "\n  backtrace: #{caller_locations}"
  end   
end

class Product
  include ArgCheck 
  attr_accessor :quantity, :type, :price, :imported

  def initialize(quantity, type, price)
    # Check arguments  
    min_check   'quantity', quantity, 0
    type_check  'type',     type,     String
    type_check  'price',    price,    Float

    @quantity = quantity
    @type     = type
    @price    = price.round(2)
  end
end

product = Product.new(-1, :cat, 3)
#  => arg_check.rb:23:in `raise_arg_err': quantity (= -1)  must be >= 0 (ArgumentError)
#    backtrace: ["arg_check.rb:11:in `min_check'", "arg_check.rb:33:in `initialize'", \
#      "arg_check.rb:43:in `new'", "arg_check.rb:43:in `<main>'"]

product = Product.new(1, :cat, 3)
#  => arg_check.rb:26:in `raise_arg_err': type (= cat) is a Symbol object, \
#       but should be be a String object (ArgumentError)
#       backtrace: ["arg_check.rb:3:in `type_check'", "arg_check.rb:34:in `initialize'", \
#         "arg_check.rb:48:in `new'", "arg_check.rb:48:in `<main>'"]

product = Product.new(1, "cat", 3)
#  => arg_check.rb:23:in `raise_arg_err': price (= 3) must be a Float object (ArgumentError)
#       backtrace: ["arg_check.rb:3:in `type_check'", "arg_check.rb:35:in `initialize'", \
#       "arg_check.rb:53:in `new'", "arg_check.rb:53:in `<main>'"]

product = Product.new(1, "cat", 3.00) # No exception raised

请注意,在 irb 中运行时,Kernel#caller_locations会带来很多您不想要的东西,而从命令行运行时您不会得到这些东西。

于 2013-10-27T21:43:54.807 回答