I admit at one point, I really didn't know what test-driven development was.  I would build the frame of an object, or the object itself, an then test immediately afterward, but I didn't see the advantage of test driven development.  Most of this comes from the fact that at work I use VB, and I hate seeing the squiggly lines when an object doesn't exist.  To me, that sets me back some and can distract me mentally.

But, with the Reminder.NET project, after reading Applying Domain-Driven Design and Patterns by Nilsson, I am convinced of this and plan  to use this with the rest of the project, when I can get to it.  In looking at his examples, it really made sense to me that creating the test helps flesh out the interface, and I've seen how on one of my other projects that it could have been better if I had done that. 

Unfortunately, I get little computer time at home to work on Reminder.NET, and when I do, I usually have something else to work on.  I apologize for that, as I have let this go by the wayside some.  I will get back to this, as this is a tool I plan to use for my personal needs as well.

Posted by bmains | with no comments
Filed under:

I had a new thought:  what if you could use other criteria.  Say, you had a values list, and in that values list, you could store values such as car mileage, or something else (car mileage was the first scenario I thought up).  So, we know that we want to have one or multiple criteria when a task is due (say due in 6 months, and also 3000 miles), to create a more complex, yet useful scenario.

If we have a values list that gets updated whenever we do it, and then build a list of triggers that can respond to that, we can create a more dynamic system.  For instance, I have a mileage for my Toyota listed in the tool, and when I update the mileage value, any trigger associated with that value is updated.  That is the essence of the trigger functionality, to check the difference between the last value and "trigger" or the next due value, and notify the user when it is complete.

But, then I thought about how to evaluate the difference between the two, and I came up with the evaluator system, where you can create a custom evaluation capabilities that checks the last value, and compares it against the current value, which then the trigger can use to perform the notification.  This evaluation system is then very dynamic, as well as allowing other developers to add their own evaluation capabilities rather easily.  So I have a base class:

namespace Reminder.ObjectModel.Evaluators
{
 public abstract class EvaluatorBase
 {
  #region " Properties "

  public abstract string MeasurementName { get; }

  #endregion

 

  #region " Methods "

  public float EvaluatePercentComplete(MeasurementTrigger trigger)
  {
   return (Convert.ToSingle(this.GetElapsedValue(trigger)) / Convert.ToSingle(this.GetTotalValue(trigger))) * 100;
  }

  #endregion

 

  #region " Abstract Methods "

  public abstract object CalculateNextValue(MeasurementTrigger trigger);
  public abstract void CompleteTask(MeasurementTrigger trigger, MeasurementValue value);
  protected abstract int GetElapsedValue(MeasurementTrigger trigger);
  protected abstract int GetTotalValue(MeasurementTrigger trigger);

  #endregion
 }
}

All evaluators will inherit from this, and this is one of the evaluators, which evaluates based on the number of days a task is allowed to go before being due:

namespace Reminder.ObjectModel.Evaluators
{
 public class DaysEvaluator : EvaluatorBase
 {
  #region " Properties "

  public override string MeasurementName
  {
   get { return "days"; }
  }

  #endregion

 

  #region " Methods "

  public override object CalculateNextValue(MeasurementTrigger trigger)
  {
   if (!trigger.TrackFromLastCompleted)
    return ((DateTime)trigger.LastValue).AddDays((double)trigger.TriggerValue);
   else
    return DateTime.Today.AddDays(Convert.ToDouble(trigger.TriggerValue));
  }

  public override void CompleteTask(MeasurementTrigger trigger, MeasurementValue value)
  {
   throw new Exception("The method or operation is not implemented.");
  }

  protected override int GetElapsedValue(MeasurementTrigger trigger)
  {
   return DateTime.Today.Subtract((DateTime)trigger.LastValue).Days;
  }

  protected override int GetTotalValue(MeasurementTrigger trigger)
  {
   return (int)trigger.TriggerValue;
  }

  #endregion
 }
}

