Ruby 从 Rails 中学习元编程的使用场景

adamshen · 2017年03月27日 · 最后由 johnsafe 回复于 2017年05月18日 · 6392 次阅读
本帖已被管理员设置为精华贴

Ruby 有许多元编程的特性,到底什么时候该用呢?这是个值得思考的问题

在不该用的地方使用这些特性,只能影响程序的可维护性,违反了写代码的蠢萌原则 (Kiss)

其实 Rails 里,就有大量使用元编程的例子。总结下来,主要目的有这么几种:

一、Rails 中元编程的使用场景

1.1 简化接口

在刚开始学习 Rails 的时候,我常常被 Rails dsl 的表达能力给震惊到

before_action :require_no_sso!, only: [:new, :create]
has_many :users, through: :team_users
resources :teams
...

短短几个单词能够干的事情很多,而为了支持这样的语法,就要在 dsl 的原方法里使用元编程来动态定义和调用一些方法

这种极简化接口的特点就是,只用最少数的语句描述关键行为,将实现的细节完全隐藏,达到代码的高度简洁

1.2 让代码更加语义化

比如 1.day.ago 这种完全可以读出来的代码,要用 money patch 来实现。

(4.months + 5.years).from_now

各种 hack scope,动态扩展类,使原本需要用函数调用实现的功能,还是依然可以用优雅的链式调用来进行表达

比如 ActiveRecord 里的 wherechain,写起来十分的优雅,令人感到舒服

Book.where('title LIKE ?', '%Rails%').order(:created_at).limit(5).update_all(author: 'David')

1.3 Warning & Patch

比如 Api 变了,可以用 alias_method 来做个兼容

# *_action is the same as append_*_action
 alias_method :"append_#{callback}_action", :"#{callback}_action"

 define_method "append_#{callback}_filter" do |*names, &blk|
   ActiveSupport::Deprecation.warn("append_#{callback}_filter is deprecated and will be removed in Rails 5.1. Use append_#{callback}_action instead.")
   send("append_#{callback}_action", *names, &blk)
 end

ruby 的动态特性使得在进行版本兼容的时候可以更加随心所欲

if RUBY_VERSION > "..."
    do something
end

1.4 DRY,减少重复性代码

如果要用相同的逻辑 wrapper 一些方法,或者注册一些逻辑相同的 helper 方法时,可以用元编程来减少代码量

%w(info debug warn error fatal unknown).each do |level|
  class_eval <<-METHOD, __FILE__, __LINE__ + 1
        def #{level}(progname = nil, &block)
          logger.#{level}(progname, &block) if logger
        end
  METHOD
end

1.5 实现功能性需求

有些 api 本身就需要利用语言的动态特性,这时就不得不用了

def helper_method(*meths)
  meths.flatten!
  self._helper_methods += meths

  meths.each do |meth|
    _helpers.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
            def #{meth}(*args, &blk)                             
              controller.send(%(#{meth}), *args, &blk) 
            end                                                    
    ruby_eval
  end
end

再者,如果想扩展或者改变一些 ruby 语言的特性,就可以用到元编程

例如 autoload 和 ActiveSupport::Concern 或者 mattr_accessor 和 class_attribute 这样的功能,原生的 ruby 是不支持的,但是可以通过元编程来进行支持

二、总结

当你想实现的功能本来就依赖 ruby 的动态特性时,就必须要使用元编程。

另外元编程可以为模块化、语义化和 Dsl 提供支撑,让我们可以写更少的代码,写更明白的代码。

jasl 将本帖设为了精华贴。 04月01日 03:25

money patch😅 我愣了一下。。

都被《元编程》那本书讲完了

Ruby 的各种黑魔法其实都有一致性 - 都是方法调用😀

naivecai 回复

哈哈 笑噴 typo

ecnelises 回复

一语中的,刚刚学的时候还以为是关键字呢。。。

7 楼 已删除

个人感觉,还没有对元编程的本质说到位。 元编程的主要使用是用于对模式进行再次抽象,抽象出模式的模式。 至于使用哪种方式实现元编程,是个技术问题。学习一个东西我们需要尽可能的通过现象抓住本质, 当然如何通过现象抓住本质,不是一个简单的问题,一般来说我们需要通过不断的学习和总结来找出本质,就想盲人摸象一样。多从不同的角度去尝试,可能会对理解问题的实质有帮助。

后记: 世界上本无元编程。只有编程。但是每种语言不管在强大都有其表达能力的局限性。我们不妨把元编程看成是对这种局限性的一种弥补方式。

lilijreey 回复

很多所谓的设计模式是语言的导致问题。一个问题既然能用编程解决,就没有模式的存在了,也没有什么再次抽象一说。

lilijreey 回复

这篇文章只是从 Rails 中学习一些元编程的使用场景,并没有野心要探讨一些高层次的东西。

saiga 回复

我所说的模式是对现实世界模型的抽象和设计模式不是一个东西。我们在讨论元编程不知你为何会扯到设 w 计模式上,给我的感觉装的感觉

adamshen 回复

我大概知道楼主的意思了,我觉得题目改为 rails 中常用 Ruby 元编程方法更贴近楼主的意思。

lilijreey 回复

你是觉得标题用词不妥吗?每个人对词语语境的理解不一样,我倒是觉得标题可以描述我这篇文章的主旨。再说一篇文章而已,你又是纠结内容,又是纠结标题,这有必要?

lilijreey 回复

...好吧😂 你说模式,我理解成设计模式应该不过分吧,毕竟以前有跟元编程一起讨论过。另外,我只是根据自己的理解跟你讨论一下而已,哪个字眼表现出我装来了,我改= =

刚作 ruby 那两年,喜欢在代码里搞些元变成的小技巧,后来发现过度的元变成带来了代码可读性差,修改困难,甚至性能问题。后来写代码,只有个别较基础的服务会使用一些元编程,其他的很多时候都避免使用,感觉好的代码在保证性能的前提下,应该是更容易懂的代码,太过晦涩,增加了培训成本。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号