I am constantly working towards becoming a better OO practitioner. To do so I like to practice by solving problems by trying to stay true to the design principles of OO. My current job is a great source of real world business domains, so this helps with my practicing. In this post I am going to focus on a specific problem on employee compensation. If you prefer to just download the source code, I have included it as a download.

Last year we released a system to the Human Resources department of our company to help them manage the compensation for each employee in the company. As part of our compensation we are all issued a base salary for the year, a target bonus, and a target LTIP (long term incentive plan.)

The bonus is split in half, and issued to employees in January, and June of each year. This is called the H1 and H2 bonuses. The LTIP is also split in half and issued in the spring and fall of each year. This is known as the spring and fall LTIP. Bonus are issued in cash, but LTIP’s are issued as grants. We offer two types of LTIP’s, one called RTU (restricted trust units) and another called PTU (performance trust units). For this article I am going to focus on our RTU grants.

When a grant is issued to an employee, 1/3 of the grant will vest on each anniversary of the date that the grant was issued. For instance, if I was issued a fall LTIP grant of $4500.00 at a unit price of $10.00, then the following year I would be issued 1/3 of the grants value. If the price doubles from $10.00/unit to $20.00/unit then I would receive a payout of $3000.00. When an employee has been working at ARC for 3 years, then they are considered “fully loaded”, which means that during either the spring or fall compensation events, that employee would receive 1/3 of 3 different grants.

So let’s model this. If an employee can have any where between 0 and 3 grants with unvested units available at any time, how can we calculate the current value of that employees LTIP. Let’s start by writing a unit test, and let test driven development guide us.

[Concern(typeof(Compensation))]
public class when_calculating_the_total_unvested_dollars_awarded : concern 
{
  context c = ()=> 
  {
    grant_date = new DateTime(2009, 09, 15);
    value_of_grant = 4500.00;
    unit_price = 10.00;
    portion_to_issue_at_each_vest = new One<Third>();
    frequency = new Annually();
  };

  because of = () =>
  {
    Calendar.stop(() => grant_date);
    sut.issue_grant(value_of_grant, unit_price, portion_to_issue_at_each_vest, frequency);

    Calendar.start();
    sut.grant_for(grant_date).change_unit_price_to(20.00);
  };

  it should_indicate_that_nothing_has_vested_before_the_first_anniversary = () =>
  {
    sut.unvested_balance(new DateTime(2010, 09, 14)).should_be_equal_to(9000);
  };

  it should_indicate_that_one_third_has_vested_after_the_first_anniversary = () =>
  {
    sut.unvested_balance(new DateTime(2010, 09, 15)).should_be_equal_to(6000);
  };

  it should_indicate_that_two_thirds_has_vested_after_the_second_anniversary = () =>
  {
    sut.unvested_balance(new DateTime(2011, 09, 15)).should_be_equal_to(3000);
  };

  it should_indicate_that_the_complete_grant_has_vested_after_the_third_anniversary = () =>
  {
    sut.unvested_balance(new DateTime(2012, 09, 15)).should_be_equal_to(0);
  };

  static DateTime grant_date;
  static double value_of_grant;
  static double unit_price;
  static One<Third> portion_to_issue_at_each_vest;
  static Annually frequency;
}

In the above set of unit tests, I am focusing on a single employees compensation. I’ve awarded that compensation a single grant valued at $4500.00 at the time of grant at a price of $10.00/unit. In each test I am checking to see that the unvested amount is correct at different times in the future. With this design I can now see what an employees compensation looks like in the future and at any point in the past. This is a form of black box testing, I am testing the expected behavior of a single class. I don’t really care what the underlying implementation is. I just want to know that in the end it produces the value that I expect. This type of testing is my preferred style when working in a domain model, it allows for much easier refactoring, less test maintenance and still preserves the expected behavior.

Compensation

Let’s take a look at the Compensation class to see how we can get these tests passing and stick to some fundamental object oriented programming principles.

public class Compensation : Visitable<Grant>
{
  IList<Grant> grants = new List<Grant>();

  public void issue_grant(Money grant_value, UnitPrice price, Fraction portion_to_issue_at_each_vest, Frequency frequency)
  {
    grants.Add(Grant.New(grant_value, price, portion_to_issue_at_each_vest, frequency));
  }

  public Grant grant_for(Date date)
  {
    return grants.Single(x => x.was_issued_on(date));
  }

  public Money unvested_balance(Date date)
  {
    var total = Money.Zero;
    accept(new AnonymousVisitor<Grant>(grant => total = total.plus(grant.balance(date))));
    return total;
  }

  public void accept(Visitor<Grant> visitor)
  {
    grants.each(x => visitor.visit(x));
  }
}

