Showing posts with label Validation. Show all posts
Showing posts with label Validation. Show all posts

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.