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(IEnumerableProduct 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(IEnumerableProduct 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 …… 阅读全文

一天一个重构方法(22):封装向下转型

2009-08-10 10:44:41

摘要:某个函数返回的对象,需要由函数调用者执行向下转型动作,将向下转型动作移到函数中。 需要转型的代码如下: public object LastReading() { return readings.lastElement(); } 将转型操作提到函数内后的代码 public Reading LastReading() { return (Reading)readings.lastElement(); } 如果你的某个函数返回一个值,并且你知道你所返回的对象的具体类型时,如果你返回了一个更一般的类型,你便是在函数用户身上强加了非必要的工作。这中情况下你不应该要求用户承担向下转型的责任,应该尽量为他们提供准确的型别。 阅读全文

一天一个重构方法(21):将构造函数替换为工厂函数

2009-08-03 20:27:24

摘要:你希望在创建对象时不仅仅是对它做简单的建构动作,将构造函数替换为工厂函数。 public Employee(int type) { _type = type; } 上面构造函数重构成工厂函数后代码如下 public static Employee Create(int type) { return new Employee(type); } private Employee(int type) { _type = type; } 你可能常常需要根据type code创建相应的对象。现在,创建名单中还得加上subclasses,那么subclasses也是根据type code来创建。然后由于构造函数只能返回被索求的对象,因此你需要将构造函数替换为工厂函数。 阅读全文

一天一个重构方法(20):以测试取代异常

2009-07-30 13:19:11

摘要:Replace Exception with Test:以测试取代异常 面对一个调用者可预先加以检查的条件,你抛出了一个异常,请修改调用者,使它在调用函数之前先做测试。 public int GetValueForPeriod(int periodNumber) { try { return _values[periodNumber]; } catch (Exception) { return 0; } } 加入条件判断后代码 public int GetValueForPeriod(int periodNumber) { if(periodNumber = _values.Length) return 0; return _values[periodNumber]; } 异常的出现是程序语言的一大进步。异常可协助我们避免很多复杂的错误处理逻辑。但是,异常也会被滥用。异常只应该被用于异常的、罕见的行为,也就是那些产生意料之外的错误行为,而不应该成为条件检查的代替品。如果你可以合理期望调用者在调用函数之前先检查某个条件,那么你就应该提供一个测试,而调用者应该使用它。 阅读全文

一天一个重构方法(19):以异常代替错误码

2009-07-25 20:25:00

摘要:Replace Error Code with Exception:以异常代替错误码 某个函数返回一个特定的代码,用以表示某种错误情况,请改用异常。 public int Withdraw(int amount) { if (amount _balance) return -1; else { _balance -= amount; return 0; } } 改成异常后的代码 public void Withdraw(int amount) { if (amount _balance) throw new Exception(); _balance -= amount; } 异常,这种方式之所以更好,因为它清楚地将普通程序和错误处理分开了,这使得程序更容易理解。我坚信:代码的可理解性应该是我们追求的目标。 阅读全文

一天一个重构方法(18):引入参数对象

2009-07-22 05:07:01

摘要:Introduce Parameter Object:引入参数对象 某些参数总是很自然地同时出现,以一个对象取代这些参数 这个重构方法也很简单,我同样就不举例了。 阅读全文

一天一个重构方法(17):尽早返回

2009-07-02 19:23:29

摘要:Return ASAP:尽早返回 该话题实际上是诞生于移除箭头反模式重构之中。在移除箭头时,它被认为是重构产生的副作用。为了消除箭头,你需要尽快地return。 public class Order { public Customer Customer { get; private set; } public decimal CalculateOrder(Customer customer, IEnumerableProduct products, decimal discounts) { Customer = customer; decimal orderTotal = 0m; if (products.Count() 0) { orderTotal = products.Sum(p = p.Price); if (discounts 0) { orderTotal -= discounts; } } return orderTotal; } } 该重构的理念就是,当你知道应该处理什么并且拥有全部需要的信息之后,立即退出所在方法,不再继续执行。 public class Order { public Customer Customer { get; private set; } public decimal CalculateOrder(Customer customer, IEnumerableProduct products, decimal discounts) { Customer = customer; decimal orderTotal = 0m; if (products.Count() == 0) return 0m; orderTotal = products.Sum(p = p.Price); if (discounts = 0) return orderTotal; orderTotal -= discounts; return orderTotal; } } 阅读全文

一天一个重构方法(16):消除双重否定

2009-06-20 01:57:34

摘要:Remove Double Negative:删除双重否定 尽管我在很多代码中发现了这种严重降低可读性并往往传达错误意图的坏味道,但这种重构本身还是很容易实现的。这种毁灭性的代码所基于的假设导致了错误的代码编写习惯,并最终导致bug。如下例所示: public class Order { public void Checkout(IEnumerableProduct products, Customer customer) { if (!customer.IsNotFlagged) { // the customer account is flagged // log some errors and return return; } // normal order processing } } public class Customer { public decimal Balance { get; private set; } public bool IsNotFlagged { get { return Balance 30m; } } } 如你所见,这里的双重否定十分难以理解,我们不得不找出什么才是双重否定所要表达的肯定状态。修改代码是很容易的。如果我们找不到肯定的判断,可以添加一个处理双重否定的假设,而不要在得到结果之后再去验证。 public class Order { public void Checkout(IEnumerableProduct products, Customer customer) { if (customer.IsFlagged) { // the customer account is flagged // log some errors and return return; } // normal order processing } } public class Customer { public decimal Balance { get; private set; } public bool IsFlagged { get { return Balance = 30m; } } } 阅读全文

一天一个重构方法(15):用显式方法替换参数

2009-06-12 04:26:49

摘要:你有一个方法,其内部完全取决于参数值而采取不同反应,针对该参数的每一个可能值,建立一个独立方法。 public void SetValue(string name, int value) { if (name.Equals(Height)) { _height = value; return; } if (name.Equals(Width)) { _width = value; return; } } 重构之后变成两个函数,从函数名就能知道函数的功能了,而不用多传一个参数 public void SetHeight(int height) { _height = height; } public void SetWidth(int width) { _width = width; } 该重构方法恰恰相反于Parameterize Method。如果某个参数有离散取值,而方法内又以条件式检查这些参数值,并根据不同参数值做出不同的反应,那么就应该使用本重构。调用者原本必须赋予参数使用,以决定方法做出何种响应;现在,你提供了不同的方法给调用者使用,就可以避免出现条件式。但是,如果参数值不会对方法行为有太多影响,就不应该使用本重构。这种情况下就只需要通过参数为一个字段赋值,那么直接使用set属性就行了。如果你的确需要条件判断式行为,那可以先不考虑该重构。 阅读全文

一天一个重构方法(14):参数化方法

2009-06-07 03:23:21

摘要:Parameterize Method:另方法携带参数 若干方法做了类似的工作,但在方法本体中却包括了不同的值,可以建立单一方法,以参数表达那些不同的值。 先看一个简单的例子: public void TenPercentRaise() { salary *= 1.1; } public void FivePercentRaise() { salary *= 1.05; } 这段代码可以替换如下: public void Raise(double factor) { salary *= (1 + factor); } 上面的那个例子太简单,所有人都能做到。下面是一个稍微复杂一点的例子: public double BaseCharge() { double result = Math.Min(LastUsage(), 100) * 0.03; if (LastUsage() 100) { result += (Math.Min(LastUsage(), 200) - 100) * 0.05; } if (LastUsage() 200) { result += (LastUsage() - 200) * 0.07; } return result; } 上述代码可以替换如下: public double BaseCharge() { double result = UsageInRange(0, 100) * 0.03; result += UsageInRange(100,200) * 0.05; result += UsageInRange(200, int.MaxValue) * 0.07; return result; } private int UsageInRange(int start, int end) { if (LastUsage() start) return Math.Min(LastUsage(), end) - start; return 0; } 本项重构的技巧在于:以可将少量数值视为参数为依据,找出带有重复性的代码。 阅读全文