Images in this post missing? We recently lost them in a site migration. We're working to restore these as you read this. Should you need an image in an emergency, please contact us at imagehelp@codebetter.com
Refactor It! Challenge 3 - The State of Things
If you're not familiar with the Refactor It! challenges, please read this post. (Yes, this one is quite a bit overdue.) Now on to challenge 3...

Welcome back to another refactoring challenge! This challenge will pit your skills against the very useful State Design Pattern along with writing your own unit tests. It should take 1-2 hours for senior developers and a couple more for others. While the previous challenge made you focus only on the refactoring without worrying about writing unit tests, this challenge requires you to complete the refactoring along with writing your own unit tests. Here's the premise...

Background & Problem

This "refactoring to patterns" challenge surfaced during the past couple of weeks on an actual project. The real-world project is a project management tool that interfaces with a schedule management tool. The schedule management tool maintains a listing of project activities which include scheduled start/finish dates, actual start/finish dates (if they're available), and a remaining duration of the activity. (For the purposes of this challenge, many other activity properties have been dropped for simplification.) Since an API was not available to work with the scheduling tool's data, I had to communicate with its database directly. The problem was that quite a lot of business rules were executed when an activity changed from Not Started to Started, for example. ("Not Started" activities have scheduled start/finish dates but no actual dates; "Started" activities have an actual start date, a scheduled finish date but no other dates; and "Finished" activities have actual start/finish dates and no scheduled start/finish dates.) Taking a quick look at the class Challenge3.Domain.ProjectActivity shows how quickly this is getting out of hand.

Your Incentive

Up for grabs this week is the following book along with a CodeIt.Once Refactoring tool 3-User license pack! This seminal writing has become a required part of any serious developer's bookshelf.

Design Patterns: Elements of Reusable Object-Oriented Software

Design Patterns: Elements of Reusable Object-Oriented Software
By Erich Gamma, Richard Helm, Ralph Johnson, John M. Vlissides.

Instructions to Enter

Make sure you read all the instructions as they're different from the previous challenge!
  1. Make sure you have NUnit installed; available at http://www.nunit.org/.
  2. Download Challenge3.zip, found below.
  3. Open the solution with VS 2005 and compile it.
  4. Open NUnit, go to File/Open, then open <solution directory>/Challenge3.Tests/bin/Debug/Challenge3.Tests.dll. Click run and make sure all the tests pass. There should be a few unit tests that are ignored; but none should be failing.
  5. Complete the refactorings as described below. Be sure to follow the style of the code that is already in place: classes, methods and public properties in PascalCase, private members in camelCase, etc. Following the style that's already there is always a good rule of thumb when modifying legacy code.
  6. When finished with the refactorings, make sure all the unit tests are still passing and that no test is being ignored, zip up your solution and send to challenge3@emccafferty.com. As you may only win once, only submit a solution if you'd like to be eligible for the prize or let me know that you don't want to be entered for the drawing.
    Because of the upcoming holiday schedule, the due date for your submission is Thursday night at midnight, January 4, US mountain time.
Challenge 3 Download Overview

The VS 2005 solution contains two projects:
  • Challenge3.Core: This class library contains the business logic layer of the application. This class library is further broken down as follows:
    • The Domain folder contains all the entities and value objects. It also contains a sub-folder, ActivityState, which will hold the state design-pattern classes.
    • The Utils folder contains a design-by-contract utility class written by Kevin McFarlane. The utility class is for all the Check.Requires you'll see throughout the code.
  • Challenge3.Tests: This class library contains the unit tests of the application. As there is no defined presentation layer, the unit tests serve as a portal to how the application works.
Now Refactor It!

Below is a listing of smells that need to be fixed within the project. Each point indicates the file that contains the smell, a description of the problem and any guidelines for completing the refactoring. There may be other smells in the code, but these are the only ones that should be addressed.

Smell: Conditional Complexity
Refactoring: Replace State-Altering Conditionals with State

Although there's only one smell to be refactoring in this challenge, it's an ugly one!
  • Challenge3.Domain.ProjectActivity: This domain class represents a specific activity within a project's schedule. In the real-world app that this was inspired by, I was communicating with a third party database and had to respect the rules of the data communications being performed by the third party client app. (I was bypassing the client app altogether with my own code but needed to ensure continued data integrity.) For example, if an activity is currently Not Started and becomes Started, then the scheduled start date needs to be set to a null date and the actual start date then needs to be set, accordingly. In addition to this, the remaining duration of the activity needs to be calculated and set. A simple implementation of the state pattern could remedy the conditional logic in the UpdateActivityStartAndFinishWith method. But here's the kicker...you need to take into account not only the object's current state but what state it will be in next. For instance, going from Not Started to Finished is not the same as going from Started to Finished. In the real-world app that this was inspired by, there were unique actions which needed to take place for each of the nine possible transitions; e.g. Not Started to Started, Not Started to Not Started (this isn't necessarily a state change, but the dates can still be modified), Not Started to Finished, Started to Not Started, etc. Your tasks are as follows:

    1. Leverage the state design-pattern to simplify the change in states currently being handled by the method UpdateActivityStartAndFinishWith. Any state-related classes should be placed into the folder Challenge3.Domain.ActivityState. There are a few comments in the mentioned method...be sure to pay attention to them!
    2. While implementing the state design pattern, existing unit tests should be maintained and new unit tests should be written for any new code. You shouldn't have to remove any unit tests from Challenge3.Tests.Core.Domain.ProjectProjectActivityStateChangeTests; but you may want to add a new suite of unit tests for the state classes which will be created. Currently 97% of Challenge3.Core.Domain is being unit tested. Your submitted solution must have at least 95% of Challenge3.Core.Domain still under unit tests. You can use a tool such as NCover or Clover.NET to verify unit test coverage. (Don't worry about unit testing the Utils namespace; consider this to be a third party DLL.)

