服务器 频道

PL/SQL构建代码分析工具之创建高水平的设计

  【IT168 服务器学院】Feuerstein 的“构建代码分析实用工具”系列的第 3 部分

    下载 codecheck.zip

    在这一 Codecheck 系列的早期文章中(参见 大纲),我仔细分析了希望我的质量保证工具解决的问题:自动识别程序包中潜在的歧义超载。我还确定了可用来实施解决方案的技术 (DBMS_DESCRIBE 和 ALL_ARGUMENTS),并提出了一个测试计划的概要。现在,我可以相当简明扼要地说明 Codecheck 应当做什么:对于一个指定的程序包,或者也许是一个程序包内某个特定程序的名称,从 DBMS_DESCRIBE 和 ALL_ARGUMENTS 中获取关于这些程序的所有信息。然后读取所有的参数信息,并确定是否存在歧义超载。最后,通过 DBMS_OUTPUT 将结果发送至屏幕,同时也发送给 cc_ambig_ovld_results 表,以便可以使用 utPLSQL 来测试 Codecheck.

    似乎够清楚了。这是否意味着下一步就可以开始编写代码了?不!在深入 IFs 和 LOOPs 之前,我需要收集足够多的信息来创建一个设计,以指导编程。本文中,我探索了如何开发这种设计,并演示了我如何小心地避免在开始时过多地纠缠于细节。

    输入和输出

    为我的实用工具创建设计从“输入什么,输出什么?”这一问题开始。这是程序的一个基本问题,因为输入(范围包括参数到底层表格)驱动程序的行为,而要检测一个程序的效果,没有其它方法比检查它的输出(从广义上定义的输出)更好。

    Codecheck 的所有输入包括:

    Codecheck 将检查的程序包或 package.program 的名称。在涉及到超载的地方,分析独立的过程或函数没有任何意义。

    ALL_ARGUMENTS 和 DBMS_DESCRIBE 所提供的与程序相关的参数。

    Codecheck 的所有输出包括:

    将分析结果显示到屏幕的输出。是否存在歧义超载?如果存在,它们违反了什么规则?

    写至 cc_ambig_ovld_results 的行,这些行包含相同的信息。

    关于 Codecheck 体系结构与流程的最初的想法

    给定这些输入和输出,我可以很快地提出 Codecheck 体系结构的概念(参见图 1)。该流程包含四个主要的阶段:

    从 ALL_ARGUMENTS 和 DBMS_DESCRIBE 中收集关于程序参数的数据。我假定将把这些信息加载到 Codecheck 中的一个集合。

    分析参数信息,并识别歧义超载(如果存在的话)。

    将结果写到屏幕和结果表中。

    运行 utPLSQL 来验证这些结果。

    图 1. 关于 Codecheck 总体流程的初步想法

    我认为这可以实现目标 — 并且以非常高的水平实现。这是起步的一个好办法,但它还没有提供开始编程所需的足够细粒度。

    第二次(和第三次……)的一些想法

    无疑,在我有望创建一个可运行的且精心编写的 Codecheck 实用工具之前,我需要更加彻底地对事情进行考虑。然而,事实是我不能独立于代码构建过程来进行这种思考和设计。您曾经多少次提出一个很好的计划然后开始编程,但随后发现实际情况(就像这样:我怎么把这给忘了?)迫使您改变设计?

    避免过度的设计是我从极限编程(一种轻型方法论,它将通行的编程实践发挥至极限,并且推动了我的 utPLSQL 单元测试框架;关于更多信息,请参见 www.xprogramming.com)中学到的最重要的原则之一。比如说,为一个三年的开发项目提出一个总体计划是否真的有意义,而我们都知道这个计划在六个月之内就会漏洞百出?在 Codecheck 的情况下,每个细节在构建程序包之前都考虑得过于详细对我是否真的有意义?

    极限编程人员喜欢问这个问题:使代码运行最简单的方法是什么?

    我喜欢回答这个问题所带来的挑战,因为它使我立刻在几个层次上展开思考。什么是简单?它应该意味着

    透明、易于理解、修改和维护的代码直接并直观地响应需求,但不超出它们的范围的代码

    但工作意味着什么?马上可以想到以下准则:

    代码满足用户的需求。

    代码在一段可接受的时间内运行。

    不过,我觉得反映一段代码可工作性的另一个至关重要的因素是它的可居住性 (habitability) — Richard Gabriel 在他的著作 Patterns of Software: Tales From The Software Community (牛津大学出版社,1996)中提供的一个术语和概念。? 他认为编写另一个程序员可居住的代码是非常重要的 — 可以方便地进入,并进行修改而不用害怕整个结构会崩溃。

    如果一个程序不是可居住的,那么它也不是可维护的。如果不费很大的劲就无法对其进行维护,那么它可能现在能工作,但要满足未来的需求就将极其困难。

    我预计维护代码将逐渐在 PL/SQL 应用程序代码中占有越来越高的比例 — 增强并修补现有的相对稳定的应用程序。因此,设计并实施我们的代码以使其具备可居住性是至关重要的。

    为 Codecheck 提供的一个简单有效的设计

    我想按照极限编程的准则来超越 — 但又不过多地超越 — 我的简单设计(图 1)。实现这一目标最有效的方法是将我的实用工具所需的不同功能区域分隔开,然后创建程序包来包含这些功能。我喜欢把这些程序包看作是一些方便的“存储桶”,当我有了新的想法时,我可以往里面放程序和数据类型。

    在编程过程的初期定义这些存储桶有助于保持代码库井然有序而且模块化。您将发现自己处理的是更大数量的简洁、目的明确的程序包,而不是最后得到一些难于管理的数量庞大的小块程序。随着您的设计的发展,修改这些程序包,或甚至重组它们都相当容易。

    抽象地讨论这一技巧已经够多了。现在让我们看看如何把它应用到 Codecheck 中。

    回到设计版本 1.0,以下是简要的步骤:接收程序包(或程序包内的程序)的名称,获取该程序包的参数数据,分析参数(它们的编号、数据类型、是否存在默认值等等),然后将结果发送至屏幕或测试信息库。

    回顾我其它的许多编程实践(我知道对于一个从 1980 年起一直编写程序的人而言,即使他在这个领域几乎没有受过任何正式的教育,自然也会积累起几分优势),我认为指定对象名可能需要技巧。用户是将程序包的名称、它的所有者和子程序名称作为单独的参数提供还是作为一个参数提供?我如何确定这个名称是否引用了一个已有的有效对象?我如何确保它是一个程序包?您可以编写一些复杂代码对 ALL_OBJECTS 进行查询和解决同义字(可能通过递归的方式)问题等。或者您可以使用 DBMS_UTILITY.NAME_RESOLVE 过程来为您完成所有这些工作。

    我可以马上确定在构建 Codecheck 的过程中会遇到许多细节问题 — 特别是与数据类型有关的问题。DBMS_DESCRIBE 使用数字代码;ALL_ARGUMENTS 提供字符串。如果我不想我的代码马上令人困窘地变得一团糟,我就需要把这些细节隐藏在大量的枚举常量和程序之后。

    您已经看到了我的参数信息源的复杂性。我需要能够干净利索地提取、合并,然后从原始数据中得出结论。这可能将涉及到相当多的后处理。

    报告分析结果也可能将是一件繁琐的事情:如何避免 DBMS_OUTPUT 的缺陷?如何设计输出的外观?

    我无疑将需要一些小型实用程序,以便不同的存储桶共享它们。例如,考虑到它所有的值这一麻烦,我费力地避免了对 DBMS_OUTPUT.PUT_LINE 的直接调用。

    随着我越来越深入到 ALL_ARGUMENTS 和 DBMS_DESCRIBE 中,并试图处理更有挑战性的分析类型时,事情变得越来越复杂。如果我试图愚弄自己,认为这很简单,一晚上就可以搞定,那我将注定要失败。我可能不想预先详细考虑每一个可能的问题,但我也想为变化作好准备。

    哦,别忘了测试的问题。我将需要一个与 utPLSQL 兼容的程序包来包含我的单元测试过程。

    在思考了所有这些以及更多的问题之后,我发现我逐渐接近了图 2 中所示的功能存储桶。仅仅识别象这样的不同区域并不够。我还需要尽可能清楚地预先确定每一个存储桶的用途、存储桶包含什么、您能在里面放入什么、以及能取出什么。处理它的另一种方式是,我需要为每一个存储桶定义握手方式 — 程序与存储桶通信的方式,以及存储桶反过来与调用程序通信的方式。

    如果我很使得存储桶之间的区别明显,那么在任意指定的区域中添加功能,以及将来修补程序都将更容易。这些存储桶之间的牵连或相互依赖性会达到最小。将新的函数或过程放在什么地方将会很清楚,因此,在哪里能找到它也会很明确(这是最重要的,否则如何使用它?)。

    图 2. Codecheck 功能存储桶

    表 1 提供了 Codecheck 存储桶(现在直接称为程序包)的一个说明。当我象这样分隔代码时,还不可避免地要对程序包的层次结构进行考虑。在这个层次结构的最顶层是最复杂的程序包或程序,它们构建于其它许多元素之上。在最底层的是不可分割的程序包 — 小型、高度集中的程序包,它们支持一种用途,并且与其它的程序包没有太多的相关性。

    图 3 显示了 Codecheck 程序包的层次结构的概念。在最底层的是 cc_util 和 cc_error.cc_util 程序包不依赖于其它的 Codecheck 程序包,并且可供所有程序包使用。它甚至不包含 Codecheck 特有的逻辑。cc_error 程序包合并了所有的错误处理和假设验证逻辑。如果其它任何程序包检测到了问题或出现了异常,您可以依靠 cc_error 来适当地处理这些情况。

    图 3. 我需要创建的 Codecheck 程序包的层次结构。

    向上一级是 cc_types 和 cc_names.这些小型、非常专门化的程序包除 cc_util 之外不引用任何程序包。再向上一个层次,我们开始遇到更强健和复杂的代码段。cc_arguments 程序包有一个非常明确的用途:合并来自 ALL_ARGUMENTS 和 DBMS_DESCRIBE 的信息。不过,要实现这一点,它将可能需要进入到更低的层次中。在 cc_arguments 之上,我们看到的是 cc_smartargs 和 cc_report.前者直接在 cc_arguments 之上构建,而 cc_report 使用更底层的程序包和 cc_smartargs 来完成它的任务。

    在这个堆栈的顶层,我们有 Codecheck,它是为终端用户而设计的程序包,包含了由希望检查代码的 PL/SQL 开发人员来实际运行的程序。

0
相关文章