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 SoftwareInstructions to Enter
By Erich Gamma, Richard Helm, Ralph Johnson, John M. Vlissides.
Make sure you read all the instructions as they're different from the previous challenge!
Challenge 3 Download Overview
- Make sure you have NUnit installed; available at http://www.nunit.org/.
- Download Challenge3.zip, found below.
- Open the solution with VS 2005 and compile it.
- 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.
- 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.
- 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 firstname.lastname@example.org. 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.
The VS 2005 solution contains two projects:
Now Refactor It!
- 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.
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 ComplexityRefactoring: 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:
- 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!
- 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!
12/19/06 9:32 AM Mtn Time - Updated project to use US culture dates within unit tests.
12-18-2006 11:23 PM