There are complex systems, and then there are Complex Systems. And nothing makes a system more complex than dealing with people. People make things complicated.
Let’s look at an example. I’ve recently been working on an employee time tracking tool. One of the tasks that this tool does is verification of a timesheet: are the minimum requirements met, are the project and task codes filled in correctly, and – most importantly for my point – are there at least 40 hours listed in the work week?
That last one’s simple, right?
if (entries.Sum(e => e.hours) + vacation.Sum(v => v.hours) <= 40) { //INVALID – DO NOT PROCESS }
Absolutely. If you’re on the standard 40-hour-per-week schedule. But some people aren’t. They’re interns or contractors or something. So, a small change.
if (entries.Sum(e => e.hours) + vacation.Sum(v => v.hours) <= 40 && employee.Type == Types.Fulltime) { //INVALID – DO NOT PROCESS }
Which is great! Until someone leaves the company in the middle of a week. OK, it’s getting tricky, but we can still do that.
if (entries.Sum(e => e.hours) + vacation.Sum(v => v.hours) <= 40 && employee.Type == Types.Fulltime && employee.Status == Status.Active) { //INVALID – DO NOT PROCESS }
Whew! We’ve got all our bases covered. Until a full time employee starts working 20 or 30 hour weeks instead of 40, or someone takes a three-day leave of absence, or HR changes the vacation policy, or someone’s manager grants them extra paid time off as a bonus, or any other number of perfectly reasonable human complications that are not accounted for.
(Note: none of these are hypothetical. The tool I’m working on has had to account for all of these and many, many more.)
So, what’s the solution? Do we keep adding validation checks every time a new possibility arises? Do we throw more and more code at the check until it’s a gigantic, unmanageable mess? And, most importantly, do we keep the users waiting while we make these changes or – even worse – give them the unsafe, unaccountable workaround of modifying data stores directly?
I say nay! And offer another solution.
if (entries.Sum(e => e.hours) + vacation.Sum(v => v.hours) <= 40) && employee.Type == Types.Fulltime) { // Warning! Possible invalid timesheet. timesheet.Status = Status.Invalid; Messaging.NotifyAdministrator("Possible invalid timesheet: " . timesheet.FriendlyName . " To override and process anyway, please confirm here: " . BuildLinkToConfirmationPage(timesheet) }
Don’t have the system freeze up when the invalid timesheet check gives a false positive. Give a user the chance to override in a friendly manner that, by still being part of the system, maintains accountability and logging. Do just enough checking to catch the majority of invalid cases, and allow an administrator to override those false positives.
By giving some of the power back to the people, you’ve decreased the complexity of your code (which increases maintainability) and increased the usefulness of the system (which decreases user frustration). According to my math, that’s a whole lot of wins.