《Effective Ruby》 总结

234 阅读3分钟
  1. 除nil和false外所有值都是真值,0也是true;用nil?区分false和nil。
  2. 常量是可变的,需总是将常量、数组常量、模块定义的常量freeze冻结。
module Defaults 
NETWORKS = ["192.168.1", "192.168.2"].freeze 
end 
def host_addresses(host, networks=Defaults::NETWORKS) 
networks.map {|net| net << ".#{host}"} 
end 
p host_addresses(1) 
["192.168.1.1", "192.168.2.1"] 
p Defaults::NETWORKS 
["192.168.1.1", "192.168.2.1"] # 改变了 
# 使用 
["192.168.1", "192.168.2"].map!(&:freeze).freeze 
  • setter方法在调用时需要显式的接收者,没有接收者时,会被解析为变量赋值。
class Counter 
attr_accessor(:counter) 
def initialize 
# 错误 
counter = 0 
# 正确的应该是 self.counter = 0 
end 
# 只是简单的变量赋值,在initialize执行时只是创建了一个新的局部变量counter赋值为0,结束的时候便丢弃了这个局部变量。 
end 
  • 实例方法中调用 setter 时,需要使用self作为接收者。
  • 在调用非 setter 方法时,不要总是使用self,会弄乱代码。
class Counter 
attr_accessor(:counter) 
def initialize(counter) 
self.counter = counter 
end 
def full 
# 类似这种不是 `赋值` 的就不要用self。 
self.counter + "hello" 
end 
  1. 使用Struct而非Hash存储结构化数据。
class Weather 
Temperature = [ 
{ 
date: '2018-11-1', 
high: '30', 
low: '20' 
}, 
{ 
date: '2018-11-2', 
high: '31', 
low: '21' 
}, 
{ 
date: '2018-11-3', 
high: '32', 
low: '22' 
} 
] 
Reading = Struct.new(:date, :high, :low) 
def initialize 
@readings = [] 
# Temperature.each do |temp| 
# @readings << { 
# :date => temp[:date], 
# :high => temp[:high], 
# :low => temp[:low] 
# } 
# end 
Temperature.each do |temp| 
@readings << Reading.new(temp[:date], temp[:high], temp[:low]) 
end 
end 
def diff 
sum = 0 
# @readings.each do |reading| 
# sum += reading[:high].to_f - reading[:low].to_f 
# end 
@readings.each do |reading| 
sum += reading.high.to_f - reading.low.to_f 
end 
sum 
end 
end 
  • 通过在模块中嵌入代码来创建命名空间,让命名空间结构和目录结构相同。
# 对应文件夹 notebooks/bindings.rb 
module Notebooks 
class Binding 
... 
end 
end 
  • 如果使用时可能出现歧义,使用“::”来限定顶级常量。
module Cluster 
class Array 
def initialize(n) 
@disk = Array.new(n){|i| "disk#{i}"} # wrong Array! SystemStackError! 
@disk = ::Array.new(n){|i| "disk#{i}"} # bingo 
end 
end 
end 
  1. 使用Array方法将nil及标量对象转换成数组,不要传递Hash。
[7] pry(main)> Array(nil) 
=> [] 
[12] pry(main)> Array(['a','b','c']) 
=> ["a", "b", "c"] 
[13] pry(main)> Array({a: 1, b: 2}) 
=> [[:a, 1], [:b, 2]] 
  1. 通过protected方法共享私有方法。
  2. reduce 总是要给累计器一个初值。
def sum(enum) 
enum.reduce(0) do |accumulator, element| 
accumulator + element 
end 
end 
def sum(enum) 
enum.reduce(0, :+) 
# or 
enum.reduce(&:+) 
end 
  • 给予 reduce 的块务必要返回一个累加器。
users.reduce([]) do |names, user| 
names << user.name if user.age >= 21 
names 
end 
  • 将数组转换为哈希,reduce更有效。
array.reduce({}) do |hash, element| 
hash.update(element => true) 
end 
  1. 考虑使用默认哈希值,推荐使用 fetch 更安全,第一个参数为key,第二个参数为找不到key时的返回值。
h = {} 
h[:weekdays] = h.fetch(:weekdays, []) << "Monday" 
  1. 使用定制的异常而不是抛出字符串。
  2. 新的异常类必须继承标准异常类。任何其他的尝试都会导致一个TypeError。
  3. 标准异常类形成了一套以Exception为基类的继承体系。但它以及它的许多子类被认为是低级别错误。多数标准异常应继承自StandardError。
  4. 通常异常类名为 Error 作为后缀。
class TemperatureError < StandardError 
attr_reader(:temperature) 
def initialize(temperature) 
@temperature = temperature 
super("invalid temperature: #{@temperature}") 
end 
end 
raise(TemperatureError.new(180)) 
  1. 捕获可能的最具体的异常。只捕获那些你知道如何恢复的异常。首先处理最特殊的类型,在异常的继承关系中,位置越高的,越应该排在 rescue 后面。
begin 
task.perform 
rescue NetworkConnectionError => e 
# Retry logic... 
rescue InvalidRecordError => e 
# Send record to support staff... 
rescue => e 
service.record(e) 
raise 
ensure 
... 
end 
  1. 永远不要无条件retry,要把它看作代码中的隐式循环,在代码块外围定义重试次数。
retries = 0 
begin 
service.update(record) 
rescue VendorDeadLockError => e 
raise if retries >= 3 
retries += 1 
logger.warn("API failure: #{e}, retrying...") 
sleep(5 * retries) 
retry 
end 
  1. 性能分析工具Gem stackprof 和 memory_profiler
  2. 将循环中不会变化的对象字面量变成常量。
errors.any? {|e| e.code == "FATAL".freeze} # 相当于把它作为常量了