2009-05-09 00:02:24
摘要:Break Dependencies:打破依赖
有些单元测试需要恰当的测试“缝隙”(test seam)来模拟/隔离一些不想被测试的部分。如果你正想在代码中引入这种单元测试,那么今天介绍的重构就十分有用。在这个例子中,我们的客户端代码使用一个静态类来实现功能。但当需要单元测试时,问题就来了。我们无法在单元测试中模拟静态类。解决的方法是使用一个接口将静态类包装起来,形成一个缝隙来切断与静态类之间的依赖。
public class AnimalFeedingService
{
private bool FoodBowlEmpty { get; set; }
public void Feed()
{
if (FoodBowlEmpty)
Feeder.ReplenishFood();
// more code to feed the animal
}
}
public static class Feeder
{
public static void ReplenishFood()
{
// fill up bowl
}
}
重构时我们所要做的就是引入一个接口和简单调用上面那个静态类的类。因此行为还是一样的,只是调用的方式产生了变化。这是一个不错的重构起始点,也是向代码添加单元测试的简单方式。
public class AnimalFeedingService
{
public IFeederService FeederService { get; set; }
public AnimalFeedingService(IFeederService feederService)
{
FeederService = feederService;
}
private bool FoodBowlEmpty { get; set; }
public void Feed()
{
if (FoodBowlEmpty)
FeederService.ReplenishFood();
// more code to feed the animal
}
}
public interface IFeederService
{
void ReplenishFood();
}
public class FeederService……
阅读全文
2009-05-07 15:43:21
摘要:Switch to Strategy:策略模式转换switch分支
switch 语句块很大,并且会随时引入新的判断条件。这时,最好使用策略模式将每个条件封装到单独的类中。
实现策略模式的方式是很多的。我在这里介绍的策略重构使用的是字典策略,这么做的好处是调用者不必修改原来的代码。
public class ClientCode
{
public decimal CalculateShipping()
{
ShippingInfo shippingInfo = new ShippingInfo();
return shippingInfo.CalculateShippingAmount(State.Alaska);
}
}
public enum State
{
Alaska,
NewYork,
Florida
}
public class ShippingInfo
{
public decimal CalculateShippingAmount(State shipToState)
{
switch (shipToState)
{
case State.Alaska:
return GetAlaskaShippingAmount();
case State.NewYork:
return GetNewYorkShippingAmount();
case State.Florida:
return GetFloridaShippingAmount();
default:
return 0m;
}
}
private decimal GetAlaskaShippingAmount()
{
return 15m;
}
private decimal GetNewYorkShippingAmount()
{
return 10m;
}
private decimal GetFloridaShippingAmount()
{
return 3m;
}
}
要应用该重构,需将每个测试条件至于单独的类中,这些类实现了一个共同的接口。然后将枚举作为字典的键,这样就可以获取正确的实现,并执行其代码了。以后如果希望添加新的条件,只需添加新的实现类,并将其添……
阅读全文
2009-05-06 13:22:28
摘要:Inline Method:将方法内联化
一个方法,其本体如果比其名称更清楚易懂。在方法调用点插入方法本体,然后移除该方法
int GetRating()
{
return (MoreThanFiveLateDeliveries()) ? 2 : 1;
}
bool MoreThanFiveLateDeliveries()
{
return _numberOfLateDeliveries 5;
}
你会发现通过下面重构合并上面的两个方法代码会更具可读性:
int GetRating()
{
return (_numberOfLateDeliveries 5) ? 2 : 1;
}
有时候你会遇到某个方法,其内部代码和方法名称同样清晰易懂。你就可以去掉这个方法,直接使用其中的代码。间接性可能带来帮助,但非必须的间接性总是让人不舒服。
阅读全文
2009-05-05 15:50:24
摘要:Extract Method:提炼方法
你有一段代码可以被组织在一起并独立出来,将这段代码放进一个独立方法中,并让方法名称解释该方法的用途。
public class Receipt
{
private IListdecimal Discounts { get; set; }
private IListdecimal ItemTotals { get; set; }
public decimal CalculateGrandTotal()
{
decimal subTotal = 0m;
foreach (decimal itemTotal in ItemTotals)
subTotal += itemTotal;
if (Discounts.Count 0)
{
foreach (decimal discount in Discounts)
subTotal -= discount;
}
decimal tax = subTotal * 0.065m;
subTotal += tax;
return subTotal;
}
}
你会发现CalculateGrandTotal函数一共做了3件不同的事情:计算总额、折扣和发票税额。开发者为了搞清楚每个功能如何处理而不得不将代码从头看到尾。相比于此,向下面的代码那样将每个任务分解成单独的函数则要节省更多时间,也更具可读性:
public class Receipt
{
private IListdecimal Discounts { get; set; }
private IListdecimal ItemTotals { get; set; }
public decimal CalculateGrandTotal()
{
decimal subTotal = CalculateSubTotal();
subTotal = CalculateDiscounts(subTotal);
subTotal = CalculateTax(subTotal);……
阅读全文