PL/SQL:控制神话代码

转帖自:http://www.oracle.com/global/cn/oramag/oracle/04-sep/o54plsql.html


控制神话代码
作者:Steven Feuerstein

研究和破解你代码中的一些神话。

"不要使用XYZ特性;在过去的版本n.n中曾存在一个问题。""要一直使用显式指针,这是提取单行数据的最有效方法。"

"要避免使用程序包,它们会占用太多的内存。"

我 可以肯定你一定听说过这些论点,或者其中这种或那种说法。它们仅仅是一直长期存在于我们PL/SQL应用程序中的几个神话。在某些情况下,最初将它们放入 到我们的软件之中肯定有过充分的理由。而在其它一些情况下,忽略它们将是一种更为积极的做法。无论最初的动机是什么,表现出这些神话的软件都是难于搜索出 的,甚至难于用"正确的"代码来替换的。本文有力批驳了各种最有害的PL/SQL神话,向你说明如何系统性地消除它们,并提供关于如何避免使当今编写的代 码成为明天的神话代码的建议。

"不要使用XYX特性"

神话是如何溜进代码中的?让我们来看软件开发的一个常见情况:编写代码错误处理程序工具。

假定我正在构建一个应用程序,其基础是一个叫做analyze_rates的第三方分析包。它包含一个optimal_plan 函数,该函数返回关于一个给定公司的最佳计划的信息。

CREATE OR REPLACE PACKAGE analyze_rates
IS
TYPE optimal_info_rt IS RECORD (
rate_level PLS_INTEGER,
rate_type PLS_INTEGER,
is_optimal BOOLEAN);
FUNCTION optimal_plan (id_in
IN company.id%TYPE)
RETURN optimal_info_rt;

假定当该计划不是最佳时,该函数在optimal_info_rt record TYPE的is_optimal 域返回一FASLSE值。 不幸的是,此函数有一个错误:即使该计划不是最佳计划,它也总是在is_optimal中返回TRUE。更糟糕的是,我不能修改这个软件包,因为它是由第三方提供的,并且是打包的。

幸运的是,有一个处理程序:它能给出,如果速度等级和类型为NULL则该计划不是最佳的这一结果。

我急忙向我那个拥有25位开发人员的团队发送了一份备忘录,以解释这一问题及其解决方案。我要求所有人任何时候当调用analyze_rates.optimal_plan时都要像下面这样编写代码:

DECLARE
l_company_id company.id%TYPE;
opt_info analyze_rates.optimal_info_rt;
BEGIN
get_company (l_company_id);
opt_info := analyze_rates.optimal_plan (l_company_id);
IF opt_info.rate_level
IS NULL
AND opt_info.rate_type
IS NULL
THEN
-- Not the optimal plan!

这个方法果然有效--至少对现在有效。所以,我的所有开发人员在他们需要时随时复制并粘贴这个IF语句。从而建立了一个"神话"基础。

但是,如果厂商改变optimal_plan函数的特性而使这个处理程序无效,那么会发生什么情况呢?如果厂商实际上解决了他们的问题,optimal_plan 最终确实在记录的is_optimal域中返回FALSE,那又会发生什么情况呢?

我的团队不大可能将所有程序都梳理一遍,查找并替换复合IF语句。

最可能的结果是这一处理程序成为该应用程序的一个永久部分,而它也成为一个反应旧有现实、导致混淆的神话,并极大地增大了产生更多未来错误的可能性。

在本文的末尾的"如何使神话远离代码" 一节中,我将向你说明如何避免这种情况,并使得在纠正了程序错误和改进了功能之后的升级变得简单,从而在神话进入代码之前将其清除。但是,首先让我们来看一下几个在PL/SQL程序中最容易产生的神话。

"要一直使用显式指针"