Good luck and let me know if you have any questions. And remember, the first submitter with a correct entry gets his/her name thrown into the hat twice!

Happy holidays!

Billy

12/19/06 9:32 AM Mtn Time - Updated project to use US culture dates within unit tests.


Posted 12-18-2006 11:23 PM by Billy McCafferty
Filed under: , , ,
Attachment: Challenge3.zip

[Advertisement]

Comments

Gary wrote re: Refactor It! Challenge 3
on 12-19-2006 3:26 AM

Hi Billy,

Good to see the refacting challenges are back!

Just a small point... for everyone outside of the U.S. that uses a different date format (e.g. dd/mm/yyyy) the unit tests fail.

Rune Rystad wrote re: Refactor It! Challenge 3
on 12-19-2006 4:05 AM

If you make a Setup-tagged method in the test classes and set the culture to "en-US" it works.

[SetUp]

public void Init(){

   Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");

}

Gary wrote re: Refactor It! Challenge 3
on 12-19-2006 5:36 AM

Thanks Rune! Makes life far easier than going through and changing all the DateTime.Parse calls.

Rune Rystad wrote re: Refactor It! Challenge 3
on 12-19-2006 7:26 AM

Besides, that would make a corresponding mess on Billy's side when he is about to evaluate your submitted, otherwise perfect, solution. :)

Billy McCafferty wrote re: Refactor It! Challenge 3 - The State of Things
on 12-19-2006 11:36 AM

Sorry guys...I keep forgetting that the world is round...it is, isn't it?  (I keep forgetting to revoke my membership in the Flat Earth Society.)  Anyway, I've updated the solution per Rune's suggestion.

Matt Payne wrote re: Refactor It! Challenge 3 - The State of Things
on 12-21-2006 12:21 AM

Hi, Billy

Question: We can only set the state of the ProjectActivity via its UpdateActivityStartAndFinishWith(...), correct? In other words, we are not allowed to add setters to the properties?

Thanks.

Matt

Billy McCafferty wrote re: Refactor It! Challenge 3 - The State of Things
on 12-21-2006 2:44 AM

My solution didn't expose any state setters as the state is easily determined without having a state explicitly set.  For example, if the actual start date is available but the actual finish date is not available, then it's "Started."  But if a setter would make the job simpler for you, then I'd say go for it!  (Incidentally, I'm not completely happy with my own solution and am looking forward to how others tackled the problem as well.)

Matt Payne wrote re: Refactor It! Challenge 3 - The State of Things
on 12-21-2006 10:34 AM

OK. I guess what I mean is it permissible to modify the state (ie: dates) of the ProjectActivity via setters instead of going through the UpdateActivityStartAndFinishWith(...) method, or should the date values only be set within that method?

It seems from the comments on the properties that the date values should only be set via the method.

Just curious as it will obviously affect the design.

Thanks,

Matt

Billy McCafferty wrote re: Refactor It! Challenge 3 - The State of Things
on 12-21-2006 4:19 PM

I didn't completely understand the question before but do now as someone asked something similarly.  A good way to go would be to provider internally visible setters on the date properties.  In this way, outside developers couldn't "mess up" the dates but they would could be modified by the state engine.  Alternatively, you could publicly expose date getters and then write a couple of internal setter methods to support public reading while restricting internal setting.

Matt Payne wrote re: Refactor It! Challenge 3 - The State of Things
on 12-21-2006 7:18 PM

OK, cool. I'm picking up what you're putting down, now.

Thanks,

Matt

Ashok wrote re: Refactor It! Challenge 3 - The State of Things
on 12-26-2006 7:44 AM

Hi,

I'm from India. I've downloaded latest version of the challenge (it has Setup tag in Init() method too). The tests are still failing due to 'System.FormatException: String was not recognized as a valid DateTime'. It's only working if I change all the dates to my local format (I've 'dd/MM/yyyy' format on my system settings)

Regards,

...Ashok

Billy McCafferty wrote re: Refactor It! Challenge 3 - The State of Things
on 12-26-2006 10:09 AM

OK, thanks for the update.  I won't have access to VS 2005 for the coming week, so feel free to submit a solution using your local datetime format.  Just be sure to change the culture info in the test fixture setup method so I know which format you're using.

pr1smiley wrote re: Refactor It! Challenge 3 - The State of Things
on 12-29-2006 2:59 AM

Billy,

Good Stuff!

About The CodeBetter.Com Blog Network
CodeBetter.Com FAQ

Our Mission

Advertisers should contact Brendan

Subscribe
Google Reader or Homepage

del.icio.us CodeBetter.com Latest Items
Add to My Yahoo!
Subscribe with Bloglines
Subscribe in NewsGator Online
Subscribe with myFeedster
Add to My AOL
Furl CodeBetter.com Latest Items
Subscribe in Rojo

Member Projects
DimeCasts.Net - Derik Whittaker

Friends of Devlicio.us
Red-Gate Tools For SQL and .NET

NDepend

SlickEdit
 
SmartInspect .NET Logging
NGEDIT: ViEmu and Codekana
LiteAccounting.Com
DevExpress
Fixx
NHibernate Profiler
Unfuddle
Balsamiq Mockups
Scrumy
JetBrains - ReSharper
Umbraco
NServiceBus
RavenDb
Web Sequence Diagrams
Ducksboard<-- NEW Friend!

 



Site Copyright © 2007 CodeBetter.Com
Content Copyright Individual Bloggers

 

Community Server (Commercial Edition)