第一部分:构建一个代码分析实用程序以确保代码首次运行时的流畅和正确
构建实用程序验证代码质量的内部详情
许多开发人员和开发经理所面对的一个突出问题就是找到改进代码质量的方法。于是我决定以一种独特的方式来解决这一难题。在随后的几个月中,我将在 OTN 上发布一系列文章说明我是如何构建 Codecheck 的。 Codecheck 是依靠数据字典中的信息,执行重要任务(即分析程序包以检测这些程序包是否包含重载多义性)的一个 PL/SQL 程序包。由于我采用的是自顶而下的设计方法并运用了许多我在 《 Oracle PL/SQL 最佳实战技巧》 中推荐的最佳实战技巧,因此不想只是简单地呈现结果,而更愿意邀请您和我一同演练这一过程。
深入了解这一实用程序要花费一些时间,因为我想借这个机会实际经历一下开发生命周期中的几个阶段。在以后的几个月中,我将进行以下工作:
确定我希望 Codecheck 解决的问题并明确需求。
给出验证实用程序正确运行的测试案例。
研究有助于解决问题的相关技术。
给出实用程序的整体设计(结果发现测试要求会对我的设计产生影响)。
逐步细化以采用易于编写、理解和部署的代码块来构建解决方案。
利用 utPLSQL 单元测试框架自动对实用程序进行复杂的回归测试。
通过对 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 的实用程序,该程序可以找到符合需要的那些视图。使用 dd_view_scan ,我可以轻松搜索视图集以便找到那些可能会提供帮助的数据源。接下来我会对某个特定的视图进行深入分析,看看它是否真的包含我所需要的信息。例如,假如我希望分析程序的重载,那么我就不仅需要知道程序名,还需要检查参数列表(也就是传递的参数)。首先我会向 dd_view_scan 查询含有单词 parameter 或 argument 的那些数据字典视图。(注重:我使用 Oracle9 i Release 2 得到这些结果。使用早期版本运行相同的查询可能会得到不同的结果。)
仔细检查该列表, ALL_ARGUMENTS 引起了我的注重。其他的参数看起来要更具针对性,不是我想要的那种。当我仔细分析该参数,我找到了程序包名、参数名和数据类型。这些看起来就比较接近我的目标,有必要深入查看一下。让我们继续进行探讨。
研究:有关 ALL_ARGUMENTS 的全部内容
下一步,查看 Oracle 文档集。我急切地去查看 Oracle9 i 文档,希望能从中找到答案。我从浏览器中打开文档,使用 Master Index ,立即缩小了 ALL_ARGUMENTS 的范围。使用下面的查询,您可以顺便获得虽不相同但还相近的信息(参见 all_arguments_