元编程


  • 避免无谓的元编程。
    [link]


  • 当编写程序库时,不要使核心类混乱(不要使用 monkey patch)。
    [link]


  • 对于 class_eval 方法,倾向使用区块形式,而不是字符串插值形式。
    [link]

    • 当使用字符串插值形式时,总是提供 __FILE____LINE__,以使你的调用栈看起来具有意义:

      1. class_eval 'def use_relative_model_naming?; true; end', __FILE__, __LINE__
    • 倾向使用 define_method 而不是 class_eval { def ... }


  • 当使用 class_eval(或其他的 eval)的字符串插值形式时,添加一个注释区块来说明它是如何工作的(来自 Rails 代码中的技巧)。
    [link]

    1. # 摘录自 activesupport/lib/active_support/core_ext/string/output_safety.rb
    2. UNSAFE_STRING_METHODS.each do |unsafe_method|
    3. if 'String'.respond_to?(unsafe_method)
    4. class_eval <<-EOT, __FILE__, __LINE__ + 1
    5. def #{unsafe_method}(*params, &block) # def capitalize(*params, &block)
    6. to_str.#{unsafe_method}(*params, &block) # to_str.capitalize(*params, &block)
    7. end # end
    8. def #{unsafe_method}!(*params) # def capitalize!(*params)
    9. @dirty = true # @dirty = true
    10. super # super
    11. end # end
    12. EOT
    13. end
    14. end

  • 避免使用 method_missing。它会使你的调用栈变得凌乱;其方法不被罗列在 #methods 中;拼错的方法可能会默默地工作(nukes.launch_state = false)。优先考虑使用委托、代理、或是 define_method 来替代。如果你必须使用 method_missing 的话,务必做到以下几点:
    [link]

    • 确保同时定义了 respond_to_missing?
    • 仅仅捕获那些具有良好语义前缀的方法,像是 find_by_*——让你的代码愈确定愈好。
    • 在语句的最后调用 super
    • 委托到确定的、非魔术的方法,比如:

      1. # 差
      2. def method_missing?(meth, *params, &block)
      3. if /^find_by_(?<prop>.*)/ =~ meth
      4. # ... 一堆处理 find_by 的代码
      5. else
      6. super
      7. end
      8. end
      9. # 好
      10. def method_missing?(meth, *params, &block)
      11. if /^find_by_(?<prop>.*)/ =~ meth
      12. find_by(prop, *params, &block)
      13. else
      14. super
      15. end
      16. end
      17. # 最好的方式可能是在每个需要支持的属性被声明时,使用 define_method 定义对应的方法

  • 倾向使用 public_send 而不是 send,因为 send 会无视 private/protected 的可见性。
    [link]

    1. module Activatable
    2. extend ActiveSupport::Concern
    3. included do
    4. before_create :create_token
    5. end
    6. private
    7. def reset_token
    8. ...
    9. end
    10. def create_token
    11. ...
    12. end
    13. def activate!
    14. ...
    15. end
    16. end
    17. class Organization < ActiveRecord::Base
    18. include Activatable
    19. end
    20. linux_organization = Organization.find(...)
    21. # 差 - 会破坏对象的封装性
    22. linux_organization.send(:reset_token)
    23. # 好 - 会抛出异常
    24. linux_organization.public_send(:reset_token)

  • 倾向使用 __send__ 而不是 send,因为 send 可能会被覆写。
    [link]

    1. require 'socket'
    2. u1 = UDPSocket.new
    3. u1.bind('127.0.0.1', 4913)
    4. u2 = UDPSocket.new
    5. u2.connect('127.0.0.1', 4913)
    6. # 这里不会调用 u2 的 sleep 方法,而是通过 UDP socket 发送一条消息
    7. u2.send :sleep, 0
    8. # 动态调用 u2 的某个方法
    9. u2.__send__ ...