Toby and the loss of domain knowledge
One morning, as Toby is drinking his cup of hot water, a thought strikes him - did the acceptance stories really cover everything he discovered about the domain in the first few days? Slightly worried, he finds the paragraph he so proudly wrote a while ago.
The company is always working on a set of projects. Each project consists of many jobs and a job can have one or more drawings. A drawing can have multiple versions, each of which must be tracked separately. There are several departments in the company who can receive a copy of the drawing and it's important to know when (and if) they received it.
Oops. The acceptance stories never mentioned jobs at all, and 'user types' are really departments. Time to fix this before the project grows too much. The updated list of acceptance tests becomes:
- The administrator can login to the system
- The administrator can create a project
- Non-administrative users can login to the system
- A department can find a project by number
- A department can find a project by name
- A department can add a job to a project
- A department can add a drawing to a job
- A department can view the list of drawings for a project
- A department can filter the list of drawings for a project
- A department can enter or modify when they received a drawing
- A department cannot modify when another department received a drawing
- The administrator can edit all details for a drawing
- A department can add drawings to a project sequentially without having to re-enter all the data each time
- A department can up-rev a drawing
- A department can modify the dates for a group of drawings
- The administrator can archive a project
- The administrator can un-archive a project.
The updated classes (now fully tested) look like this:
public class UserService : IUserService
{
public Department CurrentDepartment
{
get { return currentUser; }
}
internal void LoginAs(Department userType)
{
currentUser = userType;
}
private Department currentUser;
}
public interface IUserService
{
Department CurrentDepartment { get; }
}
public enum Department
{
None,
Administrator,
Bob,
Joe
}
public class Job
{
public Job(string number, string drawingOffice)
{
if (string.IsNullOrEmpty(number))
{
throw new ArgumentException("number",
"number cannot be null or empty");
}
if (string.IsNullOrEmpty(drawingOffice))
{
throw new ArgumentException("drawingOffice",
"drawingOffice cannot be null or empty");
}
this.number = number;
this.drawingOffice = drawingOffice;
}
public string Number
{
get { return number; }
}
public string DrawingOffice
{
get { return drawingOffice; }
}
private string number;
private string drawingOffice;
}
public class Project
{
public Project(int number, string name,
IUserService userService)
{
if (number <= 0)
{
throw new ArgumentOutOfRangeException("number",
"number must be greater than zero");
}
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException("name cannot be null or empty");
}
if (userService == null)
{
throw new ArgumentNullException("userService");
}
this.number = number;
this.name = name;
this.userService = userService;
jobs = new List<Job>();
}
public string Name
{
get { return name; }
}
public int Number
{
get { return number; }
}
public bool IsArchived
{
get { return isArchived; }
}
public IUserService UserService
{
get { return userService; }
}
public ReadOnlyCollection<Job> Jobs
{
get { return jobs.AsReadOnly(); }
}
public Job AddJob(string number, string drawingOffice)
{
Job job = new Job(number, drawingOffice);
jobs.Add(job);
return job;
}
public void Archive()
{
if (userService.CurrentDepartment == Department.Administrator)
{
isArchived = true;
}
else
{
throw new InvalidOperationException(
"Only the administrator can archive a drawing");
}
}
public void UnArchive()
{
if (userService.CurrentDepartment == Department.Administrator)
{
isArchived = false;
}
else
{
throw new InvalidOperationException(
"Only the administrator can unarchive a drawing");
}
}
private string name;
private int number;
private IUserService userService;
private bool isArchived;
private List<Job> jobs;
}