Skip to content

Instantly share code, notes, and snippets.

@arlobelshee arlobelshee/Car.cs
Last active Oct 2, 2015

Embed
What would you like to do?
Combine 2 methods into one with a discriminator variable
// Goal: Restore Drive method to all operate at one level of abstraction.
// Refactorings performed: Extract Method x 3
// Hand-edits made: update comment
// Why: A method with multiple levels of abstraction is harder to reason about. Drive
// was calling a method to do some of its work, then doing direct field operations later.
// That causes readers to distrust the one method that was called and have to read it to
// understand the whole.
//
// Now Drive is written entirely in terms of intent. They can choose
// to read it and trust all sub-methods. After they accept Drive, they can choose to read
// any sub-method that interests them. They don't have to keep all of Drive in their minds
// at one time.
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
namespace Blog_gists
{
public class Car
{
[NotNull] private readonly List<TravelSegment> drivingHistory = new List<TravelSegment>();
public Car()
{
Odometer = 0;
IsAtDestination = false;
}
public void Drive(int speed, int distance, DriveStyle how)
{
PhysicallyMoveTheCar(speed, distance);
if (DriveStyle.WithFuelMonitoring == how)
{
UpdateFuelConsumed(speed, distance);
}
UpdateDistanceTravelled(distance);
if (DriveStyle.WithAverageSpeedCalculation == how)
{
UpdateDrivingHistory(speed, distance);
}
}
private void UpdateDrivingHistory(int speed, int distance)
{
drivingHistory.Add(new TravelSegment(speed, distance));
}
private void UpdateDistanceTravelled(int distance)
{
Odometer += distance;
}
private void UpdateFuelConsumed(int speed, int distance)
{
FuelRemaining -= CalculateFuelConsumed(speed, distance);
}
private void PhysicallyMoveTheCar(int speed, int distance)
{
for (var i = 0; i < distance; ++i)
{
DriveOneMile(speed);
}
FinishDriving();
}
public enum DriveStyle
{
WithFuelMonitoring,
WithAverageSpeedCalculation
}
public decimal? AverageSpeed
{
get
{
if (drivingHistory.Count == 0) return null;
var totalDriveTime = drivingHistory.Select(h => h.Distance/h.Speed).Sum();
return Odometer/totalDriveTime;
}
}
public void FillGasTank(decimal numGallons)
{
FuelRemaining += numGallons;
}
private void FinishDriving()
{
IsAtDestination = true;
}
public class TravelSegment
{
public decimal Speed;
public decimal Distance;
public TravelSegment(int speed, int distance)
{
Speed = speed;
Distance = distance;
}
}
private decimal CalculateFuelConsumed(int speed, int distance)
{
return 3;
}
private void DriveOneMile(int speed)
{
IsAtDestination = false;
}
public int Odometer { get; private set; }
public decimal FuelRemaining { get; private set; }
public bool IsAtDestination { get; private set; }
}
}
using FluentAssertions;
using NUnit.Framework;
namespace Blog_gists
{
[TestFixture]
public class CarsDrive
{
private const int Speed = 50;
private const int Distance = 100;
[Test]
public void DrivingWhileUpdatingFuelGuageShouldMoveTheCarAndRequireGas()
{
var testSubject = new Car();
testSubject.FillGasTank(12);
testSubject.Drive(Speed, Distance, Car.DriveStyle.WithFuelMonitoring);
testSubject.IsAtDestination.Should().BeTrue();
testSubject.Odometer.Should().Be(100);
testSubject.FuelRemaining.Should().Be(9);
testSubject.AverageSpeed.Should().NotHaveValue();
}
[Test]
public void DrivingWhileUpdatingMilageShouldMoveTheCarAndCalculateMilage()
{
var testSubject = new Car();
testSubject.FuelRemaining.Should().Be(0);
testSubject.Drive(Speed, Distance, Car.DriveStyle.WithAverageSpeedCalculation);
testSubject.IsAtDestination.Should().BeTrue();
testSubject.Odometer.Should().Be(100);
testSubject.FuelRemaining.Should().Be(0);
testSubject.AverageSpeed.Should().Be(50);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.