Thursday, July 21, 2005

Declaration of event accessors.

In C# event accessors are used to add and remove event handlers in client code. By declaring custom accessors you can change subscription behavior.

Problem: Prevent a client object from subscribing to the same event multiple times.

Example: You have a view that contains a Navigate event which is raised when the user needs to navigate to the next page.
class View : IView
{
public event NavigationHandler Navigate;

...

public void raiseNavigate()
{
Navigate(typeof(View2));
}
}

When Navigate is raised a presenter should clear the existing view and present a new one.
class FormPresenter
{
... constructor inject a view factory ...

public void NavigateTo(Type type)
{
IView view = viewFactory.Find(type);
view.Navigate+=new NavigationHandler(NavigateTo);
changeView(view);
}
}

This code works well if a new view is returned by the viewFactory every time. However, if the same view is returned from the factory, the presenter will subscribe to the Navigate event multiple times.

Solution: Subscribe to the Navigate event only if the presenter hasn't previously. Declaring the event accessors allows us to manage a single Navigate subscription per client model in the publisher.

Thus, View becomes:
class View : IView
{
private NavigationHandler navigationHandler;

private ArrayList navigateSubscriberInstances = new ArrayList();

public event NavigationHandler Navigate
{
add
{
if (navigationHandler==null || !navigateSubscriberInstances.Contains(navigationHandler.Target))
{
navigationHandler += value;
navigateSubscriberInstances.Add(navigationHandler.Target);
}
}
remove
{
navigationHandler -= value;
navigateSubscriberInstances.Remove(navigationHandler.Target);
}
}

protected void raiseNavigate(Type type)
{
if (navigationHandler!=null)
{
navigationHandler(type);
}
}
}

Saturday, July 09, 2005

Notification without Flood

Problem: Users want instant notification without notification flooding.

Examples: Send a text message when an unhandled exception is thrown, but only 1 per day. When a new loan request form is submitted send an email, unless an email was sent in the past hour.

Solution: Store the datetime of the last sent notification and check it before sending a new notification.

This simple solution is often forgotten and replaced by a service that polls solution. I like Notification without Flood much more because you can use the existing application without requiring creation of a service.

Sunday, July 03, 2005

Focus on readability and avoid being SetUp

The SetUp method is used to initialize a test prior to running it. However, there are disadvantages to using SetUp that should be noted. Christian Taubman pointed out a few of the disadvantages in a previous entry. The focus of his entry is replacing SetUp with private methods. While I agree with this, the focus of my entry is more about readability.

Assume you are working with a class:

public class Reservation
{
public Reservation(DateTime checkInDate, int nights, int guests)
{
this.checkInDate = checkInDate;
this.nights = nights;
this.guests = guests;
}

private DateTime checkInDate;
private int nights;
private int guests;
public event ErrorEventHandler ReservationBeforeToday;
public event ErrorEventHandler InvalidNights;
public event ErrorEventHandler InvalidGuests;
public delegate void ErrorEventHandler();

public void Validate()
{
if (checkInDate<DateTime.Today) ReservationBeforeToday();
if (nights<1) InvalidNights();
if (guests<1) InvalidGuests();
}
}

There should be tests for the logic in Validate. The test could look like this:

public class ReservationTests
{
private static bool eventRaised;
private static Reservation reservation;

[SetUp]
public void SetUp()
{
eventRaised = false;
reservation = new Reservation(DateTime.AddDays(-2),0,0);
}

[Test]
public void InvalidReservationRaisesEvent()
{
reservation.ReservationBeforeToday+=delegate { eventRaised = true; };
reservation.Validate();
}

[Test]
public void InvalidNightsRaisesEvent()
{
reservation.InvalidNights+=delegate { eventRaised = true; };
reservation.Validate();
}

[Test]
public void InvalidGuestsRaisesEvent()
{
reservation.InvalidGuests+=delegate { eventRaised = true; };
reservation.Validate();
}

[TearDown]
public void TearDown()
{
Assert.IsTrue(eventRaised);
}
}

While this code does accomplish what I was looking for I don't consider it to be very readable. An alternative which is much more readable could look like:

public class ReservationTests
{
private static bool eventRaised;
private static Reservation reservation;

public void Init()
{
eventRaised = false;
reservation = new Reservation(DateTime.AddDays(-2),0,0);
}

[Test]
public void InvalidReservationRaisesEvent()
{
Init();
reservation.ReservationBeforeToday+=delegate { eventRaised = true; };
reservation.Validate();
Assert.IsTrue(eventRaised);
}

[Test]
public void InvalidNightsRaisesEvent()
{
Init();
reservation.InvalidNights+=delegate { eventRaised = true; };
reservation.Validate();
Assert.IsTrue(eventRaised);
}

[Test]
public void InvalidGuestsRaisesEvent()
{
Init();
reservation.InvalidGuests+=delegate { eventRaised = true; };
reservation.Validate();
Assert.IsTrue(eventRaised);
}
}

However, the most readable version is my favorite:

public class ReservationTests
{
[Test]
public void InvalidReservationRaisesEvent()
{
bool eventRaised = false;
Reservation reservation = createReservation();
reservation.ReservationBeforeToday+=delegate { eventRaised = true; };
reservation.Validate();
Assert.IsTrue(eventRaised);
}

[Test]
public void InvalidNightsRaisesEvent()
{
bool eventRaised = false;
Reservation reservation = createReservation();
reservation.InvalidNights+=delegate { eventRaised = true; };
reservation.Validate();
Assert.IsTrue(eventRaised);
}

[Test]
public void InvalidGuestsRaisesEvent()
{
bool eventRaised = false;
Reservation reservation = createReservation();
reservation.InvalidGuests+=delegate { eventRaised = true; };
reservation.Validate();
Assert.IsTrue(eventRaised);
}

private Reservation createReservation()
{
return new Reservation(DateTime.AddDays(-2),0,0);
}
}

The result is a test that can be read almost entirely in isolation and understood easily.

Saturday, July 02, 2005

Firing Silver Bullets

Every good software developer knows that there is no silver bullet. Unfortunately, each new tool or software release claims to be the previously missing silver bullet.

[The following statements are made in general. If you are different you should recognize that you are the exception, not the rule]
Following a new silver bullet release less experienced developers quickly abandon their old ways and use their new silver bullets exclusively. Conversely, the more mature developers ignore the release because they are familiar with the continual hype cycle. Eventually, the silver bullet is rolled out to all, usually mandated by management. This process is painful and often fails, because, of course, there is no silver bullet.
[End general statements]

Over the years I found a different approach that works well for me. When a new silver bullet is released I quickly adopt it for each problem that it _may_ address. This is the fastest way for me to find all of it's limitations. I already know how to do things the old way, and if the silver bullet doesn't work I can always go back. In the end, I'm much more comfortable with many approaches. This allows me to make an informed decision instead of being forced to rely on a management and hype.