瞎扯淡 Ruby 是否应该在语法上支持 AOP?

ibachue · 2012年12月06日 · 最后由 JiangYongKang 回复于 2017年12月18日 · 10446 次阅读

虽然 Ruby 有几个 AOP 库,但是这些 AOP 库最大的缺点就是使用繁琐,而且调试也不方便。 如果 Ruby 可以 native 的支持 AOP 的话就好了,就像 AspectJ 那种。

我不知道 AOP 是什么,以前讨论过几次这个话题,看有没有帮助

http://ruby-china.org/topics/6681 http://ruby-china.org/topics/4676

#1 楼 @Rei 还真的没有什么帮助 很多人把 AOP 想的太简单了我觉得 Ruby 实现 AOP 其实没有这么简单,AOP 本身非常灵活,Ruby 本身也非常灵活,把一个灵活的功能加入一个灵活的语言,这个难度不知道比加入到象 Java 这种死板语言中大多少倍。

AOP 的解释我觉得问号课堂里面已经讲的很清楚了 你可以看看 http://mba.shengwushibie.com/itbook/BookChapter.asp?id=34730

#2 楼 @iBachue 还是给个代码看比较清楚,展示一下解决了什么问题,或者减少了多少代码,让代码清晰了多少。

我甚至不知道 OOP 是什么,就知道把方法和变量按一些作用域拆分,避免全局方法和变量,这样方便记忆,错误也好调试。

#3 楼 @Rei 说起来很简单 比如在一个复杂的系统中,我们有大量的重要方法现在需要纪录日志,比如纪录 stack,纪录传入参数,返回值等等。 传统的方法也就是最傻的办法是在这些方法前后增加 log 语句,但是这样做修改的规模可能非常大,并且有可能搜索不完全,比如那些要被动态 eval 那些字符串代码里可能也包含了这些重要方法,你用 grep 基本搜索不到,除此以外代码也不美观,也增加了耦合度(毕竟日志不是必需的逻辑,不该存在于重要的系统中)。直接在方法里面增加日志也不是很好的办法,虽然修改量很小,耦合度却增加了。 AOP 的解决方法是申请对某个类中的某些方法(可以用正则表达式匹配)进行代码注入,也就是在这个方法被执行前后增加所需的代码。 比如 UserShoppingSystem 和 EnterpriseShoppingSystem 里的 buy_products 和 sell_products 是重要方法,需要被纪录日志

class UserShoppingSystem
  def buy_products(...)
     ...
  end

  def sell_products(...)
     ...
  end
end

class EnterpriseShoppingSystem
  def buy_products(...)
     ...
  end

  def sell_products(...)
     ...
  end
end

你当然可以遍历搜索代码,找到调用这些方法的地方来为其增加 log 语句,当然缺陷很明显,之前已经说了。 我现在用 AOP 代码(当然是我假象的)来解决这个问题

aop /ShoppingSystem$/./_products$/
   around(method_name, params)
      log("#{method_name} #{params}")
      return_val = yield
      log("return: #{return_val}")
   end
end

只要上述代码可以被执行到(无论任何地方,没有执行先后次序的要求),以 ShoppingSystem 结尾的类中所有方法名以_products 结尾的方法,被调用前后都会先执行到相应的 AOP 代码。

实现 AOP 的最大难度在于 Ruby 是可以动态添加和修改方法的,而且手段极多。Java 没有类似问题,所以简单。由于 AOP 并不是针对单一方法进行注入的,而是可以使用正则表达式对方法和参数进行匹配的。一个方法可能在 AOP 被申明后添加进去,如果仅仅只是添加在这个类本身,那还好点,有个 method_added 的 callback 可以用,但如果是添加在 Singleton 里呢,添加在 module 里然后 include 进去呢,在父类里添加呢?这些都不是容易实现的。 除此以外,由于 AOP 功能实在太强大,如果不注意有可能会产生非常奇怪的行为,引入 bug,因此需要支持良好的跟踪和调试。这些都不是一般的解决方案就能够解决的。

AOP 要解决的问题,在 Ruby 中不存在...

如果真想用 AOP 处理问题,你可以用 aspectJ 编译 java 代码然后用 JRuby 引入...

