AI实践哪家强?来 AICon, 解锁技术前沿,探寻产业新机! 了解详情
写点什么

使用 Ruby DSL 实现敏捷素材管理

  • 2007-09-24
  • 本文字数:4048 字

    阅读完需:约 13 分钟

领域特定语言(Domain Specific Language,简称 DSL)是一个面向语言的工具,用于解决某个特定领域的编程任务。DSL 的一般语言特征和它所被用到的问题领域,关系是非常密切的,并且在一个非常高的抽象层面上起作用。Martin Fowler 在他介绍 DSL 的文章中,将 DSL 划分为 _ 外部 DSL_ 和 _ 内部 DS_L 两类(参见原文链接)。外部DSL 是一门需要编译或者解释运行的编程语言,而内部DSL 则构建于一门通用编程语言(general-purpose programming language)之内。实际上,内部DSL 对于其宿主通用编程语言来说,就是它的一套层次非常高的API。本文讲述了了在PLANET ARGON 公司的一个开发项目中,用Ruby 实现的一个内部DSL 如何给项目带来巨大贡献。

问题的提出

我在PLANET ARGON 公司近期的一个开发项目的目标是,使用Ruby on Rails 构建一个一次性的内容管理系统,这个系统要支持18 种语言,并且管理大约1000 个的图像文件。这些文件中有许多都是专业摄影图片,每个文件大小都超过1MB。而另外的图片文件则是一些细碎的线条艺术,图片里面显示的就是许多不同的国旗。这些图片所具备的相同特点,就是它们都还不能直接投入产品使用。

我们的应用程序中包含一个 <strong>Image</strong>模型,该模型使用我们内部编写的一个文件上传插件来持久化图片数据。我们已经使用了<strong>after_save</strong>钩子来管理图形转换操作,而这些钩子在我们的模型内部使用了<strong>RMagic</strong>图形操作 API。这样看来应该就够了,但实际上我们的需求一直在不断被调整。有些图片需要进行四次变换才能投入使用;而其它图片只需要两次;其中一些图片必须被上传到一个第三方的贷款提供商那里;而又有些时候,我们把图片的尺寸弄错了。我们曾经一度编写了一个批处理脚本来上传这些原始图像,接着<strong>after_save</strong>钩子就可以重新处理这些图像了。便捷情况很快就凸现了出来,于是我们回调代码中就七荤八素地塞满条件判断语句。你可能已经猜到了,这个麻烦很快就成了往事。我们需要一个可以帮助我们以一种可持续发展的方式达到客户需求的工具。

解决方案

有一天,我们的客户又向我们的 Basecamp 发过来一个参考图片,以及他们如何在 PhotoShop 中创建这个图片的一系列指示。我抓狂了。我开始考虑使用一个更加优雅的方案来解决这个问题,我想起了 Jason Watkins 曾经跟我说起过,当他在电子游戏这一行工作的时候,他用过一类称为“素材编译器(asset compiler)”的工具。素材编译器其实是一个构建系统,将一系列媒体文件转换成用于电子游戏最终构建版用到的媒体文件。最近我用 Rake 自动化了不少重复性的任务(Rake 是一个用纯 Ruby 来实现的构建系统),所以用 Rake 搞出一套素材编译器,对我来说很有吸引力。如果创建产品图像的职责可以委派给一个外部的工具,那么不管我们客户的需求在什么时候发生改变,我们的应用程序都无须进行变更。更有意思的是,这个工具可以使得我们在需求发生变化的时候,使用一条命令就 _ 重新构建 _ 出所有的产品级图像。

拿着 Jim Weirich(Rake 的作者)的 RDoc 任务库(task library),我开始上手干活了。RDoc 任务库以模板的形式随 Rake 一起被分发。Jim 为 Rake 创建的用法模式之一就是一个实例化的类,里面带着描述了需用于自动化构建的 Rake 任务集合的合适选项。比如说,这里就给出了一个范例,展示如何在<strong>Rakefile</strong>使用 RDoc 任务库为 Rails 应用及其使用的插件,还有整个 Rails 框架构建文档。

