Friday 22 April 2016

Design - avoiding the obvious

Suppose you have a process that combs a database and generates monthly reports. The rule is simple, you run at the beginning of the month, and your universe is defined by the first and last days of the previous month.

See? Simple. You've got a begin date and an end date, and just grab everything that falls within that time period.

Since this date arrangement is a recurring requirement, I've decided it was time to abstract it into a script-lib, i.e., something other processes can call on the command line, and that can also be included as a library/module by other scripts.

And so I went about defining the interface, based on this use case - OK, we'll need a function that returns the begin date, and a function that returns the end date, and a rule for calculating the required interval. In my defense, I did kill the "and we could include formatting" thought as soon as it appeared.

Why all these functions? Well, SRP, and composability and all that jazz.

After a few attempts, I scrapped the idea. I wasn't happy with the interface, and I had no alternative design. I went on to do something else, and as I was looking at another problem, the solution hit me: The key is the first day of the current month. Everything else should flow from there.

This solution is in ruby (Ruby 1.8.7, no ActiveSupport), and it goes something like this:

def get_first_day_of_month(ref_date)
    day = ref_date.day()

    if day > 1
        ref_date -= day - 1
    end

    return ref_date
end


Now, with this, it's trivial to get the begin and end dates I actually need:

first_day = get_first_day_of_month(Date.today())


begin_date = first_day << 1 # subtract 1 month


end_date = first_day - 1 # subtract 1 day

Going back to my script-lib, I can now create an interface that can both return an array/hash with the two dates (usable as a module by other Ruby scripts), and output the dates to file/stdout, separated by a delimiter specified by the caller (usable by any script). Yes, it's a modest lib, but we all gotta start somewhere.

One thing I don't understand is the lack of modifier operators on date/time frameworks (see Java's Calendar class and Free Pascal's dateutils Recode* functions for two examples on how to do this right). It would've been a lot simpler to do something like this:

first_day = Date.today().day!(1)

Yes, I know, Ruby has open classes, I can add it myself. But just because I can, doesn't mean I should.