Progressive enhancement starts with a little nugget of functionality and then enhances it in iterations. For example, lets create a function to add two numbers:
1 | function add(a, b) { return a + b } |
This one-liner is easy to write, code review and plug into production. It provides the little nugget of functionality, but soon people start demanding its enhancement with more features: logging, argument validation, etc. The typical enhancement proceeds by cramming all features into the function itself, tightly coupling everything together. Our beautiful addition becomes this convoluted code:
1 | function add(a, b) { |
Because of the tight coupling inside the function, replacing console.log
with
a dedicated logging library becomes a question of a major rewrite. Similarly, the validation
is now included into the add
function itself, which might be an overkill for every
function (see my blog post paranoid coding).
My typical rule of thumb to determine tight coupling is to say aloud what the function does and listen for conjunction AND:
add
logs its arguments AND validates them AND returns their sum
If I hear even a single AND, the function's purpose is muddled. More than a single AND makes a function or module a prime candidate for refactoring.
Tightly-coupled refactoring
I sometimes see the following tightly-coupled refactoring
1 | function add(a, b) { |
Each function has a clear goal, but they are very tightly coupled and each feature (logging, validation) cannot be reused with any other function at all!
Progressive enhancement via function composition
A much better approach in this case is to create new functions by combining the
original add
with separate reusable function that does logging,
then with function that does argument validation.
Multiple libraries provide this simple functionality, for example
underscore-contrib juxt
provides a left to right pipeline, calling each given function and returning results as an array.
With a little bit of argument and results massaging we can combine invidual functions
into the desired complete function with minimal coupling
1 | // each function is completely independent |
The functions _log
, _numbers
could be made even more reusable now
(by handling varied number of arguments for example).
The only place where the individual functions are coupled is in the // 1 line
that creates the final add
function.
Still, this approach has its limitations:
- the product
add
has lost all sense of its individual functions. For example, we cannot change the order of logging and validation, or get to the original_add
. - we had to write additional code just to get arguments and results in the right order.
Progressive enhancement by adding flavours
Reginald Braithwaite in his new JavaScript Spessore book (due 2014) proposes a tiny flavourize function that adds before and after decorators to a given function. The decorators are kept as the properties of the original function (functions are objects too and you can add properties to them).
The same addition logic using flavours:
1 | var add = flavourize(function (a, b) { return a + b; }); |
The syntax unshift
(and its mirror push
that adds a flavour to run AFTER function)
are parallel to methods Array.unshift
and Array.push
that add new items to
Array's start and end respectively.
Decorating via source rewriting
There is one more way to add functional decorators without modifying add
-
rewrite its source on file load (via Nodejs require
hook for example).
The transformation looks for function add
and inserts extra statements
before evaluating / compiling the JavaScript. See examples of source
rewriting using abstract syntax trees (AST),
esprima and
falafel in this
blog post
by Toby Ho. This is definitely a very very extreme way of adding functionality to
a piece of code, and I would recommend against it due to its complexity
and obfuscation.
Conclusion
Imagine the future: tiny functions with clear purpose, combined in multiple ways to quickly achieve the desired functionality. Easy to use, simple to test, pleasing to look at. We just need to find ways to combine the functions without making the combination process itself a source of problems. flavourize seems to be a useful little function I am adding to my utility belt.