使用 Tcl/Tk 进行快速原型开发

作者:Richard Schwaninger

创建软件是一个复杂的过程,它将程序员置于规则和约束之中。客户需求与程序中的错误作斗争,而可用性与生产成本作斗争。

目前解决所有这些问题的方法是面向对象的设计和分析,其通过有条不紊地尝试将问题分解为适当小的子域,并提供从分析到设计再到实施和测试的预定义路径来实现。

这种程序可能对大型程序有所帮助,并且如果涉及许多程序员,则可能是必要的。大多数真正优秀的程序(例如 Linux 上可用的程序,甚至 Linux 本身)都只由少数人编写,更重要的是,他们中的大多数人在开始时并不知道他们的软件将朝着哪个方向发展。因此,需要另一种范例——一种更适合人类需求的范例。

以下是我在 Cycad Corporation 首页 http://www.cycad.com/ 上找到的关于快速原型开发 (RP) 的声明

快速原型开发与诸如“更快上市”、“击败竞争对手”、“最大化盈利能力”、“提高质量”等关键词相关联……快速原型开发不仅提供了更快上市的所有好处,而且首次有可能创建概念验证,以便在实施之前向客户展示,并在承诺生产之前创建原型生产单元。这两个验证步骤都可以节省大量的资源和时间。

虽然以上所有内容当然是正确的,但 RP 特别适合创建根据客户需求量身定制的软件。确定这些需求的最重要方面之一是找出客户真正想要什么。因为程序在开发过程中成型,而不是像标准结构化设计那样预先成型,所以程序员可以响应关于客户需求的新信息。使用 RP,这个过程是直接且允许的,而在其他方法中,这通常表示分析或设计错误。

同样重要的是一种测试新想法的简单方法。如果您在实施之前找到某种试验场,以 выяснить 您的新概念是否真的像您想象的那么出色,那么最终产品将会更好。在我们这个快速变化的世界中,我们必须对新的或不断变化的需求做出快速反应,这一点尤为重要。

使用快速原型开发进行软件开发不一定依赖于专门为此设计的语言。可以使用 C/C++ 等编译语言,但使用易于学习/易于使用的解释型语言可以获得最佳结果。为了获得最大的好处,周转时间必须短,并且应该可以毫不费力地修改现有代码。

快速原型开发真正闪耀的一个领域是用户界面设计(前提是您拥有合适的工具)。您可以以最小的努力创建一个动态可视化模型,并在系统实施之前为用户提供关键部分的物理表示。您可以更早地适应新的或意外的用户需求,并且修改起来更容易得多。

另一个方面是质量保证和控制。用于构建原型的相同工具可以被调整以构建一个测试工具,该工具在系统的最终实施之前一直有用。这减少了交付后的更改,从而提高了生产力。

Tcl/Tk

计算机语言的当前趋势之一是使用 Java、Perl 或 Tcl 等可移植语言。虽然这些语言通常比编译后的同类语言慢,但它们为开发人员提供了许多优势(可移植性、简单性、周转时间短等)。如果这样的语言可以集成编译后的代码(例如,通过使用共享库),开发人员可以首先专注于创建可行的解决方案,然后在关键位置优化代码。

Tcl/Tk 特别适合快速原型开发,因为它具有解决上述所有要求的方案

  • 它易于使用和学习。

  • 不需要编译或开发开销——只需编写并运行您的代码即可。

  • 图形用户界面可以用很少的努力构建。

  • 它可以用于测试(通过使用其内省工具)。

我不想在这里引发语言之战——以上声明是我自己的观点。为了最好地利用任何语言,您需要非常了解该语言(Tcl 也不例外),并且您需要相应的工具(语法敏感编辑器、调试器、性能分析器、测试和文档工具)。最后,您需要大量优秀的库和扩展,这些库和扩展使您能够仅用几行代码就创建大型应用程序。

一个例子

以下示例旨在让您了解快速原型开发是什么样的。为了保持简短和易于理解,它根本没有使用 Tk 的任何功能。虽然图形示例肯定更适合展示 RP 的优势,但所提出的概念可以很容易地扩展以利用 Tk 的额外功能。这是我的一个实际项目,我将以我开发它的方式向您展示它。您可能会猜到,客户程序员实际上只是一个人——我。

客户:“我需要一个小巧的库工具来处理最终用户可配置的值。这些值应该来自一个简单的文本文件,这就是全部。”

程序员:“好的。这个非常容易。”

我坐下来输入一些 Tcl 代码,这些代码为我提供了以下界面

proc Cfg <

这样,我就可以通过以下方式将配置界面用于(假设的)文本编辑器

