写点什么

使用 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:301854
用户头像

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

关注

评论

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

Vue3.0 组合式 API 分析与实践

百度开发者中心

开发者

【FlinkSQL】Flink SQL Query 语法(二)

Alex🐒

flink FlinkSQL flink1.13

泰山版震撼来袭!阿里巴巴2021年Java程序员面试指导小册已开源

Java架构师迁哥

java异常分类和处理机制

加百利

Java 后端 异常 6月日更

一图读懂丨索信达灵枢如何助力金融机构提升模型管理效能

索信达控股

大数据 金融科技 监管平台 模型开发 数据管理平台

动手实践,Linux安装php-vld全过程实录

架构精进之路

插件 6月日更 笔记分享

花了60天的时间肝出了这些spring,jvm,并发编程等学习笔记,春暖花开再战大厂!

Java架构师迁哥

从理论到实战只需七天!阿里P10撰写的Spring全家桶有多全面?

Java架构追梦

Java 阿里巴巴 架构 springboot SpringCloud

☕【JVM 技术探索】Class字节码指令操作介绍(上)

洛神灬殇

Java JVM Class字节码 6月日更

深入了解一些字符串函数,内存函数(c语言)

小写丶H

c 字符串函数

拆分电商系统为微服务

Vincent

架构训练营

推理综艺的正确打开方式!爱奇艺玩转智能技术,“互动+内容”引爆迷综季

爱奇艺技术产品团队

综艺节目 智能 影视制作

10大流行软件测试工具

百度开发者中心

测试工具

「终!」☕️【Java技术之旅】带你进入String类的易错点和底层本质分析!

洛神灬殇

Java 字符串 字符串常量池 6月日更

硬核!阿里自爆虐心万字面试手册,Github上获赞89.7K

Java架构师迁哥

Github全面爆火,这份阿里Java面试复盘笔记到底有何魅力?

Java 程序员 架构 面试

狂刷了29天的阿里面试参考指南(恒山版),竟成功收到了12个offer

Java 程序员 架构 面试

Electron 开发音视频

anyRTC开发者

Java 音视频 WebRTC Electron RTC

一文带你,彻底了解,零拷贝Zero-Copy技术

奔着腾讯去

c++ Linux 缓存 零拷贝 Linux服务器开发

四面阿里巴巴如愿拿到offer定级P7,为此我筹备了半年

Java架构师迁哥

Hadoop Committer如何炼成?爱奇艺新晋核心贡献人给出了这份攻略!

爱奇艺技术产品团队

hadoop 开源 程序员 Apache社区

【布道API】API设计应该了解的HTTP方法和特性

devpoint

RESTful HTTP协议 6月日更

奇亚矿机分币系统搭建,Bzz云算力挖矿系统

优秀的开发者每天都在做什么?

学神来啦

程序员 码农 编码 经验分享

重仓AI人才培养,打造产业落地升级护城河

百度大脑

人工智能

iOS 面试秘籍全套

程序员 编程之路 iOS 知识体系

爱奇艺M2VOC挑战赛落幕,6篇论文被ICASSP2021收录

爱奇艺技术产品团队

Apache hadoop 开源 程序员 成长

Python接口自动化之request请求封装

行者AI

接口 测试 自动化测试 封装

2021年,最新Java硬核技能微服务、虚拟机、高并发,掌握轻松拿大厂offer

Java架构师迁哥

又到一年“粽子节”,快来测测你包的粽子颜值几分

华为云开发者联盟

端午节 华为云 modelarts 粽子

相约厦门!HarmonyOS Connect伙伴峰会将于6月17日举办

科技汇

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