一段Ruby代码的思考

2012-07-15 02:13

一段Ruby代码的思考

by dreamhead

at 2012-07-14 18:13:00

original http://dreamhead.blogbus.com/logs/219662282.html

在一个Rails项目中,我们遇到了这样一个问题。

这是一个新闻(News)权限的处理。如果是一个新建的新闻,缺省的情况下,管理员和普通用户都是可见的,创建者可以根据需要设置权限;如果是一个已创建的新闻,则权限是创建时设定的。我们要在页面上显示一个form,供人编辑,按照通常的处理方法,页面上应该有所有角色,根据这个新闻当前的权限设置选择框。

从代码上讲,我们可能希望这样判断:

  news.checked_for? role

问题来了,这样的代码写着很漂亮。但是,现实的问题是,这是一段与View相关的逻辑,我们要把它放到Model里吗?如果把这些代码都放到Model里,可想而知,我们就会得到一个非常庞大的Model,事实上,很多项目里就是有这样一个庞大的Model。

对于庞大的东西,第一直觉是拆。感谢Ruby,它给了我们强大的mixin,只要把不同方面的东西写入到不同的module里面,似乎问题就迎刃而解了。

moudle NewsForNewOrEdit
  def checked_for? role
    ...
  end
end

class News ...
  include NewsForNewOrEdit
  ...
end

稍微仔细想一下,似乎还有继续优化的空间。我们的checked_for?其实只在这种情况下才起作用,而按照上面的做法,所有使用News的地方都可以使用这个方法,貌似它的作用域增大了。

一般来说,我们是希望代码的作用域越小越好,这样,才不会牵一发而动全身。

有了想法,便不难产生做法。在Ruby里面,我们可以给对象extend一个module,这样,只有在需要的时候,才这么做,避免了方法作用域的扩大:

@news = News.new.extend(NewsForNewOrEdit)
或是
@news = News.find(param[:id]).extend(NewsForNewOrEdit)

从功能实现和设计的角度,这样的代码是没有什么问题的。从Clean Code的角度看,这样的代码略显啰嗦,表达性有所欠缺,不能很好地反映我们的意图,这对于Ruby代码来说,有些暴殄天物。稍加封装,这段代码就会不一样。下面给出一种实现:

def with_new_or_edit_context
  yield.extend(NewsForNewOrEdit)
end

基于这个实现,上面的代码可以实现成这样:

@news = with_new_or_edit_news_context { News.new }
或是
@news = with_new_or_edit_news_context { News.find(param[:id]) }

当然,这样的代码写多了也是一种负担,所以,我们可以将其进一步泛化,这是后话了。

其实,这种问题已经有人做了很多的探索,从架构设计的层面上进行新的思考,引入一种新的架构模式:DCI(Data、Context and Interation),这里给出的是在Ruby上的初步探索。更多的讨论参见《The DCI Architecture: A New Vision of Object-Oriented Programming》。