Hi fellow programmers,
This article is kind of a nice (maybe) and pretty (i hope) exercise. But don't take it too seriously, no need to go into flamewars, take it as a fun exercise i did. Initially i wrote this as a comment in reaction to an article here that was deleted by it's author.
As a disclaimer, i love functional programming, object oriented programming, all kind of styles. They all have their pros/cons and beauty.
So let's begin and and imagine you have a beginner programmer writing this piece of code in javascript:
function daysUntil(day, month) {
const today = new Date();
const sameYear = new Date(today.getFullYear(), month - 1, day);
if (sameYear > today)
return Math.ceil((sameYear - today) / (1000 * 60 * 60 * 24));
else {
const nextYear = new Date(today.getFullYear() + 1, month - 1, day);
return Math.ceil((nextYear - today) / (1000 * 60 * 60 * 24));
}
}
console.log(daysUntil(1, 12));
Imagine you are this guy, doing this code maintenance :
Of course the first thing you see when taking ownership of the code is side effects (horror!). So let's quickly fix that mistake :
function daysUntil(day, month, today) {
const sameYear = new Date(today.getFullYear(), month - 1, day);
if (sameYear > today)
return Math.ceil((sameYear - today) / (1000 * 60 * 60 * 24));
else {
const nextYear = new Date(today.getFullYear() + 1, month - 1, day);
return Math.ceil((nextYear - today) / (1000 * 60 * 60 * 24));
}
}
const today = new Date(); // side effect
console.log(daysUntil(1, 12,today));
Better. Now we can start coding. Because, yes, we got rid of the side effect but come on, this code is not declarative enough. As a functional programming adept, i have to transform the code above into something as simple as this :
// fn(number,number) -> fn(date) -> number
const daysUntil = (day, month) => daysBetween(now, futureDateFrom(day, month));
const today = new Date(); // side effect
console.log(daysUntil(1, 12)(today));
I can read this code and understand what it does, because it reads like a book. I want the days between now and the first December. As simple as that.
So we all heard of, divide and conquer in programming and that's what we are aiming for. So we have in our dreams, a function called futureDateFrom
:
// fn(number,number) -> fn(date) -> date
const futureDateFrom = (day, month) => minUpperDate(sameYearOf(day, month), nextYearOf(day, month));
What does futureDateFrom
do? It computes the first date in the future given a day and a month. It's a high order function, meaning it's a function that returns a function, and we create this function by composing multiple functions together.
And then we have another function called daysBetween
:
// fn( fn(date)->date, fn(date)->date ) -> fn(date) -> number
const daysBetween = (getDateA, getDateB) => (date) =>
Math.ceil(Math.abs(getDateA(date) - getDateB(date)) / (1000 * 60 * 60 * 24));
It takes two function that take a date and return a date as parameters and return a function that take a date and computes the days between those dates.
We now see that now
is a function :
// fn(any) -> any
const now = (x) => x;
And it's the identity function, that returns whatever you give it as a parameter.
The rest of the functions are the following ones, and are simpler to understand, but still high order functions
// fn( fn(date)->date, fn(date)->date ) -> fn(date) -> date
const minUpperDate = (getDateA, getDateB) => (date) =>
minUpperValue(getDateA(date), getDateB(date))(date);
// fn(number,number) -> fn(date) -> date
const sameYearOf = (day, month) => (date) => dateFrom(day, month, year(date));
// fn(number,number) -> fn(date) -> date
const nextYearOf = (day, month) => (date) => dateFrom(day, month, year(date) + 1);
we see that the missing functions can be implemented like so :
// fn(comparable, comparable) -> fn(comparable) -> comparable
const minUpperValue = (a, b) => (pivot) => (a < pivot && pivot < b ? b : a);
// fn(date) -> date
const year = (date) => date.getFullYear();
// fn(number, number, number) -> date
const dateFrom = (day, month, year) => new Date(year, month - 1, day);
And it's finished. We defined all the functions. We see that we managed to turn a single function into 10 simpler ones. These function have the benefit of being reuseable independant bits of code. All these function can now be unit tested easily.
But it's undeniable that the produced code is harder to read for the non initiated.
Do i think that writing all this code is worth it? Divide and conquer always have proved to make things easier to grasp and test but at the cost of complexity.
So you have to get a feeling at when to stop dividing your code, because it can be a never ending task that is not worth it. And in my experience, it stops being worth it when the divided code is not reused or if the code you want to split can be understood by itself. So as a rule of thumb, always ask yourself if the code you are splitting will be reused or is complex enough to be splitted.
As an exercise, we let you imagine you are in the shoes of this guy and write in Object oriented style an over engineered piece of code :
Top comments (0)
Some comments have been hidden by the post's author - find out more