禅道博客

分享专业技术知识,文章内容干货满满

读书笔记《Unix编程艺术》第八九章

2023-12-21 15:16:27
刘梦艺
原创 587
摘要:本文是关于“Unix编程艺术”书籍第八九章的阅读笔记及心得。

8 微型语言: 寻找歌唱的乐符

从好的符号体味出的巧妙和启发,就算身边的老师也不过如此。

对软件错误模式进行大量的研究得出的一个最一致的结论时,程序员每百行代码的出错率和所使用的编程语言在很大程度上无关。更高级的语言可以使用更少的行数完成更多的任务,也意味着更少的bug。

Unix有个长期传统,即包容小型的、为专门应用领域特制、大量减少程序行数的语言。从历史上讲,这种类型的专门领域语言在Unix世界中成为“小语言”或“微型语言”,原因是早期这种语言的实例都较小,复杂度较低。明智的方法通常是保持这些设计尽可能小、尽可能简单。

专门领域的小语言是一种非常强大的设计理念,可以使我们为手头的任务定义自己更高级的语言,以详细规定恰当的方法、规则和算法;比起采用更低级硬编码的设计而言,这降低了全局复杂度。最少有三种方法可以做好微型语言的设计,两种好的,一种危险的。

  1. 1.

    正确方法,预先认识到可以使用微型语言设计把编程问题的规格说明提高一个层次;跟通用语言所能支持的表示法相比,这种表示法更紧凑、更具表达力。

  2. 2.

    正确方法,注意到规格说明的文件格式越来越类似微型语言——结构趋向复杂,控制的应用程序中包含行为。

  3. 3.

    错误方法,通过扩展变为微型语言:每次增加一个补丁或者一个仓促而就的特性。

大多数人面对第一个微型语言时就采取了错误方法,并且时候才意识到这个微型语言已经变得混乱。如何清理?但按时重新构思整个应用程序的设计。语言特性蠕变(Language-by-feature creep)的另一个臭名昭著的例子是TECO编辑器。

所有十分复杂的规格说明文件都希望成为微型语言。防御不良微型语言的唯一方法是知道如何设计一个好的微型语言。

8.1 理解语言分类法

Unix对数据文件的约定,底端时简单关联名称和属性的文件,网上时对数据结构进行列集或序列化的格式。

如果结构化的数据文件格式不仅表达了结构,而且在某些解释性上下文下表达执行行为时,这种格式开始接近于一种微型语言。

微型语言的范畴从声明性(具有隐式操作)发展到命令性(具有显式操作)。有些针对专门任务的命令性微型语言几乎快成为通用解释器。解释器的范畴也是通用性不断增加的范畴;反过来讲,更加通用的解释器对其运行上下文的假设很少。随着通用性增加,通常存在更加丰富的数据类型分类。

8.2 应用微型语言

设计微型语言面对两个截然不同的挑战。一个是在工具包中已经存在方便好用的微型语言时,认识到何时可以直接应用它们;另一个挑战是知道应该在什么时候为应用程序设计自定的微型语言。

8.2.1 案例分析:sng

结构化的数据文件使得编辑、转换和生成工具无需知道任何其他方的设计假定、秩序通过微型语言截止本身,就可以彼此协作。

8.2.2 案例分析:正则表达式

正则表达式,简写regexp。我们在此将其视为描述文本模型的一种声明性卫星语言;它经过内嵌在其他微型语言中。正则表达式表述了匹配或不匹配字符串的模式。最简单的正则表达式工具是grep。

regexp符号还存在一些小变种。

  1. 1.

    Glob表达式。这是早期Unix shell用于文件名匹配的有限通配符约定集。只有三个通配符, '*'、'?'、'[...]'。

  2. 2.

    基本正则表达式。grep。

  3. 3.

    扩展的正则表达式。egrep。

  4. 4.

    Per l正则表达式.

正则表达式是一个微型语言能够多么简练的极端例子。

8.2.3 案例分析: Glade

Glade是X的开源GTK工具包库所用的界面创建程序。通过在界面面板上交互地选择、放置和修改窗体部件来生成GUI界面。

Glade用于描述GUI的XML格式是简单的专门领域语言的好例子。

Glade的格式有效柜哥说明包含相应用户行为的GUI指令表。一方面Glade的GUI把这些规格说明作为结构化的数据文件处理;另一方面,Glade的代码生成器则用这些规格说明来编写GUI的实现码。

