January 2007 - Posts

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.