Situation
You are working for Wessex city council, working on software systems for public services in the city. Having successfully completed your previous project, you are asked to look at an important legacy codebase which is one of the council’s most publicly visible applications, managing congestion charging for cars going in and out of the city centre.
You are called in to a meeting with Jeff, the head of IT, who explains that the council have decided to change the way that people are charged for driving their cars into the city. It is supposed to make things simpler for drivers, but it will mean making changes to the management software.
Currently, vehicle number plates are scanned by cameras at the edge of city-centre zone and as the vehicle crosses the boundary, the event is logged in the congestion charge system. A process then runs once a day working out how long each vehicle spent in the zone, and charging drivers’ accounts accordingly. Vehicle owners are charged per minute that they are inside the zone.
Drivers create an account and top it up from their credit card online through another system – that payment system is looked after by an external supplier. If drivers do not have enough credit on their account when the charge is made, then they face a penalty.
Wessex have decided to make a change so that the charges are not so sensitive to exactly how long vehicles spend inside the zone, as they suspect this has led to people driving faster and also makes charges less predictable. The new rule is that if you enter the zone before 2pm, you are charged £6, and you can stay in the zone for up to 4 hours. If you enter the zone after 2pm, you will be charged £4. If you leave and come back within 4 hours, you should not be charged twice. If you stay inside the zone for longer than 4 hours on a given day then you will be charged £12.
Jeff doesn’t seem very happy about having to make these changes, perhaps he has had bad experiences in the past with changes to these systems leading to problems in production. He repeats that it is an important project, and that apart from this change the system should continue to behave as is does now. As you leave his office, he adds that he is sure you will make a good job of it.
When you get back to your desk, you have an email from Jeff, saying that although everyone who developed the congestion charging system originally has since left the organisation (somehow he had neglected to mention this in the meeting), he has been able to recover the source code. Attached to the email is a zip file of what Jeff assures you is the latest code.
Examining the codebase you are given, you find the code shown in the Appendix (you can also download it as a zip file, see below). The system doesn’t appear to have any test code, but it is written in Java, so at least you can understand it. You set to work…
Part 1 – In a group of 3
Work through the task and complete the work that Jeff has requested. You should:
a) add unit tests for the existing code
b) refactor the existing code in order to achieve the above
c) write new code and tests for the new functionality
d) refactor your new code to improve its design
e) do anything else you think is important
Part 2 – Individual
Write a short report explaining how you approached this task, what design principles and techniques you applied, how, and most importantly why.
For each technique that you chose to use, or change that you made, write about your reasons and motivations, how it helped you in completing the project, and also if it might make future changes to this codebase easier.
Show selected snippets of code to illustrate and demonstrate the changes that you made – do not paste large chunks of code into the report. You might also want to include some diagrams. Illustrate specifically where in the code you applied the principles and techniques that you describe.
A long report is not necessary. If you cannot describe everything you did in 3 pages, then try to highlight the most interesting parts, the connections to the principles from the course, and the motivations for your decisions. Write clearly but concisely.
Hints
For the purposes of the assignment, you can assume that all vehicles that entered the zone on one day will exit again before the charges are calculated – so you don’t have to deal with vehicles that stay inside the zone over night.
If you want to use Java’s java.util.Date but find it cumbersome, to work with you might like to look at the JodaTime project and try using that in your code.
Penalties for Over-Length Coursework
The maximum length of report is 3 pages excluding diagrams (emphasising quality over quantity). That is roughly 1,500 words. Clear, concise, descriptions are favoured over more verbose content, but the writing quality should be high.
The submission must not exceed the prescribed length by more than 10%. Where submissions exceed the specified maximum length by more than 10%, the mark will be reduced by ten percentage marks; but the penalised mark will not be reduced below the pass mark, assuming the work merits a pass.
The Code
You can find the code you receive in the Appendix of this document, but you can download it from Moodle in a zip file.
Included in the download is trafficmon-externals.jar which includes binary versions of some of the components that you will need to build and run the system. You don’t have the source code for these components though as they are built and maintained by other departments or companies.
As a starting point, you may want to try running the system as it is now. For example, you could implement something like the following:
public class Example { public static void main(String[] args) throws Exception {
CongestionChargeSystem congestionChargeSystem = new CongestionChargeSystem();
congestionChargeSystem.vehicleEnteringZone(Vehicle.withRegistration("A123 XYZ")); delaySeconds(15); congestionChargeSystem.vehicleEnteringZone(Vehicle.withRegistration("J091 4PY")); delayMinutes(30); congestionChargeSystem.vehicleLeavingZone(Vehicle.withRegistration("A123 XYZ")); delayMinutes(10); congestionChargeSystem.vehicleLeavingZone(Vehicle.withRegistration("J091 4PY"));
congestionChargeSystem.calculateCharges(); }
private static void delayMinutes(int mins) throws InterruptedException { delaySeconds(mins * 60);
}
private static void delaySeconds(int secs) throws InterruptedException { Thread.sleep(secs * 1000);
} }
This, combined with reading the source code, should show you what the system does, and how other components use the CongestionChargeSystem.
Appendix: The code you receive to work with
package com.trafficmon;
import java.math.BigDecimal; import java.util.*;
public class CongestionChargeSystem { public static final BigDecimal CHARGE_RATE_POUNDS_PER_MINUTE = new BigDecimal(0.05); private final List<ZoneBoundaryCrossing> eventLog = new ArrayList<ZoneBoundaryCrossing>();
public void vehicleEnteringZone(Vehicle vehicle) { eventLog.add(new EntryEvent(vehicle));
}
public void vehicleLeavingZone(Vehicle vehicle) { if (!previouslyRegistered(vehicle)) {
return; }
eventLog.add(new ExitEvent(vehicle)); }
public void calculateCharges() {
Map<Vehicle, List<ZoneBoundaryCrossing>> crossingsByVehicle = new HashMap<Vehicle, List<ZoneBoundaryCrossing>>();
for (ZoneBoundaryCrossing crossing : eventLog) { if (!crossingsByVehicle.containsKey(crossing.getVehicle())) {
crossingsByVehicle.put(crossing.getVehicle(),
new ArrayList<ZoneBoundaryCrossing>()); }
crossingsByVehicle.get(crossing.getVehicle()).add(crossing); }
for (Map.Entry<Vehicle, List<ZoneBoundaryCrossing>> vehicleCrossings : crossingsByVehicle.entrySet()) {
Vehicle vehicle = vehicleCrossings.getKey(); List<ZoneBoundaryCrossing> crossings = vehicleCrossings.getValue();
if (!checkOrderingOf(crossings)) { OperationsTeam.getInstance().triggerInvestigationInto(vehicle);
} else {
BigDecimal charge = calculateChargeForTimeInZone(crossings);
try { RegisteredCustomerAccountsService.getInstance().accountFor(vehicle).deduct(charge);
} catch (InsufficientCreditException ice) { OperationsTeam.getInstance().issuePenaltyNotice(vehicle, charge);
} catch (AccountNotRegisteredException e) { OperationsTeam.getInstance().issuePenaltyNotice(vehicle, charge);
} }
private BigDecimal calculateChargeForTimeInZone(List<ZoneBoundaryCrossing> crossings) { BigDecimal charge = new BigDecimal(0); ZoneBoundaryCrossing lastEvent = crossings.get(0); for (ZoneBoundaryCrossing crossing : crossings.subList(1, crossings.size())) {
if (crossing instanceof ExitEvent) { charge = charge.add(
new BigDecimal(minutesBetween(lastEvent.timestamp(), crossing.timestamp())) .multiply(CHARGE_RATE_POUNDS_PER_MINUTE));
} }
}
lastEvent = crossing; }
return charge; }
private boolean previouslyRegistered(Vehicle vehicle) { for (ZoneBoundaryCrossing crossing : eventLog) { if (crossing.getVehicle().equals(vehicle)) {
return true; }
}
return false; }
private boolean checkOrderingOf(List<ZoneBoundaryCrossing> crossings) {
ZoneBoundaryCrossing lastEvent = crossings.get(0);
for (ZoneBoundaryCrossing crossing : crossings.subList(1, crossings.size())) { if (crossing.timestamp() < lastEvent.timestamp()) {
return false; }
if (crossing instanceof EntryEvent && lastEvent instanceof EntryEvent) { return false;
} if (crossing instanceof ExitEvent && lastEvent instanceof ExitEvent) {
return false; }
lastEvent = crossing; }
return true; }
private int minutesBetween(long startTimeMs, long endTimeMs) { return (int) Math.ceil((endTimeMs - startTimeMs) / (1000.0 * 60.0));
} }
package com.trafficmon; public abstract class ZoneBoundaryCrossing {
private final Vehicle vehicle; private final long time;
public ZoneBoundaryCrossing(Vehicle vehicle) { this.vehicle = vehicle; this.time = System.currentTimeMillis();
}
public Vehicle getVehicle() { return vehicle;
}
public long timestamp() { return time;
} }
package com.trafficmon;
public class EntryEvent extends ZoneBoundaryCrossing { public EntryEvent(Vehicle vehicleRegistration) {
super(vehicleRegistration); }
}
public class ExitEvent extends ZoneBoundaryCrossing { public ExitEvent(Vehicle vehicle) {
super(vehicle); }
}
package com.trafficmon; public class Vehicle {
private final String registration;
private Vehicle(String registration) { this.registration = registration;
}
public static Vehicle withRegistration(String registration) { return new Vehicle(registration);
}
@Override public String toString() {
return "Vehicle [" + registration + "]"; }
@Override public boolean equals(Object o) {
if (this == o) return true; if (o == null || getClass() != o.getClass()) return false;
Vehicle vehicle = (Vehicle) o;
if (registration != null ? !registration.equals(vehicle.registration) : vehicle.registration != null)
return false;
return true; }
@Override public int hashCode() {
return registration != null ? registration.hashCode() : 0; }
}
You don’t have the source code for some components written by other teams, but trafficmon- externals.jar contains the classes that you need.