Rake::RDocTask.new('my_docs') do |rdoc|<br></br> # configuration<br></br> rdoc.rdoc_dir = 'doc/my_docs'<br></br> rdoc.main = "doc/README_FOR_APP"<br></br> rdoc.title = 'Comprehensive Documentation'<p> # app documentation</p><br></br> rdoc.rdoc_files.include('doc/README_FOR_APP')<br></br> rdoc.rdoc_files.include('app/**/*.rb')<br></br> rdoc.rdoc_files.include('lib/**/*.rb')<p> # plugins documentation</p><br></br> rdoc.rdoc_files.include('vendor/plugins/*/lib/**/*.rb')<p> # framework documentation</p><br></br> # ...snip...<br></br> end按照这个范例在你的Rakefile中实例化一个<strong>Rake::RDocTask</strong>对象将会建立一系列 Rake 任务,用于创建、删除及重新构建你的文档。被<strong>Rake::RDocTask.new</strong>使用<strong>rdoc</strong>这个 block 参数,实际上就是正被实例化的<strong>Rake::RDocTask</strong>对象,而正被初始化的选项以及<strong>rdoc</strong>的属性则是用<strong>attr_accessors</strong>定义在<strong>Rake::RDocTask</strong>中的。

我开始将构建产品级图像的代码从我们的Image模型中抽取出来,放进一个 Rake 任务库中。在建立了一个操作起来类似于 RDoc 任务库的可工作任务库之后,我捣鼓出来的代码就像下面这样:

ImageTask.new :bronze_thumbnail do |t|<br></br> t.src_files = image_src 'images/*.jpg'<br></br> t.build_path = build 'greyscale_thumbnail'<br></br> t.remote_dirs << REMOTE_DIR[:greyscale_thumbnail]<br></br> t.transformation do |img|<br></br> # apply bronze image effect<br></br> img = img.quantize 256, Magick::GRAYColorspace<br></br> img = img.colorize 0.25, 0.25, 0.25, '#706000'<br></br> # crop to thumbnail size<br></br> ...snip...<br></br> end<br></br> end上面的代码定义了以下 Rake 任务,用<strong>rake -T</strong>可以得到以下描述:

rake assets:bronze_thumbnail:build # Build the bronze_thumbnail files<br></br> rake assets:bronze_thumbnail:clobber # Remove bronze_thumbnail files<br></br> rake assets:bronze_thumbnail:rebuild # Force a rebuild of the bronze_thumbnail files<br></br> rake assets:build # Build all assets<br></br> rake assets:clobber # Clobber all assets<br></br> rake assets:rebuild # Rebuild all assets我意识到,如果描述这些图像变换的代码与客户给我们的 PhotoShop 指令非常相似的话,这个工具将变得极具灵活性——代码就成了为图形变化定义 Rake 任务的内部 DSL 了。

我为我的任务库创建了一些单元测试,以保证任务可以被正确地创建出来,并且以测试驱动重构迈开第一步。由于我创建的是一个内部 DSL,它在我这个例子里是一个 Ruby API,这套 DSL 的创建完全可以遵循测试驱动的实践。我开始将我单元测试中初始化任务库各个类的范例代码替换成我尚未实现的 DSL 的范例代码,并且修改了数次,直到我满意为止。

define_image_transformation 'thumbnailize' do<br></br> crop_to '62x62', :north<br></br> end<p> define_image_transformation 'bronze' do</p><br></br> greyscale<br></br> lighten<br></br> # r g b tint<br></br> tint 0.25, 0.25, 0.25, '#706000'<br></br> end<p> image_task 'bronze_thumbnail' do</p><br></br> from images 'images/*.jpg'<br></br> to build 'greyscale_thumbnail'<br></br> remote_dirs << REMOTE_DIR[:greyscale_thumbnail]<br></br> transformation do<br></br> bronze<br></br> thumbnailize<br></br> end<br></br> end我开始运行我的单元测试,然后看见一连串错误消息跳将出来。这是件好事——现在我知道了我要的是什么,还有我需要哪些步骤才能实现我的目标。接着,我重命名了几个方法(比如说,把<strong>src</strong>改成了<strong>from</strong>,然后创建了一些辅助方法(helper methods)来辅助初始化任务库的类。此外,我还将一个低层的 RMagick 代码包装进一个类库之中,类库使用了对设计师友好的方法命名。随后我创建了一个<strong>define_image_transformation</strong>,该方法通过聚合那些 RMagick 的包装方法,定义了针对应用程序的高级图形变换。通过把类似于 Photoshop 的图形工具可以做到的事情和我们的客户需要我们做到的事情区别对待,我就可以保证我的工具能被抽取出来,并且重用在今后的项目之中——高层的图形变换代码是存放在我应用程序中<strong>lib/tasks</strong>目录的,而低层的包装器代码则存放在<strong>vendor/asset_compiler</strong>目录之下,和任务库分开存放。

成果

目前,我们的<strong>lib/tasks/assets.rake</strong>文件包含对<strong>image_task</strong>的 12 个调用,包括上面展示的实例。尽管对于外行程序员(lay programmers)来说,这门语言不一定合适(Fowler 在文章中说,外行程序员就是非专业的程序员,但他们可以使用简单的编程工具来自动化业务任务),但是它和我们客户的语言是同步的,而且优雅地将他们的想法和目标建模出来。

