首页 新闻 论坛 群组 Blog 文档 下载 读书 Tag 网摘 搜索 开源 FAQ 第二书店 博文视点 程序员
频道: 研发 数据库 中间件 信息化 视频 .NET Java 游戏 移动 服务: 人才 外包 培训
    图书品种:235680
       
热门搜索: ASP.NET Ajax Spring Hibernate Java

 

   领域语言

语言的界限就是一个人的世界的界限。

  —维特根斯坦

 

  计算机语言会影响你思考问题的方式,以及你看待交流的方式。每种语言都含有一系列特性——比如静态类型与动态类型、早期绑定与迟后绑定、继承模型(单、多或无)这样的时髦话语——所有这些特性都在提示或遮蔽特定的解决方案。头脑里想着Lisp设计的解决方案将会产生与基于C风格的思考方式而设计的解决方案不同的结果,反之亦然。与此相反——我们认为这更重要——问题领域的语言也可能会提示出编程方案。

  我们总是设法使用应用领域的语汇来编写代码(参见210页的需求之坑,我们在那里提出要使用项目词汇表)。在某些情况下,我们可以更进一层,采用领域的语汇、语法、语义——语言——实际进行编程。

  当你听取某个提议中的系统的用户说明情况时,他们也许能确切地告诉你,系统应怎样工作:

    在一组X.25线路上侦听由ABC规程12.3定义的交易,把它们转译成XYZ公司的43B格式,在卫星上行链路上重新传输,并存储起来,供将来分析使用。

  如果用户有一些这样的做了良好限定的陈述,你可以发明一种为应用领域进行了适当剪裁的小型语言,确切地表达他们的需要:

    From X25LINE1 (Format=ABC123) {

      Put TELSTAR1 (Format=XYZ43B);

      Store DB;

    }


 

  该语言无须是可执行的。一开始,它可以只是用于捕捉用户需求的一种方式——一种规范。但是,你可能想要更进一步,实际实现该语言。你的规范变成了可执行代码。

  在你编写完应用之后,用户给了你一项新需求:不应存储余额为负的交易,而应以原来的格式在X.25线路上发送回去:

 

    From X25LINE1 (Format=ABC123) {

      if (ABC123.balance < 0) {

        Put X25LINE1 (Format=ABC123);

      }

      else {

        Put TELSTAR1 (Format=XYZ43B);

        Store DB;

      }

    }

 

  很容易,不是吗?有了适当的支持,你可以用大大接近应用领域的方式进行编程。我们并不是在建议让你的最终用户用这些语言实际编程。相反,你给了自己一个工具,能够让你更靠近他们的领域工作。

 

提示17

 

Program Close to the Problem domain
靠近问题领域编程

  无论是用于配置和控制应用程序的简单语言,还是用于指定规则或过程的更为复杂的语言,我们认为,你都应该考虑让你的项目更靠近问题领域。通过在更高的抽象层面上编码,你获得了专心解决领域问题的自由,并且可以忽略琐碎的实现细节。

  记住,应用有许多用户。有最终用户,他们了解商业规则和所需输出;也有次级用户:操作人员、配置与测试管理人员、支持与维护程序员,还有将来的开发者。他们都有各自的问题领域,而你可以为他们所有人生成小型环境和语言。

具体领域的错误

  如果你是在问题领域中编写程序,你也可以通过用户可以理解的术语进行具体领域的验证,或是报告问题。以上一页我们的交换应用为例,假定用户拼错了格式名:

    From X25LINE1 (Format=AB123)

  如果这发生在某种标准的、通用的编程语言中,你可能会收到一条标准的、通用的错误消息:

    Syntax error: undeclared identifier

  但使用小型语言,你却能够使用该领域的语汇发出错误消息:

    "AB123" is not a format. known formats are ABC123,

            XYZ43B, PDQB, and 42.

实现小型语言

  在最简单的情况下,小型语言可以采用面向行的、易于解析的格式。在实践中,与其他任何格式相比,我们很可能会更多地使用这样的格式。只要使用switch语句、或是使用像Perl这样的脚本语言中的正则表达式,就能够对其进行解析。281页上练习5的解答给出了一种用C编写的简单实现。

  你还可以用更为正式的语法,实现更为复杂的语言。这里的诀窍是首先使用像BNF这样的表示法定义语法。一旦规定了文法,要将其转换为解析器生成器(parser generator)的输入语法通常就非常简单了。CC++程序员多年来一直在使用yacc(或其可自由获取的实现,bison[URL 27])。在Lex and Yacc[LMB92]一书中详细地讲述了这些程序。Java程序员可以选用javaCC,可在[URL 26]处获取该程序。282页上练习7的解答给出了一个用bison编写的解析器。如其所示,一旦你了解了语法,编写简单的小型语言实在没有多少工作要做。

  要实现小型语言还有另一种途径:扩展已有的语言。例如,你可以把应用级功能与Python[URL 9]集成在一起,编写像这样的代码

    record = X25LINE1.get(format=ABC123)

    if (record.balance < 0):

            X25LINE1.put(record, format=ABC123)

    else:

            TELSTAR1.put(record, format=XYZ43B)

            DB.store(record)

