JavaScript:使用函数式编程编写更简洁的代码
作为一名全栈 Web 开发人员,我花了很多时间编写和阅读 JavaScript 代码,这些代码经常写得很差,以至于我需要花费比预期更多的时间才能理解。
确实,当我们需要在基于 JS 的项目中重构一些遗留的、无人维护的代码片段时,会感到非常沮丧,因为它们缺乏JSDocs,变量声明模式混杂const, let, var,函数声明从 `<std::function>` 到function f() {}` <std::function>`var f = function() {}或 ` <std::function const f = () => {}>`,更重要的是,模块中的所有代码都包含在单个函数体中。
我们来看一下下面的代码:
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);
});
在上面的例子中,我们尝试使用DEV API请求带有“javascript”和“node”标签的文章;请求成功了。那么,问题出在哪里呢?嗯,随着“完成”的定义不断变化,如今衡量我们完成工作的能力,不仅在于能否让工作正常运行,还在于工作内容是否易读、有意义且易于维护。
虽然我们可以使用代码注释或 JSDoc 来解释代码中每一行的作用,但我们更应该考虑利用函数式编程语言的强大功能。由于我们可以抽象所使用的函数,因此我们也可以使用一种自描述性的词汇来命名它们。这样,我们就可以只为需要导出的函数编写文档。
让我们按照以下步骤重构我们的 API 调用:
- 优化代码通常需要使用最新的语言特性。虽然我们可能并不了解所有特性,但到目前为止,所有 JavaScript 开发者都应该了解 ES6 中引入的特性。因此,作为第一步,我认为我们应该删除
var代码中的所有声明,例如,这些声明可以与 `.` 互换const。
const fetch = require('node-fetch'); // <-
function articles () {
const arr = []; // <-
...
}
articles().then(function(d) {
console.log(d);
});
- 有些人会同意,有些人不会,但我认为编程初期最难的事情之一就是给函数命名。然而,这却是我们工作的重要组成部分。比如,我们的主函数被命名为 `main`
articles,这是什么意思呢?这个名字毫无意义,因为它没有表达任何动作(动词),无法告诉我们它具体做什么。我认为我们应该能为这个函数找到一个更好的名字,因为我们已经知道它应该做什么。
...
function fetchDevArticles () {
...
}
fetchDevArticles().then(function(d) {
console.log(d);
});
新名称看似合适,但并不准确。如果我们想根据这个函数的实际功能来命名,代码会变得非常冗长,难以阅读。例如,这样的代码fetchDevArticlesAndFilterThemByJavascriptAndNodejsTags肯定难以理解。
- 由于主函数负责同步执行多项任务,因此函数和变量的命名会成为一个问题。在函数式编程中,我们可以为函数取一个与其具体行为相关的名称。这样,我们就可以将主函数拆分成多个描述自身功能的子函数。
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);
});
太棒了!我们的代码看起来更简洁了,而且没有添加代码注释或 JSDoc。不过,代码仍然存在一些问题。正如你所看到的,我使用了一个模块数组变量,仅仅是为了过滤另一个数组并返回结果。
- 虽然目前这种方法可行,但如果我们能找到更好的数组方法来帮助我们,代码可以变得更加简洁。
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);
});
这差别太大了!我用了几个简单的数组方法来减少代码行数。此外,我还使用了箭头函数,因为它允许我们编写单行辅助函数。
现在我们的代码可读性大大提高,因为我给每个函数都起了与其功能完全相符的名字。但还有更多工作要做。
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);
这次我通过将所有回调函数都改成一行箭头函数来简化代码,避免使用花括号和 return 语句。我觉得这样已经很好了,但有了这些技巧,你应该会更有动力去进一步精简代码,至少我是这么希望的。
结论
作为 JavaScript 开发者,我们需要了解函数式编程这种编程范式,以便编写简洁的代码。编写出完美的代码并不可怕,尤其对于初学者来说,更应该有机会从错误中学习成长。但你应该尽力做到最好,同时也要记住,代码总有可以改进的地方。
总结如下:
- ES6很重要。
- 或许可以用数组方法来实现你想要的功能。
- 如果没有,试试lodash :)
- 代码注释并非提高代码可读性的必要条件。
- 力争做到最好。