Creating option objects for these methods, though, is a different story. The task is often simple and repetitive and it is often overlooked during the day- to-day development. It is common to see code like that spread over your program:
It is pretty clear that this method is performing a POST request to a well known
URL to search for users, optionally providing a name and an age to narrow the
search results. Authentication information is also provided in the
object to authenticate to the remote service.
There are different problems with the previous example:
optionobject is created and modified manually. This approach is sensitive to different kinds of error, like misspelling an option name.
- Cross-cutting concerns are repeated without any generalization. Setting the authentication data should be encapsulated in its own method. You don’t want to change the whole application if you later decide to change your authentication strategy.
- Conditionally adding parameters to the option object is tedious and fills the
method with a lot of
- The workflow to build an option object is not standardized, but shows small and subtle differences every time it is executed. This is what happens if you manually build an option object.
A first generalization
Tho solve the first problem you could encapsulate option assignment in different functions. Every function receives an option object and modifies it, as shown in the following code snippet.
This brings our original method to the next level.
This solves the very first problem in our bullet list. You avoided every
possible spelling mistake during the construction of the
Moreover, if an option parameter will change its name, you have to modify your
code in only one place.
You can apply an additional improvement to the previous code by eliminating the
if statements. It is as easy as writing a more specialized function to
change the option object.
This makes the function code shorter and clearer.
Stretch it a little bit more
Our function is in a better shape than before, but it still feels boring to write. Well, nobody said that your job should be funny, but it should at least by as easy as possible.
I assume that you have a lot of functions in your codebase very similar to the previous one. If they are so similar, there should be a way to generate them. The first step in this direction is to generalize the creation of an option object. At the moment this is not possible because each helper function you created has a different signature. While this can be good for you, it is surely a bad thing for your computer.
What if each helper function had the same semantic?
If every helper function was written like this, you could execute all of them in
order and pass the
options parameter from one function to the other, until all
of them are executed.
You can change your previous helper function to generate functions of this type, instead of directly accessing the options object. They will just create a closure for other functions which perform the actual work. Let’s give it a shot.
Note that I changed the logic of the
setAuth() function. This implementation
require an external method to provide the
auth parameter to be set in the
option object. In this way, you can throw out the user name and password from
the parameter list of your functions. By applying the previous modification, you
will end up with the code shown below.
Please note that the operations array is a sequence of functions, and each of them has the same signature: they all receive an option object in input and return an option object as output.
You use the
Array.reduce() method to invoke every function in order, and
incrementally construct the final
option object. As shown from the invocation
reduce(), the `option object is initially an empty object.
Less work for everyone
Given the improvements in the previous section, can you really generalize our function to generate it at runtime? In the end, a function like that is only a sequence of steps which must be performed to create an option object, and it always performs a call to the request() method with the the option object and a callback. Moreover, the callback is always the last parameter of the function.
The only information left in the original function, at the end of the transformation, will be this:
What you have to write is a generator which takes something like that in input
and generates a working function as output. Writing such a generator, as shown
below, is a very simple task which requires only a little bit of juggling with
arguments special variable.
createFullMethod() function creates a new function. The arguments of this
function will be evaluated dinamically, and the number of arguments can be
variable. Let’s go through the steps performed by this function:
- You convert the
argumentsspecial variable to a full array by using the
- You extract the
donecallback from the end of the arguments. This is a convention which enforces every function to have a callback as last parameter. The
argsvariable now contains the remaining parameters passed to the function.
- You call
partialMethodwith the rest of the arguments to obtain a list of operations.
reduce()the operations to have a full option object.
- You perform the request and pass the `done callback to be called when a response is received.
This final improvement means that you can generate other functions very similar to your original one with a minimum effort. Let’s see how you would now rewrite the original function and how we would use the same API to also create a different remote function.
If, while reading this article, you thought that this approach is overkill, your view was probably limited to the only function you refactored. The described approach is useful if you have plenty of functions doing almost the same thing with some small variations, because you have a common API to describe the parts these functions are composed from.
In particular, you should focus on how we represented a function as a list of operations. The moment you treat functions as first class objects which you can store, manipulate, and combine, your code will be automatically become simpler, your abstractions stronger and your flexibilty endless.