Compensation is our aggregate root. Within it’s boundary it creates an instance of Grant via a static factory method. It implements a Visitable<T> interface to adhere to the interface segregation principle as well as the open closed principle. By allowing the Compensation to accept visitors it leaves this class closed for modification but still for extension. We can create new implementation of the visitors and pass them to collect the information necessary. In our calculation we are visiting each Grant and telling it to calculate the balance remaining as of a particular date. Notice the message passing, and information hiding. Compensation doesn’t need to “know” about any of Grants “data” it is invoke specific behaviors on Grant instead of picking off values from getters. I prefer not to use getters and setters, not only are they an anti-patterns in object oriented design, but they help produce brittle software. Every time you add a getter or worse, setter you are adding a future maintenance cost to your software. Focus on behavior rather than on data.

Grant

static public Grant New(Money purchase_amount, UnitPrice price, Fraction portion, Frequency frequency)
{
  var grant = new Grant
  {
    issued_on = Calendar.now(),
  };
  grant.change_unit_price_to(price);
  grant.purchase(purchase_amount);
  grant.apply_vesting_frequency(portion, frequency);
  return grant;
}

There’s a couple of things that happen when we create an instance of Grant. First we record that date that the grant was issued on, second we record the unit price, then we purchase units, and finally we apply a vesting frequency. When we record the unit price we are actually tracking the each price change, which allows us to move forward and backwards in time.

History<UnitPrice> price_history = new History<UnitPrice>();

public virtual void change_unit_price_to(UnitPrice new_price)
{
  price_history.record(new_price);
}

The history of each price change is record in the generic History. This will record the date that the change occurred and keeps a stack of these changes. Again, we are pushing message forward. We have also wrapped the primitive double type in a UnitPrice class that allows us to extend double with additional behavior as well as allows to quickly glean the intention rather than the implementation. If we were to store dollars and units in primitive types, there’s little that blocks us from accidentally adding these two values together. This is just now how Money and UnitPrice behave with one another. This relationship now become explicit when we use actual classes.

void purchase(Money amount)
{
  units = units.combined_with(current_unit_price().purchase_units(amount));
}

UnitPrice current_unit_price()
{
  return unit_price(Calendar.now());
}

UnitPrice unit_price(Date on_date)
{
  return price_history.recorded(on_date);
}

When we purchase a certain amount of units, we look up the current unit price, and purchase as many units as we can for the dollars given. Notice how it’s the UnitPrice class that is calculating the number of Units that can be awarded for a certain amount of Money. We then combine those Units with the existing number of Units already awarded to this grant. The Unit Price History allows us to look up the most relevant Unit Price for any given date.

public virtual Money balance()
{
  return balance(Calendar.now());
}

public virtual Money balance(Date on_date)
{
  return unit_price(on_date).total_value_of(units_remaining(on_date));
}

Units units_remaining(Date on_date)
{
  var remaining = Units.Empty;
  foreach( var expiration in expirations)
  {
    remaining = remaining.combined_with(expiration.unvested_units(units, on_date));
  }
  return remaining;
}

The final balance calculation looks up the unit price for the given date and calculates the total monetary value for the unit that have not expired. We iterate through each expiration and accumulate the units that have not vested. Let’s take a look at how that is done.

Vest

public class Vest
{
  Fraction portion;
  Date vesting_date;

  public Vest(Fraction portion, Date vesting_date)
  {
    this.portion = portion;
    this.vesting_date = vesting_date;
  }

  public Units unvested_units(Units total_units, Date date)
  {
    return expires_before(date) ? Units.Empty : total_units.reduced_by(portion);
  }

  bool expires_before(Date date)
  {
    return vesting_date.is_before(date);
  }
}

Each Vest has a date that the vest occurs. In our example this happens on each anniversary of the original grant date until each 1/3 has completely vested. To calculate the unit remaining we check to see if the vest expired before the given date. If so then 1/3 has expired. If not then we take the total units available and divide that by 1/3. We have a Fraction interface so that if in the future the rules need to changes from 1/3 to 1/12 we can accommodate that.

In this post I hope that I have given you an opportunity to see the benefit of object oriented modeling. By modeling real world business processes as closely to the real thing, we allow for change, in fact we embrace it. We make the code easy to read and hopefully easy to understand. The small pieces are easier to digest and get new team members up to speed on the core domain much faster. The way we name our classes and methods should be intention revealing and mimic the language used in the core business domain. Focusing on behavior rather than data, allows us to achieve things in a model that a data model simply cannot easily do. I have done my best to illustrate some of the principles of object oriented design such as “Tell don’t ask”, “Single Responsibility Principle”, “Open/Closed Principle”, “Interface Segregation Principle”.

Download

comments powered by Disqus