Now, let me start by saying "I love Code Contracts". They will save you time, code and headaches if you implement them correctly. Rather than just talk theory I thought it would be more informative to go through a worked example together...
The Bank Demo
We've been asked to add a simple feature to an existing banking application that allows transfers to be made from one account to another. Here are the, entirely sensible, starting classes we've created...
interface IAccount { string Name { get; } double Balance { get; } } class BankAccount : IAccount { string Name { get; set; } double Balance { get; set; } } static class TransferService { public static void TransferMoney(IAccount from, IAccount to, double amount) { from.Balance -= amount; to.Balance += amount; } }
Well, that would work - mostly; but we have no exception handling or validation. So, here's how we would normally amend that...
Ugly Validation
interface IAccount { string Name { get; } double Balance { get; set; } } class BankAccount : IAccount { // Name is required, so add it to the constructor... public BankAccount(string name) { if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name"); Name = name; } public string Name { get; private set; } public double Balance { get; set; } } static class TransferService { public static void TransferMoney(IAccount from, IAccount to, double amount) { // None of the accounts can be null and the amount must be positive... if (from == null) throw new ArgumentNullException("from"); if (to == null) throw new ArgumentNullException("to"); if (from.Balance < amount) throw new ArgumentOutOfRangeException("from account does not have enough money to transfer"); from.Balance -= amount; to.Balance += amount; } }
Ok, so that's better; but we're starting to really muddy our previously clean classes with all the new validation code. The other problem is that the interface (IAccount) doesn't define the full contract. What we actually want to enforce is 'has a Name property', 'has a Balance property' AND 'the Name property cannot be empty'. Clearly there's no mechanism in the language to specify our final requirement - but it's important. To fix this we find ourselves leaking contract information into classes - in this case our BankAccount class. Now, we could solve this by creating an abstract Account class but then the TransferService would have to change as well and, to be honest, you may as well just get rid of the interface because you cannot use it without the abstract class.
Whilst we've been thinking about this the 'boss' reminds us that the Balance on any account can never be negative. OK, well that destroys our nice, clean auto-implemented property! The BankAccount class now needs to be amended to this...
class BankAccount : IAccount { // Name is required, so add it to the constructor... public BankAccount(string name) { if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name"); Name = name; } public string Name { get; private set; } private double _balance; public double Balance { get { return _balance; } set { if (value < 0) throw new ArgumentOutOfRangeException("Your Bank does not allow your account to be negative."); _balance = value; } } }
I think I prefer the cleanness of our original BankAccount class - how do I get that back?
Well, wouldn't it be the best thing you'd heard this month if you could define the whole of your contract just via the interface? 3rd party developers could then create their own account classes to use in your Transfer Service without you having to tell them about the extra Name or Balance validation. This is where Code Contracts come in.
Contract Requirements
The Contract class in the System.Diagnostics.Contracts namespace has a number of static methods that can be used in a similar way to assertions in unit tests. With them we can tidy up our validation code...
interface IAccount { string Name { get; } double Balance { get; set; } } class BankAccount : IAccount { // Name is required, so add it to the constructor... public BankAccount(string name) { Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(name)); Name = name; } public string Name { get; private set; } private double _balance; public double Balance { get { return _balance; } set { Contract.Requires<ArgumentNullException>(value >= 0, "Your Bank does not allow your account to be negative."); _balance = value; } } } static class TransferService { public static void TransferMoney(IAccount from, IAccount to, double amount) { Contract.Requires<ArgumentNullException>(from != null, "from cannot be null"); Contract.Requires<ArgumentNullException>(to != null, "to cannot be null"); Contract.Requires<ArgumentNullException>(from.Balance >= amount, "'from' account does not have enough money to transfer"); from.Balance -= amount; to.Balance += amount; } }
So now you're thinking "Well, that's nice. My code is a little tidier, but I've effectively just changed syntax.". And you'd be right - almost. The benefit to using the Contract methods is that they can be evaluated at compile time and during static analysis. However, in order to reach the tipping point where you might start thinking "OK, I'm going to use this!" we have to start using some of the Contract Attributes.
Contract Attributes
Contract Attributes allow you to move your validation to a separate class.
[ContractClass(typeof(AccountContract))] interface IAccount { string Name { get; } double Balance { get; set; } } [ContractClassFor(typeof(IAccount))] class AccountContract : IAccount { public string Name { get { return string.Empty; } } public double Balance { get { return 0; } set { Contract.Requires<ArgumentNullException>(value >= 0, "Your Bank does not allow your account to be negative."); } } } class BankAccount : IAccount { public string Name { get; private set; } public double Balance { get; set; } public BankAccount(string name) { Name = name; } }
Now that's much nicer. Now all my validation logic is in a separate class and, with the use of the ContractClass and ContractClassFor attributes, my interface now defines my entire contract.
Contract Invariance
Another very useful attribute is ContractInvariantMethod. This allows you to decorate one, and only one, method in your class that contains contract logic that will be validated every time a property changes on your class. You don't even have to worry about calling it yourself! We could use that to tidy up our AccountContract class...
[ContractClassFor(typeof(IAccount))] class AccountContract : IAccount { public string Name { get; private set; } public double Balance { get; set; } [ContractInvariantMethod] private void ObjectInvariant() { Contract.Invariant(!string.IsNullOrEmpty(Name), "Name cannot be empty."); Contract.Invariant(Balance >= 0, "Balance cannot be negative."); } }
In order to introduce another feature of the Contracts namespace I will change the IAccount interface so that Balance is only gettable. This, then, requires the addition of 2 new methods to withdraw and deposit money to the account...
[ContractClass(typeof(AccountContract))] interface IAccount { string Name { get; } double Balance { get; } void WithdrawMoney(double amount); void DepositMoney(double amount); }
Change Awareness (OldValue)
So, what contract should we define for these new methods? In each case 'amount' needs to be non-negative but also the Balance needs to change by the correct amount. If I deposit £10 I expect my Balance to increase by the same amount. We can make good use of Contract.OldValue
[ContractClassFor(typeof(IAccount))] class AccountContract : IAccount { public string Name { get; private set; } public double Balance { get; set; } [ContractInvariantMethod] private void ObjectInvariant() { Contract.Invariant(!string.IsNullOrEmpty(Name), "Name cannot be empty."); Contract.Invariant(Balance >= 0, "Balance cannot be negative."); } public void WithdrawMoney(double amount) { Contract.Requires<ArgumentNullException>(amount >= 0); Contract.Ensures(Balance == Contract.OldValue(Balance) - amount); } public void DepositMoney(double amount) { Contract.Requires<ArgumentNullException>(amount >= 0); Contract.Ensures(Balance == Contract.OldValue(Balance) + amount); } }
Putting it all together
Finally, you're going to need to download a Microsoft DevLabs addon to Visual Studio to make all this work. DevLabs: Code Contracts automatically changes your code during compilation to bind you contract classes referenced in your interfaces to you concrete classes.
Conclusions
Code Contracts simplify validation, complete your interface definitions and keep you code clean. It enhances Design Patterns, for instance the Adapter Pattern, by allowing your interface to express your entire intent. I haven't covered anywhere near what this namespace can do for you, so go check out the rest yourselves!
No comments:
Post a Comment