#5 楼 @luikore

  1. 那你如何解决我上面所述的大型系统的日志纪录问题呢?还是你认为大型 Ruby 系统是不存在的?
  2. 要解决一个问题就不得不更换语言不是很幽默嘛?

几乎所有 AOP 示例都拿打日志做例子,其实在 Java 有 dynamic proxy 后这个应用场景已经几乎没有了...

#7 楼 @luikore 日志很典型嘛。我们在讲 Transaction 的时候还不是都拿银行转帐做例子?

如果对普通业务代码和其它 AOP 代码不做区分,通通都要 AOP,并且一定要让被植入逻辑的目标代码对此一无所知,这个难度并不小,但这对 java 也是一样,因为这更象是对 AOP 代码进行 AOP,然而 java 工程师们并没有为此纠结,因为这个问题本身也许是个伪命题。

看 LZ 说的 ActiveSupport 就可以解决吧,而且不是很繁琐

#9 楼 @fsword 额 其实 Java 程序员也不纠结有没有使用 Ruby。PHP 程序员也不纠结没有 Rails 这样的框架,那么 Ruby 和 Rails 是否也没有存在的必要?任何图灵完备的语言都可以解决任何问题,反正可以靠人力堆代码呢。

alias_method_chain? 不行的话 Callbacks? 我不知道什么是 AOP 不过从你描述来说这两个绝对够了

在 spring 中实现 aop 需要 xml 文件来进行配置,那么在 ruby 中是不是我用配置文件或者注解等方式来实现的话也算呢

#11 楼 @iBachue 你没理解我的意思,java 工程师们想出了 AOP 技术来解决一些通用问题,但是他们没有在多个 AOP 组件之间建立更进一步的 AOP over AOP 的技术结构,而只是简单的规定——”AOP 功能代码要保持自我完备和功能最小化”,这样,开发 AOP 功能仅仅针对“普通代码”。

ruby 也是一样,类似 AOP 的功能模块重点是服务“普通代码”,至于用元编程技术“植入”或者各种技巧增强的代码,可以简单忽略,这样你就不用纠结了。

当然,实际情况可能比这个复杂,因为有 DSL 技术存在,基于 ruby 的方案可能是多个 DSL 从底层到高层“堆叠”的过程,那么你的“AOP”代码就要明确自己关注的是哪个层次,仅为那个层次的语言编写横切逻辑即可

#4 楼 @iBachue 我觉得这样的代码很恐怖,这种修改真的确定自己改动了什么吗?等我真的产生这样的需求再来参加讨论好了。

#8 楼 @iBachue AOP 说白了就是代码生成工具,玩代码生成是玩不过 Ruby 元编程的。

你觉得系统一定要大型么?大型 Ruby 系统就一定要有这么多重复打日志的调用么?打日志就一定要 AOP 么?我整过自动给 java 方法加统一格式日志的方法,尝试过用 AOP, 但当时就是大型 Java 系统,包依赖很变态各种 alibaba logger 和 log4j 和 slf4j 混在一起,还得看配置按情况插不同的代码,最后还是自己写了 annotation processor 各种改语法树才弄好...

这种问题归根就是:1. 编程环境没有足够强的 debug 能力,只好用大量手动日志来分析系统状况,2. 编程环境没有足够强的动态能力,只好依赖代码生成工具和容器,甚至各种半吊子的解释器引擎。

p.s. 如果用 aspectJ 我猜一半人的 eclipse 搞不定,如果用 spring 的动态 aspect 运行又很慢... p.s. 最悲惨的 debug 方式莫过于看日志了,最耗资源吃力不讨好的数据分析也莫过于日志分析了...

#17 楼 @luikore 我就是拿 Ruby 做大型系统的,需求各种变态,代码各种冗余+丑陋,很多代码甚至失去了可读性,而且很多流程直接涉及到钱,需要海量日志跟踪,这就是现实。如果都是小打小闹何须 Ruby,C 不也能搞定嘛? 你的一大堆抱怨归根结底是 Java 的问题,不是 AOP 的问题。我以前也遇到过类似的痛苦,但是我可没有怪罪到 AOP 上。 还有,我不觉得 AOP 是代码生成工具,它只是元编程的一种而已。