set Lines [Cfg "NumLines"]
文件的格式应保持简单,因此我尝试以下方法从中读取值
NumLines 20
WordWrap 1
将文件读取到内存中将需要另一个函数。由于我不想污染命名空间(Tcl 和 C 都是如此),所以我将我的过程的签名修改为以下内容
proc Cfg <
这给了我以下内容
Cfg read "myconfig.cfg"
Cfg get NumLines
实现所有这些的代码在 15 分钟内编写完成。我再花 15 分钟编写一些测试用例,这样我就可以始终验证整个实现的正确性。这是一个测试
Test c1 "get simple value" {
  Cfg read "test.cfg"
  Cfg get NumLines
} 20
它读取我的简单测试配置文件,并检查 NumLines 的值是否真的与配置文件中设置的值相同。这一切都在 30 分钟内完成。当然,我必须用 C 重写它,但首先我将与我的客户交叉检查。

客户:“干得不错,但是你看,如果应用程序变得更大,我们可能会遇到名称冲突——考虑两个模块使用名为 Width 的配置值。一个可能是窗口的宽度,另一个是编辑器中文本的宽度。我们真正需要的是一个模块依赖的解决方案。”

程序员:“咕噜。我应该知道的——简单的事情往往会随着时间的推移而变得复杂。”

修改界面很简单,但是文件格式呢?在我的脑海深处有一个名为 win.ini 的小提示。我与我的 DOS 分区上的真实 Windows 安装进行了交叉检查——是的,该格式非常适合我的问题,并且很多用户已经熟悉它。

所以,我修改了我的配置文件,使其看起来像这样

; my config test file: 24/02/1997
[Editor]
WordWrap=1
NumLines=20
Width=80
[Window]
Width=320
Height=400
; EOF

读取这样的文件比之前采用的简单方法复杂得多。我拍拍自己的肩膀,庆幸自己还没有编写任何 C 代码。

使用 Tclx(Tcl 的扩展)的一些额外功能,文件读取器在半小时内就准备就绪并开始工作。

现在,我按如下方式修改界面

Cfg read "myconfig.cfg"
Cfg get Editor NumLines
Cfg get Editor Width
Cfg get Window Width

并且我修改了所有测试用例。再次,我将整个东西呈现给我的客户。

客户:“是的,这正是我想要的。我有一些真实世界的例子,你能验证它们是否正常工作吗?”

程序员:客户给了我一些真实的 X Window 系统配置文件——你认为上面的那些是真实世界的例子吗?无论如何,我对它们运行了一些测试,结果砰的一声,我所有好看的代码都崩溃了。快速查看文件揭示了问题——反斜杠、括号、空格和跨越多行的条目。解析 X 配置文件显然比我最初想象的要复杂。

我添加了许多新的测试用例,这些用例显示了上述所有失败。它们帮助我确保我最终得到了一段可工作的代码,而不会因为一个更改而破坏以前可以工作的东西。

调整 Tcl 代码相对容易。不过,这需要一些时间,因为我必须稍微摆弄一下解析器的正则表达式。一旦所有测试都无错误地完成,我就向客户展示我的工作。

客户:“在等待期间,我在一个真实世界的程序中使用了你的代码,但它有一个很大的缺点。考虑以下示例配置

lpr -#1 -Plj5 myfile.txt

这是一个打印命令,应该使用 exec 执行。某些部分是静态的,但取决于操作系统(例如,lpr 与 lp),其他部分(例如,打印机的选择)可能由用户完成,还有一些部分(例如,文件名)直接来自程序。我需要的是一种灵活的配置值替换方案。”

程序员:“哈。现在这是一个非常大的请求。我该如何创建一个灵活的替换方案呢?”

我花了一天的时间思考(实际上这是由我大脑中的一个后台进程处理的)才找到这个解决方案——Tk 使用替换作为其按键绑定机制,那么为什么不在这里使用相同的想法呢?

再次,我修改了界面,以便它可以处理如下代码

Cfg get Printer Command\
   "%c=1" "%f=myfile.txt"

并且我在我的配置文件中添加了一些行

[Printer]
Command= lpr "#%c "Plj5 %f
在将最终值返回给调用者之前,我的例程会将所有“%?”序列替换为来自参数列表的相应值。使用 Tcl 的正则表达式使这非常简单,唯一需要花费一些时间的事情是编写测试来检查所有这些疯狂的条件,例如“%%”和“\%”。

上面的例子将返回

lpr -#1 -Plj5 myfile.txt

正如客户所期望的那样。

客户:“太棒了。这就是我多年来一直在寻找的东西。只剩下一个不太理想的项目了。如果用户不小心删除了他的配置文件,程序将不再工作。是否可以为这种情况保留一些默认值?”

