|
||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Note: To access the latest sources for the IntroductionIn this final installment of the series, I'll discuss some of the concepts behind Design by Contract, and I'll show you how you can use Another note: If you're wondering what the entire BackgroundAfter discovering the Eiffel programming language a few years ago (and subsequently, Eiffel.NET), I wondered if it would be possible to implement the same type of error checking that the Eiffel programming language employs without having to switch to the Eiffel programming language itself. In the process, I ended up studying the intricacies of Design by Contract 101Disclaimer: Since there’s already comprehensive methodology that surrounds Design by Contract, I'm only going to focus on explaining the aspects of DbC that pertain to this article, so if you're looking for a more in-depth explanation, I strongly suggest looking at some of the other articles on DbC, such as this one. Otherwise, read on and I'll explain DbC in the simplest way I possibly can. A Simple ExplanationAt its very essence, Design by Contract is a way to find bugs by applying running constraints on a program’s behavior. These constraints are runtime assertions that continually check if the program is running in a valid state. If any of those predefined constraints are violated, it means that one or more bugs exist in the program. If you've ever used the The Rule of ThreeEvery given method defined in a class (no matter how simple, or complex) has three points where the method can fail. First, a method can fail if it has an invalid input state (such as a Identifying the CulpritThe theory behind Design by Contract states that if a method has an invalid input state, then generally, the source of the bug is the calling method and the invalid parameters that were passed to the target method. If the target method itself returns an invalid output state, then the source of the bug is the target method itself. If, on the other hand, the target method has both valid input parameters and a valid output (presumably a return value), yet still leaves the object in an invalid state before or after the method call, then the source of the bug still lies in the target method itself. Input and Output States?You're probably wondering why I simply didn't call a method’s input state its parameters, or call its output state its return value. I call it an input state because there might be times when a method doesn't take parameters, yet there might be some conditions that must hold true about the internal state of its host object before the method can be called. For example, I suppose that I have a public class PayrollAccount
{
public int Balance
{
get { return _balance; }
set { _balance = value; }
}
public void PayWages()
{
// Disburse the wages here..
}
}
In this example, the actual input state is the Catching the BugsThe basic theory behind Design by Contract states that one can identify the source of a bug in any given program by placing assertions at any (or preferably all) of these three failure points. In order to catch those bugs, all you have to do is cause the program to fail if any one of those assertions fails. It’s also possible to have a method maintain a valid input and output state, yet leave its host object in a completely invalid state. Since a class’ methods typically operate on a common internal state (such as Seeing DoubleAt first, it might seem like checking for a valid return value after a method call and separately verifying the object state before and after the method call might be redundant, given that they appear to perform the same function. However, an object’s overall state needs to be verified before and after every method call (apart from any checks placed on that method), and that effectively guarantees that the object will always be in a valid state before and after any method is executed. Using this technique makes it incredibly simple to identify bugs, given that identifying the source of any bug is simply a matter of identifying the assertion type that failed, and locating the method that caused that failure. With this in mind, the theory of DbC describes the following types of assertions that should exist within a program, which brings us to the next section. Assertions GaloreAccording to DbC, since there are three points of failure in any given class, it naturally implies that there are three equivalent types of assertions that check failures at those given points. These assertions types are: preconditions, postconditions, and invariants. Preconditions are assertions that identify invalid input (or an invalid input state), and postconditions are assertions that identify an invalid output state (or an invalid return value). Invariants, in contrast, are object-level assertions that are checked before a method is called (and thus, before its preconditions are called), and the same invariants are also called after the method is executed and only invoked after the last method postcondition executes. When all three of these assertion types are applied to a given type, they collectively form what is known as a contract. Up to this point, however, the mechanism that I've just described for DbC only accounts for contracts for types that have a flat class hierarchy (that is, types that directly inherit from Inherited ContractsGenerally, the collective behavior of combined contracts depends on the types of assertions being combined. Let’s suppose, for example, that I have a base class named public class ConnectionManager
{
private int _openConnections;
public int OpenConections { get { return _openConnections; } }
[EnsureConnected]
public virtual void Open([NotNull] IDbConnection connection)
{
// Do something useful here...
_openConnections++;
}
}
The first question you might be asking is exactly how those custom attributes are defined as contract assertions (don't worry--we'll get to that part later). For now, let’s assume that the embedded attributes are equivalent to writing the following code: public class ConnectionManager
{
private int _openConnections;
public int OpenConections { get { return _openConnections; } }
public virtual void Open(IDbConnection connection)
{
// The [NotNull] implementation is equivalent to:
Debug.Assert(connection != null, “The connection cannot be null!”);
// Do something useful here...
// The [EnsureConnected] implementation is equivalent to:
Debug.Assert(connection.State == ConnectionState.Open,
“The connection should be open!”);
}
}
So far, the implementation of public class EnhancedConnectionManager : ConnectionManager
{
[EnsureConnectionCountIncremented]
public override void Open([ConnectionStringNotEmpty] IDbConnection connection)
{
// [ConnectionStringNotEmpty] is equivalent to:
// Debug.Assert(!string.IsNullOrEmpty(connection.ConnectionString),
// “There must be a valid connection string!”);
// Do something useful here...
// [EnsureConnectionCountIncremented] is equivalent to:
// Debug.Assert(this.OpenConnections == OpenConnections + 1,
// “The connection count should have been incremented!”);
}
}
As you can see from the example above, the Combining ContractsIn layman’s terms, this simply means two things. First, it means that the more inheritance levels you impose upon a method contract, the more lenient you have to be when checking method input using preconditions. Secondly, when you add a postcondition to a derived method implementation, it means that the derived method must not only fulfill the postcondition requirements defined in its own contract—it must also fulfill the method postconditions defined on its parents. According to DbC, the actual combined contract of the public class EnhancedConnectionManager : ConnectionManager
{
public override void Open([ConnectionStringNotEmpty] IDbConnection connection)
{
// Combine the preconditions (Logically OR them together)
// Debug.Assert(!string.IsNullOrEmpty(connection.ConnectionString) ||
// connection != null);
// Do something useful here...
// Combine the postconditions (Logically AND them together)
// Debug.Assert(this.OpenConnections == OpenConnections + 1 &&
// connection.State == ConnectionState.Open);
}
}
Puny Preconditions?At first glance, it might seem a bit counterintuitive to weaken the postconditions for every level of contract inheritance in the class hierarchy. After all, there doesn't seem to be a fair tradeoff if a single true precondition on one inheritance level can effectively nullify the preconditions defined on previous (or subsequent) method implementations defined in the hierarchy. In reality, however, for every precondition you define on each inheritance level, there’s a good chance that you'll probably define a corresponding postcondition. While the additional weakened precondition makes it easier to allow for invalid input, its matching postcondition makes it even more difficult for the overridden method implementation to fail. The true tradeoff, in essence, is that the derived method implementations are given more freedom for invalid input at the expense of more responsibility in fulfilling their postconditions. Comparing the old with the NewThe original implementation of the Eiffel language (and thus, Design by Contract itself) had a language feature that allowed postconditions to compare the state of a given object (read: its properties) before and after a given method is executed using something called the Inherited InvariantsInherited invariants in a derived class (much like method postconditions) are simply combined with its parent contract’s invariants using the logical Failing HardAccording to DbC, any failure from any one of the three assertion types should cause the entire program to come crashing to a halt. This makes bugs much easier to find since any assertion error will cause the system to come to a standstill until the source of the bug is fixed by the developer. The failure message in a precondition, postcondition, or invariant assertion should tell the user which method caused the failure in addition to telling why the method failed in the first place. Causing the program to grind to a halt immediately causes the bug to show itself. In short, the process of Failing Hard makes any bug immediately stand out from the rest of the code, and needless to say, that can save quite a lot of debugging time. On to the Playing FieldNow that we a good sense of what kind of behavior one should expect from an implementation of DbC, I'll show you how Design by Contract, with LinFu
Using Features and UsageGift-Wrapped in Dynamic ProxiesDespite AdHocContract someContract = new AdHocContract();
// (Add some preconditions, postconditions, or invariants here)
IDbConnection someConnection = new SqlConnection();
// Use the ContractChecker to bind the contract to a proxy instance
// and wrap it around the target connection
ProxyFactory factory = new ProxyFactory();
ContractChecker checker = new ContractChecker(someContract);
checker.Target = someConnection;
IDbConnection wrappedConnection = factory.CreateProxy<IDbConnection>(checker);
// Do something useful with the wrappedConnection here...
wrappedConnection.Open();
The Transparently BoundUsing proxies allows the client code to use contracts without ever knowing about the existence of the DbC assertions. Using LinFu Contracts
public interface ITypeContract
{
IList<IInvariant> Invariants { get; }
}
public interface IMethodContract
{
IList<IPrecondition> Preconditions { get; }
IList<IPostcondition> Postconditions { get; }
}
public interface IContractProvider
{
ITypeContract GetTypeContract(Type targetType);
IMethodContract GetMethodContract(Type targetType, InvocationInfo info);
}
As you can see from the example above, there are two types of contracts that can be applied to any given type. The The Anatomy of an AssertionThe three assertion types are defined as: // Note: For now, ignore the IContractCheck and IMethodContractCheck
// interfaces--we'll get into that later in the article.
public interface IInvariant : IContractCheck
{
bool Check(object target, InvocationInfo info, InvariantState callState);
void ShowError(TextWriter output, object target, InvocationInfo info,
InvariantState callState);
}
public interface IPrecondition : IMethodContractCheck
{
bool Check(object target, InvocationInfo info);
void ShowError(TextWriter output, object target, InvocationInfo info);
}
public interface IPostcondition : IMethodContractCheck
{
void BeforeMethodCall(object target, InvocationInfo info);
bool Check(object target, InvocationInfo info, object returnValue);
void ShowError(TextWriter output, object target, InvocationInfo info,
object returnValue);
}
The first thing that you might have noticed is that each assertion type has its own custom implementation of the An OddityFor the most part, the three assertion interfaces are fairly self-explanatory, except for the The Peeking PostconditionAs I mentioned earlier in this article, some postconditions need to be able to compare the current state of a given object to its previous state. For example, suppose that I had a Public Class BankAccount
Private accountBalance As Integer
Public Overridable Property Balance() As Integer
Get
Return accountBalance
End Get
Set(ByVal value As Integer)
accountBalance = value
End Set
End Property
Public Overridable Sub Deposit(ByVal amount As Integer)
accountBalance += amount
End Sub
End Class
Using that example, I want to ensure that the account balance is updated every time I make a deposit into that Imports System.IO
Imports LinFu.DynamicProxy
Imports LinFu.DesignByContract2.Core
<AttributeUsage(AttributeTargets.All)> _
Public Class EnsureBalanceReflectsDepositAmountAttribute
Inherits Attribute
Implements IPostcondition
Private oldBalance As Integer
Private expectedBalance As Integer
Public Sub BeforeMethodCall(ByVal target As Object, ByVal info As InvocationInfo) _
Implements IPostcondition.BeforeMethodCall
If target = Nothing Then Return
If Not (TypeOf target Is BankAccount) Then Return
'Save the old balance
Dim account As BankAccount = CType(target, BankAccount)
oldBalance = account.Balance
End Sub
Public Function Check(ByVal target As Object, ByVal info As InvocationInfo, _
ByVal returnValue As Object) As Boolean _
Implements IPostcondition.Check
'Compare the new balance with the old balance
Dim account As BankAccount = CType(target, BankAccount)
Dim newBalance As Integer = account.Balance
'Determine the deposit amount and use it calculate the expected balance
Dim depositAmount As Integer = CType(info.Arguments(0), Integer)
'The expected balance should be the original amount + the new deposit amount
expectedBalance = oldBalance + depositAmount
'Perform the actual comparison
Return account.Balance = expectedBalance
End Function
Public Sub ShowError(ByVal output As TextWriter, ByVal target As Object, _
ByVal info As InvocationInfo, _
ByVal returnValue As Object) Implements IPostcondition.ShowError
output.WriteLine("Deposit Failed! The expected balance should have been '{0}'", _
expectedBalance)
End Sub
Public Function AppliesTo(ByVal target As Object, ByVal info As InvocationInfo) _
As Boolean _
Implements IContractCheck.AppliesTo
'Only BankAccount instances should be checked
Return TypeOf target Is BankAccount
End Function
Public Sub [Catch](ByVal ex As Exception) Implements IContractCheck.[Catch]
'Ignore the error
End Sub
End Class
As you can see in this example An Assertion for Every OccasionAs you probably might have noticed from previous examples, all three of the assertion types inherit from the public interface IContractCheck
{
bool AppliesTo(object target, InvocationInfo info);
void Catch(Exception ex);
}
Simply put, the The CatchThe next thing that you might be wondering about is the System.Exception, Handle Thyself
Dynamic ContractsIf you need something more lightweight than implementing your own versions of the Creating PreconditionsFor example, suppose that I wanted to create a precondition on an AdHocContract contract = new AdHocContract();
IDbConnection connection = new SqlConnection();
// Create the precondition
Require.On(contract)
.ForMethodWith(m => m.Name == "Open")
.That<IDbConnection>(c => !string.IsNullOrEmpty(c.ConnectionString))
.OtherwisePrint("This connection must have a connection string
before call the Open() method");
ProxyFactory factory = new ProxyFactory();
ContractChecker checker = new ContractChecker(contract);
checker.Target = connection;
// Bind the contract to the proxy
IDbConnection wrappedConnection = factory.CreateProxy<IDbConnection>(checker);
// This call will fail
wrappedConnection.Open();
As you can see from the example above, the Creating PostconditionsIf you need to add a postcondition that verifies that the connection has been opened, then all you have to do is add the following lines of code to the above example: Ensure.On(contract)
.ForMethodWith(m => m.Name == "Open")
.That<IDbConnection>(c => c.State == ConnectionState.Open)
.OtherwisePrint(“The connection failed to open!”);
Creating InvariantsIf, on the other hand, you wanted to add an invariant that made sure that the same Invariant.On(contract)
.WhereTypeIs(t => typeof(IDbConnection).IsAssignableFrom(t))
.IsAlwaysTrue<IDbConnection>(c => !string.IsNullOrEmpty(c.ConnectionString))
.OtherwisePrint("The connection string cannot be empty!");
Or if, for some reason, you wanted to invert the logic in the example above and make sure that the // The differences are highlighted in bold
Invariant.On(contract)
.WhereTypeIs(t => typeof(IDbConnection).IsAssignableFrom(t))
.IsAlwaysFalse<IDbConnection>(c => string.IsNullOrEmpty(c.ConnectionString))
.OtherwisePrint("The connection string cannot be empty!");
One could also create an invariant that says that having an open // The differences are highlighted in bold
Invariant.On(contract)
.WhereTypeIs(t => typeof(IDbConnection).IsAssignableFrom(t))
.HavingCondition<IDbConnection>(c => c.State == ConnectionState.Open)
.ImpliesThat(cn => !string.IsNullOrEmpty(cn.ConnectionString))
.OtherwisePrint("You have an open connection with a blank connection string?
How is this possible?");
Unlike in Eiffel (or even in Spec#), all of the contracts seen in the above examples are created at runtime, not compile time. These contracts themselves don't even need to be written using the same .NET language. I'll let you ponder that for a moment. Once you're done pondering all of its possible uses, I'll show you how to create even more powerful contracts in the next section. Attribute-Based ContractsIf you need something more robust rather than lightweight, Imports System.IO
Imports LinFu.DynamicProxy
Imports LinFu.DesignByContract2.Core
<AttributeUsage(AttributeTargets.All)> _
Public Class EnsureBalanceReflectsDepositAmountAttribute
Inherits Attribute
Implements IPostcondition
' Methods omitted for brevity
End Class
If you were to apply the same attribute assertion to the Public Class BankAccount
Private accountBalance As Integer
Public Overridable Property Balance() As Integer
Get
Return accountBalance
End Get
Set(ByVal value As Integer)
accountBalance = value
End Set
End Property
<EnsureBalanceReflectsDepositAmount()> _
Public Overridable Sub Deposit(ByVal amount As Integer)
accountBalance += amount
End Sub
End Class
It’s simple, elegant, and yet at this point, it’s still fairly useless. There has to be a way one can take an embedded precondition, postcondition, or invariant attribute and combine all of them into one cohesive contract. That’s where the ProxyFactory factory = new ProxyFactory();
ContractChecker checker = new ContractChecker(new AttributeContractProvider());
checker.Target = new BankAccount();
BankAccount wrappedAccount = factory.CreateProxy<BankAccount>(checker);
// Make a deposit
wrappedAccount.Deposit(100);
With the Custom Assertion AttributesNow that you have a general idea of what the A bank account has an owner, which is a person.
A person has a name, and an age.
You can do these things with a bank account: deposit, withdraw, and get its balance.
The bank account will have the following restrictions:
1) An account will always have one and only one person (Invariant)
2) Accounts with persons under the age of 18 cannot withdraw any money. (Precondition)
3) No account will ever have a negative balance. (Invariant)
4) Bank accounts should never allow negative amounts for withdrawals or deposits.
(Parameter Precondition)
5) The new balance after a deposit or a withdrawal should reflect the amount of
the transaction (Postcondition)
The model in the above example has all the elements needed for a full-fledged contract. The only thing that we need to do is expand the definition of the // Requirement #1: An account will always have one and only one person
[type: ShouldAlwaysHaveAnOwner]
// Requirement #4: Bank accounts should never allow negative amounts for
// withdrawals or deposits
[type: BalanceIsAlwaysNonNegative]
public class BankAccount
{
private int _balance;
private Person _owner;
public BankAccount(int balance)
{
_balance = balance;
}
public virtual Person Owner
{
get { return _owner; }
set { _owner = value; }
}
public virtual int Balance
{
// Requirement #3: No account will ever have a negative balance.
[return:NonNegative] get { return _balance; }
}
// Requirement #5a: The new balance after a deposit or a withdrawal
// should reflect the amount of the transaction.
[EnsureBalanceReflectsDepositAmount]
// Requirement #4: Bank accounts should never allow negative amounts for
// withdrawals or deposits.
public virtual void Deposit([NonNegative] int amount)
{
_balance += amount;
}
// Requirement #2: Accounts with persons under the age of 18
// cannot withdraw any money.
[MinorsCannotWithdrawAnyMoney]
// Requirement #5b: The new balance after a deposit or a withdrawal
// should reflect the amount of the transaction.
[EnsureBalanceReflectsWithdrawalAmount]
public virtual void Withdraw([NonNegative] int amount)
{
// Notice that I'm not checking for a negative
// balance on the postcondition--this is an intentional error
_balance -= amount;
// The non-negative balance invariant should catch
// the error
}
}
The public class Person
{
private int _age;
private string _name;
public Person(string name, int age)
{
_age = age;
_name = name;
}
public virtual string Name
{
get { return _name; }
set { _name = value; }
}
public virtual int Age
{
[return: NonNegative] get { return _age; }
set { _age = value; }
}
}
As you can see, having assertion attributes directly embedded in the code makes it much easier to understand. More importantly, it isolates the details from the developer about how the assertion is made, and how it works. To create your own custom assertions, you'll need to follow the general set of guidelines for each assertion type: Precondition AttributesIf you need to create your own custom precondition attribute, then all you need to do is implement the [AttributeUsage(AttributeTargets.Method)]
public class MinorsCannotWithdrawAnyMoneyAttribute : Attribute, IPrecondition
{
// ...
}
Once you have defined your custom precondition attribute and declared it on a given method, the public virtual void Deposit([NonNegative] int amount)
{
// ...
}
public virtual void Withdraw([NonNegative] int amount)
{
// ...
}
The Parameter Precondition AttributesParameter precondition attributes are simply custom attributes that implement the public interface IParameterPrecondition : IMethodContractCheck
{
bool Check(InvocationInfo info, ParameterInfo parameter, object argValue);
void ShowErrorMessage(TextWriter stdOut, InvocationInfo info, ParameterInfo parameter,
object argValue);
}
Like any other contract assertion attribute, the [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.ReturnValue)]
public class NonNegativeAttribute : Attribute, IParameterPrecondition, IPostcondition
{
// ...
}
When the Postcondition AttributesThe public virtual int Age
{
[return: NonNegative] get { return _age; }
set { _age = value; }
}
When a postcondition attribute is declared on a method or declared on a return value (and that attribute implements the Invariant AttributesLike the previous examples, the process for defining your own custom invariant attributes is fairly straightforward. First, you have to make sure that the attribute can only be used on type declarations. Secondly, the attribute must implement the [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class)]
public class BalanceIsAlwaysNonNegativeAttribute : Attribute, IInvariant
{
#region IInvariant Members
public bool Check(object target, InvocationInfo info, InvariantState callState)
{
BankAccount account = (BankAccount) target;
return account.Balance >= 0;
}
public void ShowError(System.IO.TextWriter output, object target,
InvocationInfo info, InvariantState callState)
{
output.WriteLine("You cannot have a negative balance!");
}
#endregion
#region IContractCheck Members
public bool AppliesTo(object target, InvocationInfo info)
{
return target != null && target is BankAccount;
}
public void Catch(Exception ex)
{
// Do nothing
}
#endregion
}
Since the public enum InvariantState
{
BeforeMethodCall = 1,
AfterMethodCall = 2
}
As you can see, Inferred ContractsIf you prefer to separate your actual implementation code from the contract declarations, Externally DefinedFor example, let’s suppose that I wanted to define an [ContractFor(typeof(BankAccount))]
[type: ShouldAlwaysHaveAnOwner]
[type: BalanceIsAlwaysNonNegative]
public interface IContractForBankAccount
{
int Balance { [return: NonNegative] get; }
[EnsureBalanceReflectsDepositAmount] void Deposit([NonNegative] int amount);
[EnsureBalanceReflectsWithdrawalAmount,
MinorsCannotWithdrawAnyMoney] void Withdraw([NonNegative] int amount);
}
Aside from intentionally lacking any actual implementation, the assertions declared on the Using Inferred ContractsNow that the contract for the Imports System
Imports LinFu.DesignByContract2.Injectors
Imports Simple.IoC
Imports Simple.IoC.Loaders
Module Module1
Sub Main()
Dim container As New SimpleContainer()
Dim loader As New Loader(container)
Dim directory As String = AppDomain.CurrentDomain.BaseDirectory
' Explicitly load the contract loader DLL
loader.LoadDirectory(directory, _
"LinFu.DesignByContract2.Injectors.dll")
' Load everything else
loader.LoadDirectory(directory, "*.dll")
' Load the BankAccount contract that was written in C#
Dim contractLoader As IContractLoader = _
container.GetService(Of IContractLoader)()
contractLoader.LoadDirectory(directory, "BankAccountContract.dll")
' Manually add a service for a BankAccount
container.AddService(Of BankAccount)(New BankAccount())
' Withdraw money from an empty account, causing a PreconditionViolationException
Dim account as BankAccount = container.GetService(Of BankAccount)()
account.Withdraw(1000)
End Sub
End Module
Behind the scenes, the Dim directory as String = "c:\YourDirectory"
Dim contractLoader As IContractLoader = _
container.GetService(Of IContractLoader)()
contractLoader.LoadDirectory(directory, "YourNewContracts.dll")
This can be useful if you want to debug a large application (such as an ASP.NET site) without having to rebuild the entire application itself. Conceivably, one can even extend the contract loader’s abilities using a Pervasive ContractsWrap One, Get One FreeAny return value from all methods of an object instance that is wrapped in a contract using the contract loader (provided that all of the object’s methods are marked as virtual) is also wrapped in its own separate contract. For example, you might have noticed in the previous examples above that we did nothing to wrap the public class Person
{
// ...
public virtual int Age
{
[return: NonNegative] get { return _age; }
set { _age = value; }
}
}
...and if we look at public class BankAccount
{
// ...
public virtual Person Owner
{
get { return _owner; }
set { _owner = value; }
}
}
The fact that Contract Inheritance
Inheriting Contracts from Multiple SourcesMoreover, public interface IInterface1
{
[SomePrecondition1] void DoSomething();
}
public interface IInterface2
{
[SomePrecondition2] void DoSomething();
}
public class BaseClass : IInterface1, IInterface2
{
public virtual void DoSomething() {}
}
public class Derived : BaseClass
{
[SomePrecondition3] public override void DoSomething() {}
}
The // Wrap the derived class in a contract
ProxyFactory factory = new ProxyFactory();
ContractChecker checker = new ContractChecker(new AttributeContractProvider());
checker.Target = new Derived();
derived = factory.CreateProxy(checker);
// Use the derived class with the contract itself
derived.DoSomething();
Alternatively, the same task can be done by the Points of InterestMixing Test Driven Development, and Design by ContractGiven that Design by Contract isn't in the mainstream at this point, it would be interesting to see how one could mix the two paradigms together. Conceivably, one could use DbC to write some of the same tests that TDD would write, however, in my own practice, TDD’s tests seem to be geared towards testing whether or not a piece of code semantically meets a particular application requirement (such as determining whether or not feature “x” works according to specification), while DbC’s assertions serve to be a pervasive sanity check that continuously checks the application for bugs as the program is running. Although the two paradigms seem to be performing the same function, in truth, it would seem that their features complement each other. Since this topic is both controversial and can be potentially cumbersome, I'll leave it as an exercise to the readers to explore on their own, in greater depth. Sharing Your ContractsSince contracts can be compiled as separate library files with The DotNetRocks! PodcastIn DotNetRocks! Episode #242, Carl Franklin jokingly mentioned something about Design by Contract: Carl Franklin: But all kidding aside. Well
anyway, we have some emails to read here. The
first is from Leon Bambrick, you might recall him
being a writer in previous versions of .NET
Rocks!
Richard Campbell: We have read an email from
him before, I am sure.
Carl Franklin: Yes, and this one says, sexy
Spec#...
Richard Campbell: Nice.
Carl Franklin: He says, “Hi, Richard and the
Seaman, great show on Spec#. I am really glad
you covered this topic as it is one of those
interesting research projects that none of us ever
have the time to look into personally. I hope that
simple, verifiable, pre and post conditions can be
included in a future version of C#. Won't that be
sweet?”
Richard Campbell:
Yes.
Carl Franklin: It will be sweet in VB too man.
Goddamn it! “Or is there something ironic about
Microsoft research, spending a lot of effort to
create not nullable reference types, while the
other teams have spent so much effort creating
nullable value types.”
With those words in mind, License
| |||||||||||||||||||||||||||||||||||||||||