#16 楼 @Rei 功能确实挺恐怖的,所以才需要把可调试性考虑到范围内。

#14 楼 @ywjno Java 式配置文件和注解不符合 Ruby 文化吧。

#13 楼 @jjym 都是小儿科的东西吧,Callback 的源码我都读过。ActiveSupport 不过是满足 Rails 框架自身使用需求而已,根本不能满足大型企业级系统更变态的需求。

#18 楼 @iBachue

set_trace_func 实现追踪日志应该可以满足你在 #4 楼 描述的需求,而且添加切点的逻辑不局限于 AOP 的定义。

http://www.ruby-doc.org/core-1.9.3/Kernel.html#method-i-set_trace_func

23 楼 已删除

#22 楼 @luikore 这个方法牛啊。。多谢分享

@iBachue 我是从 Java 转到 Ruby 的,我最大的感受是 Java 总是在过度设计,很多时候和很多地方都在为并不存在也并不需要的可扩展性、可通用性之类的需求生造出许多漂亮的设计来,尽管这个设计非常难用、非常晦涩,但它的确非常完美

Java 的哲学是用 80% 的精力思考未来和解决未来的问题,被解决的问题根据我的经验,99% 的情况下一千年都不会出现,但对于 Java 程序员来说,明知公元 2012 年—公元 3012 年之间的某个时候会出现这个问题而不去解决它,那绝对是不可饶恕的,不解决它饭都吃不下去。然后,Java 程序员会在百忙之中抽出 20% 的宝贵时间来解决当下的问题,如有可能,当年我还想把这个比例压缩到 10%,你知道的,未来的事情已经够让我头大,现在这些小事儿最好不要来烦我!

当我用 Ruby 后,才发觉,1000 年太久,我非常怀疑我的应用能不能够活过 1000 天,如果 1000 天后我的程序还有问题让我去解决,天啊,那不是太 TMD 幸福了吗?!

这就是,我理解的 Java 哲学Ruby 世界

@iBachue 用 meta programming 实现的 AOP,听说有个 aqua 你可以试试

class A
  def a
    puts "a"
  end
end

module Logger
  def log(method_name)
    m = self.instance_method(method_name)
    undef_method method_name
    define_method method_name.to_s do
      puts "start"
      m.bind(self).call
      puts "end"
    end
  end
end

A.extend Logger

A.log :a

A.new.a

#5 楼 @luikore AOP 要解决的问题不仅在 Ruby 中不存在,在 Java 中也不存在。显然语言自身的问题和 AOP 要解决的问题没任何关系啊。

Ruby 和 Java 都只是一门语言而已,而且从 OO 的角度看,都只能算是很拙劣的 Smalltalk 模仿。即便分别对于某一些问题,他们都还算是不错的。

AOP 作为一种范式,是用来解决一类问题的。但,现实是,太多人介绍 AOP 的时候,就喜欢拿 logging 当例子,特别是和 Java 扯上关系之后,花了很大篇幅在讲如何在 Java 里实现 AOP 这种范式。于是,很多人都把精力花在了怎么实现上,而忽视了去理解为什么这种范式适合解决某一类问题。

基本上是和设计模式类似的情况。比如 Visitor Pattern,即便某个语言支持了 Pattern Match,所谓 Visitor Pattern 就是用一下 Pattern Match 而已。但重点不是 Pattern Match 如何优越,而是你用 Visitor Pattern 把问题解决了。

总结一下就是重点是怎么用来解决问题,而不是怎么实现。哪怕你用一个很残废的实现把问题解决了,也比整天研究怎么实现地更好一点但完全不考虑能用来怎么解决问题好。这只是为某一种解决问题的思路起个名字而已,没有人天生就能想到所有思路,起个恰当的名字,大家都在术语上统一一下,这样碰到问题的时候一说就能明白了,而在具体问题里够用的实现就是好实现,没必要深究啊,除非你被坑到了

#22 楼 @luikore 好吧 我承认这个方法经过封装能够达到我要的效果,除了 rdebug 没能 debug 进 proc 里面,不过已经差不多 OK 了,谢谢。

