[重构]一次用ramda重构的记录
前言
最近写的几个方法,事后看起来觉得有点重复了。想要重构试试,正好想起ramda,那就试试用ramda来重构,看下能否减少重复吧。
成都创新互联长期为上千多家客户提供的网站建设服务,团队从业经验10年,关注不同地域、不同群体,并针对不同对象提供差异化的产品和服务;打造开放共赢平台,与合作伙伴共同营造健康的互联网生态环境。为延吉企业提供专业的网站设计、成都网站建设,延吉网站改版等技术服务。拥有十年丰富建站经验和众多成功案例,为您定制开发。
注: 这次重构主要为业余消遣,本文用到的代码也不是原文, 只是仿照源代码的特点写的示例。如何编写代码跟项目所处环境有关,所以本次重构方法仅为业余娱乐和学习,不对结果和方法作推荐。
重构前代码
故事是这样的,假设我有一个学生类:
function Student(name, score, age) {
this.name = name;
this.score = score;
this.age = age;
}
然后有一个学生数组,并需要得到这些学生中的最低分。 那么学生数组和求最低分的方法如下:
var students = [
new Student('Peter', 90, 18),
new Student('Linda', 92, 17),
new Student('Joe', 87, 19),
new Student('Sue', 91.5, 20),
]
function getMinScore(students) {
if (
!Array.isArray(students) ||
!students.every(student => student instanceof Student)
) {
return undefined;
}
return students.reduce(
(acc, student) => Math.min(acc, student.score),
Number.MAX_SAFE_INTEGER
);
}
嗯, 这代码看着还行,至少自我感觉良好。 其中对类型判断的部分,用typescript的话可以免去,咱这里没用,就简单直接点。
好,故事当然不会到这里就结束,否则就没啥重构的了。
接下来,发现我还需要获得学生中的最大年龄是多少。 好吧,很简单啊,复制粘贴上面的再稍微改一下不就有了吗? 如下:
function getMaxAge(students) {
if (
!Array.isArray(students) ||
!students.every(student => student instanceof Student)
) {
return undefined;
}
return students.reduce(
(acc, student) => Math.max(acc, student.age),
Number.MAX_SAFE_INTEGER
);
}
好了,代码写完,工作正常。收工。。。了吗?
回头看了看代码,这两个方法中,其实只有Math.min\Math.max
和student.score\student.age
不同,其他都一样。这很不DRY
啊。
看着不是很爽。 这时我想到了ramda
。
能否用ramda
,用组装的方式来完成这两个函数,以此减少重复呢?
好,说干就干。(下列重构基于个人经验和认知,不对的地方欢迎指出共同讨论
)
重构ING
先来重温下重构前的代码:
点击查看代码
function getMinScore(students) {
if (
!Array.isArray(students) ||
!students.every(student => student instanceof Student)
) {
return undefined;
}
return students.reduce(
(acc, student) => Math.min(acc, student.score),
Number.MAX_SAFE_INTEGER
);
}
function getMaxAge(students) {
if (
!Array.isArray(students) ||
!students.every(student => student instanceof Student)
) {
return undefined;
}
return students.reduce(
(acc, student) => Math.max(acc, student.age),
Number.MAX_SAFE_INTEGER
);
}
这两个方法做的事情基本可以拆分成以下步骤:
- 类型判断,确定传入参数是Student类实例的数组
- 若类型错误,返回undefined
- 若类型正确,遍历数组,并返回最小/最大的score/age属性值
好,先上个ramda的文档地址: https://ramdajs.com/docs/#
类型判断可以用以下方法实现:
const isInstanceof = (type) => (instance) => instance instanceof type;
const isStudent = isInstanceof(Student)
const allAreStudents = R.all(isStudent)
allAreStudents(students) => true
allAreStudents([1, 2, 3]) => false
这里的类型判断,其实就是一个if else, 可以直接使用ramda
的ifElse
方法,下面再写上类型错误时的处理,加起来就是:
const whileNotStudent = () => undefined
const processIfStudent = R.ifElse(allAreStudents, R.__, whileNotStudent)
熟悉ramda的同学肯定知道了,R.__
是一个参数占位符,而ramda的方法几乎都经过柯里化处理,当它的函数缺少足够的参数时,执行后依然会返回一个函数,当获得全部所需参数时,才会真的执行被柯里化的方法。
所以上面这么调用,会让processIfStudent
成为一个需要一个参数的方法,这个参数会被传递给ifElse
调用。
即下面两种写法等价:
假设我们有一个叫doSomething的函数
// 1
processIfStudent(doSomething)
// 2
R.ifElse(allAreStudents, doSomething, whileNotStudent)
这时我们需要的第一层逻辑就满足了:
传入参数是学生数组时做XX, 不是时返回undefined
。
processIfStudent
需要的参数,就是这个做XX
的方法了。
接下来,我们先抽象一下我们需要做的事情。
无论是获取最大还是最小, score还是age,我们要做的事情都可以抽象成:
1. 遍历数组
2. 用一个逻辑记录下每次遍历的结果,直至遍历完成
3. 返回这个结果
这个逻辑就是获取最大
或最小
的某属性
了。
然后我们让这个最大
,最小
和某属性
都可以被定制。
看了下ramda
的文档,我觉得我可以使用以下这些方法:
prop => 用于获取属性
minBy/maxBy => 通过两个对象的某属性进行比较并返回较小或较大的对象
reduce => 用于遍历数组
compose => 用于组合方法,具体的后面一点再说
好了,方法选好了,就可以开始实施了。
先根据我的需求,写了以下几个方法:
// 获取分数属性
const getScore = R.prop('score')
// 获取年龄属性
const getAge = R.prop('age')
// 该函数可以返回2个传入参数中分数较小的那个
const minByScore = R.minBy(getScore)
// 该函数可以返回2个传入参数中年龄较大的那个
const maxByAge = R.maxBy(getAge)
// 该函数用reduce进行遍历,可自定义遍历所需的逻辑,和被遍历的数组
const reduceByHandler = handler => instances => R.reduce(
handler,
instances[0],
instances)
// 可以获取传入的数组中,分数最小的数组对象
const reduceMinScoreStudent = reduceByHandler(minByScore)
// 可以获取传入的数组中,年龄最大的数组对象
const reduceMaxAgeStudent = reduceByHandler(maxByAge)
到这里其实已经差不多了,使用上面的两个reduce方法,可以得到分数最小和年龄最大的学生,但是我们现在需要的不是学生,而是那个数字,所以还需要用compose和prop来获取得到的实例的具体属性,即:
const reduceMinScore = R.compose(getScore, reduceMinScoreStudent)
const reduceMaxAge = R.compose(getAge, reduceMaxAgeStudent)
再套用回前面的processIfStudent
方法,就能得到我们最终需要的方法了:
const getMinScore = processIfStudent(reduceMinScore)
const getMaxAge = processIfStudent(reduceMaxAge)
好了, 到这里, 我们终于得到我们需要的getMinScore和getMaxAge方法了。
直接用students
调用一下,就能得到我们要的结果了:
const minScore = getMinScore(students)
console.log("minScore", minScore) // 87
const maxAge = getMaxAge(students)
console.log("maxAge", maxAge) // 20
以下是完整代码:
点击查看代码
function Student(name, score, age) {
this.name = name;
this.score = score;
this.age = age;
}
var students = [
new Student('Peter', 90, 18),
new Student('Linda', 92, 17),
new Student('Joe', 87, 19),
new Student('Sue', 91.5, 20),
]
const isInstanceof = (type) => (instance) => instance instanceof type;
const isStudent = isInstanceof(Student)
const allAreStudents = R.all(isStudent)
const whileNotStudent = () => undefined
const processIfStudent = R.ifElse(allAreStudents, R.__, whileNotStudent)
const getScore = R.prop('score')
const getAge = R.prop('age')
const minByScore = R.minBy(getScore)
const maxByAge = R.maxBy(getAge)
const reduceByHandler = handler => instances => R.reduce(
handler,
instances[0],
instances)
const reduceMinScoreStudent = reduceByHandler(minByScore)
const reduceMaxAgeStudent = reduceByHandler(maxByAge)
const reduceMinScore = R.compose(getScore, reduceMinScoreStudent)`
const reduceMaxAge = R.compose(getAge, reduceMaxAgeStudent)
const getMinScore = processIfStudent(reduceMinScore)
const getMaxAge = processIfStudent(reduceMaxAge)
const minScore = getMinScore(students)
console.log("minScore", minScore)
const maxAge = getMaxAge(students)
console.log("maxAge", maxAge)
重构到此结束了。 回头看看,好像还是有很多类似的代码,例如score和age的方法都是成对出现,如reduceMinScore
和reduceMaxAge
。
是的,这些都很相似。但因为本身他们包含的逻辑很少,只是调用了相同的方法,但参数都不同,重复已经比之前少多了。 而且细粒度的拆分后,可读性会好一点,也更方便调试(函数式编程有时真的挺难调试的)。
最后
重构结束了。 再次声明,这里的重构方法主要为娱乐和学习,大家是否要在项目中使用,还要根据自身项目情况而定。 上面的重构涉及很多小的方法和类似的方法,乍看起来好像代码行数没有少多少,但是,因为越靠上的方法,越独立且业务无关,也就越是容易被复用。
当类似需求出现得越多, 小而精得方法被复用次数越多,代码量的差距也会越大。我们每次需要重新写的逻辑也就越少。
另外,如果上面哪里有更好的重构方法,也欢迎提出共同探讨学习。
谢谢观看。
原创不易,转载请注明出处: https://www.cnblogs.com/bee0060/p/.html
作者: bee0060
发布于: 博客园
网站标题:[重构]一次用ramda重构的记录
文章出自:http://ybzwz.com/article/dsopooh.html