PL/SQL始终证明自身被设计成为一种通向Oracle数据库的最快速、最容易的编程途径。当在一个数据库中查询数据时,这一点变得非常明显,因为PL/SQL为你提供了几种编写和执行SELECT 指针的方法,包括隐式静态指针、显式静态指针、DBMS_SQL动态指针、本地动态SQL指针和BULK COLLECT INTO。隐式和显式的静态指针是从数据库中查询信息的最常见方法。

  • 隐式指针:在程序中只编写一个SELECT语句,并包含一个INTO 子句,以接收被查询的值,Oracle数据库就会为你完成其余的工作(打开、提取、关闭)。这里有一个例子:

    CREATE OR REPLACE change_team (
    id_in IN team_member.id%TYPE
    ,new_team_id_in IN team_member.team_id%TYPE
    )
    IS
    l_team_member team_member%ROWTYPE;
    BEGIN
    SELECT * INTO l_team_member
    FROM team_member
    WHERE id = id_in;
  • 显式指针:如果你希望更多地控制指针处理过程,还可能希望重新利用你的SQL语句,那么你可以显式声明你自己的指针,然后自己执行每一个操作。下面是对同一个change_team 程序进行重新编写后的结果,这一次采用的是显式指针:

    CREATE OR REPLACE change_team (
    id_in IN team_member.id%TYPE
    ,new_team_id_in
    IN team_member.team_id%TYPE
    )
    IS
    CURSOR member_cur IS
    SELECT *
    FROM team_member
    WHERE id = id_in;
    l_team_member member_cur%ROWTYPE;
    BEGIN
    OPEN member_cur;
    FETCH member_cur INTO l_team_member;

多年来,Oracle世界的许多专家(包括我在内)都在宣传关于显式指针和隐式指针的一个相当刻板的信条。这个信条的说法如下:

你 应当一直使用显式指针(CURSOR <cursor_name> IS),完全避免使用隐式指针(SELECT INTO) ,因为显式指针的效率总是更高一些。它们更高效是因为隐式指针的行为必须符合ANSI标准,而该标准规定:即使是对于单行查询,你也必须总是执行两次提 取:一次用于取得该行,另一次用于查看是否有太多的行(两行或更多行)。因此,采用隐式指针要比使用显式指针的运行速度更慢一些,对于显式指针,你可以发 出一个单一的FETCH命令。

这是否合乎逻辑,并令人信服呢?问题在于,尽管这在很久之前对于Oracle6是正确的,但它对于Oracle8i、Oracle9i或Oracle 10g当然就不正确了。事实上,隐式指针往往比显式指针运行得更快。"显式指针的效率总是更高一些"这一观点是持续时间最久的PL/SQL神话之一,因为它曾传播得更早和更广泛。

那么,在编写指针时,你应当怎么做呢?我推荐以下一些原则:

1.只要有可能,就使用BULK COLLECT--它可以极大地提高性能。在Oracle9i 第2版中,你甚至可以使用BULK COLLECT 取得多行数据,并将其直接置于一个记录集合中。

2.如果仅取一行,那么使用BULK COLLECT 就没有什么意义。无论你决定采用哪一种指针来提取单行数据,都将那个指针隐藏于函数接口之后。在应用程序逻辑和基础SQL之间放置一个PL/SQL 代码层,可以使你自己能够修改查询的执行而不会影响应用程序代码。采用这一方法,我的change_team 程序如下所示:

CREATE OR REPLACE change_team (
id_in IN team_member.id%TYPE
,new_team_id_in
IN team_member.team_id%TYPE
)
IS
l_team_member team_member%ROWTYPE;
BEGIN
l_team_member :=
team_member_qp.onerow (id_in);

team_member_qp 是用于team_member的一个查询包,封装了大量用于表格的查询逻辑,其中包括onerow函数。在onerow 函数中,我可以选择一个显式指针或隐式指针--或者甚至查看在程序包级别集合中缓存team_member 数据是否有意义。重要的是,应用程序编程人员能检测到这一执行问题,这样可以在不引起连锁反应的情况下对其进行修改。

