Showing posts with label Open Source. Show all posts
Showing posts with label Open Source. Show all posts

Thursday, March 13, 2008

NValidate: Misunderstood from the Outset

Occasionally, I will post questions about the design or feature set of NValidate on Google Newsgroups. More recently, I posted a question about it to LinkedIn. Almost immediately, I got this response:

I'd suggesting looking at the Validation Application Block portion of the Enterprise Library from the Microsoft Patterns and Practices group.

Now, I'm not belittling the response, because it's perfectly valid, and the Validation Application Block attempts to solve essentially the same problem. But when I talk about NValidate, which I find myself doing a lot as I interview for jobs (it's listed on my résumé), people often ask me questions like it:

  1. How is that any different from the Validator controls in ASP.NET?
  2. Why don't you just use the Validation Application Block?
  3. Why didn't you go with attributes instead?
  4. Why didn't you use interfaces in the design?
  5. Why not just use assertions instead of throwing exceptions?

These days, I find myself answering these questions with alarming frequency. It occurs to me that I should probably get around to answering them, so I'm going to address them here and now.

It helps, before starting, to understand the problem that NValidate is trying to solve: Most programmers don't write consistent, correct parameter validation code because it's tedious, boring, and a pain in the neck. We'd rather be working on something else (like the business logic). Writing parameter validation code is just too difficult. NValidate tries to solve that problem by making it as easy as possible, with a minimal amount of overhead.

Q. How is NValidate any different from the Validator controls in ASP.NET?

A. The Validator controls in ASP.NET can only be used on pages. But what if I'm designing a class library? Isn't it vitally important that I make sure I test the parameters on my public interface to ensure that the caller passes me valid arguments? If I'm not, I'm going to fail spectacularly, and not in a pretty way. You can't use the Validator controls (RangeValidator, CompareValidator, and so on) in a class library you're writing that's intended to be invoked from your Web application.

Q. Why don't you just use the Validation Application Block?

A. This one's pretty easy to answer. NValidate is designed to accommodate lazy programmers (like me).

Here's the theory that essentially drives the design of NValidate: Developers don't write parameter validation code with any sort of consistency because it's a pain in the neck to write it, and because we're in a big hurry to get to the business logic (the meat and potatoes of the software). Let's face it: if the first chunk of the code has to be two to twenty lines of you checking parameters and throwing exceptions, and doing it all over the place, you'd get tired of doing it, too. Especially if that code is extremely repetitive.

if(null == foo) throw new ArgumentNullException(foo);
if(string.Empty == foo) throw new ArgumentException("foo cannot be empty.");
if(foo.length != 5) throw new ArgumentException("foo must be 5 characters.");

We hate writing this stuff. So we skip it, thinking we'll come back to it later and write it. But it never gets done, because we get all wrapped up in the business logic, and we simply forget. Then we're fixing bugs, going to meetings, putting out fires, reading blogs, and it gets overlooked. And the root cause is because it's tedious and boring.

I'm not making this up, folks. I've talked to lots of other developers and they've all admitted (however reluctantly), that it's pretty much the truth. We're all guilty of it. Bugs creep in because we fail to erect that impenetrable wall that prevents invalid parameter values from slipping through. Then, we have to go in after the fact and add the code after we've got egg on our face and fix it, at increased cost.

So, if you want to make sure that developers will write the parameter validation code, or are at least more likely to do it, you have to make it as easy as possible to do so. That means writing as little code as possible.

Now, if we look at the code sample provided by Microsoft on their page for the Validation Application Block, we see this:

using Microsoft.Practices.EnterpriseLibrary.Validation;
using Microsoft.Practices.EnterpriseLibrary.Validation.Validators;
public class Customer
{
    [StringLengthValidator(0, 20)]
    public string CustomerName;
    public Customer(string customerName)
    {
        this.CustomerName = customerName;
    }
}

public class MyExample
{
    public static void Main()
    {
        Customer myCustomer = new Customer("A name that is too long");
        ValidationResults r = Validation.Validate<Customer>(myCustomer);
        if (!r.IsValid)
        {
            throw new InvalidOperationException("Validation error found.");
        }
    }
}

A couple of things worth noting:

  1. You have to import two namespaces.
  2. You have to apply a separate attribute for each test.
  3. In your code that invokes the test, you need to do the following:
    1. Declare a ValidationResults variable.
    2. Execute the Validate method on your ValidationResults variable.
    3. Potentially do a cast.
    4. Check the IsValid result on your ValidationResults variable.
    5. If IsValid returned false, take the appropriate action.

That's a lot of work. If you're trying to get lazy programmers to rigorously validate parameters, that's not going to encourage them a whole lot.

On the other hand, this is the same sample, done in NValidate:

using NValidate.Framework;
public class Customer
{
    public string CustomerName;
    public Customer(string customerName)
    {
        Demand.That(customerName, "customerName").HasLength(0, 20);
        this.CustomerName = customerName;
    }
}

public class MyExample
{
    public static void Main()
    {

        try
        {

            Customer myCustomer = new Customer("A name that is too long");

        }
        catch(ArgumentException e)
        {
            throw new InvalidOperationException("Validation error found.");
        }
    }
}

A couple of things worth noting:

  1. You only have to import one namespace.
  2. In the property, you simply Demand.That your parameter is valid.
  3. In your code that invokes the test, you need to do the following:
    1. Wrap the code in a try...catch block.
    2. Catch the exception and handle it, if appropriate.

See the difference? You don't have to write a lot of code to validate the parameter, and your clients don't have to write a lot of code to use your class, either.

Q. Why didn't you go with attributes instead?

A. I considered attributes in the original design of NValidate. But I ruled them out for a number of reasons:

  1. Using them would have meant introducing a run-time dependency on reflection. While reflection isn't horrendously slow, it is slower than direct method invocation, and I wanted NValidate to be as fast as possible.
  2. I wanted the learning curve for adoption to be as small as possible. I modeled the public interface for NValidate after a product I thought was pretty well known: NUnit. You'll note that Demand.That(param, paramName).IsNotNull() is remarkably similar to NUnit's Assert.IsNotNull(someTestCondition) syntax.
  3. In NValidate, readability and performance are king. Consequently, it uses a fluent interface that allows you to chain the tests together, like so:

    Demand.That(foo, "foo").IsNotNull().HasLength(5).Matches("\\d5");

    This is a performance optimization that results in fewer objects created at runtime. It also allows you to do the tests in a smaller vertical space.

My concerns about attributes and reflection may not seem readily apparent until you consider the following: it's conceivable (in theory) that zealous developers could begin validating parameters in every frame of the stack. If the stack frame is sufficiently deep, the costs of invoking reflection to parse the metadata begins to add up. It may not seem significant yet, but consider the scenario where any one of those methods is recursive; perhaps it walks a binary tree, a DOM object, an XML document, or a directory containing lots of files and folders. When that happens, the costs of reflection can become prohibitively expensive.

In my book, that's simply not acceptable. And since, as a framework developer, I cannot predict or constrain where a user might invoke these methods, I must endeavor to make it as fast as possible. In other words, take the parameter information, create the appropriately typed validator, execute the test, and get the hell out as quickly as possible. Avoid any additional overhead at all costs.

Q. Why didn't you use interfaces in the design?

A. I go back and forth over this one all the time, and I keep coming back to the same answer: Interfaces would tie my hands.

Lets assume, for a moment, that we published NValidate using nothing but interfaces. Now, in a subsequent release, we decided we wanted to add new tests. Now we have a problem. We can't extend the interfaces without breaking the contract with clients who are built against NValidate. Sure, they'll likely have to recompile anyway; but if I add new methods to interfaces, they might have to recompile lots of assemblies. That's something I'd rather not force them to do.

On the other hand, abstract base classes allow me to extend classes and add new tests and new strongly typed validators fairly easily. Further, it eliminates casting (because that's handled by the factory). If, however, the system is using interfaces, some methods will return references to an interface, and some will return references to strongly typed validators, and some casting will have to be done at the point of call. I want to eliminate manual casting whenever I can, to keep that call to Demand.That as clean as possible: the cleaner it is, the more likely someone is to use it, because it's easy to do.

Q. Why not just use assertions instead of throwing exceptions?

A. This should be fairly obvious: Assertions don't survive into the release version of your software. Additionally, they don't work as you'd expect them to in a Web application (and rightly so, since they'd kill the ASP.NET worker process, and abort every session connected to it. [For a truly educational experience, set up a test web server, and issue a Visual Basic Stop statement from a DLL in your Web App. You'll kill the worker process, and it will be reset on the next request. Nifty.]).

Wisdom teaches us that the best laid plans of mice and men frequently fail. Your most thorough testing will miss some points of your code. The chances of achieving 100% code coverage are pretty remote; if you do it with a high degree of frequency, I'm duly impressed (and I'd like to submit my resume). But for the rest of us, we know that some code never gets executed during testing, and some code gets executed, but doesn't get executed under the precise conditions that might reveal a subtle defect. That's why you want to leave those checks in the code. Yes, it's additional overhead. But wouldn't you rather know?

In Summary

Sure, these are tradeoffs in the design. But let's keep in mind who I'm targeting here: lazy programmers who are typically disinclined to write lots of code to validate their parameters. The idea is that we want to make it so easy that they're more likely to do it. In this case, less code hopefully leads to more, which (I hope) leads to fewer defects, and higher quality software.

Sunday, August 19, 2007

Heroes, Villains, and Software

I'm a big fan of comics, and I'm also a big fan of software development. Coincidentally, I see metaphors and analogies for software development just about everywhere I look.

Comic books have come a long way from the days when it was just about the guy wearing the mask or the brightly colored costume who rushed in to save the day. In the early days, the stories were pretty straightforward: the bad guys were always easily identified, and so were the good guys. But stories these days have become more complex, more adult, and they tackle tough issues. But one common element persists: the heroes and villains tend to wear costumes or masks that exemplify their heroic or villainous persona.

The attire of a hero or villain isn't simply done for theatrical reasons (although there is certainly a large and undeniable element of theatricaility to it). The mask grants the wearer anonymity; those who behold the wearer have no idea who the wearer is, and with that anonymity the wearer is emboldened to do things he or she might never do or have the courage to do if people knew who he or she was.

Anonymity grants the wielder power. What separates the heroes from the villains is a sense of responsibility and what they choose to do with it. A villain might have a sense of responsibility but choose to ignore it, and use his power for personal gain.

When a civilian in a comic book views a mask-clad individual for the first time, they don't really know whether that individual is a hero or a villain, or just some bloke in a costume. They might be able to infer some things from the costume, but they have to trust their instincts. A great failing of classic villains seemed to be that they went out of their way to look villainous; they were so easily identified that they were easy to avoid or locate and defeat.

But the greatest villains actually looked like one of the good guys, or just a regular Joe. They managed to garner the trust of those around them, and then used that trust against those who placed it in them. You see, sometimes, wearing no costume at all is a mask in and of itself.

So now we come to the part where we apply this to software.

All software wears a mask. It's called the user interface. It doesn't matter if it's a desktop application, a Web application, a framework, a Web service, a device driver, or anything else. The mask is the face that your software presents to the public, how the public perceives the software when they first look at it.

Most software presents itself as the next silver bullet to any given problem domain. After all, what software doesn't want to be perceived as a Hero? A user, looking at your software for the first time, is forced to place a certain amount of trust in it because all they can see is its mask. And the mask grants the software anonymity and power.

Unless you're giving the user the source code, and the user knows how to read it, and can build it themelves, they're at your mercy. They're trusting you to be heroic on their machine: to have a sense of responsibility, and to use the power you've been granted wisely.

Sadly, much precompiled software relies on its anonymity to conceal its true state: that it is miserable, ugly, unmaintainable, untestable, bloated, unreliable, and unsafe. Software of this nature is hardly heroic; rather, it's villainous. It might handle user data with reckless disregard for integrity, security, or consistency. It might crash frequently, and for no apparent reason. It might install spyware or adware. It might log keystrokes. It might be riddled with bugs, be poorly documented, and have no consistent coding model or standard. It might use an inconsistent user interface paradigm, or hog system resources. It might break other software. It might do all of these things.

The question we are compelled to ask is this: What is hiding behind the mask of the software that we're writing, right now, at this moment? Is it heroic? Or is it villainous? Despite claims by certain advertisers, it isn't really enough that "it just works." It has to work well, and responsibly.

Some of the greatest heroes in comic book lore were brave enough to take their masks off so that people could see them and know who they were. Would you be willing to open your code base up to let the world see what you're doing? Would you feel comfortable letting everyone see the code that you write and what it's doing? Would you be confident or proud of your work? Or would you feel shame? Embarrassment? Even guilt?

Not all software can be heroic. There are always mitigating circumstances. But we can all strive to be heroic. It's when we strive to be villainous that we need to be worried. Or when we're writing villainous software, and we know it, and we choose not to do anything about it. We all have a choice. We can choose to make great software that behaves responsibly, and does what it says its going to do, or we can abuse the trust placed in us and serve ourselves.

Take stock of the software you're writing. Find out what's behind its mask. If it's not software you'd be prepared to unmask in front of its users (aside from intellectual property issues), chances are, something somewhere has gone horribly awry. What are you going to do about it?

Monday, July 16, 2007

Announcing NValidate

Yeah, so I bit the bullet. I decided to go ahead and port the parameter validation code to C# and make it open source. And I settled on a name. After an extensive search on the web to make sure the name wasn't taken, I chose the apt name of NValidate.

Yes, it's a play on words.

So I've registered the domain name (www.nvalidate.org) and will soon have the web site up for it. First thing I'll get out the door is the user documentation so that you can see what's coming.

The initial release will provide basic parameter validation tests for each of the following types in the Framework:

  • Boolean
  • Byte
  • Char
  • DateTime
  • Decimal
  • Double
  • IDbConnection
  • IDbTransaction
  • Int16 (Short)
  • Int32 (Integer)
  • Int64 (Long)
  • Object
  • Single
  • String

For those who haven't seen my earlier post, NValidate provides a way to streamline the code that tests method parameters for validity. It turns this:

protected int getPlus4(string zipCode)
{
const string ZipFormat = "\\d[5]-\\d[4]";
if (zipCode == null)
throw new ArgumentNullException("zipCode");
if (!((new Regex(ZipFormat)).IsMatch(zipCode)))
throw new ArgumentNullException("zipCode");

return int.Parse(zipCode.Substring(6, 4));
}


into this:

protected int getPlus4(string zipCode)
{
const string ZipFormat = "\\d[5]-\\d[4]";
Validate.That(zipCode,
"zipCode").IsNotNull().Matches(ZipFormat);
return int.Parse(zipCode.Substring(6, 4));
}


To ensure that the learning curve is small, the "interface" to NValidate is modeled after that of NUnit; that is, Validate.That is similar to NUnit's Assert.That. The difference is that Validate.That always throws an exception when the test fails, and the exception is always an ArgumentException (or one of its derivatives).


The tests supported by NValidate will be pretty exhaustive. I've got plans for a full suite of tests already planned. They're shown at the bottom of this post. If you can think of one that you do very frequently and it's not on the list, let me know, and I'll add support for it. This list of tests is based on the tests that I normally perform myself in software, plus a few that I gleaned from NUnit itself. I plan to expand the library down the road, adding more tests and more type support as demand for it increases. (That supposes, of course, that said demand actually exists.)


NValidate will support .NET 1.1 and 2.0; it will ship with compiled binaries and the full source code. Additionally, the license I select will permit its use for any reason, free of charge. If you find a bug in it, I would hope that you'll let me know so that I can fix it.



Proposed Tests for Initial Release of NValidate



  • Contains: String
  • DoesNotContain: String
  • DoesNotMatch: String
  • EndsWith: String
  • HasDay: DateTime
  • HasHour: DateTime
  • HasLength: String
  • HasMinute: DateTime
  • HasSecond: HasValidConnection
  • HasYear: DateTIme
  • IsBoolean: String
  • IsClosed: IDbConnection
  • IsEqualTo: All
  • IsFalse: Boolean
  • IsGreaterThan: Byte, Char, DateTime, Decimal, Double, Int16, Int32, INt64, Single, String
  • IsGreaterThanOrEqualTo: Byte, Char, DateTime, Decimal, Double, Int16, Int32, INt64, Single, String
  • IsInRange: Byte, Char, DateTime, Decimal, Double, Int16, Int32, INt64, Single, String
  • IsLessThan: Byte, Char, DateTime, Decimal, Double, Int16, Int32, INt64, Single, String
  • IsLessThanOrEqualTo: Byte, Char, DateTime, Decimal, Double, Int16, Int32, INt64, Single, String
  • IsNegative: Byte, Char, Decimal, Double, Int16, Int32, INt64, Single
  • IsGreaterThan: Byte, Char, Decimal, Double, Int16, Int32, INt64, Single
  • IsNotEqualTo: All
  • IsNotInRange: Byte, Char, DateTime, Decimal, Double, Int16, Int32, INt64, Single, String
  • IsNotNull: IDbConnection, IDbTransaction, Obect, String
  • IsNotOneOf: All
  • IsNull: IDbConnection, IDbTransaction, Object, String
  • IsNumeric: String
  • IsOneof: All except Boolean
  • IsOpen: IDbConnection
  • IsPositive: Byte, Char, Decimal, Double, Int16, Int32, Int64, Single
  • IsTrue: Boolean
  • IsValid: All
  • IsZero: Byte, Char, Decimal, Double, Int16, Int32, Int64, Single
  • Matches: String
  • StartsWith: String

Wednesday, July 11, 2007

A Parameter Validation Framework

I need some help here. I'm hoping you'll review the following "framework" and give me your feedback. I need to know (1) if it's a good idea, (2) if the architecture is sound, and (3) how it can be improved. If it's a generally useful idea, I'll open it up on an open source project forum where it can be expanded for general use. Otherwise, I'll go back to the drawing board. (I'm not sure at this point if it's a worthwhile idea.)

The Background

In reviewing my projects, I noticed that I tend to be lazy about validating parameters to methods. I reviewed a number of projects that I was working on and noticed that it tended to be true no matter what the project was, or what the parameter types were. When I sat back and thought about it, and paid attention to myself doing it, it came down to two primary issues that led to me avoiding it:

  • I was usually in a rush to get a product out the door. This tends to always be the case, since the product's schedule is out of my control, and there isn't much that I can do about it. I can only negotiate for a minor change in the project's time. Then, I'm left to work with the amount of time I'm given. So I try to do the best work I can in the time I'm allotted, and hope to high heaven that it's good work. That often means that I code like hell (classic software mistake), and hope that nothing breaks.
  • Parameter validation code tended to be verbose. When I really wanted to say, "Parameter y should be a positive number," the resulting code looked like this:
    If myParameter <= 0 Then
       Throw New ArgumentOutOfRangeException("myParameter", "Must be a positive number.")
    End If

These two issues alone might not seem like a big deal in and of themselves. But when it comes to encouraging code quality, I take them very seriously. I abhor sloppy code, especially when it's mine. I want to be sure that parameters are valid, and that when a method receives parameters, they are checked rigorously to ensure that I'm getting valid values throughout any system I'm developing.

The Goal

I set out to come up with a way to make it easier to validate parameters. I wanted a way to make it so easy to validate them that I would find it enjoyable to do so. I wanted it to make my code more legible, with a minimal amount of impact on its performance. Knowing that I basically lack discipline, I knew I needed a software solution that would exploit Visual Studio's capabilities to remind me to check for tests that I might not have normally thought of. (We have a tendency to inadequately document the requirements—again, due to time constraints—and I need to be able to think about logical tests that make sense for the parameter as I'm coding them.)

So the goals for the framework are:

  • Ease of use. This was the number one design goal. If the thing wasn't easy to use, it was pointless to build it in the first place, since it was being designed to encourage validation of parameters. If the framework made it harder to do that, it was defeating the purpose.
  • Understandability. The code should enhance the readability of the code.
  • Performance. The methods were going to be called with a high degree of frequency. It was critical that the methods have a very low overhead on the call stack. If at all possible, the number of objects being created should be kept to a minimum. However, because we're adding code, some overhead is unavoidable.
  • Reliability. It has to work. And it has to work reliably and predictably every time.
  • Extensibility. It has to support other data types.
  • Maintainability. The framework's code has to be easy to read, understand, and maintain. We do not want to have to bloat the development time of the projects that rely upon it with weeks of maintenance time just to fix and expand the parameter validation library.

The Solution

I set out to come up with a way to make it easier to validate parameters. I wanted a way to make it so easy to validate them that I would find it enjoyable to do so. I wanted it to make my code more legible, with a minimal amount of impact on its performance. I wrote a set of classes that, I believe, accomplished that. The resulting "framework" (if you can call it that) is based on the Assert.That model popularized by NUnit and JUnit. When you want to prove that a parameter is positive, you write the following:

Sub Foo(ByVal integerParameter As Integer)
   Validate.That(integerParameter, "integerParameter").IsPositive()
   ' Do something interesting with integerParameter
End Sub

It's short, sweet, and to the point. If integerParameter contains any value that is less than 1, an ArgumentOutOfRangeException is thrown, with an appropriately formatted message bearing the parameter's name.

The solution involves the following classes:

Class Description
Validate A class factory that instantiates strongly-typed parameter validator objects. These are derived from ParameterValidatorBase. Provides the heavily overloaded That method.
BooleanValidator Inherits from ParameterValidatorBase. Provides methods for validating Boolean parameters, such as IsTrue and IsFalse.
ConnectionValidator Inherits from ParameterValidatorBase. Provides methods for validating IDbConnection parmaeters, such as IsNotNull, IsOpen, and IsClosed.
DateValidator Inherits from ParameterValidatorBase. Provides methods for validating date parameters, such as Equals, IsBetween, IsGreaterThan, IsGreaterThanOrEqualTo, IsLessThan and IsLessThanOrEqualTo
EnumValidator Inherits from ParameterValidatorBase. Provides methods for validating enum parameters. This is the only parameter validator that relies on reflection. It's sole method is IsValid.
IntegerArrayValidator Inherits from ParameterValidatorBase. Provides methods for validating integer array parameters, such as IsNotNull and IsNotEmpty.
IntegerValidator Inherits from ParameterValidatorBase. Provides methods for validating integer parameters, such as Equals, IsGreaterThan, IsGreaterThanOrEqualTo, IsInRange, IsLessThan, IsLessThanOrEqualTo, IsNegative, IsNotNegative, IsOneOf, IsPositive, and NotEqualTo.
ListValidator Inherits from ParameterValidatorBase. Provides methods for validating IList parameters, including IsEmpty, IsNotEmpty, IsNotNull, and IsNotNullOrEmpty.
ObjectValidator Inherits from ParameterValidatorBase. Provides methods for validating any Object parameter, including IsNotNull and Equals.
ParameterValidatorBase The base class for all validators. Provides the name of the parameter and a protected property to store the parameter's value.
StringArrayValidator Inherits from ParameterValidatorBase. Provides methods for validating string arrays, including IsNotNull, IsNotEmpty, and IsEmpty.
StringValidator Inherits from ParameterValidatorBase. Provides methods for validating strings, including Contains (overloaded), Endswith (overloaded), IsNotEmpty, IsNotNull, IsNotNullOrEmpty, and StartsWith (overloaded).
TransactionValidator Inherits from ParameterValidatorBase. Provides methods for validating IDbTransaction parameters, including HasValidConnection, HasOpenConnection and IsNotNull.

Other parameter validator types are being added as the need for them arises.

When a call is made to Validate.That(), the framework instantiates an appropriately typed parameter validator object. The caller then invokes one or more of the methods on that object to prove that the parameter is valid. The parameter validator merely tests the condition specified by the method name; if the test evaluates to False, an appropriately typed exception (derived from ApplicationException) is thrown. The parameter validator is responsible for properly formatting the message and passing the name of the parameter; this is spiffy because some of the exception objects are inconsistent about the order in which the name and message parameters are passed to them. The framework standardizes the order through the use of overloads: parameter, name, message.

For example, the source code for the ConnectionValidator class looks like this:

Option Explicit On 
Option Strict On

Imports
System.Data

Namespace Validation

Public Class ConnectionValidator
Inherits ParameterValidatorBase

Friend Sub New(ByVal connection As IDbConnection, ByVal name As String)
MyBase.New(connection, name)
End Sub

Public Sub IsClosed()
If Not Connection Is Nothing Then
If (Connection.State And ConnectionState.Closed) = 0 Then
Throw New ArgumentException("Operation requires a closed connection.", Name)
End If
End If
End Sub

Public Sub IsNotNull()
If Connection Is Nothing Then
Throw New ArgumentNullException(Name)
End If
End Sub

Public Sub IsOpen()
If Not Connection Is Nothing Then
If (Connection.State And ConnectionState.Open) = 0 Then
Throw New ArgumentException("Operation requires an open connection.", Name)
End If
End If
End Sub

Public Sub IsValid()
IsNotNull()
IsOpen()
End Sub

Private ReadOnly Property Connection() As IDbConnection
Get
Return DirectCast(InnerValue, IDbConnection)
End Get
End Property
End Class

End
Namespace

The Pros & Cons


I've noticed that I am, indeed, far more likely to validate parameters now with this framework. I'm catching a lot more defects with it as well. The idea behind it seems to be working. However, it does have a few problems:



  • It's failing to meet its extensibility requirement. In order to add new types, I have to hand-code a new overload to the That method into the Validate class. I need to find a new way to do that.
  • It's not properly localized. (You can see the hard-coded English strings in the code sample above.)
  • It adds overhead to the stack trace, which can be confusing to users who don't know what it is.
  • It lacks a way to conveniently perform multiple tests on the same parameter validator object (aside from Visual Basic's With...End With block, which just looks unnatural). The current implementation tends to ask you to create multiple objects to work with the same parameter; although the objects maintain very little state (two variables, both object references) it's still more than I'm comfortable with.

Despite its drawbacks, most of which are addressable, its benefits appear to be worthwhile. My code quality is rapidly improving. Defect rates are dropping noticeably (and I feel comfortable attributing a good portion of it to better parameter validation).


The Request for Comments


So there you have it. At this point, I would really like to hear back from the community, and find out what you think of this thing. Should I be doing this? Should I be doing it better? How would you improve on it? What would your concerns be if you were using it or designing it yourself?


Remember, I'm the quintessential vacuum coder here, so your input is valuable to me.

Wednesday, June 27, 2007

Who Killed NDoc?

NDoc has been dead for some time now. In fact, if you visit the NDoc project site at SourceForge, you'll see that it hasn't been touched since February of 2005. In an email written by NDoc's author, we find the reasons why:

Unfortunately, despite the almost ubiquitous use of NDoc, there has been no support for the project from the .Net developer community either financially or by development contributions. Since 1.3 was released, there have been the grand total of eleven donations to the project. In fact, were it not for Oleg Tkachenko’s kind donation of a MS MVP MSDN subscription, I would not even have a copy of VS2005 to work with!

While lack of developer participation and user contributions wasn't the only reason that Kevin quit, they were certainly very compelling reasons to quit.

NDoc was and is a fantastic open source project. It's ease of use, feature set, and the frequency with which updates were made available made it a joy to work with. And Kevin was right on the money when he described its use as "almost ubiquitous." It enjoyed widespread use, and folks downloaded it as a must-have utility without even thinking about it. I know I did.

The problem, however, is that in not thinking about it, we also didn't think about the hours and energy that went into building, debugging, testing, documenting, publishing, and maintaining it. We didn't think about the time to cull through the defect reports, interact with the users, respond to feature requests, deal with mail bomb threats, and so forth. We just grabbed our freebie and ran with it. We didn't think about the poor guy who had a family to feed, bills to pay, and a completely separate life to maintain. We just took, and never gave in return.

We expected much, and gave very little beyond demands, requests, criticisms, and an increase in bandwidth costs. It's no surprise that the man lost his enthusiasm for the project. No one volunteered to help maintain it. No one volunteered to lend a hand, and very few offered to help pay the meager $5 donation.

When the mail bomb attacks came, it was all over. But that was only the last straw. By then, the damage had already been done. The mail bomber wasn't the one who killed NDoc.

We killed NDoc.

By our very apathy, by our refusal to support the open source products that we love, and the developers who created and maintain them, we doom them to death.

Microsoft has released the CTP of Sandcastle, their attempt to fill the void left by NDoc. As will inevitably be the case, it will be big, slow, and not nearly as flexible or fun to work with as NDoc. It will have fewer releases, and Microsoft will be far slower to respond to defect reports. (Ready yourselves for the "It's not a bug, it's a feature" disclaimer.) All the beauty that comes with a highly interactive open source community effort will be lost in the shadow of a giant corporate software project.

We will have no one to blame for it but ourselves.

I was excited to hear that Microsoft was incorporating unit testing into Visual Studio 2005. But I loathe their implementation of it. Instead of embracing the standard models used by NUnit and JUnit, they bastardized it and threw all our previous tests out the window, resulting in a conform or die scenario. As always, Microsoft's assimilation strategy prevails. It will likely happen again with Sandcastle, and I fear that the masses of us who have to maintain and document .NET 1.0 and .NET 1.1 code bases will be intentionally left behind as the .NET 2.0 marketing machine wields its iron grip on Sandcastle's design.

NDoc did not die; we killed it. And now, we have to pay the price for our own apathy and greed. Another product--bound to be something with which we are unhappy--will take its place.

Which open source project is next on the chopping block? We love them so much that we're willing to bleed their developers dry of enthusiasm by refusing to contribute to them. What shall we kill next? Who will deliver the first killing blow?

The sooner we clear them out of the way, the sooner a corporate giant can replace the innovative projects we love and control with some knock-off that we despise and can't control.