Glade标记实际上是一种相当简单的语言。

透明性和简单些是一个微型语言设计良好的标志。符号和域对象之间的映射非常清晰。对象之间的关系表达得很直接,舍弃了必须经过思考才能理解的间接表达方式,如名字引用。

8.2.4 案例分析:m4

m4宏处理程序解释描述文本转换的声明性微型语音。一个m4程序就是一套宏命令集,规定了把文本串扩展成其他字符串的方式。

8.2.5 案例分析:XSLT

8.2.6 案例分析:The Documenter's Workbench Tools

8.2.7 案例分析:fetchmail的运行控制语法

8.2.8 案例分析:awk

8.2.9 案例分析:PostScript

8.2.10 案例分析:bc和dc

8.2.11 案例分析:Emacs Lisp

8.2.12 案例分析:JavaScript

8.3 设计微型语言

微型语言提供了把问题说明规格提升一个层次的方法,并已经提供了数个案例分析中的施行方式。只要应用领域的域原语简单而固定不变,微型语言机就可能成为一红好方法,但用户可能希望应用方式要灵活多变

8.3.1 选择正确的复杂度

设计微型语言的第一要素是尽可能保持微型语言的简单。不必坚持结构化数据。沙盒化代码,限制对环境的访问。设置一个偶尔图灵完备的微型语言也非常危险。把微型语言当做语言来考虑。

8.3.2 扩展和嵌入语言

通过扩展或嵌入现有脚本语言是实现命令性语言的正确方法,对于声明性语言不那么合适。

8.3.3 编写自定义语法

至于声明性语言,一个主要是问题是,是否应该用 XML作为语法基础并把语法规定为XML文档类型。对于层次复杂的说明性微型语言这是正确做啊。如果不用XML,就遵循最小立异原则,并支持我们描述数据文件谈到的Unix约定。

如果确实需要自定义语法,yacc和lex也许是我们最好的朋友。如果确实需要宏技能,考虑一下用m4进行预处理是否正确。

8.3.4 宏——慎用

宏扩展机能是早期Unix语言设计者青睐的策略;C语言以及pic也有宏扩展。m4预处理器是实现扩展预处理器的通用工具。

在汇编程序中,宏通常是架构程序唯一的可用方法。

宏扩展的优势在于它无需知道基础语言的任何基础语法就可以用来扩展该语言。这种能力很容易滥用,生成奇怪、不透明的代码,滋生难于辨认的bug。

宏扩展旺旺扰乱了错误诊断。基础语言处理器生成的错误报告针对的是宏扩展文本,而不是程序员正在查看的原始文本。

8.3.5 语言还是应用协议

增加一个选项,改变会话行为,使其相应地更像应用协议,同时给出明确的输出结束分隔符和类似的字节补齐,也许是个不错的主意。

9 生成:提升规格说明的层次

程序员束手无策……只有跳脱代码,直起腰,仔细思考数据才是最好的行动。表达是编程的精髓。

人类其实更善于肉眼观察数据而不是推导控制流程。

数据比程序逻辑更容易驾驭。尽可能把设计的复杂度从程序代码转移到数据中是一个好实践,选择便于人类维护和操作的数据表示法也是好实践。

9.1 数据驱动编程

进行数据驱动编程时,需要把代码和代码作用的数据结构划分清楚,这样,在改变程序的逻辑时,就只要编辑数据结构而不是代码。

数据推动编程和面向对象的区别:

  1. 1.

    在数据驱动编程中,数据不仅仅是某个对象的状态,实际上还定义了程序的控制流

  2. 2.

    OO首先是考虑的封装,而数据驱动编程看重的时编写尽可能少的固定代码。

Unix中数据驱动编程的传统比OO更深厚。

进行任何类型的代码生成或数据驱动编程的重要原则时:始终把问题层次往上推。不要手动修改生成的代码或中间形式——相反,应找到一种方式来改进或替换转换工具。

9.1.1 实例分析:ascii

9.1.2 实例分析:统计学的垃圾邮件统计

9.1.3 实例分析:fetchmailconf中的元类改动

9.2 专用代码的生成

9.2.1 实例分析:生成ascii显示的代码

9.2.2 实例分析:为列表生成HTML代码

尽可能少干活;让数据塑造代码;依靠工具;把机制从策略分离。专家级Unix程序员要学会迅速自动地看出这种可能性。建设性地懒惰是大师级程序员的基本美德之一。

暂时没有记录
评论通过审核后显示。