方法重构分为两种类型:
- 接口变更重构,修改了数据库向外部程序提供的接口,因此要求外部程序也进行相应修改。
- 内部重构,改进了方法实现的质量,同时保持接口不变。
1、接口变更重构
1.1、增加参数
这里重构为原有的方法增加了新的参数。
如图1所示,MiddleName作为第3个参数加入到ReadCustomer方法中。增加参数最安全的做法是将它加在参数列表的末尾,入参放在出参之前。
图1 增加一个参数
1.2、方法参数化
有时候两个或者多个方法基本上是同样的东西。例如图2中,GetAmericanCustomers、GetCanadianCustomers以及GetBrasilianCustomers存储过程分别得到美国、加拿大以及巴西的顾客列表。这些存储过程被GetCustomerByCountry所取代,它将国家标识作为一个参数。这样减少了代码的维护,并且很容易增加对新国家的支持。
图2 存储过程参数化
1.3、删除参数
删除不再使用的参数。在图3中,GetAccountList有一个多余的参数AsOfDate被删除。
图3 删除一个参数
1.4、方法改名
有时候方法的命名很糟糕,或者它没有遵守公司的命名惯例。如图4中,GetAccountList存储过程被改名为GetAccountForCustomer,以符合你的标准命名惯例。
图4 存储过程改名
1.5、参数重排序
改变方法参数的顺序,使其更加符合业务需要,易于理解。如图5。
图5 对方法的参数重排序
如图6所示,GetCustomers的参数顺序被重新排序,以反映通用的业务顺序惯例。这是一个高风险的例子,不能有转换期,因为3个参数的类型相同,因此不能简单地重载这个方法。这意味着可能重新排列了参数的顺序,但是外部程序没有重新排列参数的顺序,方法仍能执行,但是参数错了。如果没有好的测试用例,很难发现这个缺陷。
图6 对方法的参数重排序,无转换期
1.6、用明确的方法取代参数
有时候一个方法做几件事情,根据传入的参数值不同而做不同的事。如图7,GetAccountValue实际上是在一行中取得某列的值的通用取值方法,它被GetAccountBalance、GetAccountCustomerID和GetAccountOpeningDate等更明确的存储过程所代替。
图7 用明确的存储过程取代参数
2、内部重构
2.1、合并条件表达式
在方法包含的逻辑中,常常有一系列的条件测试,它们产生相同的结果。在下例中,3个条件合并为一个。这类重构也可以通过“提取方法”重构来创建一个方法来实现这些条件。
参考:http://refactoring.com/catalog/consolidateConditionalExpression.html
-- 重构之前的代码CREATE OR REPLACEFUCTION GetAccountAverageBalance
(inAccountID IN NUMBER)
RETURN NUMBER;
AS
averageBalance := 0;
BEGIN
IF inAccountID = 1000 THEN
RETURN 0;
END IF;
IF inAccountID = 123456 THEN
RETURN 0;
END IF;
IF inAccountID = 987654 THEN
RETURN 0;
END IF;
-- 计算平均余额的代码
RETURN averageBalance;
END;
-- 重构之后的代码
CREATE OR REPLACEFUCTION GetAccountAverageBalance
(inAccountID IN NUMBER)
RETURN NUMBER;
AS
averageBalance := 0;
BEGIN
IF inAccountID = 1000 || inAccountID = 123456 || inAccountID = 987654 THEN
RETURN 0;
END IF;
-- 计算平均余额的代码
RETURN averageBalance;
END;
2.2、分解条件
在一个条件判断IF-THEN-ELSE中,表达式非常复杂,因此难以理解。可以用方法调用替代判断条件、then部分以及else部分,然后在方法中实现表达式的逻辑。
参考:http://refactoring.com/catalog/decomposeConditional.html
-- 重构之前的代码
CREATE OR REPLACE FUNCTION CalculateInterest
(inBalance IN NUMBER)
RETURN NUMBER;
AS
lowBalance NUMBER;
highBalance NUMBER;
lowInterestRate NUMBER;
highInterestRate NUMBER;
BEGIN
lowBalance := GetLowBalance();
highBalance := GetHighBalance();
lowInterestRate := GetLowInterestRate();
highInterestRate := GetHighInterestRate();
IF inBalance < lowBalance then
RETURN 0;
END IF;
IF inBalance >= lowBalance && inBalance <= highBalance then
RETURN inBalance * lowInterestRate;
ELSE
RETURN inBalance * highInterestRate;
END IF;
END;
-- 重构之后的代码
CREATE OR REPLACE FUNCTION CalculateInterest
(inBalance IN NUMBER)
RETURN NUMBER;
AS
BEGIN
IF BalanceIsInsufficient(inBalance) then
RETURN 0;
END IF;
IF IsLowInterestBalance(inBalance) then
RETURN CalculateLowInterest(inBalance);
ELSE
RETURN CalculateHighInterest(inBalance);
END IF;
END;
2.3、提取方法
方法中的一个代码片段可以提取成一个新的方法,方法名称可以解释代码片段完成的工作。提取出的方法同时也可以复用。
参考:http://refactoring.com/catalog/extractMethod.html
-- 代码的初始版本
CREATE OR REPLACE FUNCTION CalculateAccountInterest
( inAccountID IN NUMBER,
inStart IN DATE,
inEnd IN DATE )
RETURN NUMBER;
AS
medianBalance NUMBER;
startBalance NUMBER;
endBalance NUMBER;
interest := 0;
BEGIN
BEGIN
-- 确定开始的余额
SELECT Balance INTO startBalance
FROM DailyEndBalance
WHERE AccountID = inAccountID && PostingDate = inStart;
EXCEPTION WHEN NO_DATA_FOUND THEN
startBalance := 0;
-- 确定结束的余额
SELECT Balance INTO endBalance
FROM DailyEndBalance
WHERE AccountID = inAccountID && PostingDate = inEnd;
EXCEPTION WHEN NO_DATA_FOUND THEN
endBalance := 0;
END;
medianBalance := ( startBalance + endBalance ) / 2;
IF medianBalance < 0 THEN
medianBalance := 0;
END IF;
IF medianBalance >= 500 THEN
interest := medianBalance * 0.01;
END IF;
RETURN interest;
END;
-- 代码的最终版本
CREATE OR REPLACE FUNCTION CalculateAccountInterest
( inAccountID IN NUMBER,
inStart IN DATE,
inEnd IN DATE )
RETURN NUMBER;
AS
medianBalance NUMBER;
startBalance NUMBER;
endBalance NUMBER;
BEGIN
startBalance := GetDailyEndBalance ( inAccountID, inStart );
endBalance:= GetDailyEndBalance ( inAccountID, inEnd );
medianBalance := CalculateMedianBalance ( startBalance, endBalance );
RETURN CalculateInterest ( medianBalance );
END;
-- Code for GetDailyEndBalance、CalculateMedianBalance、CalculateInterest
2.4、引入变量
代码中包含复杂的表达式,难以理解。可以引入一个命名良好的临时变量,存储表达式或者部分表达式的结果。
参考:http://refactoring.com/catalog/introduceExplainingVariable.html
-- 重构之前的代码
CREATE OR REPLACE DetermineAccountStatus
( inAccountID IN NUMBER,
inStart IN DATE,
inEnd IN DATE)
RETURN VARCHAR
AS
lastAccessDate DATE;
BEGIN
-- 计算lastAccessDate的代码
IF (inStart < lastAccessDate && inEnd > lastAccessDate)
&& (inAccountID > 10000)
&& (inAccountID != 123456 && inAccountID != 987654) THEN
--做一些事
END IF;
--做另一些事
END;
-- 重构之后的代码
CREATE OR REPLACE DetermineAccountStatus
( inAccountID IN NUMBER,
inStart IN DATE,
inEnd IN DATE)
RETURN VARCHAR
AS
lastAccessDate DATE;
isBetweenDates BOOLEAN;
isValidAccountID BOOLEAN;
isNotTestAccount BOOLEAN;
BEGIN
-- 计算lastAccessDate的代码
isBetweenDates := inStart < lastAccessDate && inEnd > lastAccessDate;
isValidAccountID := inAccountID > 10000;
isNotTestAccount := inAccountID != 123456 && inAccountID != 987654;
IF isBetweenDates && isValidAccountID && isNotTestAccount THEN
--做一些事
END IF;
--做另一些事
END;
2.5、删除控制标记
使用了一个变量作为控制标记,目的是跳出循环这样的控制结构。可以在控制结构中使用表达式检查来取代控制标记,简化代码。
参考:http://refactoring.com/catalog/removeControlFlag.html
-- 重构之前的代码
DECLARE
controlFlag := 0;
anotherVariable := 0;
BEGIN
WHILE controlFlag = 0 LOOP
-- 做一些事
IF anotherVariable = 20 THEN
controlFlag := 1;
ELSE
-- 做另外一些事
END IF;
END LOOP;
END;
-- 重构之后的代码
DECLARE
anotherVariable := 0;
BEGIN
WHILE anotherVariable <= 20 LOOP
-- 做一些事
-- 做另外一些事
END LOOP;
END;
2.6、消除中间人
一个方法作为其他方法的通道,称为中间人。如果一个存储过程只是简单地调用另一个,就会出现这种情况。
参考:http://refactoring.com/catalog/removeMiddleMan.html
下例中,应该将所有调用AProcedure的地方修改为调用AnotherProcedure,同时可以删除AProcedure:
CREATE OR REPLACE PORCEDURE AProcedure
parameter1 IN NUMBER;
...
parameterN IN VARCHAR;
AS
BEGIN
EXECUTE AnotherProcedure(parameter1, ..., parameterN);
End;
2.7、参数改名
原有的参数名难于理解,修改为更能解释它的意义的名称。
2.8、用查找表取代文字常量
在方法中硬编码的文字常量代表特殊的意义,这使得维护变得困难。更好的方法是将值存储在一个表中,然后在需要时从表中读取,可以缓存表以提高性能。还可以使用“提取方法”重构统一从查找表中获取值的逻辑。
个人觉得也可以在一个包中统一定义符号常量替代文字常量,简化维护。
参考:http://refactoring.com/catalog/replaceMagicNumberWithSymbolicConstant.html
-- 代码的初始版本
CREATE OR REPLACE FUNCTION CalculateInterest
(inBalance IN NUMBER)
RETURN NUMBER;
AS
interest := 0;
BEGIN
IF inBalance > 500 then
interest := medianBalance * 0.01;
END IF;
RETURN interest;
END;
-- 代码的中间版本
CREATE OR REPLACE FUNCTION CalculateInterest
(inBalance IN NUMBER)
RETURN NUMBER;
AS
interest := 0;
minimumBalance NUMBER;
BEGIN
BEGIN
SELECT MinimumBalanceForInterest INTO minimumBalance
FROM CorporateBusinessConstants
WHERE RowNumber = 1;
EXCEPTION WHEN NO_DATA_FOUND THEN
minimumBalance := 0;
END;
IF inBalance > minimumBalance then
interest := medianBalance * 0.01;
END IF;
RETURN interest;
END;
-- 代码的最终版本
CREATE OR REPLACE FUNCTION CalculateInterest
(inBalance IN NUMBER)
RETURN NUMBER;
AS
interest := 0;
minimumBalance NUMBER;
interestRate NUMBER;
BEGIN
minimumBalance := GetminimumBalance();
interestRate := GetinterestRate();
IF inBalance > minimumBalance then
interest := medianBalance * interestRate;
END IF;
RETURN interest;
END;
2.9、用条件短语取代嵌套条件
嵌套的条件语句可能很难理解,可以使用单独的条件语句替代。
参考:http://refactoring.com/catalog/replaceNestedConditionalWithGuardClauses.html
-- 重构之前的代码
BEGIN
IF condition1 THEN
-- 做一些事1
ELSE
IF condition2 THEN
-- 做一些事2
ELSE
IF condition3 THEN
-- 做一些事3
END IF;
END IF;
END IF;
END;
-- 重构之后的代码
BEGIN
IF condition1 THEN
-- 做一些事1
RETURN;
END IF;
IF condition2 THEN
-- 做一些事2
RETURN;
END IF;
IF condition3 THEN
-- 做一些事3
RETURN;
END IF;
END;
2.10、拆分临时变量
一个临时变量在方法中用于多个目的时,很难给它一个合适的名字。可以为不同的用途引入不同的临时变量。
参见:http://refactoring.com/catalog/splitTemporaryVariable.html
-- 重构之前的代码
DECLARE
aTemporaryVariable := 0;
farenheitTemprature := 0;
lengthInInches := 0;
BEGIN
-- 获取farenheitTemprature
aTemporaryVariable := (farenheitTemprature-32) * 5 / 9;
-- 做一些事
-- 获取lengthInInches
aTemporaryVariable := lengthInInches * 2.54;
-- 做一些事
END;
-- 重构之后的代码
DECLARE
celciusTemprature := 0;
farenheitTemprature := 0;
lengthCentimeters := 0;
lengthInInches := 0;
BEGIN
-- 获取farenheitTemprature
celciusTemprature := (farenheitTemprature-32) * 5 / 9;
-- 做一些事
-- 获取lengthInInches
aTemporaryVariable := lengthInInches * 2.54;
-- 做一些事
END;
2.11、替换算法
使用一种更清晰的方法编写原有的逻辑。可以先通过其他重构,如“提取方法”,进行简化,然后再进行替换算法。
参考:http://refactoring.com/catalog/substituteAlgorithm.html
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/24945919/viewspace-743386/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/24945919/viewspace-743386/