A Simple Problem
In order to understand how programmers might make poor choices in writing their code and why they may not initially see these as poor choices, that can deeply impact the user, we’ll need to start with a very simple problem derived form a basic need and quantify the solution.
Let’s assume that we have a programmer, named Baz, that has taken a position with a company, named Foobar Inc. Foobar Inc is starting a work from home program and their employees have previously all been submitting their time sheets to their managers on paper. Now, Foobar Inc needs Baz to develop an Electronic Time and Attendance application that they can deploy on their network. It will need to be in PHP so that they can implement it on their existing servers. Luckicly, Baz is very fluent in PHP and thinks he can accomplish this task rather quickly and efficiently (of course, he’s a pretentious geek – what did you expect). Baz gets to work right away and having reviewed the spec he realizes his first challenge will be to make sure the punches are recorded in 15 minute increments. So when an employee goes online to log in their punch at 9:02 AM or 9:05 AM it must still be recorded in the system as 9:00 AM as the employees are paid in 15 minute increments. Similarly, a punch made at say 9:09 AM must be logged as 9:15 AM, given that it is closer to the halfway mark of the quarter of the hour. Payroll will be able to automatically use their own software to total up the amount of time each employee has worked at the end of a pay period according to this new ETA software and quickly compile the data for that periods payroll. However, managers also need to be able to see the actual time an employee is punching in and out so now two dates need to be stored in the database.
Baz, starts seeing the problem instantly just as Foobar Inc realized it is a problem that needs to be solved. Though, computers are far more efficient at solving mundane, repetitive tasks very quickly and efficiently. Baz, begins solving the problem by assuming everything is as simple as it seems. Given a principle like Occam’s Razor, we try to make as few assumptions as possible – as programmers and as engineers. However, it is not always simple to tell a computer how to solve a problem. The computer must be told how to understand what you already know. So when we consider the fact that each punch in and punch out in the system must be in 15 minute increments we can solve the problem in our minds the simplest way we know how. To the average person this equate to the following code or something similar.
function minutes ($minutes) {
if ($minutes < 8 ) $minutes = 0;
elseif ($minutes > 7 && $minutes < 23) $minutes = 15;
elseif ($minutes > 22 && $minutes < 38) $minutes = 30;
elseif ($minutes > 37 && $minutes < 53) $minutes = 45;
else $minutes = 0;
return $minutes;
}
As you can see this is just a simple set of if/elseif/else statements that make the determination of what the minutes should be given a punch at any given minute of the hour. That’s a pretty simple set of conditions that anyone can follow. If we simply take the minutes from the timestamp that the employee would make their punch and send it to this function the problem is easily solved. The function will return the correct number of minutes to log and we can update the timestamp, right? Of course not! Nothing can be THAT simple, right?
When we originally thought of how to solve the problem we were able to quantify the scope of the problem to the given number of minutes in the hour. Which means there are only 5 possible outcomes of any given minute from 0 to 59. This is because the hour is divided into quartiles, where each quartile represents a period totalling 15 minutes. If the current minutes are anywhere in between those quartiles (which they always are unless they are exactly on the quartile) then we must determine to which quartile they fall closest and update the minutes to that exact point of the quartile. This actually works, if you think about it, because it is what we would do on a piece of paper. Though, this presents us with a fundamental problem. What happens once we get to the final quartile? This would mean the number of minutes must revert to 60, but there is no 60th minute in an hour. That is why we simply put 0 instead, because once we exceed 59 minutes we simply start back at 0 and increment the number of hours by one. For a person, this is a non-issue. However, for a computer – remembering that we must specifically tell a computer how to do something that already comes very naturally to us – this is not an issue they’ve been made aware of through our high-level code.
Not So Simple ‘a Problem Anymore…
Well, now what happens when an employee logs out at 4:59 PM? The existing code would interpret that as 4:00 PM using this function and we would have effectively cheated an employee out of an entire hour of labour. Great for Foobar Inc, horrible for the employee. Then again, we are building this software in order to serve the user (in this case the employees of Foobar Inc). Thus, if we haven’t provided a more convenient and error-free way to do what they were already doing by hand, we haven’t really solved the problem at all.
At first Baz will realize the next problem that was initially unexpected in the code design. That would be that the hours of the day depend on the minutes. Of course, we want to solve this problem as quickly and as seamlessly as possible. After all we don’t have time to spend all day fixing one problem… wait… actually we’re programmers that’s exactly what we do! *grumble*
So the quickest solution would be to write another function that checks the return value of minutes and if we fix line 6 in the previous code to return a Boolean true instead of 0, we can use this as an identifying factor that the number of hours need incremented by one. OK, so huge sigh of relief; problem solved and the application is bug-free again, right? Now, where would be the fun in that?
Extrapolating The Problem
What we quickly realize now is that our first assumption of there only being 5 possible outcomes has been proven wrong. Since our entire theory is now flawed, we must realize that as programmers this is a sign that we have done something very wrong to begin with. We built our code on a flawed hypothesis. This is clearly a problem that needs our attention. Rather than accept this and refactor our code – some programmers will chose to ignore this problem altogether and continue trying to fix the problem by writing more code. Clearly, however, more code is not the answer. Why is more code not going to solve our problem – despite the fact that we know computers need explicit instructions because they can’t learn on their own? The simplest answer is that more code means more potential for further problems. Further problems require further debugging and ultimately increase the cost of maintaining the code.
So, now the question remains, how do we solve the problem if more code is not the answer? The answer lies in exploring the bug. This is actually quite contrary to what many programmers in the early days of computers have been taught to do. In fact, at the very sight of a bug in the code, a programmers job was to get rid of the bug as quickly as possible. Though, if the result is that we get rid of the bug by writing more code we are only writing more potential bugs. This is evident in the extrapolation of the problem. Think what happens when an employee logs out at 11:59 PM. Baz can also solve this problem by writing another function that will update the day in the given timestamp if the hours increase beyond 23. But then what happens at the end of the month or even if an employee happens to log out on December 31st at 11:59 PM. Now we need to account for the hours, days, months and years and have spread the operation over numerous functions that all have a potential for error. This is clearly not a good solution.
Back to Basics
Let’s come down from the mountain we’ve just climbed. If we think about this logically, the time stamp is already a part of an existing system of some sort. For example, Object Oriented Programming is a paradigm that attempts to solve some of these fundamental code design problems along with specific Design Patterns and proven principles. Take Dependency Injection, for example. We are relying on every one of our functions to know and correctly process its bit of information and assume the process will work flawlessly as long as each function does its part correctly. Though what happens when the external factors that each function depends on happen to change? Consider Day Light Savings Time, timezone offsets, and locales that affect timestamp formatting just to name a few. All of these external factors can effect the output our existing functions produce.
To further complicate the problem we have to consider Polymorphism, which is another principle commonly brought up in the context of OOP. Since PHP is a loosely typed language it already observes some form of polymorphism. When we store timestamps in our database we can rely on the native Date type of the DBMS and can additionally convert things like UNIX time in PHP relying on libraries commonly implemented in PHP, Python, Perl, C, etc…
So, the answer actually lies in making things simpler and narrower, not more complicated and diverse. We want the system to work together as a whole and not come apart when one thing changes. Clearly, one thing does effect the other as we’ve seen with minutes effecting hours, hours effecting days, and so on…
A Eureka Moment in Code
A Eureka moment happens when a programmer suddenly realizes that he or she was right all-along. It really is as simple as it seems! All we really need to know are the number of minutes that need to be added or subtracted from the existing timestamp (recorded in the system by the employees’ punches) in order to satisfy the existing quartile. Rather than waste our precious developers time, Baz, in figuring out how to adjust a timestamp accurately, we can simply rely on existing libraries and extensions that have already found proven ways to deal with timestamps natively on the system.
In PHP, of course, this would be the DateTime class. Since we want to use things like Dependency Injection to make our code more modular and easier to pick apart and reconfigure, we can easily extend the DateTime class in our own application and we would have no problem converting the existing timestamps in the application in anyway we may deem fit now or in the future.
So, Baz, wrote the following code, which reverts back to the KISS principle (or Keep It Simple Stupid).
class Punch Extends DateTime {
public function __construct($time = NULL, $timezone = NULL) {
if ($timezone === NULL) $timezone = new DateTimeZone(date_default_timezone_get());
parent::__construct($time, $timezone);
}
public function adjust() {
$minutes = $this->format('i');
$quartile = floor($minutes / 15);
$diff = $minutes - ($quartile * 15);
if ($diff >= 15 / 2) $add = 15 - $diff;
else $sub = $diff;
if (isset($sub)) $this->sub(new DateInterval("PT{$sub}M"));
else $this->add(new DateInterval("PT{$add}M"));
return $this;
}
}
Sample Usage
$time = new Punch("4/15/2011 11:53 PM");
echo "Actual Time: 11:53 PM - Adjusted Time: " . $time->adjust()->format('m/d/Y h:i A') . '<br/>' . PHP_EOL;
// OUTPUT WOULD BE SOMETHING SIMILAR TO
// Actual Time: 11:53 PM - Adjusted Time: 04/16/2011 12:00 AM
Now, Baz can take a native timestamp and easily adjust the employees punches according to the desired format for the ETA system and all is well again! The employees that use the system get the correct punches logged in their time sheets and are paid accordingly. The employees (the users) are happy, the company is happy and Baz is thrilled that Foobar Inc wants to keep him on for not writing crappy applications that break and don’t always do what they’re supposed to do.
Baz Can Take a Break Now, Before He Looses His Sanity
As we can see building applications is by no means a simple task. Realizing your need for a particular application, however, usually comes naturally. For instance, imagine that you’ve just purchased a brand new computer with just the vanilla operating system software and nothing more. If the computer does not have any utility applications then it doesn’t take me very long to realize I will need some word processing software installed on my computer in order to write and store documents. If I pop my new Lady Gaga CD in I won’t have to think twice before I realize I need to download and install a media player in order to listen to my music. However, when it comes to actually writing these applications that we use and depend on so much – there is a certain sense of reluctance for a programmer to build them. Not only that, but a programmer is even more reluctant to maintain them for several years, because so much can change and maintaining code is expensive.
Here is where the men are separated from the mice. Some programmers will simply shy away from fixing code or even admitting that their code is bad. Why would anyone – not – want to make something better, you ask? The answer actually has a lot to do with the programmer wanting to make something work instead of trying to work with the things on which it depends. In all honesty, who can blame the poor guy or gal? After all, they did get it to work. You just happened to use it in a way that they had not intended. Though, this begs the question who is the puppet and who is the puppeteer? Are end-users really the ones pulling the strings in demanding computer software that tends to their needs, or do engineers depict what is or is not useful to the user?
As both a programmer and end-user I like to find a middle ground. Seeing things from the users’ perspective can be very helpful, because after all they are the ones using the software – while I’m just the poor geeky programmer locked up in a dark room with long hair and a beard writing endless code and always under appreciated. Yes, that was a run on sentence worth writing!
Izkata, since says:
Baz wrote bad code, as well. Are you trying to extend DateTime or create a wrapper? ‘Cause that example code is a horrible mishmash of both.
The best way to go will be to create a wrapper. The code only requires two changes, then:
1) Remove ” Extends DateTime”.
2) Use “new DateTime(” instead of “new parent(” in the constructor.
GoogleGuy says:
Not at all. You seemed to have missed the point entirely.
First, by removing the “Extends” keyword from the class definition we lose inheritance. That means we can no longer extend the functionality of DateTime, but are now simply coupling two different objects together. Second, we don’t have to call the parent constructor at all if we didn’t overload the constructor. We could have simply left the entire constructor out, but for demonstration purposes I left it in to give you an idea of how you could further do any necessary logic upon instantiating the object.
Baz is not trying to create a new class, but merely modify the behavior of the existing object slightly. According to the SOLID OOP principle of Open/Close:
Baz is doing two things here. 1) He is following KISS (Keep It Simple Stupid), 2) He is complying with the Open/Close SOLID OOP principle and thus he doesn’t have to worry about how the DateTime object is being used elsewhere since it still complies with all of the other SOLID OOP principles (including the Liskov substitution principle – which is a design by contract).
Your suggestions would actually break the code entirely and defy the purpose since Baz intends for this to work with the existing DateTime objects being used throughout the code (i.e. any dates retrieved from the database and stored as datetime objects throughout).
Baz did his job perfectly and shouldn’t have done it any other way.