As a full-stack web developer, I've spent a lot of time writing and reading JavaScript code, often so poorly written that takes me more time to understand than it should.
It is, indeed, frustrating when we need to refactor some legacy and unmaintained code pieces in our JS based projects, but they lack JSDocs, it has a mixed variable declaration pattern of const, let, var
, function declarations go from function f() {}
to var f = function() {}
or const f = () => {}
, and more importantly, all code in a module is contained in a single function body.
Let's take a look at the following code:
var fetch = require('node-fetch'); // if using NodeJS
function articles () {
var arr = [];
return fetch('https://dev.to/api/articles').then(function(a) {
if (a) {
return a.json().then(function(b) {
if (b) {
b.forEach((c) => {
if (c.tag_list.indexOf('javascript') !== -1 && c.tag_list.indexOf('node') !== -1) {
arr.push(c);
}
});
return arr;
}
});
}
});
}
articles().then(function(d) {
console.log(d);
});
In the above example, we're trying to request 'javascript' and 'node' tagged articles using the DEV API; it works. So, what's the problem? Well, as the definition of "Done" changes significantly over time, nowadays our ability to get things done is not only measured by getting things to work, they should also be readable, meaningful and maintainable.
Although we could use code comments or JSDocs to explain what each line in this code is doing, we should rather think of leveraging the power of a functional programming language. Since we can abstract the functions used, we can also name them using a vocabulary such that the code is self-descriptive. This way, we can save the docs for to-be-exported functions only.
Let's try to refactor our API call by following these steps:
- Optimizing code often involves using the latest language available features. We might not know all of them, though, but by this time all JavaScript developers should know the features introduced in ES6. So, as a first step, I guess we should kill all
var
declarations in our code, for this example, these declarations can be interchanged withconst
.
const fetch = require('node-fetch'); // <-
function articles () {
const arr = []; // <-
...
}
articles().then(function(d) {
console.log(d);
});
- Some of you will agree, some won't, but I think that something that results really difficult, at first, in programming is naming things appropriately. However, that's an important part of our job. We have our main function named as
articles
, what does it mean? It doesn't make sense because this function name doesn't express any action (verb) that tells us what it is doing. I think we should be able to find a better name for this function, because we already know what we're expecting from it.
...
function fetchDevArticles () {
...
}
fetchDevArticles().then(function(d) {
console.log(d);
});
The new name seems to be appropriate, but not accurate. If we'd want to name this function for what it is exactly doing, it'd gain so much verbosity that it'd be rather annoying to read. For example, fetchDevArticlesAndFilterThemByJavascriptAndNodejsTags
would definitely be hard to read.
- Our function and variable naming gets to be a problem because the main function is responsible for synchronously doing multiple things. In functional programming, we're able to give a function a name related to its exact behavior. So, we can split the main function into multiple smaller functions that describe themselves.
const fetch = require('node-fetch'); // if using NodeJS
const arr = [];
function pushFilteredArticlesToAuxArray (c) {
if (
c.tag_list.indexOf('javascript') !== -1
&& c.tag_list.indexOf('node') !== -1
) {
arr.push(c);
}
}
function filterAndReturnValues (b) {
if (b) {
b.forEach(pushFilteredArticlesToAuxArray);
return arr;
}
}
function fetchJSDevArticles () {
return fetch('https://dev.to/api/articles').then(function(a) {
if (a) {
return a.json().then(filterAndReturnValues);
}
});
}
fetchJSDevArticles().then(function(d) {
console.log(d);
});
Great! We've got our code better looking without adding code comments nor JSDocs. However there's still some issues with the code. As you can see, I'm using a module array variable just to filter another array and return the output.
- Despite this works for now, the code can get much more simpler if we can find better array methods to help us.
const fetch = require('node-fetch');
const tagsToFilter = ['javascript', 'node'];
const isIncludedIn = (arr) => tag => arr.includes(tag);
const byTags = (tags) => (article) => tags.every(isIncludedIn(article.tag_list));
const filterAndReturnValues = (articles) => articles.filter(byTags(tagsToFilter));
function fetchJSDevArticles () {
return fetch('https://dev.to/api/articles').then(function(a) {
if (a) {
return a.json().then(filterAndReturnValues);
}
});
}
fetchJSDevArticles().then(function(d) {
console.log(d);
});
That's a huge difference! I used a couple simple array methods to reduce the amount of lines in our code. Moreover, I'm using arrow functions because it allows us to write one-line helper functions.
Our code is now literally more readable because I named every function for exactly what it is doing. But there's still more to do.
const fetch = require('node-fetch');
const tagsToFilter = ['javascript', 'node'];
const devArticlesApiURL = 'https://dev.to/api/articles';
const isIncludedIn = (arr) => tag => arr.includes(tag);
const byTags = (tags) => (article) => tags.every(isIncludedIn(article.tag_list));
const filterAndReturnValues = (articles) => articles.filter(byTags(tagsToFilter));
const fetchJSDevArticles = () =>
fetch(devArticlesApiURL)
.then(response => response.json())
.then(filterAndReturnValues)
.catch(console.log);
fetchJSDevArticles().then(console.log);
This time I reduced the code by turning all callbacks into one-line arrow functions, avoiding curly braces and the return statement. To me, this looks good already, but given these tips you'll be motivated to try to reduce the code even more, at least I hope so.
Conclusion
Functional Programming is a paradigm that we need to be aware of as JavaScript developers in order to write clean code. It's just fine not to write perfect code, especially if you're a beginner developer, you need to have the chance to grow from your mistakes. But you should try to do it as best you can, having in mind that there's always something that can be improved.
As wrap-up notes:
- ES6 is important.
- There might be an array method for what you're trying to do.
- If there's not, try lodash :)
- Code comments isn't always what you need to make your code more readable.
- Strive for the best.
Top comments (3)
May I suggest also creating more "generic" helper functions. Not sure how readable this is but I'd like to show how far you can get using general purpose functions.
Forgive my use of
var
, I was testing this in the console.Hey, what you've done here is really cool. I definitely think this pattern is valuable, especially when your intention is to have "similar" functions in your projects. Thanks!
BTW: Most modern browsers support the newest JavaScript versions, so you should be able to run ES6+ syntaxes on the console.
The problem is not the browser, is me. I made a lot of mistakes while making that snippet, so I had to run it multiple times. If I use
const
I would have to keep changing the variable names after each attempt, 'causeconst
doesn't let me redeclare variables.