程序员:“我已经考虑过这种情况了。如果存在实际值,代码将更具可读性,并且可以更轻松地进行测试,因为我不必一直编写配置文件。”

我再次修改界面,添加了一个新的子命令

Cfg def <module> <name> \
   <default> ?<sub>? ..."

getdef 子命令的通用语法是相同的(并且当使用相同的替换值调用时,两者都返回相同的配置值),但是 def 在没有给出任何值时也会设置一个默认值。

如果 Cfg 已读取配置文件,则此文件中的值优先,否则使用默认值。

这是一个例子

Cfg def Printer Command \
   "lpr -#%c -Plj5 %f" \
   "%c=1" \
   "%f=myfile.txt"

此命令返回

lpr -#1 -Plj5 myfile.txt
客户:“我在我的所有 Tcl 应用程序中都使用了你的代码,它非常有效。我唯一注意到的是程序启动时非常慢。我怀疑这与你的配置代码有关。你能不能做些什么来解决这个问题?”

程序员:“啊!”

回到配置业务。首先,我检查了一些客户的应用程序。难怪他的启动速度很慢——他过度使用配置,甚至用于语言国际化。我对应用程序进行检测以进行性能分析,并创建一些快照进行分析。结果图表(参见图 1 和图 2)清楚地表明了两个最高的 CPU 周期消耗者:读取配置文件和替换配置值。

图 1. Tcl 性能分析器条形图

图 2. Tcl 性能分析器图

界面现在相对稳定(客户已在他的所有程序中使用它,他不会轻易更改它)。我开始用 C 重写一些代码。由于我从分析中知道了确切的瓶颈,因此前两个 C 例程替换了配置读取器和替换引擎。

现在,所有以前编写的测试都派上了用场——我可以毫不费力地检查新代码是否正常工作。这两个例程都相当费力,因为它们执行一些非常棘手的操作,并且指针总是指向意想不到的地方。当然,有一个用于 Tcl 的调试器(参见图 3)很好,但是如果没有 C 调试器,C 程序员的生活将非常艰难。

图 3. Tcl 调试器

新代码物有所值。速度可以是原始代码的 5 到 10 倍,这可能意味着一秒启动和十秒启动之间的差异。

要将这个小例子变成一个成熟的库还需要做很多工作,但这应该足以了解一般的概念。以下是关于 RP 优势的最重要学习点

  • 工作可以从不完整的规范开始。

  • 可以使用 Tcl 以直接且简单的方式创建不同的实现,直到客户满意为止。

  • 可以在应用程序完成很久之前就呈现可工作的示例。

  • 如果速度是一个问题,相关的部分可以用 C 重写。

  • 质量随着添加的测试程序而提高。

我做了一个非常大的项目,该项目集成了 CAD(计算机辅助绘图)和 PPC(生产计划和控制)软件,并且我成功地使用了上面提出的想法。(参见图 4 和图 5。)该项目历时一年多,规范更改不止一次。最终的解决方案为 Tcl 的多功能性提供了新的视角:用户可以使用 Tcl 及其面向对象的扩展来定义自己的组件(窗口框架、自动执行器等)。此功能只是我们快速原型开发理念的产物,因为我们只是为客户提供了我们自己的工具。

图 4. PPC 软件屏幕

图 5. 第二个 PPC 软件屏幕

使用 Tcl 进行软件开发并不是最终的完美解决方案。它存在一些缺点,例如缺少真实的数据类型(任何东西都是字符串)、解释代码的效率低下(尽管现在可以使用编译器),以及 eval 即使对于 Tcl 狂热者来说仍然是一个谜。当您的程序主要围绕图形用户界面时,Tcl 会发光发热。其他任务可能由 Perl、C 甚至 FORTRAN 更好地完成。由于动态加载的共享库现在在多个平台上非常常见,我们可以将问题分解为块,并使用最适合它的语言来解决这些块。在这种情况下,Tcl/Tk 可能是图形界面的粘合剂。

什么是 Tcl/Tk?

什么是快速原型开发?

Rapid Prototyping with Tcl/Tk
Richard Schwaninger (risc@finwds01.tu-graz.ac.at) (risc@ping.at) 为产品自动化系统创建软件。他使用 Linux 作为他的主要开发平台,并为 Tcl/Tk 构建工具。自 CP/M 时代以来,他一直从事软件业务,并且使用 AutoCad Lisp 进行了大量的深夜黑客攻击。他已婚,居住在奥地利施蒂里亚州首府格拉茨,并且喜欢户外活动,例如攀岩、滑雪和排球。
加载 Disqus 评论