NVIDIA 初创加速计划,免费加速您的创业启动 了解详情
写点什么

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

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

关注

评论

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

银行4.0的AI世界——开启算法力的时代

索信达控股

Linux-通过 liveCD 进入救模式-重装 grub 修复损坏的系统

学神来啦

Linux 运维 linux运维 linux学习

电脑里的视频被误删了可以用EasyRecovery恢复吗?

淋雨

EasyRecovery 文件恢复 硬盘数据恢复

最壕逆天改命:18名Java程序员凭阿里P8笔记,同时斩获一线大厂offer

Java架构师迁哥

不收藏你就后悔吧!费了三天才从GitHub上扒下的阿里Java优化笔记

来自网络资源资产管理的灵魂拷问

鲸品堂

网络 资源 运营商

哔哩哔哩B站视频下载器推荐(简单又好用)

资源君

工具 分享 哔哩哔哩 b站视频下载 教程分享

保安小王分享:四面字节跳动,终拿Offer,只有努力,方能成功

看完这篇文章,你也可以手写MyBatis部分源码(JDBC)

Java虚拟机之CMS垃圾收集器

【云洲智造】直播间下午4:30准时开播!

浪潮云

工业互联网

架构训练营模块三作业

晨晨

架构训练营

还在用Jenkins?试试Gitlab的CI/CD功能吧,贼带劲!

聊一聊在阿里做了 8 年研发后,我对打造大型工程研发团队的再思考

尔达Erda

开源 云原生 研发管理 PaaS 研发

保洁阿姨分享:腾讯架构师JDK源码笔记,13万字,带你飙向实战

基于 Golang 构建高可扩展的云原生 PaaS(附 PPT 下载)

尔达Erda

开源 云原生 数字化转型 PaaS 数字化

Abp太重了?轻量化Abp框架

Patronum

学习 程序员 架构 框架 Abp

科技监管能源运作?智慧能源从光热发电技术开始描述

一只数据鲸鱼

数据可视化 智慧能源 光热发电

Cypress 自动化测试

admin

自动化测试 Cypress 测试 单元测试 UI测试

文档内容结构化在百度文库的技术探索

百度Geek说

百度 大前端

小透明学弟的华为上岸之路

程序员鱼皮

Java c++ Python 大前端 后端

东京奥运会与网络安全背后的速度博弈!

郑州埃文科技

秒懂 Java 的三种代理模式

Qunar容器平台网络之道:Calico

Qunar技术沙龙

容器 TCP/IP calico BGP #Kubernetes#

2021,你还在写“赤裸裸”的API吗?

Lazada首届技术开放日开麦在即 共享技术创新最佳实践

EMQ X Cloud 正式支持 Microsoft Azure 平台,助力企业出海业务

EMQ映云科技

azure 云端 云上数据 emq

一夜爆火!完美贴合开发实际!阿里SpringBoot宝典助你面试超神

Java 编程 程序员 架构师 计算机

如何基于磁盘 KV 实现 Bitmap

Kvrocks

redis BitMap storage KV存储引擎

Unity ML-agents 参数设置解明

行者AI

模块三作业

NewBranSTONE

架构实战营

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