"避免使用程序包"

一般来说,我对Oracle数据库管理员非常尊重。他们不得不掌握大量关于Oracle体系结构及其内部结构的知识,而且他们还必须跟上每个新版本带来的所有变化。但是,如果DBA 跟不上Oracle的最新发展,或者不能正确理解数据库体系结构的某一方面,那么人们就可能会坚持某些并没有什么意义的规则。

例如,当DBA 告诉开发人员要避免使用包,因为它们会使用太多内存时,就显示出对PL/SQL的运行时体系结构的无知。当然,同其它任何神话和传说一样,在其核心内总有一点点正确的东西,这条神话也不例外。

当你调用软件包中的任何单个程序时,有一点是正确的,那就是整个软件包的部分被编译的代码被加载到存储器中。所以,如果你在设计软件包时比较粗心,那么你可能就会利用过多的存储器,其含意如下: 假定我已经创建了一个非常大的软件包BIGPKG,其中包含有100个程序,共需要250K 的内存,而且这些程序包含大量功能。假定我要在我的应用程序中调用程序BIGPKG.ONETHING ,但并不使用软件包中的其它程序。如果ONETHING 仅需要20K的内存,则我将会不必要地使用额外的230K 系统全局区(SGA)内存。

所以存在着过量消耗内存的可能--但这不是软件包结构的错误。这是软件包设计与应用程序设计太差的结果。事实上,如果你仔细设计了你的软件包(参见以下原则),那么可以改进代码在SGA中的内存利用。

为了构建一个基于PL/SQL的应用程序,我们建议以下使用软件包的原则:

  • 将所有代码放入软件包中;避免独立的过程和函数。(确实,这一原则恰好与前面所讲的那个神话相反。)即使你的软件包中现在仅有一个程序,你也应当考虑以后会有具有类似功能的另一个程序,那时你将会有放置该程序的逻辑空间位置。
  • 创 建大量小而功能非常有针对性的软件包。例如,拥有一个叫做UTILITIES 的软件包没有任何意义,它是所有东西的"垃圾场"。相反,应当创建几个其功能经过仔细定义的软件包,如string_utils、date_utils、 constants_pkg和config_pkg。
  • 确保软件包和各个程序的名称准确描述它们的内容。仔细选择名称可以使一个应用程序易于理解、易于维护,而不会变成一团糟。

如何使神话远离你的代码

重要的问题仍然是:如何避免将更多的和更新的神话放入代码中?

对于这个问题有两个基本答案:询问权威和封装。

询问权威。如果你的DBA告诉你因为曾发生过一个历史性的冤案(如,那曾经有一个错误…)而避免使用一个PL/SQL功能领域,或者让你相信一个高级策略论点,如"它会占用过多内存",那么你一定要自己检验一下。那里是否仍然存在错误,或者是否已经解决了该问题?Oracle在MetaLink中提供了非常好的资源,可以帮助你回答这些问题。

如果你的问题更多地是与性能或功能有关,那么你可以编写自己的测试程序。毕竟不存在哲学性的问题,它们都是一些可以挑战的技术观点,既可能对其进行验证, 也可以将其消除。软件的重要吸引力之一(确实这也是我相信编写代码有时颇具吸引力的一个主要原因)是我们正在从事一个封闭式系统。计算机是一个按指令运行 的机器。这里没有神秘的灵魂,因而PL/SQL运行时引擎绝对不会对你撒谎。

因此,如果有人宣称软件包存在内存问题,那么你可以运行你自己的测试程序并分析内存的使用情况。代码清单1中包含了一个程序,你可以用它来显示一个特定会话期中UGA和PGA的内存使用情况,这对PL/SQL开发人员非常有用,因为程序数据(如集合)会占用PGA中的空间。

