API's that Suck

November 30, 2009

AddRange with Code Contracts

Filed under: Uncategorized — Grauenwolf @ 7:40 am

Lets start with our AddRange function from last time.

  1. <Extension()>
  2. Public Sub AddRange(Of T)(ByVal target As ICollection(Of T), ByVal source As IEnumerable(Of T))
  3.     For Each item In source
  4.         target.Add(item)
  5.     Next
  6. End Sub

Functionally this works, but what happens if target or source is null? You get a NullReferenceException without any clue as to which argument is messed up.

  1. If target Is Nothing Then Throw New ArgumentNullException("target")
  2. If source Is Nothing Then Throw New ArgumentNullException("source")

And of course you don’t want to try adding items to a read-only collection.

  1. If target.IsReadOnly Then Throw New ArgumentException("target cannot be a read-only collection")

So far so good. But those rules are a little soft and there is nothing to enforce them at compile time. To fix that, we can add some Code Contracts support. By enabling Code Contracts, developers using Team System will get compiler warnings when they try to break a rule. (Developers not using Team System will just get the usual runtime exceptions.) So now we have this:

  1. <Extension()>
  2. Public Sub AddRange(Of T)(ByVal target As ICollection(Of T), ByVal source As IEnumerable(Of T))
  3.     If target Is Nothing Then Throw New ArgumentNullException("target")
  4.     If source Is Nothing Then Throw New ArgumentNullException("source")
  5.     If target.IsReadOnly Then Throw New ArgumentException("target cannot be a read-only collection")
  6.     Contract.EndContractBlock()
  7.  
  8.     For Each item In source
  9.         target.Add(item)
  10.     Next
  11. End Sub

Right now we are making three demands: Source cannot be null, target cannot be null, and target cannot be read-only. But we aren’t making any promises, this code could literally do anything and still obey the contract. Wouldn’t it be nice if we could promise something, like say the target.Count would be incremented correctly? Such as example might look like this.

  1. <Extension()>
  2. Public Sub AddRange(Of T)(ByVal target As ICollection(Of T), ByVal source As ICollection(Of T))
  3.     If target Is Nothing Then Throw New ArgumentNullException("target")
  4.     If source Is Nothing Then Throw New ArgumentNullException("source")
  5.     If target.IsReadOnly Then Throw New ArgumentException("target cannot be a read-only collection")
  6.     Contract.Ensures(target.Count = Contract.OldValue(target.Count) + source.Count)
  7.  
  8.     For Each item In source
  9.         target.Add(item)
  10.     Next
  11. End Sub

In order to make this promise the source cannot just be an enumerable, it must be a collection with a pre-defined size. Looks good, too bad it doesn’t work. According to the compiler, line 6 cannot be proven. It appears, though I’m not certain, that we currently don’t have a contract that says “For all ICollection<T>, calling Add will increment Count by 1.” And without that contract, our code can’t make any promises.

Design by contract is hard. Retrofitting design by contract onto a system that never before had it is downright painful.

Advertisements

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.

%d bloggers like this: