Spiga

一天一个重构方法(23):契约式设计

2009-08-12 20:18:00

Introduce Design By Contract:契约式设计
契约式设计(DBC,Design By Contract)定义了方法应该包含输入和输出验证。因此,可以确保所有的工作都是基于可用的数据,并且所有的行为都是可预料的。否则,将返回异常或错误并在方法中进行处理。在我们的示例中,输入参数很可能为null。由于没有进行验证,该方法最终会抛出NullReferenceException。在方法最后,我们也并不确定是否为用户返回了一个有效的decimal,这可能导致在别的地方引入其他方法。

public class CashRegister
{
	public decimal TotalOrder(IEnumerable<Product> products, Customer customer)
	{
		decimal orderTotal = products.Sum(product => product.Price);
		customer.Balance += orderTotal;
		return orderTotal;
	}
}

在此处引入DBC 验证是十分简单的。首先,我们要声明customer不能为null,并且在计算总值时至少要有一个product。在返回订单总值时,我们要确定其值是否有效。如果此例中任何一个验证失败,我们将以友好的方式抛出相应的异常来描述具体信息,而不是抛出一个晦涩的NullReferenceException。在.NET Framework 3.5的Microsoft.Contracts命名空间中包含一些DBC框架和异常。我个人还没有使用,但它们还是值得一看的。

public class CashRegister
{
	public decimal TotalOrder(IEnumerable<Product> products, Customer customer)
	{
		if (customer == null)
			throw new ArgumentNullException("customer", "Customer cannot be null");
		if (products.Count() == 0)
			throw new ArgumentException("Must have at least one product to total", "products");
		decimal orderTotal = products.Sum(product => product.Price);
		customer.Balance += orderTotal;
		if (orderTotal == 0)
			throw new ArgumentOutOfRangeException("orderTotal", "Order Total should not be zero");
		return orderTotal;
	}
}

在验证过程中确实增加了不少代码,你也许会认为过度使用了DBC。但我认为在大多数情况下,处理这些棘手的问题所做的努力都是值得的。追踪无详细内容的NullReferenceException的确不是什么美差。