构建实用程序验证代码质量的内部详情
2024-07-21 02:37:50
供稿:网友
通过对 Codecheck 及其相关概念的学习,您将了解到 Oracle PL/SQL 最新、最好的一些特性,如多层集合。此外,我还将提供一套平台,您可以在该平台上构建和添加自己的 QA 检查,例如检查参数是否太多或太少、查找所有程序包都要用到的程序,并确保代码符合命名规则。 也许亲身演练的最大好处在于有机会看到实际运用的一些最佳实战技巧,这可能是学习如何使用这些技巧最简单的方法。
我需要声明一点:我已经完成了 Codecheck 的一个运行版本。我计划一边写下该实用程序的构造经过,一边对其进行改进。因此,该系列每一篇文章的下载都将包含一个 Codecheck (codecheck.zip)。请随意下载并立即使用。假如您在调试的过程中碰到什么问题,或是有一些改进意见,请发送至 steven@stevenfeuerstein.com。
确定问题:程序包中的重载多义性
为了写出高质量的程序而顾及到方方面面会令人发疯。因此,我打算集中到几个典型的问题,以免屡屡受挫而不得不放弃创建 Codecheck 的初衷。编写和成功编译包含不可调用程序的 PL/SQL 程序包是极有可能的,而这并没有多大的意义,不是吗?让我们来看看这一希奇的情形是怎样出现的。
Oracle PL/SQL 支持负载,也就是所谓的静态多态性。这就意味着,您能在任何声明段或程序包中用相同的名称定义两个或多个程序,只要这些程序区别显著(通常是参数列表不同),编译器能够辨别您想要使用哪个程序。重载对于提高代码的可用性来说是一项有用的技术。然而,它也会带来一些问题,非凡是假如参数列表很长,其中的参数有些有缺省值,有些没有,则尤其如此。
为了向您证实开发人员的确会面临这一问题,我挑选了几个可能会出错的例子,这些例子是以下面所示的程序包说明开头的:
CREATE OR REPLACE PACKAGE salespkg
IS
PROCEDURE calc_total (zone_in IN VARCHAR2);
PROCEDURE calc_total (reg_in IN VARCHAR2);
END salespkg;
/
这一部分在编译时不会有任何问题,正如其程序体一样。其中有两段重载程序,都命名为 calc_total。其中之一接收一个地段,例如 'ZONE 15',然后计算该地段的总销售额。而第二个程序则接收一个区域,例如 'SOUTHWEST',然后计算该区域的总销售额。但当我试图调用其中某个程序,却出现一个错误。
SQL> exec salespkg.calc_total ('ZONE 15')
BEGIN salespkg.calc_total ('ZONE 15'); END;
*
ERROR at line 1:
ORA-06550:line 1, column 7:
PLS-00307:too many declarations of 'CALC_TOTAL' match this call
该错误消息明确地指出了这一问题:"Too many declarations of CALC_TOTAL match this call."(有多个 CALC_TOTAL 的声明与该调用相匹配)。您可以看到,计算机并不是十分的聪明。您我都可以看出 'ZONE 15' 是一个地段;难道 PL/SQL 编译器就不能识别出这是"地段"的 calc_total 吗(即带有 zone_in 参数的重载)?不幸的是,编译器并不是这样工作的。'ZONE 15' 是字符串的字面意义,编译器无法分析。编译器无法知道应该使用哪段程序,然后就甩手不管了。
我们该如何解决这一问题呢?在这种特定情况下,我可以通过使用指定的参数值来消除多义性:
BEGIN
salespkg.calc_total (zone_in => 'ZONE 15');
END;
在这个实例,我想要告诉编译器使用特定的参数 (zone_in)。因为这两个重载程序中,只有一个程序的参数名与之相符,这样编译器就知道该调用这两个中的哪一个了。尽管如此,不得不 使用参数名引用来调用一个过程或一个函数还是令人难以接受。这明显是一个糟糕的设计—而且会越来越糟。考虑下面的程序包说明:
CREATE OR REPLACE PACKAGE salespkg
IS
PROCEDURE calc_total (zone_in IN VARCHAR2);
PROCEDURE calc_total (zone_in IN CHAR);
END salespkg;
/
同样,这个程序包在编译时也不会有任何问题。但现在又面临另一种情况:无法调用这些过程中的任意一个。他们共享程序名和参数名。唯一的区别就是数据类型。VARCHAR2 当然不同于 CHAR,因此编译器会让您轻松过关。但不幸地是,这两种数据类型的差异性还不够大,这将导致在真正试图使用代码的时侯出现难题。考虑下面的一段代码:
BEGIN
salespkg.calc_total ('ZONE15');
END;
'ZONE 15' 是固定长还是可变长?PL/SQL 文档中说道 "All string literals except the null string ('') have datatype CHAR,"(所有非空串的字符串文字都是 CHAR 型)但是编译器并没有意识到这个问题。非常希奇,即使向程序传送了一个显式声明为固定长度的字符串,仍然会出现问题。
SQL> DECLARE
2 l_zone CHAR(6) := 'ZONE15';
3 BEGIN
4 salespkg.calc_total (l_zone);
5 END;
6 /
salespkg.calc_total (l_zone);
*
ERROR at line 4:
PLS-00307:too many declarations of 'CALC_TOTAL' match this call
如您所见,确实可以定义这样一个程序包重载,即它可以顺利通过编译,但要么不可用,要么只能通过"非自然行为"使用,例如强制使用参数名引用。
我将采用什么样的解决方案呢?
为了识别这些重载问题,我产生如下的想法:也许我可以构建一个实用程序"自动"扫描程序包,检查所有可能的有效程序调用置换,并提醒我注重那些多义重载。我能够作到这一点吗?看起来我应该要能够将程序包定义解析为各段程序,同时解析出每个程序的参数列表。我应该能够取出这些参数的数据类型和缺省值。我如何才能获得这些信息呢?遗憾的是,我对 PL/SQL 解析器没有 API 级的访问权限,非凡是不能从 PL/SQL 自身内部来进行访问。而且我压根不想考虑自己编写一个解析器。那么一个积极的(深受困扰的)实用程序构造者会如何去做呢?他会查找可以替代的方法。
还有没有其他的方法可以从程序中提取这一信息呢?我想起每当我编译一个 PL/SQL 程序,Oracle 数据库就会对源代码进行解析并将其装载到数据字典中。然后就可以提供各种数据字典视图给出对所存储代码的不同描述。ALL_SOURCE 揭示了源代码。ALL_DEPENDENCIES 显示各个对象之间的依靠性。ALL_OBJECTS 告诉我哪个程序是 INVALID。也许有某个数据字典视图有助于解决这一问题。我该如何找到它呢?数据字典中有很多视图,而且这些视图都非常晦涩。
为了能对此有所帮助,我构建了一个名为 dd_view_scan 的实用程序,该程序可以找到符合需要的那些视图(参见表 1;完整的实现行位于 ddviewscan.sp 文件中)。使用 dd_view_scan,我可以轻松搜索视图集以便找到那些可能会提供帮助的数据源。接下来我会对某个特定的视图进行深入分析,看看它是否真的包含我所需要的信息。例如,假如我希望分析程序的重载,那么我就不仅需要知道程序名,还需要检查参数列表(也就是传递的参数)。首先我会向 dd_view_scan 查询含有单词parameter 或 argument 的那些数据字典视图。您可以在 列表 2 中查看到相应的结果。(注重:我使用Oracle9i Release 2 得到这些结果。使用早期版本运行相同的查询可能会得到不同的结果。)
仔细检查该列表,ALL_ARGUMENTS 引起了我的注重。其他的参数看起来要更具针对性,不是我想要的那种。当我仔细分析该参数,我找到了程序包名、参数名和数据类型(参见列表 3)。这些看起来就比较接近我的目标,有必要深入查看一下。让我们继续进行探讨。
研究:有关 ALL_ARGUMENTS 的全部内容
下一步,查看 Oracle 文档集。我急切地去查看 Oracle9i 文档,希望能从中找到答案。我从浏览器中打开文档,使用 Master Index,立即缩小了 ALL_ARGUMENTS 的范围。表 1 显示了对该视图每一列的描述。使用下面的查询,您可以顺便获得虽不相同但还相近的信息(参见 all_arguments_cols.sql):
SELECT column_name, comments
FROM all_col_comments
WHERE table_name='ALL_ARGUMENTS'
遗憾的是,您在表 1 中所见的就是 Oracle 可提供的所有信息。无论如何,我们已经迈出了第一步。许多列的引入都是不言而喻的。而对于其他的,例如 OVERLOAD、POSITION 和 DATA_LEVEL,就不太清楚了。
下面,我需要确信我已经理解了 ALL_ARGUMENTS 的内容。我还必须证实 ALL_ARGUMENTS 包含了 Oracle 所说的它包含的东西。假如您研究 Oracle 技术超过六个月,那么您就会明白任何事都不能想当然。文档中对某技术有某种说法,并不意味着该技术本身就是那样的。某个作者(例如 Steven Feuerstein)说某技术能够在其笔记本电脑中 windows 2000 下的 Oracle9i Release 2 上运行,并不意味着该技术在您的系统中也会以同样的方式运行。自己进行测试来验证取决于您的应用程序的那些操作是非常重要的。
下面就是我所作的一些工作:我将名为 allargs_test 的程序包(位于 all_arguments.tst 文件中)放在一起。该程序包定义了一个带有子程序的包,这些子程序含有各种不同的参数组合(或者没有),这些参数会用到大量不同的数据类型、参数模式等。
然后,我又构造了一些查询以检查 ALL_ARGUMENTS 必须向该程序包提供哪些信息。可以在 allargs*.sql 脚本中找到这些查询。为了让您能够看到 ALL_ARGUMENTS 内容,我会回顾这些查询的某些结果。随后我将提供一个发现结果列表,该列表将引导我设计和实现 Codecheck。
假如有一个带有下列说明的