数据语言与命令语言

  可以通过两种不同的方式使用你实现的语言。

  数据语言产生某种形式的数据结构给应用使用。这些语言常用于表示配置信息。

  例如,sendmail程序在世界各地被用于在Internet上转发电子邮件。它具有许多杰出的特性和优点,由一个上千行的配置文件控制,用sendmail自己的配置语言编写:

    Mlocal, P=/usr/bin/procmail,

            F=lsDFMAw5 :/|@qSPfhn9,

            S=10/30, R=20/40,

            T=DNS/RFC822/X-Unix,

            A=procmail -Y -a $h -d $u

  显然,可读性不是sendmail的强项。

  多年以来,Microsoft一直在使用一种可以描述菜单、widget(窗口小部件)、对话框及其他Windows资源的数据语言。下一页上的图2.2摘录了一段典型的资源文件。这比sendmail的配置文件要易读得多,但其使用方式却完全一样——我们编译它,以生成数据结构。

  命令语言更进了一步。在这种情况下,语言被实际执行,所以可以包含语句、控制结构、以及类似的东西(比如58页上的脚本)。


2.2 Windows .rc文件

        graphics/02fig02.gif

  你也可以使用自己的命令语言来使程序易于维护。例如,也许用户要求你把来自某个遗留应用的信息集成进你的新GUI开发中。要完成这一任务,常用的方法是“刮屏”(screen scraping):你的应用连接到主机应用,就好像它是正常的使用人员;发出键击,并“阅读”取回的响应。你可以使用一种小型语言来把这样的交互编写成脚本

    locate prompt "SSN:"

    type "%s" social_security_number

    type enter

 

    waitfor keyboardunlock

 

    if text_at(10,14) is "INVALID SSN" return bad_ssn

    if text_at(10,14) is "DUPLICATE SSN" return dup_ssn

    # etc...

    当应用确定是时候输入社会保障号时,它调用解释器执行这个脚本,后者随即对事务进行控制。如果解释器是嵌入在应用中的,两者甚至可以直接共享数据(例如,通过回调机制)。

  这里你是在维护程序员(maintenace programmer)的领域中编程。当主机应用发生变化、字段移往别处时,程序员只需更新你的高级描述,而不用钻入C代码的各种细节中。

独立语言与嵌入式语言

  要发挥作用,小型语言无须由应用直接使用。许多时候,我们可以使用规范语言创建各种由程序自身编译、读入或用于其他用途的制品(包括元数据。参见元程序设计144页)。

  例如,在100页我们将描述一个系统,在其中我们使用Perl、根据原始的schema规范生成大量衍生物。我们发明了一种用于表示数据库schema的通用语言,然后生成我们所需的所有形式——SQLC、网页、XML,等等。应用不直接使用规范,但它依赖于根据规范产生的输出。

  把高级命令语言直接嵌入你的应用是一种常见做法,这样,它们就会在你的代码运行时执行。这显然是一种强大的能力;通过改变应用读取的脚本,你可以改变应用的行为,却完全不用编译。这可以显著地简化动态的应用领域中的维护工作。

易于开发还是易于维护

  我们已经看到若干不同的文法,范围从简单的面向行的格式到更为复杂的、看起来像真正的语言的文法。既然实现更为复杂的文法需要额外的努力,你又为何要这样做呢?

  权衡要素是可扩展性与维护。尽管解析“真正的”语言所需的代码可能更难编写,但它却容易被人理解得多,并且将来用新特性和新功能进行扩展也要容易得多。太简单的语言也许容易解析,但却可能晦涩难懂——很像是60页上的sendmail例子。

  考虑到大多数应用都会超过预期的使用期限,你可能最好咬紧牙关,先就采用更复杂、可读性更好的语言。最初的努力将在降低支持与维护费用方面得到许多倍的回报。

相关内容:

l       元程序设计144

挑战

l       你目前的项目的某些需求是否能以具体领域的语言表示?是否有可能编写编译器或转译器,生成大多数所需代码?

l       如果你决定采用小型语言作为更接近问题领域的编程方式,你就是接受了,实现它们需要一些努力。你能否找到一些途径,通过它们把你为某个项目开发的框架复用于其他项目?

练习

5.      我们想实现一种小型语言,用于控制一种简单的绘图包(或许是一种“海龟图形”(turtle-graphics)系统)。这种语言由单字母命令组成。有些命令后跟单个数字。例如,下面的输入将会绘制出一个矩形:

    P 2 # select pen 2

    D   # pen down

    W 2 # draw west 2cm

    N 1 # then north 1

    E 2 # then east 2

    S 1 # then back south

    U   # pen up

  请实现解析这种语言的代码。它应该被设计成能简单地增加新命令。(解答在281

6.      设计一种解析时间规范的BNF文法。应能接受下面的所有例子:(解答在282

    4pm, 7:38pm, 23:42, 3:16, 3:16am

7.      yaccbison或类似的解析器生成器为练习6中的BNF文法实现解析器。(解答在282

8.      Perl实现时间解析器(提示:正则表达式可带来好的解析器)。(解答在283

查看所有评论(0)条】

最近评论



正在载入评论列表...
热点评论