Communication glitches happen all the time and software development is no different from any other area that involves two or more people (or computers) talking to each other. We’re using programming languages after all, aren’t we?
Many such situations come from the fact that we’re often being implicit about the way we describe solutions in code. Assumptions stay in our heads instead of being expressed directly. We’re too quick to dive into development without getting a deep understanding of the problem first. This, in severe cases, may even cause thousands of other developers to make dearly mistakes.
Let’s look at one of the most extreme examples — dates and how Domain-Driven Design solved it once and for all — and what it says about the way we should be designing our solutions.
More than just a date
First, let’s consider the concept of a date. What exactly is a date? It’s not a tricky question, nor am I in a mood for being pretentiously philosophic. The first result from Oxford dictionary gives us:
The day of the month or year as specified by a number.
A particular day on the calendar, as everyone would have expected, isn’t it? Let’s check Wikipedia as well just to be sure:
A calendar date is a reference to a particular day represented within a calendar system.
Date class, let’s see what that is all about then:
A Date object contains a Number indicating a particular instant in time to within a millisecond. Such a Number is called a time value. (…) Time is measured in ECMAScript in milliseconds since 01 January, 1970 UTC.
Wait, what? We expected just a plain old date, but all of a sudden we get an “instant in time”, with milliseconds and a UTC time zone. If you look at the documentation a little closer, you’ll notice they even provide formulas for you to calculate a day yourself. How kind of them!
(…) a specific instant in time, with millisecond precision.
Nope, broken as well. Perhaps .NET then?
DateTimevalue type represents dates and times (…) Time values are measured in 100-nanosecond units called ticks. A particular date is the number of ticks since 12:00 midnight, January 1, 0001 A.D. (C.E.)…
On the upper side, they gave their type a better name to let us know time is involved as well. On the Microsoft side, they of course used a starting offset that is different to what everybody else seems to have settled for. But let’s not miss this little gem over here:
DateTime date1 = new DateTime(2008, 6, 1, 7, 47, 0);// Get date-only portion of date, without its time.
DateTime dateOnly = date1.Date;
A “date-only portion” that is a
DateTime instance. Right. If you’ll inspect the docs, you’ll see that they’re actually setting the time to midnight (00:00:00). That’s a bit different than “not returning” the time, isn’t it?
Welcome to the fold
You may be wondering why I am going on a rant with this. After all, it’s not that the language and API designers were trying to hide something or intentionally mislead developers — I’m sure they weren’t, and all details and quirks are extensively documented in every case. Not to mention that people have been using these tools since a long time and in many cases to good results.
This concept of “dates” actually being Unix timestamps is software development’s status quo. All engineers are aware of this and it’s rarely a surprise. Still, you have to admit that giving something a mismatched name and then taking the effort of describing that it’s actually not what it appears to be is a little mind boggling. Another question is — does this make anything easier?
The answer is “of course not.” Software development can be difficult as is, why then should we burden our thinking with additional, confusing layer of abstraction?
If you were ever tasked with anything related to dates being transferred between boundaries of a single system (read: user submitting a form on a web page), chances are you came across timezone problems.
There are various ways that things can go wrong in this matter, to name a few:
- server using a timezone the developer is not expecting it to,
- some part of the communication flow deliberately or accidentally ignoring timezone information,
- serialization layer trying to be clever by adjusting the timezone and not taking into account that the operation can shift time,
- timestamps being used to convey data that doesn’t require as much precision,
Either way, the outcome is the same — the time is off by a number of hours, sometimes changing the day as well. The latter is very common in situations where timestamp is employed to represent a calendar date and the time portion is “removed” by setting it to midnight (i.e. what we’ve seen in the .NET sample).
Now imagine that in order to get all the edge cases right, you should be taking daylight saving into account as well. Headache and late hour bug reports guaranteed.
The hero we need
It would’ve been quite surprising if developers just kept putting up with this kind of nonsense through all these years without trying to do anything about it. However, there is a limit to what you can accomplish with a tool that’s inherently broken. Solving deep problem domains requires precise, robust solutions and this sometimes means binning what you already have and starting anew.
Java’s Joda Time library kicked off in 2002 and paved a new way in the field of dates and times handling. They let go of the core
Date class, instead providing a fresh set of building blocks, each crisply defined and tailored to tackle different time-related aspects.
To name a few of them, there is an
Instant which functionally corresponds to the faulty antihero of this article, but is named correctly. There is
LocalDate which is just a date on the calendar, no strings (or milliseconds/timezones) attached. There is
LocalTime which is a time of day one could see when looking at their watch, regardless of what date it is. There are
Durations for telling how many milliseconds, or
Periods for telling how many years/weeks/days have passed between arbitrary events. Lower level concepts like
Chronology ease working with different calendar systems. You call
daysBetween to tell how many days passed between dates and
isAfter to verify the order of events — no more folly with extracting millisecond counts and comparing numbers.
You could say the Joda Project were a hipster of their time, because they created a domain-driven API before Eric Evans’ book Domain-Driven Design made the concept popular. Domain-Driven Design, among other things, puts emphasis on using precise, domain-specific language (ubiquitous language, as it’s called in the book) to directly model different concepts of given problem, including any interactions between them, in code.
Instead of using vaguely defined, catch-all solutions, engineers are required to get a deep understanding of issue at hand and model it in a way that straightforwardly corresponds to reality. This translates to code that is easier to read and maintain, effectively resulting in fewer bugs. Consider the following piece of code, possibly taken from a definition of some
private Date dateOfBirth;
Looks completely legit, doesn’t it? Now let’s pull the mask off this Scooby-Doo villain to see who they really are:
private Instant dateOfBirth;
Something like this would instantly (pun not intended) spark a discussion during code review. Do we really need millisecond precision for a date of birth? How will this be modelled in the database? Will this be convenient to work with in other modules?
And most importantly — why can’t we use a type that actually represents dates? We can and we should:
private LocalDate dateOfBirth;
But Date is a core API, everyone is using it!
If you have any recent Java/JVM development experience, you probably could’ve skipped most of previous paragraph as it was nothing new or surprising to you. The truth is, Joda Time library had such widespread success across the community that, starting with Java 8, it got incorporated into JVM core API as
java.time package (with some modifications).
The fact that a solution a mature platform like JVM has incorporated into its core is marginal within Node.js spectrum means Node.js still has some way to go, but let’s not forget it’s in our hands to take the effort and make a change for the better.
I’ve picked dates and times to make my point tonight, but I hope it’s clear that the whole idea is not limited to temporal matters only. Domain-Driven Design approach suggested by the Joda Project shows how carefully chosen names can make a massive difference, facilitating development and maintenance of applications.
We should seek out frameworks and libraries which enable that, but more importantly, aim to tackle problem domains of our own projects with no less attention to detail. Being explicit and deliberate about the way we describe solutions in code means that we have thorough understanding of the problem itself, and that leaves little room for costly logical mistakes.