【优雅的Ruby】CONFIDENT RUBY 读书笔记(一)

December 05, 2018 15:10
访问量:373
摘要:本书和Ruby元编程,Ruby原理解析姐妹篇。适用一到两年以上的ruby开发者,进阶必备。对很多细节的东西讲的很详细,减少bug, 提高代码质量,让你的Ruby代码更加优雅。

前言

1.一般方法由4个部分组成:

  • 输入处理
  • 功能实现
  • 输出处理
  • 异常处理

2.面向对象的基础就是-消息。 任何方法都要找到他的角色和消息 role(caller), message.

使用内置的类型转化协议

  • 显示类型转化: to_a, to_h, to_i, to_s ...
  • 隐式类型转化: to_ary, to_hash, to_int, to_str ...

有些情况,ruby会隐式的自动区调用 to_str,to_ary这种方法,有时候就需要手动去to_a,to_s. 下面举个例子:

class ArticleTitle
  def initialize(text)
    @text = text
  end

  def to_str
    @text
  end
end

title = ArticleTitle.new('Cool Title')
"Today's title is:" + title  => '......'

# 这里使用String + ArticleTitle对象是可以工作的。因为定义了 to_str, 会隐式自动调用该方法。

另外一个例子:

arr = ['first item', 'second item']

Item = Struct.new(:index, :name)
first = Item.new(0, "first")
second = Item.new(1, "second")
arr[first] => 'typeerror'

#但是可以隐式的定义 to_int

Item = Struct.new(:index, :name) do
    def to_int
        index
    end
end

first = Item.new(0, "first")
second = Item.new(1, "second")
arr[first] => 'first item'

Ruby会自动在数组的下标参数调用 to_int方法, 以便将其转化成integer

隐式的转化方法比显式的要严格一些。 比如 nil.to_i => 0 , nil.to_int => error. 显示要更灵活, 隐式能够确保代码简洁和保持参数输入的一致性,起到门卫的作用。

内置强制的类型转化方法

确保输入一定是某种类型: Integer(), Array(), Float(), String() ....

实战中 Array.wrap() 用的比较多

优点:

  • 幂等性。 对于非目标类型会转化为目标类型, 如果已经是目标类型,原样返回。
  • 相比于 to_* 方法,这种可以转化的类型更多。

特例:

“I am cool”.to_i  => 0
Integer(“I am cool”)  =>  error

对待字符串更加严格。

Hash[]

arr = ['name', 'wen', 'size', 'big']
Hash[*arr] => {'name' => 'wen', 'size'=> 'big'}

自定义强制类型转化

通过自定义幂等的强制类型转换的方法,有几个技巧可以说一下。

1.module_function 这个关键字:

moudule Conversions
  module_function

  def Point(*args)
  end
end

module_function 做了两件事: 1.把后面的方法都标注成了私有方法。 2.把后面的方法变成了当前模块的单例方法。

2.一个特别适用的技巧: 使用 Lambdas 表达式作为case 分支。

因为case语句用的是三等于运算符(===),而 三等于运算符是 #call 方法的别名。

even = ->(x) {(x % 2) == 0}

even === 4  => true
even === 9  => false

def test_lambda(number)
    even = ->(x) {(x % 2) == 0}
    case number
    when 42
        puts 'ture'
    when even
        puts 'even'
    else
        puts 'odd'
    end
end

利用先决条件排除非法输入

在方法的最开始,去处理非法的参数, 让程序今早的抛出异常,总好过局部正常,将来突然出现让人 confuse 的异常问题。

def hire_date(date)
  raise ArgumentError, '无效的date' unless data.is_a?(Date)

  ...
end

合理使用 #fetch 这个强大的方法

确保Hash键的存在性

基本原理: fetch 方法非常简洁,而且可以避免 Hash 元素中合法的 nil 或 false 引发的bug.

# 如果传入的参数是 false or nil也会引起报错。
def add_user(attributes)
    login = attributes[:login]
    raise ArgumentError unless login
    password = attributes[:password]   
    raise ArgumentError, 'password must be supplied' unless password
    ...
end

# 像这种对先决条件冗长的检查,完全可以用 fetch 方法来简化。
# 这里就算参数是false nil程序会继续运行。
def add_user(attributes)
    login = attributes.fetch(:login)
    password = attributes.fetch(:password) do
      raise KeyError, 'password must be supplied' 
    end
    ...
end

Hash[key] 和 Hash.fetch 区别就是在于当 key不存在时,使用[]会返回nil, 而使用fetch会报出KeyError这个Ruby内置异常。

自定义 #fetch

fetch 还接受一个 block 作为参数。 当有代码块时,fetch遇到key缺失的情况下,会执行block中的内容。 key存在时,block被忽略(上面的代码很好的解释了这点)

值得注意的是, Hash不是唯一定义了#fetch的核心类, Array和单例对象 ENV 上也能发现它的踪迹。 Array.fetch 抛出的不是 KeyError 而是 IndexError.

利用fetch 提供默认参数

一般我们会用这这样的方式给一个变量默认值:

logger = options[:logger] || default_logger

使用fetch: logger = options.fetch(:logger) { default_logger }

可重用的fetch代码块

DEFAULT_LOGGER = -> { default_logger }
logger = options.fetch(:logger, &DEFAULT_LOGGER)

双参数的 fetch

如果你熟悉 fetch, 会疑惑为什么不使用2个参数,而要使用代码块呢。 logger = options.fetch(:logger, default)

如果default 是一个非常复杂,性能开销比较大的方法呢?

def default
  ..性能开销大的操作, 比如访问外部的 api.
end

fetch双参数,不管default有没有被使用,该方法总是被执行.
然而使用block的时候,只有是logger这个key不存在的时候才能调用 default方法。

总结: 既然fetch这么强大,以后我们要用这种方式去替代以前的 || ||= 这种方式。 并且,使用block的方式,而不是双参数。

用断言验证假设

场景: 比如方法接受的参数,是调用外部的api返回的数据时,不太确定数据的类型。

贴上一段充满断言修改后的代码:

class Account
    def refresh_transactions
        transactions = bank.read_transactions(account_number)
        # 这里确保对象是一个数组, 先决条件
        transactions.is_a?(Array) or raise TypeError, "not an Array"
        transactions.each do |transaction|
            # 如果没有 key 'amount' 会抛出 TypeError 异常。
            amount = transaction.fetch('amount') 
            # '$3.45'.to_f => 0.0   使用Float() 会抛出异常
            amount_cents = (Float(amount) * 100).to_i  
            cache_transaction(:amount => amount_cents)
        end
    end
end

有时候我们对第三方提供的数据格式少之又少,对数据一致性也毫无把握。 这时我们用这样的方式来表明我们的假设: 假设出错时,就可以立即知晓。 在入口处断言假设,避免内部代码受到干扰。假设外部系统的假设过期,就及时警告我们。

用卫语句来处理特殊场景

处理特殊情况, 在方法的开始,一旦不符合条件,立即返回。

def log_reading(readings)
  return if @quiet
  ...
end

评论

暂无相关评论,快来抢占沙发吧!
评论框离家出走了,点击找回!
昵称
邮箱
网站
昵称
邮箱
网站