数据库重构10之方法重构

方法重构分为两种类型:

  • 接口变更重构,修改了数据库向外部程序提供的接口,因此要求外部程序也进行相应修改。
  • 内部重构,改进了方法实现的质量,同时保持接口不变。

1、接口变更重构

1.1、增加参数

这里重构为原有的方法增加了新的参数。

如图1所示,MiddleName作为第3个参数加入到ReadCustomer方法中。增加参数最安全的做法是将它加在参数列表的末尾,入参放在出参之前。
addParameter.gif

图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个参数的类型相同,因此不能简单地重载这个方法。这意味着可能重新排列了参数的顺序,但是外部程序没有重新排列参数的顺序,方法仍能执行,但是参数错了。如果没有好的测试用例,很难发现这个缺陷。
reorderParameters.gif

图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/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值