Archive for March, 2007

Most of the time when something comes across my aggregator that I disagree with, I just let it slide and ignore it. There’s really no point to arguing about philosophical differences with other bloggers because both sides think they’re right. But occasionally differences of opinion are actually nothing more than miscommunication; misunderstanding. Many times two people are on the same side, they just don’t know it. It is at these moments that I see opportunity; a chance to bring two seemingly opposites sides to the realization that they’re actually on the same side. They just don’t know it yet.

For instance, take Paulo Morgado. He posed a question yesterday, asking whether TDD is a good or bad thing. Then he says it’s bad. On the surface, I completely disagree with him. But if you look closer at his post you might find it’s simply a case of misunderstanding. And I think this is pretty common when it comes to TDD. Test Driven Development is new, and like all things new and shiny people are going to be skeptical or make snap judgements. They’re going to form opinions quickly. Hey, that’s what we do – we’re human.

Paul writes:

…my beef is with the Test Driven part (or Example Driven Development (EDD) if you like other flavors). Came on, is your software really being driven by the tests? Are you really writing software just to satisfy your tests? I’m not.

Just to satisfy tests? No. We’re writing software to satisfy customers. But in the process, we take their requirements and turn them into tests. So in a technical sense, yes, we are writing software to satisfy tests. And the tests are definately driving our design.

Think about what design really is. It’s making decisions. In software, like other disciplines in life that require you to make decisions, there’s more than one way to solve a problem. There’s more than one way to skin a cat. The decisions we make are often determined by a lot of factors; there’s a lof of elements to influence which way we’ll go and how we’ll proceed. But unless those factors eliminate every possible solution from our vision save one, we still have to make a decision. This is the process of design.

TDD is just another factor in our decision making process. It doesn’t drive the whole design – it can’t. Humans are the final decision makers, and we ultimately control the wheel. But TDD helps lead us down one path among many paths. It points us in a good direction. It drives us down a particular road.

Paul continues:

Test driven development also makes you design for testability instead of designing for funcionality, scalability and other such much nicer things.

This is simply incorrect, and I’m sure any number of TDD practioners will back me up on this. Testability is not mutually exclusive to functionality, scalability or other important software aspects like performance. In fact, quite the opposite. TDD helps make sure such things are done properly with automated tests to ensure correctness. By writing the tests first, and then writing the code to make the tests pass, we ensure that the requirements are met. And those requirements can be anything, from functionality to performance.

More Paul:

My software development is driven by the need to comply with requirements.

Everyone’s software is driven by the need to comply with requirements.

Test Driven Development helps us, as developers, steer the solution toward a particular path. In a sea of solutions, we can choose many. TDD helps us focus on a particular subset of solutions. In return we get many benefits. We have automated regression tests to aid us during code changes. We get the confidence that comes with tests, knowing that our software works the way it is supposed to. TDD helps us design for simplicity, decoupling and aids us in building code that is easy to refactor, change, and maintain. You get a lot of bang for your buck when you invest in TDD. It’s so much more than just writing code to satisfy tests.

Paul finishes by writing:

My software development is driven by the need to comply with requirements. How am I doing it? Assisted by tests. So, I’m doing Test Assisted Development (TAD).

Which brings me back to my original point: many of the differences people have are often nothing more than misunderstanding or miscommunication. Test Assisted Development? Is that really any different than Test Driven Development? I suppose if you never allow a unit testing scenario to guide or influence a design decision, then maybe they are different. But that seems like a very difficult thing to do, because if you’re writing test-first then the very act of writing code to make the test pass is going to influence your design (decision-making) process. And what’s the difference between “driving” the design and “assisting” the design?

I’d say: nothing.

In the end, we’re all after the same thing: we want to build the best software we can.

A few months ago I read a blog post where someone discussed familiarity and automobiles. I can’t remember if the specific post was about software design or not, but I do remember the crux of the message: users don’t have to relearn how to drive a car each time they get into a new automobile. Each car has the same basic set of features: a steering wheel, a throttle, an engine, some mirrors and a shifter. These controls are basically in the same spot in every car. All the user has to do is familiarize themselves with the minor differences from model to model, but the overall contraption is the same.

I think we, as software developers, ought to heed this sort of design principle more frequently when it comes to API’s.

For instance, take today. Myself and two colleagues spent some time this afternoon trying to configure an Infragistics UltraWinGrid to display data a particular way. We have a love/hate relationship with Infragistics. Their controls are spectacular in the sheer amount of properties that can be set and the methods they expose; they are highly configurable. They’re power is awesome and monolithic. Unfortunately, by contrast, their documentation is scant and good examples are difficult to find. This puts a huge burden on the developer; you must be able to inspect the API and glean important information from it. And it would help the developer greatly if the API resembled the API of similar objects (like the .Net DataGridView, for instance).