The measurement name property is the value that will appear when creating the trigger, so "days" measurement is linked to this evaluator.  An internal system will use these evaluators to determine which item is the most due, and post it in the main form to the user, so they can get a picture of where the status of the task is at.  GetElapsedValue and GetTotalValue are the means to make the determination as to the status; basically, the evaluation method takes the elapsed integer value and divides it by the total value.

Now, notice that the Complete method is throwing an exception; in working with the base class, I've done many refactorings because I didn't know what exactly I needed.  I got to this point, which I think is finally where I need to be; however, yet I do not know how the Complete functionality will work.

It may seem like I'm incompetent, but this is actually a good approach.  I know I need a complete method, but don't know what I need yet.  As I redesign the completion mechanism, that will flush that out, and then I can go back and fill in that method body as I know what I'm looking for, what I want to do with the response, and the like.  So, this is not a problem in the sense that some see it, but rather a great way to avoid forcing the design to create something that will most likely be flawed or needing refactoring.

As you keep that in mind, remember that because you have to change something doesn't mean you aren't a good programmer.  I'm a good programmer, and I can't think through the whole scenario all the time.  But, using iterative mindset in refactoring, introducing small redesigns as I go, I develop a better and better software program until the time it finally goes to production.  And, even after production, doesn't mean any code is safe from refactoring.  It shouldn't be that way.

Posted by bmains | with no comments

Previoulsy, tasks were determined by a due date.  Now, they can be determined by many things; a date, a custom value (say a mileage value setup), and do many other things.  Because of that, I realize that I no longer need the deferrment option.  In addition, I plan to keep complete, but instead of completing directly in the edit form, this functionality will now be in the main form, as a menu option.

XP can work like that; changes to how the system work can drastically change the interface, if that makes it more meaningful for the users.  In this case, it makes it somewhat easier, because you can also complete multiple items at a time in the system.  For the task class, this was my complete method:

public void Complete()
{

 MetadataProperty dueDateProperty = this.Properties["DueDate"];

 if (this.GetPropertyValue<bool>("IsReoccurring"))
 {
  //Get the amount of reoccurrence from the Properties
  int amount = this.GetPropertyValue<int>("ReoccurrenceAmount");

  //If the amount is a positive number
  if (amount > 0)
   //Add the amount to the date
   dueDateProperty.Value = dueDateProperty.GetValue<DateTime>().AddDays(Convert.ToDouble(amount));
  else
   //No amount provided; throw an error
   throw new ArgumentException("The reoccurrence amount is not a positive amount", "ReoccurrenceAmount Property");
 }
 else
  //The task is completed, so remove the date Property from the
  //collection; this means that it is completed
  this.Properties.Remove(dueDateProperty);

 //Set the last completed date to the current date
 this.SetPropertyValue<DateTime>("LastCompletedDate", DateTime.Today);
 this.OnTaskCompleted(EventArgs.Empty);
}

However, the methodology now changes, and so this is no longer value; I'm not storing in the task object half of these values anymore.  A new updated version will be:

public void Complete(bool trackFromLastCompleted)
{
 foreach (MeasurementTrigger trigger in this.Triggers)
 {
  trigger.TrackFromLastCompleted = trackFromLastCompleted;
  trigger.Update();
 }
 
 this.OnTaskCompleted(EventArgs.Empty);
}

This is much simpler, and works nice.  You may wonder what the triggers are, and I will explain this in my next post.  For now, I want you to understand some of the complete design changes made.  In the XP methdology, it is worth throwing out code if it is no longer valid or isn't what the user wants.  I know this can be a hard thing to do, as a developer, but we must.  Actually, I was relieved to be able to remove code.  It was like the power of God freeing my soul.

Anyway, there has been other code to remove.  I had a large PercentDue property, which I won't post here because of the size, which I was able to remove.  It is no longer useful, and so I removed it, as that evaluation will be a part of what I am going to explain next.

This is part of the XP methodology, which can seem radical to redesign the interface; however, after using the tool, you learn about it, and locking into a specific design hurts the usefulness of that tool.  Since this feature is helpful, it is great to then change it, to increase its usefulness.  That should be one of our first and foremost criteria in refactoring the UI; making it more useful.

In the task details interface, when we create or update the task, certain properties were created that we no longer need.  We have this setup right now:

