The Payroll Machine.

Or, as some would say, the story of how some complexity might be necessary sometimes. Or, yet another edition of Looking at the Wrong Abstraction.

The difficult solution

Imagine you need to develop an application that calculates a company’s payroll. Let’s assume that we have three different roles within the company (Developer, Systems Architect, Development Manager, and since this is a modern cool and hip company that does scrum, a Product Owner), and that different roles have different salaries.

The simplest approach

Well, you could argue that it does not actually matter too much, when calculating the payroll, if an employee is a Developer or a Systems Architect. So, one sensible approach might be to model all employees as instances of a class like this:

enum Role {
    case Developer
    case SystemsArchitect
    case DevelopmentManager
    case ProductOwner
}
 
class Employee {
    private (set) var name: String
    private (set) var role: Role
    private (set) var salary: Int   
 
    init(name: String, role: Role, salary: Int) {
        self.name = name
        self.role = role
        self.salary = salary
    }
}

Now, calculating the payroll is just a matter of doing something like this:

let developer = Employee(name: "Jane Doe", role: .Developer, salary: 100)
let productOwner = Employee(name: "John Doe", role: .ProductOwner, salary: 100000)
let systemArchitect = Employee(name: "John Smith", role: .SystemsArchitect, salary: 1000)
 
func calculatePayroll(employees: [Employee]) -> Int {
    return employees.reduce(0, combine: { $0 + $1.salary })
}
 
calculatePayroll([developer, productOwner, systemArchitect])

Real life sucks. Or requirements change

I bet you have been there a thousand times. Your app is all app and running, processing data full steam ahead, doing what is expected to do, and doing it well, when suddenly… requirements change.

In this case, the CTO demands a new feature: he should be able to assign multipliers to specific employees, so he can modify their salaries temporarily.

The flexible solution. Or, the mega-awesome although apparently slightly over-engineered solution, that after some consideration, happens to be just awesome and not over-engineered at all

First, I’ll show you all the codez. The I’ll discuss all the codez.

/*
What is it that actually matters? Not what things ARE, but how things BEHAVE
The behaviour we want is:
1.- Each role needs to be assigned a different multiplier
2.- We should be able to get the salary assigned to a role. Multipliers are an implementation detail
3.- Developers and Product Owners are not the same, do not behave the same. Thanks god!
    This is an important distinction. A class modelling a Developer does not have to be the same class modelling a Product Owner.
*/
 
protocol Employee {
    func getName( ) -> String
    func getSalary( ) -> Int
}
 
class Developer: Employee {
    private final let name: String
    private final let baseSalary: Int
    private final let multiplier: Int   
 
    init(name: String, baseSalary: Int, multiplier: Int = 1) {
        self.name = name
        self.baseSalary = baseSalary
        self.multiplier = multiplier
    }   
 
    func getName( ) -> String {
        return name
    }   
 
    func getSalary( ) -> Int {
        return baseSalary * multiplier
    }
}
 
class ProductOwner: Employee {
    private final let name: String
    private final let baseSalary: Int
    private final let multiplier: Int   
 
    init(name: String, baseSalary: Int, multiplier: Int = 1) {
        self.name = name
        self.baseSalary = baseSalary
        self.multiplier = multiplier
    }
     
    func getName( ) -> String {
        return name
    }
     
    func getSalary( ) -> Int {
        return baseSalary * multiplier
    }
 
    //Product owner's extra behaviour.
}
 
class SystemArchitect: Employee {
    private final let name: String
    private final let baseSalary: Int
    private final let multiplier: Int   
 
    init(name: String, baseSalary: Int, multiplier: Int = 1) {
        self.name = name
        self.baseSalary = baseSalary
        self.multiplier = multiplier
    }   
 
    func getName( ) -> String {
        return name
    }   
 
    func getSalary( ) -> Int {
        return baseSalary * multiplier
    }   
 
    //System Architect's extra behaviour. It would probably include drugs and plenty of alcohol.
}
 
 
//Names removed to protect the innocent
let developerNumberOne = Developer(name: "Dev1", baseSalary: 100)
let developerNumberTwo = Developer(name: "Dev2", baseSalary: 100, multiplier: 2)
let productOwner = ProductOwner(name: "ProductOwner", baseSalary: 100000)
let systemArchitect = SystemArchitect(name: "SA's are annoying", baseSalary: 10000000, multiplier: 4)
 
 
func calculatePayroll(employees: [Employee]) -> Int {
    return employees.reduce(0, combine: { $0 + $1.getSalary()})
}
 
calculatePayroll([developerNumberOne, developerNumberTwo, productOwner, systemArchitect])

Now, there are different kinds of Employees. To me, that means that each of those “kinds” has to be modelled by a different class. Why? Because they represent different behaviours. As you know, a PO does not do the same things a Developer does.

However, it is true that I am only interested in part of the behaviour of those different kinds of Employees. That is already abstracted by the interface!

Is there repetition? Yes, apparently, but I wouldn’t call it repetition, I would call it specialisation. If there is something else to be taken into account when calculating a salary (for example, manage,went might have part of their salaries payable in shares dividends and whatnot), the specific class that models a PO can implement that specific behaviour.

Also, this solution improves encapsulation. The responsibility of calculating each employee’s salary is well encapsulated, and is completely opaque to the calculatePayroll method.

Summary

Often times, a flexible solution requires more code than a simple one. Well, that’s life, I guess: if you want something done properly, you will need to put some serious work into it. It does not matter if it is installing a new fridge in your kitchen, or writing a new method in your code.

But the key here, again, is that being able to identify the right abstractions makes a huge difference in the scalability, modularity and cleanness of your code.

One Reply to “The Payroll Machine.”

Leave a Reply

Your email address will not be published. Required fields are marked *