A specific example: We were immediately impressed when we first bound a business object collection to the Infragistics grid. Our business objects inherit from base classes that have code for databinding and propertynotification, so that they can track things like dirty and new states, and handle databinding to grids automatically (very similar to what Rocky did with CSLA.Net). The particular collection we were dealing with consisted of business objects that contained a sub collection of objects, so a nested graph. To our great surprise and thrill the Infragistics grid displayed the collection and the nested collections automatically without any additional tweaks by us, with the proper level of “bands”. So much GUI goodness for so little effort.

But our business objects also have fields we didn’t want to display. There are properties like IsDirty and ConcurrencyTimestamp that the user just doesn’t need to know about and wouldn’t know what to do with.

Now, in a normal .Net DataGridView it’s really easy to prevent all the columns from showing by default. You simply set the DataGridView’s AutoGenerateColumns property to false and bind your data. The only columns that display after that point are ones that you explicitly define when you declare the schema for the grid.

You might think that the UltraWinGrid from Infragistics also handles things this way, but you would be wrong.

After an hour or so of fiddling around, manually duplicating the object schema and hiding columns, we finally fell upon the SetDataBinding method, which can prevent the columns from being automatically generated if you set the third parameter to false when you call it (as opposed to simply setting the DataSource property, like normal).

That sounds intuitive, doesn’t it?

Which brings me back to the point about the automobile. API’s are things that developers become familiar with over time, like a car. We become as familiar with API’s as we do with general language constructs (foreach…) or design patters. We don’t have to relearn the Subject/Observer pattern every time we want to wire up an event. We don’t have to relearn enumeration every time we want to iterate over data.

Some of us have been writing .Net code for a few years now, and we’ve gone through all the Google searching to figure out how to configure controls to do our bidding. We don’t want to have to do that again. Or at least we don’t want to have to do it with the same regularity as we did when we were first learning the framework and API’s.

Third party tools, controls and frameworks, whether they’re open source or not, are there to make our jobs easier. To keep us from having to reinvent the wheel every time we need to do something productive. But an API that doesn’t at least try to follow existing, established “standards” (for lack of a better term) only slow down our productivity, because we have to research the proper way to do things all over again. It’s worse when your API doesn’t reveal the most obvious and natural ways to do things through simple inspection.

I know API design isn’t easy. And I’m certain that in my own code I’ve bucked a trend here and there that caused someone else to break out the Advil while they beat their head against a wall trying to figure out how to use my code. But that doesn’t mean we can’t get better at this stuff. Take some time to look at your code and ask this question: “A year from now, if a junior developer on my team is using this API while I’m on vacation and I’m not there to answer questions, will they be able to use it productively? Or are they going to get stuck because I didn’t follow an established pattern/principal/naming convention?”

I don’t know about you, but I’d like to be sitting on a beach without a care in the world, and that includes wondering if people are going to get stuck using my code.

Updated source code is available here. Links on this website have been changed to point to the new code.

CabSample_v2.zip

After I posted the code for DialogBox and PopupBox I got a few interesting questions on the CodePlex message boards. One key request was the ability for the DialogBox to remain active if certain validation conditions were not met. This made sense.

I went back and examined how my code worked and how the WindowWorkspace class works. I discovered that I was wiring up some events for no reason at all. It turns out that the WindowWorkspace understands how to close itself by default as long as you have a button on the UserControl that has a DialogResult set to something other than DialogResult.None. My guess is this behavior has less to do with the WindowWorkspace and more to do with default Window Forms behavior,l which is what the WindowWorkspace utilizes to show your faux Dialog/UserControl.

With that knowledge I was able to eliminate the events and simplify the DialogBox base class. A couple other changes that are more noticable:

  • The base DialogBox class is no longer marked abstract. It turns out that if you mark a class as abstract, the designer in Visual Studio cannot display any subclassed objects of that type. And since the most convenient way to design dialogs is with the visual designer I eliminated the abstract declaration.
  • Changed InitDialog() to OnShow(). This method is still overridable and it is the method you should override if you want to handle any dialog initialization prior to displaying the dialog. The WindowWorkspace changes I’ve made call this method when it shows the DialogBox. This was more semantics than anything, but it made more sense to me.

So how do these changes affect the code? The big difference now is that must wire-up the click handler for your dialog buttons and set the DialogResult on the Control. This is not necessarily a bad thing, however, because now it gives you the chance to perform validation prior to setting the DialogResult. An example in the sample code is a Logon dialog. The LogonDialog has one button on it. Handling the Click event looks like this:


private void _logonButton_Click(object sender, System.EventArgs e)
{
    if (_usernameTextBox.Text == "Admin" && _passwordTextBox.Text == "password")
    {
        DialogResult = DialogResult.OK;
    }
    else
    {
        DialogResult = DialogResult.None;
    }
}

A DialogResult of None will cause the Dialog to not close. Any other DialogResult will close the dialog. Then it’s up to you to check the DialogResult in code to make sure you got the result you want.

Once again, I hope people find this useful.

CabSample_v2.zip