task.Name = this.TaskName;
task.Category = this.Category;
task.SetPropertyValue<bool>("IsReoccurring", this.IsReoccuring);
if (this.cboReoccurrenceMeasurement.Visible)
     task.SetPropertyValue<string>("ReoccurrenceMeasurement", this.ReoccurrenceMeasurement);
if (this.txtReoccurrenceAmount.Visible)
     task.SetPropertyValue<int>("ReoccurrenceAmount", this.ReoccurrenceAmount);
task.SetPropertyValue<string>("Description", this.Description);
task.SetPropertyValue<DateTime>("CreationDate", DateTime.Today);
task.SetPropertyValue<DateTime>("DueDate", this.DueDate); 

But, we don't need most of the reoccurring information and the due date; this will now be rolled into the Triggers collection that belongs to the task.  The reason is, maybe we don't want due date limitations, but would rather want limitations based upon other factors, such as mileage or another evaluation method.  With the new features coming up, task reoccurring criteria will be more advanced.

So, instead of using this, I loop through and create what I call trigger objects.  What is this trigger and value objects I mentioned before?  What I am using a trigger for is to allow the user to setup multiple constraints.  For instance, if you want to set multiple constraints on an item, you can do so.  Take an "Oil Change" task, for example, you can set two triggers on it: a trigger based on the mileage and a trigger based on a specific period of time.  The mileage trigger can be based on a value; a static list of values that can be updated by the user, and whenever the value is updated, any trigger associated with it can be updated as well.  This value will be in a list that can be updated whenever you would like.

I have a method that looked like this:

public override float EvaluatePercentComplete(MeasurementTrigger trigger)
{
    int range = (DateTime.Today.Subtract((DateTime)trigger.LastValue).Days);
    int total = ((int)trigger.TriggerValue);
    if (total == 0) throw new ArgumentOutOfRangeException("total", "The total value denominator is zero");
    return Convert.ToSingle(range / total);
}

But, I ended up refactoring it like this:

public override float EvaluatePercentComplete(MeasurementTrigger trigger)
{
    if (((int)trigger.TriggerValue) == 0) throw new ArgumentOutOfRangeException("total", "The total value denominator is zero");
    return Convert.ToSingle((DateTime.Today.Subtract((DateTime)trigger.LastValue).Days) / ((int)trigger.TriggerValue));
}

Is that a useful refactoring?  After all, it is a complicated division, and is untested code as of now.  But, I remember reading Martin Fowler's comments about avoiding the declaration of variables when possible; he found that it was often just as quick to perform the conversion directly and use the object instead.  But, to really know if it is a performance problem requires some sort of profiling tool at the end of the process.

I leave the final word up to you...

Some of the properties that were created look something like this:

public string Category
{
 get
 {
  if (this.Properties.Contains("Category"))
   return this.Properties["Category"].GetValue<string>();
  else
   return null;
 }
 set
 {
  if (this.Properties.Contains("Category"))
   this.Properties["Category"].Value = value;
  else
   this.Properties.Add(new MetadataProperty("Category", value));
 }
}

I created a get and set method to do the same thing that you see above, which reduces the lines of code, but uses generics to be type-specific.  The new version of the property looks like this:

public string Category
{
 get { return this.GetPropertyValue<string>("Category"); }
 set { this.SetPropertyValue<string>("Category", value); }
}

I also had an event argument class that I could get rid of.  There will be other slight changes that need made like this, like renaming method, object, or property names that will be made.

Some other refactorings that were found, as I work through  the code to add triggers/values (to appear next), I found that there will be changes to the Task object.  PercentageDue will not be needed, as there will be many possible values that are due, as well as the complete method.  Complete method will be moved to a lower level during refactoring that will make it more useful.  I don't know completely how that will work, so these values will be refactored later; however, I know they need to change, and they've been identified.

I also have a TaskCollection class, which has three special events that I used:  TaskAdded, TaskCleared, and TaskRemoved, which I used in the application; however, the MetadataEntityCollection generic class has these methods, as well as going beyond, to handle this capability.  So, I plan to remove this class.  I did a find on the TaskAdded event, and found it in one place in a form.  So I changed the event declarations to my base class:

MGR.Tasks.ItemAdded += new DataEventHandler<Task>(Tasks_ItemAdded);
MGR.Tasks.ItemCleared += new EventHandler(Tasks_ItemCleared);
MGR.Tasks.ItemRemoved += new DataEventHandler<Task>(Tasks_ItemRemoved);

I moved the code so it appears as this:

void Tasks_ItemAdded(object sender, DataEventArgs<Task> e)
{
    //Code to add a new task in the Task interface
}

void Tasks_ItemCleared(object sender, EventArgs e)
{
    //Code to clear tasks in Task interface
}

void Tasks_ItemRemoved(object sender, DataEventArgs<Task> e)
{
    //Code to remove task in Task interface
}

Tasks_ItemAdded(object sender, DataEventArgs<Task> e)
{
    //Code to add a new task in the Task interface
}

void Tasks_ItemCleared(object sender, EventArgs e)
{
    //Code to clear tasks in Task interface
}

void Tasks_ItemRemoved(object sender, DataEventArgs<Task> e)
{
    //Code to remove task in Task interface
}

void Tasks_ItemCleared(object sender, EventArgs e)
{
    //Code to clear tasks in Task interface
}

void Tasks_ItemRemoved(object sender, DataEventArgs<Task> e)
{
    //Code to remove task in Task interface
}

void Tasks_ItemRemoved(object sender, DataEventArgs<Task> e)
{
    //Code to remove task in Task interface
}

I just wanted to drop a note that I will be beginning the 1.1 version of Reminder.NET, which will have some new features planned.  The next set of features, after talking with the users, will include some additions to the expirations part of the project, such as allowing additional custom values to expire by.  In addition, notifications is an important feature, and this was deemed necessary.

There were some readability issues in the names we chosen, such as the base class MetadataElement/MetadataAttribute.  Entity/Properties was a better association, so this change was made to MetadataEntity/MetadataProperty.  In addition, several refactorings need to be made, as other projects I did had a better approach, so I will be making those changes as well, as you will see.  This should be a very useful several iterations to increase the value of the product.

Posted by bmains | with no comments
Filed under:

I put the source code out on http://www.codeplex.com/Release/ProjectReleases.aspx?ProjectName=ReminderNet so it is available for download.  Sorry for not getting this out there sooner.  You can download and take a look at the project, recommend refactorings, or just check out the code/tests.


 

Posted by bmains | with no comments
Filed under:

Who would have ever thought that people would think comments are a bad thing?  That is the opinion that you get about comments; actually, Martin Fowler said that comments aren't bad; however, if the comments are explaining what your code is doing, without your code being readable enough to handle it's own, it's time for a refactoring.  Comments can be a great way to prototype changes, or to think out your design as well, as Steve McConnell said in Code Complete.  He further said to leave in the comments, even though they will be very verbose.  You will see in my code, that I usually follow this approach.

Whether that is right or wrong, your code should be readable and understandable.  With custom objects, especially framework or third-party components, that can't always be the case, but it should be as readable and understandable as possible.  I still employ comments, and though your code should be readable throughout, I still do it.
 

Posted by bmains | 1 comment(s)
Filed under:

The idea about creating the user interface in an XP project isn't known.  The biggest challenge, when designing a tool, is how it will look.  Though you do mentally design it, and gather what the interface should look like and function from requirements, it isn't always known what to do with it.  If you are using an XP methodology, you don't have to know; that's part of the refactoring process.  When the customer is readily involved, you can show them different designs, and once they can see how it looks, they can make better decisions about the appearance.

It is hard to understand how a tool will work when you don't have the ability to see it for yourself.  Even with systems with a lot of up-front design, you tend to still say to your self "I wish they would have done that differently."  Plus, if you are coding abstract enough, you should be able to reuse the same code with different interface controls, with minor rework (this is a major benefit of MVC).  It's a lot tougher when you have so much invested in an interface, especially when embedding your code in the interface.
 

Posted by bmains | with no comments
Filed under:

I actually really liked the idea of the customer being readily involved in the project.  If the customer was easily accessible, any ideas/problems can be directly bounced off of them, guiding the project to a more successful and welcomed completion.  This can be really hard though.

Let's face it; the customer sitting with the developer won't happen in some environments.  I'm on a project where I have managers as the users, and so it is hard to get them in a meeting, let alone working with them directly.  Sometimes, the environment causes this.

But, this approach is handy, and even if you aren't using an agile/XP approach, it is a good idea to regularly meet with the users and understand their needs.

Posted by bmains | with no comments
Filed under:

In the downtime on the project, I wanted to talk about a tenet of XP programming, which is paired programming.  The general idea is that if we worked in pairs, we could bounce ideas off of each other.  Code would be better, because we could double-check each other's thought process, as well as literally checking the code.

In addition, it helps with those who are strong in one area and weak in another, because the other person could be the opposite way.  So, the concept of pair programming can be very helpful.

But this has a negative connotation; it could be seen as inefficient; that is a lot of work for the two to handle, instead of both working on the project.  Most people have the philosophy that more time coding means more of the project is done; but what XP'ers are saying is that by ensuring you do it right the first time, and testing that it does work ahead of time, you ensure that the code will not have to be fixed later (unless the user changes their mind that it affects that piece of code), and it will be well refactored, making the design easier.

Still, it can be hard to work with someone, especially with conflicting ideas/opinions.  I like the idea of working together; however, if paired programming is not for you, there are other agile methodologies that may be more suitable.

Posted by bmains | 1 comment(s)
Filed under:

I've decided to stop there and release the product.  I've placed the zip file that contains the executable and DLL's on CodePlex (under planned releases, or link directly).    I haven't gotten the source code out there yet; I will when I can overcome some technical difficulties.  What I plan to do is this:  I'm going to let this project go for a couple of months (or so; I don't have an exact figure in mind).  I ask that all of you download it, run it, and find any bugs associated with the tool.  Also, figure out things that you would like to see, and enter bugs/features into the Issue Tracker tool.  There is also a forum where you can post about the project; I will try to answer questions if I can, and expand the list of forums that are out there.

Furthermore, when using it, whether the feature is large or small, if you believe it is beneficial, put it in the issue tracker.  You are my user base, and this will craft the shape of the project to come.

Thanks for reading the blog, and I hope it has been of benefit to you.  Any future posting will be minimal until it starts up again, so it will be on hiatus. When I get the code out there, I'll post and let you know.
 

Posted by bmains | with no comments

I found a flaw in my test, which was less detectable since I didn't have a good range test.  The values were six months apart from each other, and I didn't realize that the partial value was calculated incorrectly.  It should have been instead this:

TimeSpan fullValue = completionDate.Subtract(lastCompletionDate);
TimeSpan partialValue = currentDate.Subtract(lastCompletionDate);

