【第1073期】巧用匿名函数重构你的代码
来源:互联网 发布:做淘宝代销怎么样 编辑:程序博客网 时间:2024/05/24 07:34
前言
国庆七天,大家的朋友圈摄影比赛如何啊?今日早读文章由@Mapping分享。
正文从这开始~
匿名函数最早是LISP语言引入,后面发展为不仅是函数式语言所特有,在解释型语言和编译型语言中也越来越多地看到匿名函数的身影,它或许有个更潮的名字叫 lambda 表达式。
闭包多是用匿名函数实现,在匿名函数中引用了外部变量,那这个匿名函数就形成了闭包。由于闭包和匿名函数之间有着千丝万缕的关系,所以经常会把两者搞混淆。其实在 Js 中匿名函数、闭包、自执行函数、回调函数、箭头函数,这些概念似乎相同,却又不同,请读者朋友自行了解。
匿名函数拥有可动态编程的执行过程。巧妙使用可以让你的代码简约而不失优雅,灵活而不失约束。好了,正式切入本文的正题,巧用匿名函数重构代码。按照重构的惯例,先指出代码中的坏味(Bad Smell):
定义冗长的重复配置
条件多变的集合过滤
说一不二的方法调用
定义冗长的重复配置
在写配置代码时,经常会遇到大量重复的配置,如果要修改一些内容,所有的都要修改,着实很累,而且容易遗漏。比如:
{
html: `
<label><input type="checkbox" name="apple" /> Apple</label>
<label><input type="checkbox" name="banana" /> Banana</label>
<label><input type="checkbox" name="orange" /> Orange</label>
`
}
这里面有三个选项,三个选项的结构完全一样,如果要给所有选项加一个 title,那你就要把这件事重复做三遍。
{
html: `
<label><input type="checkbox" name="apple" title="Apple" /> Apple</label>
<label><input type="checkbox" name="banana" title="Banana" /> Banana</label>
<label><input type="checkbox" name="orange" title="Orange" /> Orange</label>
`
}
程序猿是个奇怪的群体,他们宁愿做五件不同的事,也不愿重复三次做同一件事。所以这怎么能忍,把配置中不同的内容提取出来:
{
html: (function(fruits) {
return fruits.map(it => `<label><input type="checkbox" name="${it.name}" title="${it.title}" /> ${it.title}</label>`).join('');
}([
{name: 'apple', title: 'Apple'},
{name: 'banana', title: 'Banana'},
{name: 'orange', title: 'Orange'}
]))
}
这样的修改对配置的调用方来说是完全透明的,但是对于配置的维护者来说,将结构和数据分离开,要改结构就改自执行方法体,要改数据就改传入自执行方法的参数,可以大大减少犯错的风险,又避免一些无脑的复制粘贴。
虽然在配置中写代码逻辑不是特别推荐的做法,但相对于代码可维护性来说这不算啥了。
条件多变的集合过滤
集合过滤是个非常常见的需求,假设有个学生集合:
let students = [
{name: 'Lucy', age: 20, sex: 'female'},
{name: 'LiLei', age: 21, sex: 'male'},
{name: 'Jim', age: 18, sex: 'male'}
]
现在要过滤出年龄 20 岁的同学:
function filterByAge(list, age) {
let filtered = [];
for (let i = 0; i < list.length; i++) {
if (list[i].age === age) {
filtered.push(list[i]);
}
}
return filtered;
}
又要过滤出姓名叫 LiLei 的同学:
function filterByName(list, name) {
let filtered = [];
for (let i = 0; i < list.length; i++) {
if (list[i].name === name) {
filtered.push(list[i]);
}
}
return filtered;
}
还要过滤出性别为男的同学:
function filterBySex(list, sex) {
let filtered = [];
for (let i = 0; i < list.length; i++) {
if (list[i].sex === sex) {
filtered.push(list[i]);
}
}
return filtered;
}
就在你觉得大功告成,可以站起弯伸腰的时刻,突然被一股强大的力量按回了座椅,帮我找出姓名以 “L” 开头的童鞋。虽然你内心是 mmp~,但还能怎么办,写啊,于是又吭哧吭哧加了如下方法:
function filterByNameStart(list, nameStart) {
let filtered = [];
for (let i = 0; i < list.length; i++) {
if (list[i].name.indexOf(nameStart) === 0) {
filtered.push(list[i]);
}
}
return filtered;
}
于是乎,这个调用方式:
filterByName(filterBySex(filterByAge(students, 21), 'male'), 'LiLei');
就可以理解为找出年龄为 21 岁的男性 LiLei。好吧,可读性还不算太差。
但是,不难看出以上 filterByAge、filterByNameStart 等这一系列方法中,除了过滤条件不同,其他逻辑完全一样,造成了大量代码的重复;并且没有任何灵活性可言,调用方改需求,你就要加方法。
我们现在就使用匿名函数把不同的部分抽出去,让调用方想怎么过滤就过滤。filter 方法的主干逻辑只关心当前的元素是不是要放入结果集合中,其他的判断逻辑都交给匿名函数 fn 去做:
function filter(list, fn) {
let filtered = [];
for (let i = 0; i < list.length; i++) {
if (fn(list[i], i) === true) {
filtered.push(list[i]);
}
}
return filtered;
}
上面的例子会变成这种写法:
filter(students, function(member) {
return member.name === 'LiLei' && member.age === 21 && member.sex === 'male';
});
使用箭头方法可以更简洁:
filter(students, member => member.name === 'LiLei' &&
member.age === 21 &&
member.sex === 'male');
现在调用方再提一些变态的过滤方式,你可以回一个眼神,自己写去。
说一不二的方法调用
假设有个 Api 接口,将传入的数字翻倍并返回,这个接口支持单个和批量的方式。
传入 5
执行成功返回 {status: 'success', data: 10}
执行失败返回 {status: 'failed', error: 'xxx'}
传入 [2, 3]
执行成功返回 {status: 'success', data: [{status: 'success', data: 4}, {status: 'success', data: 6}]}
执行失败返回 {status: 'success', data: [{status: 'failed', error: 'xxx'}, {status: 'failed', error: 'xxx'}]}
也就是单个输入会按单个格式输出,批量输入会按批量格式输出。三下五除二,实现了下面这个版本:
function multiple(inNum) {
if (Array.isArray(inNum)) {
// 处理批量情况
return {
status: 'success',
data: inNum.map(it => {
if (isNaN(parseFloat(it))) {
return {
status: 'failed',
error: 'The input is not a number'
};
}
return {
status: 'success',
data: it * 2
}
})
};
} else {
// 处理单个情况
if (isNaN(parseFloat(inNum))) {
return {
status: 'failed',
error: 'The input is not a number'
};
}
return {
status: 'success',
data: inNum * 2
};
}
}
这里面单个和批量两种方式除了输入和输出格式不同,其他逻辑完全一样。如果要将乘 2 改成乘 3,两个地方都要改。那看下怎么使用匿名函数来避免两处修改:
function execute(data, fn) {
// 最小执行单元
let single = it => {
try {
return {
status: 'success',
data: fn(it)
};
} catch (e) {
return {
status: 'failed',
error: e.toString()
}
}
};
if (Array.isArray(data)) {
return {
status: 'success',
data: data.map(single)
}
} else {
return single(data);
}
}
function multiple(inNum) {
return execute(inNum, it => {
if (isNaN(parseFloat(it))) {
throw new Error('The input is not a number');
}
return it * 2;
});
}
现在 execute 方法只管输入输出的格式和错误处理,包揽了所有脏活累活;multiple 方法则只关心业务的具体实现,也不用关心输入的是单个元素还是数组。如果要改乘 3,只要修改 multiple 方法最后一个 return。如此一来,execute 还可以被其他的 Api 方法复用,可谓一举两得。
总结
本文的目的只是抛砖引玉,代码中可利用匿名函数重构的坏味还有很多,这种重构方式不只是适用于 Js 中。大家只要多思考、多动手,代码质量一定会day day up~~
关于重构我还想多说一点,重构的过程应该是渐进的方式,当你改第一次的时候可能觉得还ok,第二次就要想下是不是有更好的方式来实现。如果修改对调用方透明那是最好了,实在不行让调用方配合修改也是值得的,当然这其中还要权衡时间成本。
最后,为你推荐:
提醒|国庆假期综合症大作战
【第1054期】高阶函数:利用Filter、Map和Reduce来编写更易维护的代码
关于本文
作者:@Mapping
原文:https://iammapping.com/the-good-things-of-fn/
- 【第1073期】巧用匿名函数重构你的代码
- 巧用匿名函数重构你的代码
- JavaScript代码重构系列-重新组织你的函数
- 匿名函数的代码模式
- 用VS.NET 2005重构你的代码
- 用VS.NET 2005重构你的代码
- 用Visual Studio 2005重构你的代码
- 如何正确重构你的代码
- 学会重构你的代码
- PHP 杂谈《重构-改善既有代码的设计》之一 重新组织你的函数
- PHP 杂谈《重构-改善既有代码的设计》之一 重新组织你的函数
- PHP:《重构-改善既有代码的设计》之一 重新组织你的函数
- 函数的重构、代码优化例子
- 四个代码让你明白委托,匿名函数,Lamda表达式
- 重构—改善既有代码的设计006:重新组织你的函数(Composing Methods)
- <<重构改善既有的代码设计>>第2章
- 重构读书笔记 第3章 代码的坏味道
- 匿名带你从零开始做四轴-第二期--四轴的硬件组成
- 【第1076期】 如何无痛降低 if else 面条代码复杂度
- 基于Hibernate架构的Web应用开发环境的搭建
- PAT乙级题1005.1005. 继续(3n+1)猜想
- 跟随 Google 工程师学习前端开发,是种怎样的体验?
- 【视频】如何用JavaScript做好一个大型应用
- 【第1073期】巧用匿名函数重构你的代码
- 【第1074期】写给前端应届生的职业规划建议
- window下响应键盘按键
- 【视频】从Cycle.js谈函数式与响应式编程
- 【Android】让Python在Android系统上飞一会儿
- Jmeter-Http接口测试过程及问题总结
- 谈谈HTTP协议中的短轮询、长轮询、长连接和短连接
- VIDE支持微信和支付宝小程序开发
- 一道题