因为 Ruby DSL 就是 Ruby 的代码,所以我强烈建议要遵循我在这篇文章中描述的测试驱动过程:编写你希望使用的 Ruby 内部 DSL 的一个范例代码,然后编写为这个范例的期望结果编写测试用例,接下来还是编写代码,直到所有的测试都能通过为止。此外,在一开始时就要考虑你 DSL 的范围。由于我强调了图像转换功能和我们应用程序的需求之间的分离,在我们的 DSL 基础上构建出一个强大、可重用而又可扩展的框架就是一件自然而然的事情了。我认为这是一个正确的决定,因为我知道很快我又将把这套类库用于其它项目。如果你的需求更加特别,那么你就不需要多层的抽象了。如果你打算重用你的内部 DSL,那么请确定没有任何针对应用程序的业务逻辑掺杂其中,而是提供一个机制来俘获业务逻辑。

最后,告诉你应当如何使用哪种语言创建出一门内部 DSL 的圣经,是不存在的。在这个主题上我所读到的所有文章中,绝大多数都认为你必须边做边学。留意观察客户和领域专家如何沟通他们的需求,并且专注于使用可执行的代码来捕捉这些需求。即便你无法将你的领域专家转变成一个外行程序员,他们应当可以在一次代码评审或者一个结对编程过程中明白清楚地看到他们的需求,而你的目标则应当是,在你得到他们需求的同时就马上捕捉下来。

查看英文原文: Agile Asset Management with Ruby DSLs

2007-09-24 23:302313
用户头像

发布了 117 篇内容, 共 18.9 次阅读, 收获喜欢 0 次。

关注

评论

发布
暂无评论
发现更多内容

破茧成蝶,如何用数据之力重塑您的业务?| 云途专栏

亚马逊云科技 (Amazon Web Services)

线程与线程池的那些事之线程篇

秦怀杂货店

Java 线程 多线程 进程 并发

高性能 JavaScriptの笔记(四)

空城机

JavaScript 大前端 5月日更

珠联壁合地设天造|M1 Mac os(Apple Silicon)基于vscode(arm64)配置搭建Java开发环境(集成web框架Springboot)

刘悦的技术博客

Java vscode spring Boot Starter JDK11 m1

从技术趋势看质量赋能

BY林子

敏捷 软件测试 质量赋能

聊聊dubbo协议

捉虫大师

Flink的广播变量

大数据技术指南

大数据 flink 5月日更

【epoll】epoll多路复用和Reactor设计思想

Linux服务器开发

后端 网络编程 C/C++ epoll Linux服务器开发

520,珍惜眼前人

数据社

520 520单身福利 520 单身福利

架构学习笔记:复杂度来源

风翱

5月日更 架构复杂度

520节日快乐

IT蜗壳-Tango

5月日更

浪潮云洲链斩获2020-2021年度新一代信息技术创新产品殊荣

高并发调优backlog多大合适?

运维研习社

nginx 运维 5月日更 并发调优

【偶尔一道ctf】xctf adword mobile easy-apk

Thrash

为什么“放弃”?

Nydia

学习

写给大神

小梨蕊

情人节 520单身福利 520 单身福利

Nginx基础配置-反向代理

梁龙先森

nginx 大前端

夺魁!亚马逊云科技荣膺中国数据管理解决方案领导者!

亚马逊云科技 (Amazon Web Services)

Dubbo 延迟与粘滞连接

青年IT男

dubbo

产品思维

lenka

5月日更

数据预处理&特征工程

Qien Z.

Python 5月日更

❤【520特别祝福篇】愿有情人终成眷属,已成眷属爱情稳固

码界西柚

爱情 5月日更 520单身福利 520 单身福利

这一次,彻底搞懂 Go Cond

HHFCodeRv

Go 语言

数据挖掘从入门到放弃(七):TensorFlow和keras实现线性回归LinearRegression

数据社

机器学习 5月日更

父母为什么催婚,而你又在逃避什么

小天同学

婚姻 爱情 5月日更

PKI系统简介

上海派拉基础研发

CA PKI

Golang function

escray

学习 极客时间 Go 语言 5月日更

Hadoop定位问题日志跟踪

InfoQ_Springup

hadoop

造车潮

ES_her0

5月日更

音频均衡器EQ

floer rivor

音视频 Eq

拥有5大核心竞争力的华为云GaussDB,成SACC2021最靓那一个…

华为云开发者联盟

数据库 华为云 GaussDB 存算分离 GaussDB(for Redis)

使用Ruby DSL实现敏捷素材管理_Ruby_Jeremy Voorhis_InfoQ精选文章