#22 楼 @luikore 额 set_trace_func 还是有点缺陷,虽然能够完美实现在调用/返回一个匹配的方法前后执行一定的代码,但是似乎不能对参数进行匹配。此外我记得 AspectJ 能够修改被匹配方法的参数和返回值,还能抓住抛出的异常,还能拒绝调用被匹配的方法,这个不知道配合 alias 能不能实现

@iBachue 我刚开始从 java 转 ruby 时,也老在 ruby 里面找 java 的对应概念,多态啦,aop 呀。。 这些都是在 java 那样的强类型,静态编译语言中诞生的概念,对 ruby 不是说没用,但没那么适用。 像多态,duck type,天生就是多态。 只要你传进去的 object 能 response 某个方法调用,那方法就会被调用,不用 care 它是三角形还是圆形。 所以接口啊,抽象类啊这些东西,ruby 世界也没有了。

#31 楼 @feitian124 Ruby 是动态语言,Java 是静态语言,所以在涉及到类型的地方二者自然天差地远,但是这和 AOP 没什么关系啊。

@iBachue 所以我的意思是: 多态,接口 ruby 世界 90% 不需要,所以也确实没有。 aop 在 ruby 世界估计也 80% 不需要,所以也没几个人 care。。 所以参照你的标题,我已经回答了你的问题了,怎么没关系?

凑凑热闹,前不久我也问过 aop 的问题。我估计是 ruby 的应用领域可能有关系,所以 aop 在 ruby 很少人关注。

据说目前银行还是不用 ruby 的。当初 spring 就是用 aop 解决了事务处理问题。

aop 一般大型应用才比较有用。

#33 楼 @feitian124 二者不能类比,多态,接口是类型相关的问题,我说了 Ruby 和 Java 在这块地方有本质差异,所以 Java 的理论不适用于 Ruby。AOP 不是类型相关的问题,因此这不能证明 Ruby 不需要 AOP。

ruby 本身都够 magical 了,还加上这种东西…… 是要鼓励大家写难维护的代码吗

#37 楼 @nouse 哦 我见过,其实就是看了这个库让我觉得 Ruby 应该 native 的支持 AOP,因为语法实在过于复杂了,native 支持会好很多呢 不过这个库具体效果如何,我还没试过呢 等会尝试下:-)

#38 楼 @iBachue native 支持 AOP 的语言有很多,全都死掉了,AspectJ 也只是改写了编译过程,但是绝对进不了 Java 标准里

是不是 Java 阵营转过来的同学习惯把问题 java 化,然后用 java 化的方法解决呢?既然是用 ruby,那就 多多考虑一下 ruby style 的方法。

#40 楼 @googya 其实我写 JavaEE 只有几个月,作为学生作业,由于过于恶心而放弃了,不算 Java 阵营的吧。PHP 倒是玩了一年,也是学生时代。但是 Ruby 一年半了,而且是毕业后的第一份工作,算起来我算是比较红根苗正苦大仇深的 Ruby 程序员吧。

#39 楼 @luikore 那些语言也不是因为 AOP 而死掉吧。一门语言能流行的因素非常多,当然也包括运气。不能说一门语言因为有了什么特性而死掉了吧。

@iBachue 哈哈,我跟你一样,开发过 java 应用,上手就用 EJB 这样的重型武器,后来放弃了,弄 ROR,还是 ROR 比较合味口。

Java 并不鼓励 AOP,Java 里的只是 Proxy,在 EJB 里用得很谨慎很规整,大量使用 AOP 的是 Spring,简直为了制造噱头滥用,每次动 Spring 的那堆配置文件的时候都好像在擦一堆粘粘乎乎的东西,太恶心了,那东西做时间长了审美观必然扭曲

我想 Ruby2.0 的Module#prepend是个很简单优雅的 AOP 方案 http://dev.af83.com/2012/10/19/ruby-2-0-module-prepend.html

#47 楼 @iBachue prepend 一个 module 进来,于是类里面的方法,在不改变原有行为的基础上,就额外有了这个 module 定义的功能,比如日志。不过可能还需要配合元编程使用才方便

rails 的一个 module 就提供这个功能。

class Person

  def getName
    puts "andyshi"
  end
end