如果有人宣称某种特定技术(如隐式指针)很慢,或者比另一种方法慢,那么你可以编写一个自己的程序,来对各种技术进行比较。你可以使用SQL*Plus中的SET_TIMING ON ,或者利用DBMS_UTILITY 中的函数(GET_TIME 或GET_CPU_TIME--后者是Oracle 10g中新的函数),用以帮助你以亚秒精度来计算所花费的时间。例如,代码清单2中包含了一个对象类型的说明,允许你启动和停止你程序中的定时器。

封装。处 理神话般代码的第二个主要机制是封装。当你进行封装时,在软件的其它两部分(通常为应用程序逻辑和一些基本功能)之间构建一个代码层。如果你有理由相信在 未来会改变这一基本功能,则将其隐藏在一个函数或过程之中,然后调用该程序。当该功能确实改进了之后(例如,纠正了该错误),你只需要用新的实现代替旧的 实现,那么该神话代码在成为神话之前就已经消失了。

因为你没有改变程序的公共外观--其名称和参数列表--所以该代码应用层中的各个程序都不会受到影响。 编程界非常熟悉这种通过重新设定内部组成要素的方式来改变程序内部组织而不修改其外部接口的过程。

对错误处理程序或处理错误的补丁进行封装非常重要,在进行这种封装时,你可以采取一些专门步骤。 现在让我们重新回顾一下在本文开始时所介绍的optimal_plan错误。

早些时候,我曾提醒开发人员通过编写类似于下面这样的代码来披露其代码中的错误处理程序:

IF opt_info.rate_level IS NULL 
AND opt_info.rate_type IS NULL
THEN
-- Not the optimal plan!

让我们来看一下在这种情况下封装如何帮助我们摆脱困境。下面是一组可以在对错误做出响应时可遵循的替代步骤:

1. 创建一个软件包,以包含用于analyze_rates 包的错误处理程序。我们将其称作analyze_rates_wa。

2. 在这个错误处理程序包中创建一个函数,它位于optimal_plan的顶部,具有相同的名称,调用第三方函数,但是也对is_optimal错误实施处理程序。这一错误处理程序函数的操作方式与最初提出的操作方式相同。

3. 为你的应用程序开发设定一条规则:永远不要调用analyze_rates.optimal_plan ,而是调用analyze_rates_wa.optimal_plan 。注意,你可以通过仔细地执行和检查全局查找和替换来实现已有代码中的这一修改。

4. 在错误处理程序中准确说明什么出了错和如何排除它。


5. 当厂商解决了代码错误/升级了其代码后,你可以做以下两件事情之一:将对analyze_rates_wa.optimal_plan 的引用改回到对analyze_rates.optimal_plan的引用,或者如果存在其它错误,将你代码中对这错误处理程序的调用保留于该代码中,但是要升级该程序的内部组织,来使用所提供的软件中的新功能。

代码清单3 包含了我的用于optimal_plan 函数的错误处理程序包的实施示例。我已经在我的函数中包含了一个注释,来解释问题产生的原因、错误处理程序的特性,以及在排除了程序错误后如何升级代码。

假定在你编写了应用程序的两年后解决了此程序错误。原来的开发团队早就已经解散了。由于你很有远见编写了一个错误处理程序,所以现在维护该应用程序的开发人员可以很容易地、自信地升级该代码,以反应新的现实,而不用与后来会成为神话的模糊不清的错误处理程序做斗争。

没有神话的代码是可维护的

你所编写的程序其预期寿命通常比任何人所料想的都要长。而且,你编写的代码越多,进入生产和维护状态的代码也就越多。如果你不能精心地编写你的程序,你(或者接管你的程序的任何人)最终将必须应对复杂、混乱、脆弱的应用程序。

神话代码--一些表现一种错误情景的PL/SQL代码行--只会使这一情况变得更糟。今天你为了消除和避免你代码中的神话所付出的努力越多,那么每个人就会生活和工作得更好!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值