超长的PL/SQL代码
影响可维护性性能
症状
在复杂的企业应用中存在动辄成百上千行的存储过程或上万行的包
为什么是最差
太长的PL/SQL代码不利于阅读第三方工具在调试时也会出现代码行混乱等问题PL/SQL存储对象(存储过程包函数触发器等)行数上限约为行但实际工作中当包大小超过行就会出现调试问题
解决之道
PL/SQL代码在执行前会被加载到shared pool中shared pool以字节为单位UNIX下为K桌面环境下为K可以通过查询数据字典USER_OBJECT_SIZE的PARSED_SIZE字段查看对象大小对于较大的包应采用拆包策略抽取复用部分减少重复代码对于较大的存储过程应将存储过程组织到包中易于管理对于较大的匿名块应将匿名块重新定义成子过程保存在数据库中
脱离控制的全局变量
影响可维护性
症状在包中使用了全局变量在多个位置对全局变量进行操作
CREATE OR REPLACE PACKAGE BODY PKG_TEST IS
GN_全局变量 NUMBER( );
PROCEDURE 过程A IS
BEGIN
GN_全局变量:=;
END;
PROCEDURE 过程B IS
BEGIN
GN_全局变量:=; 这里对全局变量进行了另外的操作
END;
为什么是最差
全局变量可以在整个包范围内被访问到因此对全局变量的跟蹤和调试会比较困难如果变量是在package中定义的变量还可以被其他包访问这将会更为危险
解决之道
减少或取缔全局变量的使用对于要在过程间交互的变量通过参数传递来实现如果必须使用全局变量应对全局变量进行get/set函数封装规范对全局变量的访问
PL/SQL中嵌入复杂SQL语句
影响可维护性
症状
在PL/SQL代码中嵌入SQL语句如
PROCEDURE 过程A IS
BEGIN
UPDATE T_A SET COL = ;
END;
PROCEDURE 过程B IS
BEGIN
DELETE FROM T_A WHERE COL=;
END;
为什么是最差
? PL/SQL代码中嵌入SQL语句使得代码含义变得难于阅读和理解
? 在多个位置对表进行访问不利于SQL优化
解决之道
? 将分散SQL语句进行封装例如上例中的删除语句可以封装为prc_删除T_A()过程参数为T_A的type类型对T_A的删除操作都委托此过程处理当T_A表增加或删除字段时主要的变化都集中在这些过程中对其他逻辑影响较少
? 对SQL的优化集中在封装的过程中
异常的异常处理
影响可维护性健壮性
症状我们来看下面的代码
PROCEDURE 过程A(错误代码 out varchar错误信息 out varchar) IS
BEGIN
UPDATE T_A SET COL = ;
SELECT FROM T_A WHERE ;
DELETE FROM T_A WHERE COL = ;
EXCEPTION
WHEN OTHERS THEN
END;
为什么是最差
整个过程只有一个WHEN OTHERS 的异常段示例中的三个语句发生的异常只能被最外层捕捉无法区分发生异常的种类和位置
解决之道
? 不使用WHEN OTHERS捕捉所有异常例如不应该捕捉NO_DATA_FOUND异常使用专用的Exception来捕捉特定的异常
? 声明自己的异常处理机制处理与业务相关的异常将业务异常与系统运行期异常分开处理
? 自定义完整的异常信息异常信息中包含异常发生时的场景
固定的变量长度和变量类型
影响可维护性
症状当声明基于字段类型的变量时尤其是varchar类型直接使用固定长度声明
为什么是最差
? 这种硬编码的变量大小很可能与数据库中实际大小不符
? 如果字段的类型大小等发生变化还需要到PL/SQL中调整变量
解决之道
使用%Type声明与字段类型相关的变量
不做单元测试
影响健壮性
症状PL/SQL代码中蕴含大量的业务逻辑这些逻辑编写完毕后没有提供合适的单元测试用例用于验证
为什么是最差 不做单元测试的危害这里就不再废话了
解决之道
PL/SQL并没有提供诸如JUnit之类易用的单元测试工具现在有一些开源工具可以使用使用utPLSQL()工具进行单元测试或DBUnit进行二次开发满足不同应用的需要
使用代码值而不使用代码名称
影响可维护性
症状我们看下面的代码
方法
V_sex:=; 男
方法
CONST_MALE CONSTANT VARCHAR() := ; 定义常量 男
V_sex:=CONST_MALE;
为什么是最差
? 从例子中可以看出同样是使用性别方法是直接使用代码值方法是使用常量看上去似乎方法要比方法麻烦一些但方法比方法更为直观代码的可读性也更好代码的阅读者不需要关注代表什么含义
? 当其他项目男性性别定义修改为时采用方法编码的程序需要仔细查找每一段代码容易产生错误而采用方法编码的程序只修改常量定义即可
解决之道
将常量定义放入到公共的代码包中供其他程序共享所有涉及到代码值的比较引用等都必须使用常量名而不能直接书写代码值对于一些复杂的代码值间的关系可以进一步封装以函数的方式提供调用
不对PL/SQL对象进行配置管理
影响可维护性
症状PL/SQL对象(packagepackage bodytriggerproceduretypetype body函数等)的代码没有使用配置管理工具进行维护和更新
为什么是最差
因为Oracle内部结构的差异对象的管理具有一定的难度尤其是在并行开发的情况下
? 对象职责划分不清造成多人同时修改一个对象在编译时如果后来者没有获取最新的代码会造成前一个开发人员修改的代码被覆盖
? Oracle对象不能追溯既往数据库中只能保存最新
解决之道
? 规范开发过程以配置管理工具上的PL/SQL代码为最新
? 使用第三方插件减少同步工作量如PL/SQL Developer下的VCS版本控制插件
IF … ELSE …的坏味道
影响可维护性
症状大量使用IF … ELSE
为什么是最差
大量存在IF/ELSE造成代码逻辑混乱不易修改无论是PL/SQL还是其他编程语言这种代码都已经飘着bad smell了
解决之道
? 使用Oracle数据库的继承特性通过type实现对象的继承利用策略模式封装差异对外提供统一的调用接口
? 将频繁使用的IF/ELSE代码重构为单独的过程或函数供其他代码复用
在非自治事务中控制事务
影响数据一致性
症状
在PL/SQL非自治事务代码中控制事务例如
PROCEDURE 过程A(错误代码 out varchar错误信息 out varchar) IS
BEGIN
SAVEPOINT A;
UPDATE T_A SET COL = ;
COMMIT;
DELETE FROM T_A WHERE COL = ;
ROLLBACK TO A;
EXCEPTION
WHEN OTHERS THEN
END;
为什么是最差
这种行为是我认为最差实践中危害最大的一种随处可见的事务控制代码会造成数据不一致引发的问题难于跟蹤和调试
解决之道
? 由调用者决定何时提交或回滚事务
? 对于需要特殊事务管理的过程如记载日志使用自治事务
不使用绑定变量
影响性能
症状直接使用值而不使用绑定变量进行查询尤其是在拼写sql的程序中这种情况更突出
为什么是最差
这是一个常见问题当代码中大量充斥固定的代码值时数据库引擎每次都需要重新解析不能使用既有的执行计划
解决之道对于这种经常执行的语句使用绑定变量而非实际参数值执行
慎用ROWNUM=
影响可维护性数据一致性
症状在读取数据时有时只需要取一行这时WHERE条件中就会用到ROWNUM=
为什么是最差
之所以将这个实践评成最差是因为笔者在实际工作中曾经遇到过这类问题跟蹤和调试都很困难ROWNUM本身的处理顺序是在ORDER BY 之前所以当ROWNUM=时产生的结果很可能是随机的
解决之道了解要查询数据的含义使用其他条件限制结果集
灵活的动态SQL
影响可维护性性能
症状EXECUTE IMMEDIATE SELECT A FROM TAB INTO v_a;
为什么是最差
动态SQL失去了编译期检查能力将发生问题的可能性推迟到运行期动态SQL也不利于优化因为只有在运行期才能得到完整的SQL语句
解决之道尽量避免使用动态SQL对于易变的业务逻辑可以抽取到中间层实现
对ROWID进行访问
影响数据一致性
症状使用ROWID作为数据更新删除的WHERE条件
为什么是最差
ROWID属于Oracle底层存储结构会随着数据的迁移导入导出发生变化而业务逻辑则不应依赖底层存储结构
解决之道使用主键进行数据操作