class Person

  def getName_new
    puts "before"
    getName_old
    puts "after"
  end

  alias_method :getName_old, :getName
  alias_method :getName, :getName_new
end




irb(main):017:0> p.getName
before
andyshi
after


#50 楼 @woaigithub 就是 alias_method_chain 嘛,这个只是实现了 AOP 的部分功能而已。另一部分我在 4 楼已经描述了,这里才是真正的难点

我刚看到 rails 的源码中有这个一回事,又想到了你的帖子,然后就给你回复了。哈哈!

用了jruby然后直接用AspectJ来做的话能实现 4 楼的那个功能么?

#53 楼 @ywjno 未必吧 AspectJ 是针对 Java 的 而 Ruby 比 Java 复杂太多了 还有 没有人愿意为一个小 feature 就更换解释器的吧

我认为所谓的 AOP 要解决的问题在具体场景下还是可以用各种其他方法和模式来解决的 比如惯用例子 - 日志,在 rails 中用 Active Record Observer 不可以解决吗(其实我不是写 rails 的,我就是随便搜了下文档) 我是写 php 的,实际上我在 Yii 中的解决方案是使用了全局的事件系统,给某类任务(比如动态、日志、积分、活跃度)做一个单独的 eventHandler 类来处理 acticerecord 抛出的事件,这样同样实现了分离关注点

相反,在这种场景使用 AOP 是一种简单粗暴的做法,就像一提钓鱼岛就要扔原子弹(PHP 也有 AOP 扩展啊,反正我是不可能用)

#55 楼 @chuck911 我针对的是 Ruby,Rails 已经把 Web 能碰到的大部分问题都解决了,active record 的事件可以用 observer,action 的事件可以用 filter,其它基本上没机会用此类 callback,自然不需要 AOP 了。所以我提 AOP 是针对 Ruby 的,比较 General 的情况。顺便说下,其实 Rails 这套框架本身也有很多可以应用 AOP 的地方,但是由于 Ruby 不提供(可能也难以实现),于是 Rails 做了另外一套库,比如前面提到的 alias_method_chain 和 Callback,然后在自己项目中大规模使用。但是 Rails 由于是一个独立项目,可以自己指定自己的规则,而真正接近实际生产环境的开发,就不是这么容易的了,没每一个模块都可能由不同的人在不同的地点,不同的时间段内为了不同的目的而开发的。想要完全实现加日志这个例子,运气好的时候多花点人力时间还能勉强做到,运气不好的时候就不可实现了(比如那部分代码你没有修改权限之类的,或是政策上不允许出现此类依赖之类的)。

#56 楼 @iBachue Rails 都是用 Ruby 写的,如果 Ruby 想用类似 alias_mehtod_chain, 看看 Rails 的源码就知道怎么做了。

代码注入的本质就是元编程,如果 Java 能做到的,比 Java 的元编程强很多的 Ruby 肯定能做到。只是 Ruby 更加灵活所以不需要把 AOP 提出来作为一个框架来做

Ruby 强大到可以看不起 AOP 了。。。

#26 楼 @nouse #49 楼 @woaigithub

牛,Ruby 只要 10 行代码就实现了 AOP。

#58 楼 @sevk AOP 本来就是个炒作的产物,某些 Java 框架的作者为了推销他们的产品炒作各种概念,仿佛他们的概念先进到你不用他们的东西就寸步难行一样,实际上很早之前的 EJB 里用的 Proxy 机制已经很简单明了,某些人为了 Without EJB 搞了另外一套更复杂的东西,再加上 JCP 委员会里的人拼命维持现状拒绝改进,把这种炒作当成繁荣,於是塑造了这么个空前的泡沫繁荣,这种做法加重了软件项目的负担,而且依赖上之後就是强耦合,你拿不掉的,这是软件项目的大忌,为了达到他们的商业目的不惜引诱你犯这种大忌,所以这种做法应该抵制

meta programming 里已经包含了 AOP 的所有概念,远比 AOP 优雅,没必要去迎合

woaigithub 回复

假如是多个方法该怎么办?比如有若干个 controller,每个 controller 里面都有各自的方法,需要在每个方法调用前后都打印 before after ..

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