Functions Should Do One Thing
Functions doing one thing—having only a single responsibility—has nothing to do with SOLID’s Single Responsibility Principle (SRP).
The SRP applies a higher level of code organisation; at the level of modules and classes—above mere functions.
Yet, SOLID’s objectives for classes mirror those for functions—functions should be easy to understand, easy to change, and reusable. No surprise then that similar principles to SOLID operate at the function level.
OK, what do we mean by a function doing only ‘one thing’?
Let’s look at an example:
private int GetEmployeeSalaries(IEnumerable<Employee> employees) { int payroll = 0; foreach (var employee in employees) { employee.Salary = SalaryLookup.GetSalary(employee); payroll += employee.Salary; } return payroll; }
Function GetEmployeeSalaries() does two things:
- It gets and sets the employee salaries, and
- It adds up the salaries to calculate the payroll.
For sure, these two actions are closely related—both work with employee salaries. But they are still two responsibilities and should be separated.
Why should they be separated?
There are several reasons:
Better Reuse
When a function intertwines two or more responsibilities, we might want to use those behaviours separately, yet we won’t be able to. When we manage to reduce the workings of a function to one thing, we get better reuse.
Easier to Understand
A function doing only one thing is necessarily more straightforward to understand than a similar function doing that same thing and something else. Every additional responsibility increases the difficulty of reasoning about a function.
Fewer Bugs
Changing one behaviour may inadvertently introduce a fault in one of the co-hosted behaviours. A function focused on one thing does not have this problem.
OK, so how shall we split up the two responsibilities from our example?
How about like this:
private void PopulateEmployeeSalaries(IEnumerable<Employee> employees) { foreach (var employee in employees) employee.Salary = SalaryLookup.GetSalary(employee); }
PopulateEmployeeSalaries() gets and sets the salary for each employee in a collection of employees. Nothing more.
private int CalcStaffPayroll(IEnumerable<Employee> employees) { int payroll = 0; foreach (var employee in employees) payroll += employee.Salary; return payroll; }
CalcStaffPayroll() independently calculates the payroll by summing over the employee salaries.
We have made it so that PopulateEmployeeSalaries() and CalcStaffPayroll() may now be called independently from one another.
However, there is still a minor niggle we need to resolve: It stands to reason that we retrieve the employee salaries before attempting to determine the payroll. Don’t we need a function combining those two responsibilities, i.e. get the salaries before calculating the payroll?
Not quite. The one responsibility a function can have is to coordinate a workflow:
private int GetStaffPayroll(IEnumerable<Employee> employees) { PopulateEmployeeSalaries(employees); return CalcStaffPayroll(employees); }
The one responsibility of GetStaffPayroll() is ensuring the correct order of calls—i.e. that the population of employee salaries happens before the calculation of payroll.
Notice that GetStaffPayroll() is not performing any detailed work associated with getting salaries or calculating staff payroll—it’s calling other functions that do this as their ‘one thing’.
So, please remember that Functions Should Do Only ONE Thing!
You can also watch the YouTube video on ‘Functions Should Do One Thing’:
Leave a Reply
Want to join the discussion?Feel free to contribute!