In
first part I tried to explain Code Contracts through analogy. In this part I
will try to talk about compile checks that already existed and they always
existed in any strongly typed language.
Writing
programs is like writing a law. There are many things which must be taken into
account so could our system work optimally. Programs, just like laws, are
constantly changing. I always liked to say "introducing new definition(s)
in our existing set of definitions". Just like there are many
contradictory laws, there are also many contradictory definitions inside any
program and these are all compensate by a programmer by writing shortcuts and
adding more complexity to system as a whole. For writing a good program you
must be a visionary, you must see "clashes" between various
definitions (truths) and you must resolve them without creating a shortcuts.
But, seeing these "clashes" is very hard, because there could be many
new things, which human brain is not capable to comprehend. The only way to see
whether you have "clashes" is to evaluate your definitions through
many combinations (this process is known in testing as "Code
Coverage") and then you can see if your program is in the valid state.
The problem with evaluation is - it is slow process if you are doing it manually
(this includes writing tests) and on the other hand it is too late to have
information at runtime, when your program is already deployed.
The
better way (at least in my opinion) for detecting illogical things is to let a
compiler warn you about irregularity you just introduced by writing new
definitions.
So,
writing code with Code Contracts can help you with that, but even without Code
Contracts we are still able to constraint ourselves.
Let
me start with same example as in previous post:
public string CutExtension(string
text)
{
string output =
text.Substring(text.Length - 3);
return output;
}
OK,
we have a function that strips extension from given file name. With Code
Contracts we can add preconditions, so we can constraint callers what
"type" they'll pass to us. But, even without Code Contracts, we can
still create same constraint using new type:
public struct FileNameNotNull
{
private string
_value;
public string Value
{
get { return _value;
}
}
public FileNameNotNull(string
value)
{
if (value == null)
{
throw new ArgumentNullException();
}
_value
= value;
}
}
Here
we defined class which will allow us to wrap condition in its constructor. In
this particular constructor we check whether string is null or not. So we can
refactor our CutExtension function as follows:
public string CutExtension(FileNameNotNull
text)
{
string output =
text.Value.Substring(text.Value.Length - 3);
return output;
}
Now, we shouldn't care whether null is passed to our function. OK, but we can ask
ourselves “but what if FileNameNotNull instance is null?”. Answer is simple, we are back to the
beginning, at least with C#, in C++ we can choose whether to pass parameter
as reference or as pointer, but unfortunately we don’t have that
luxury in C#. What we can do is to use struct instead, but problem with struct
is that it has default constructor so we cannot constraint ourselves as we
wanted. So, just for the purpose of example, let’s imagine that we can choose
same as in C++ whether we want reference or pointer. In that case, we don’t need
to check for null value in the first place, because just like in C++ we can
write that next to parameter type with “&” sign.
So our
function CutExtension would look like:
public string CutExtension(string&
text)
{
string output =
text.Value.Substring(text.Value.Length - 3);
return output;
}
So we don’t
need FileNameNotNull class anymore. But purpose of this blog post is to explain how to leverage
type system to constraint our program, so I will emphasize this, handling null value
is somehow special case, so
we cannot do that on type system level, it must be supported by compiler.
Now let us use type system for other constraints.
In previous part I showed how can we constraint ourselves for string length
using Code Contracts. But same can be achieved with type system, we just need a new type:
public class StringGreaterThan3
{
private string
_value;
public string Value
{
get { return _value;
}
}
public StringGreaterThan3(string
value)
{
if (value.Length < 3)
{
throw new ArgumentException();
}
_value
= value;
}
}
public string
CutExtension(StringGreaterThan3 text)
{
string output =
text.Value.Substring(text.Value.Length - 3);
return output;
}
Here we can
see, that caller must supply us string with more than 2 characters in length,
otherwise he will get exception during construction:
var parameter = new StringGreaterThan3("33");
CutExtension(parameter);
Caller will
not ever call the CutExtension unction in this case; he will get ArgumentException in StringGreaterThan3constructor. But what if, for
instance, we want to check whether given string is also in lowercase, just for
the purpose of example, let’s say that our business logic requests that from
us:
public string
CutExtension(StringGreaterThan3 text)
{
if (text.Value != text.Value.ToLower())
{
throw new ArgumentException("Expected
string in lower case");
}
string output =
text.Value.Substring(text.Value.Length - 3);
return output;
}
OK, we added
precondition to CutExtension but now caller should know that, and as I said
before, he has several options: to test it, to read documentation, or to read
code (if he has it or through reflector).
But, we can
change this and push arguments checking to our wrapper function:
public class StringGreaterThan3
{
private string
_value;
public string Value
{
get { return _value;
}
}
public StringGreaterThan3(string
value)
{
if (value.Length < 3)
{
throw new ArgumentException();
}
if (text.Value != text.Value.ToLower())
{
throw new ArgumentException("Expected
string in lower case");
}
_value
= value;
}
}
Now we have
a bit awkward name StringGreaterThan3. Because it’s clear that it is a lot
more. So we can change our class name to something exotic like
“StringGreaterThan3AndInLowerCase”. It’s obviously too difficult to comprehend
that name, and also, it's too complex code to understand. We replaced simple
preconditions checking with new class just because… what? Well, we replaced
that just because we don’t want to propagate simple string through many layers.
Imagine this - you have some kind of settings class, which is populated by GUI,
which then is passed to some engine which works in parallel and uses this
information on many places, and what you discover sometimes that someone has
put string in your settings class that is not greater than 3 chars and it is
not in lowercase, so in case of preconditions directly incorporated in CutExtension function you will get exception about wrong arguments and then you can do what? It is really hard then to decide what to do next. If
you are smart enough, you will never catch these exceptions, but reality is, that
there are a lot of code out there which is catching everything it can. On the other hand we can push this
checking from the point where data is entering from “outside” world and then we
can count on compiler - because in rest of the code, we know exactly what are
the traits of underlying arguments (in this case string). But once again, wouldn't be great if
compiler could generate these types, somehow, invisibly for us, so we can just write our
preconditions without writing additional classes for parameters, because it is overwhelming to do it manually. Unfortunately it
cannot, but that’s why we have Code Contracts.
You can
imagine like this, whenever you have some precondition, new type will be
generated. So, in similar way Code Contracts are able to infer whether you supplied "type" as it was expected. But there’s one thing, how can we compensate
Exceptions, I mean, how can we instead of catching exceptions, be warn by
compiler that we didn't supplied valid arguments?
Well that
definitely must be implemented on the compiler level, or in case of Code
Contracts, Static Checker is used. Static Checker will inspect your code for branching and it will check whether you checked for lowercase:
public string CutExtension(string text)
{
Contract.Requires(text
!= null);
Contract.Requires(text == text.ToLower());
string output = text.Substring(text.Length - 3);
return output;
}
And if we
call this function like this:
string s = "TEST.tst";
CutExtension(s);
We will get the following error by static checker:
CodeContracts:
requires unproven: text != text.ToLower()
And if we add checking before calling the function, then everything is OK.
string s = "TEST.tst";
if (s == s.ToLower())
{
if (s == s.ToLower())
{
CutExtension(s);
}Which is great, no need for exceptions! Static Checker will warn us every time we don’t supply “proven” arguments. That means, for every Contract.Requires(condition) we write we must have equivalent if (condition)check from caller side.
But lets extend our example. Let’s say we have one additional function which expects
non-null string parameter in lowercase and then it converts it to uppercase:
public string ToUpperCase(string text)
{
Contract.Requires(text != null);
Contract.Requires(text == text.ToLower());
return text.ToUpper();
}
And our
example should go like this:
string s = "TEST.tst";
if (s == s.ToLower())
{
var res1 = CutExtension(s);
var res2 = ToUpperCase(res1);
}
In this
case, Code Contracts will complain about (among other things) that it
cannot prove that input parameter is in
the lower case:
CodeContracts: requires unproven: text ==
text.ToLower()
But why? CutExtension should return string that is proven not null and text is in the
lowercase. I know that Substring call in CutExtension method will not retrieve proven lowercase string, but it doesn't too, because Substring method only affects length property of original string,
it doesn't converts characters. So to prove that return type is still in
lowercase, we need to write CutExtension method as:
public string CutExtension(string text)
{
Contract.Requires(text != null);
Contract.Requires(text == text.ToLower());
Contract.Ensures(Contract.Result<string>().ToLower() == Contract.Result<string>());
string output = text.Substring(text.Length - 3);
return output;
}
So we added Contract.Ensures call, which will ensures that input parameter will have same
“properties” when returning same (“derived”)
parameter from method! This is a bit overwhelming, because this will introduce
code cluttering. But, on the other hand, this is the best we have.
Unfortunately, I also think that MS should have had continue developing Spec#,
instead of creating new API. I am aware of the fact that they wanted to have
this feature available to every .NET language, but on the other hand, code is
unnecessary “long”, and not so clean, and above all, I believe it would be much faster, because Code Contract Static Checker is so slow, that compiling will look to you as light speed compering to it...
At the end,
here is interesting view on Code Contracts, I must admit, I agree with author
on pretty whole thing.
No comments:
Post a Comment