不要被题目骗了:D, 此文不讲概念(题目太大),只和大家分享一个很简单的案例, 权当抛砖引玉。
背景
最近在做一个项目,对接的API要求对response body进行签名,验签规则此处不赘述了,有一点比较好玩的事要求对嵌套的字段进行stringify再进行验签处理,有点类似于lodash的flatten, 不过不是处理array而是json object, 可能说到这里有点费解,看代码便知。
TALK IS CHEAP, SHOW ME THE CODE!
测试先行
上述的功能我要用一个叫flatten
的函数实现,test code如下,
const chai = require('chai'); |
可以看出主要变化就2点,
sign
字段要被过滤掉- 如果不是primitive的值,要做stringify
太简单了是不是?是不是primitive用一个isObject
的函数来判断,其他的for 加 if 就解决了,
function isObject(obj) { |
看! 测试也通过了,就这样结束了吗?
不。。。我不喜欢上面这个flatten方法,因为它不是immutable的,作为flatten的入参的这个object已被改的面目全非了, 比如在调用完flatten后你还想去找sign
的值,对不起它已经不存在了。对immutable不太理解的同学可以移步这篇博客。
如何改进
因为有测试代码在保护着功能代码,我们可以大胆重构,比如,function flatten(obj) {
const newObj = {}
Object.keys(obj).forEach((key) => {
if (key === 'sign') {
return
}
if (isObject(obj[key])) {
newObj[key] = JSON.stringify(obj[key])
} else {
newObj[key] = obj[key]
}
})
return newObj
}
好一些了,可是代码看上去好像变复杂了,如果有了解过lodash的话好像可以写的跟简洁一些,比如const _ = require('lodash')
function flatten(obj) {
const objWithoutSign = _.omit(obj, ['sign'])
return _.mapValues(objWithoutSign, (val) => {
return isObject(val) ? JSON.stringify(val) : val
});
}
大功告成?
不,这个flatten不够好,它没有复用性,比如我不需要过滤sign
字段,但是还要stringify怎么办?再拷贝一份_.mapValues(..)
处理?
轮到Ramda 登场,为什么要用Ramda此处还是不赘述了,官网有好几篇很好的文章,我这里只分享我是如何实现的。同样地,因为有测试代码,又可以放心重构, 此处思路要有所转变,以funtional programming的方式,我们不考虑有什么数据要处理,而是考虑有哪些处理要aggregate, 显然,
- 要过滤某一个字段 => R.omit
- key/value中的value要转变 => R.map
- value怎么转变? => stringify
const R = require('ramda')
const stringify = (val) => {
return isObject(val) ? JSON.stringify(val) : val
}
const flatten = R.compose(
R.map(stringify),
R.omit(['sign'])
);
可以看到,现在我们用R.compase
定义了flatten
,同时还有一个辅助的fucntion stringify
,
但是既然用了Function Programming的方式就尽量写的纯粹一些,stringify
可以改写成
const stringify = R.ifElse(isObject, JSON.stringify, R.identity); |
百密一疏
智者千虑必有一疏,上述这个代码,测试是跑不过的。。。output
是长得这幅样子,你看出来哪里有问题了吗?Object
bar: function()
foo: function()
qux: function()
quz: function()
都是Curry惹的祸
我们知道Ramda中所有的function都是curried化过的,详细解释移步此处, 所有的value都变成function, 看起来很像是partial application,即参数不够(没吃饱), 默默地再翻了一下MDN关于stringify的文档
JSON.stringify(value[, replacer[, space]])
呃,忘了stringify 是三个参数(虽然不常用), 此处需要R.unary 来帮忙,因为Ramda的 compose/pipe都是需要function只支持一个参数的。
Holy Grail版
const R = require('ramda'); |
其实isObject
也可以继续用Ramda改写,留给你当作业了 :)