I was calculating the higher end of the value (what was to come instead of what has elapsed.  So, my final value was calculated in the Task's PercentageDue property defined below.  I took the code from the test and adapted, refactoring the variable names to make more sense:

public double PercentageDue
{
    get
    {
        //Return 100 if no due date
        if (!this.Attributes.Contains("DueDate"))
            return 100;
        //Get the date values
        DateTime dueDate = this.GetAttributeValue<DateTime>("DueDate");
        DateTime currentDate = DateTime.Today;
        //If the due date is less than the current date (overdue), return 100
        if (dueDate < currentDate)
            return 100;
       
        DateTime originDate;
        //If the last completed date has been provided, use this value
        if (this.Attributes.Contains("LastCompletedDate"))
            originDate = this.GetAttributeValue<DateTime>("LastCompletedDate");
        //Otherwise, first time so use the creation date
        else
            originDate = this.GetAttributeValue<DateTime>("CreationDate");

        //Get the time span difference between the date values
        TimeSpan totalTime = dueDate.Subtract(originDate);
        TimeSpan timeElapsed = currentDate.Subtract(originDate);
        //Get the days value in double form
        double totalDays = Convert.ToDouble(totalTime.Days);
        double elapsedDays = Convert.ToDouble(timeElapsed.Days);

        //Get the percentage value of dividing partial value
        //by the full value; multiply by 100
        return Math.Round((elapsedDays / totalDays) * 100, 1);
    }
}

I also return a rounded value, instead of a true floating value.

Adding another column to the task list, I also provided the means to add the new field to the subitems list, so it is displayed in the interface.  In addition, the due date calculation turned out to be complex, so I added another method to render it in the list view.  If the current date is overdue, I wanted it to say "Overdue"; however, if the due date attribute isn't provided, I wanted it to say "Complete", and lastly, if the value is there, write it.

private string RenderDueDate(Task task)
{
    if (task.Attributes.Contains("DueDate"))
    {
        DateTime dueDate = task.GetAttributeValue<DateTime>("DueDate");
        if (dueDate < DateTime.Today)
            return "Overdue";
        else
            return dueDate.ToShortDateString();
    }
    else
        return "Completed";
}

It is rendered in the list view as such:

internal void UpdateTaskInformation(Task task)
{
    string name = task.Name;
    string category = task.Category;
    bool reoccurring = task.GetAttributeValue<bool>("IsReoccurring");
    string dueDate = this.RenderDueDate(task);
    double percentage = task.PercentageDue;

    ListViewItem item = this.FindByTag(task);

    if (item != null)
    {
        item.Text = name;
        item.SubItems[1].Text = category;
        item.SubItems[2].Text = reoccurring.ToString();
        item.SubItems[3].Text = dueDate;
        item.SubItems[4].Text = percentage.ToString();
    }
}

I created two tests to test the common types of progression; the first will use date/time comparisons, where the next one will use numerical comparisons.  The first is in the case of calculating a reoccurring task, where the other option is for calculating custom measurements, as we will see.  I created the first test as below:

[Test()]
public void TestDueDatePercentage()
{
    DateTime completionDate = new DateTime(2007, 1, 1);
    DateTime lastCompletionDate = new DateTime(2006, 6, 1);
    DateTime currentDate = new DateTime(2006, 9, 1);

    //Get the time span difference between the date values
    TimeSpan fullValue = completionDate.Subtract(lastCompletionDate);
    TimeSpan partialValue = completionDate.Subtract(currentDate);

    //Make sure the values and day counts are different, with full value being larger
    Assert.AreNotSame(fullValue, partialValue);
    Assert.AreNotEqual(fullValue.Days, partialValue.Days);
    Assert.Greater(fullValue.Days, partialValue.Days);

    double fullDays = Convert.ToDouble(fullValue.Days);
    double partialDays = Convert.ToDouble(partialValue.Days);

    //Get the percentage value of dividing partial value by the full value
    double percentage = partialDays / fullDays;
   
    Console.WriteLine("TestDueDatePercentage percentage:  " + percentage.ToString());
    //Make sure the value is within a particular range
    Assert.IsTrue(percentage > 0.40);
    Assert.IsTrue(percentage < 0.60);
}

I created a task that was last completed on 6/1/2006 and will be due on 1/1/2007.  It is currently 9/1/2006 in this scenario, and I test to see that whenever I get the days, that I can calculate the percentage.  I get a value in the .57 range, and so this test worked; this is the reason for the big range, because the actual date comparison value will vary greatly.  Also, to actually get a decimal value, you have to use double or float.  I use double for ease of use, and this works great for this purpose (int returns 0 or 1).  The next test compares integer comparison.

[Test()]
public void TestMeasurementPercentage()
{
    double fullValue = 5000;
    double partialValue = 2500;

    //Calculate the current percentage
    double percentage = partialValue / fullValue;

    Console.WriteLine("TestMeasurementPercentage percentage: " + percentage.ToString());
    //Make sure the value is within a particular range
    Assert.IsTrue(percentage > 0.49);
    Assert.IsTrue(percentage < 0.51);
}

I am comparing a task that is halfway complete, and so I create the percentage and test the range.  I use a +1 approach because it could be off slightly; decimal point values have a habit of being slightly off, even though the measurement should be exact.  This test also works.

Posted by bmains | with no comments
More Posts Next page »