[reconstruction] a record reconstructed with ramda

Time:2022-6-16

preface

Several methods written recently seem to be a bit repeated after the event. If you want to refactor and just think of ramda, try ramda to refactor and see if you can reduce repetition.

Note: This refactoring is mainly an amateur pastime. The code used in this article is not the original text, but an example modeled on the characteristics of the source code. How to write code is related to the project environment, so this refactoring method is only for amateur entertainment and learning, and the results and methods are not recommended.

Pre refactoring code

The story goes like this. Suppose I have a student class:

function Student(name, score, age) {
  this.name = name;
  this.score = score;
  this.age = age;
}

Then there is an array of students, and you need to get the lowest score of these students. Then the student array and the method for finding the lowest score are as follows:

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
  );
}

Well, the code looks ok, at least I feel good about myself. The type judgment part can be omitted if typescript is used. It’s useless here. Just be simple and direct.

Well, of course, the story will not end here, otherwise there will be no reconstruction.
Next, I found that I still need to get the maximum age of students. Well, it’s very simple. Just copy and paste the above and change it a little? As follows:

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
  );
}

OK, the code is finished and works normally. End of work… Did you?
Looking back at the code, of the two methods, onlyMath.min\Math.maxandstudent.score\student.ageDifferent, everything else is the same. It’s notDRYAh.
It doesn’t look very good. Then it occurred to meramda

Can I use itramdaTo complete these two functions in an assembly way, so as to reduce repetition?

OK, just do it. (The following refactoring is based on personal experience and cognition. If there is something wrong, please point it out and discuss it together

Refactoring ing

Let’s review the code before Refactoring:

Click to view the code
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
  );
}

The two methods can be basically divided into the following steps:

  1. Type judgment: determine that the incoming parameter is an array of student class instances
  2. If the type is wrong, return undefined
  3. If the type is correct, traverse the array and return the minimum / maximum score/age attribute value

OK, let’s start with the last ramda document address:https://ramdajs.com/docs/#

Type judgment can be realized by the following methods:

const isInstanceof = (type) => (instance) => instance instanceof type;
const isStudent = isInstanceof(Student)
const allAreStudents = R.all(isStudent)

allAreStudents(students) => true
allAreStudents([1, 2, 3]) => false

The type judgment here is actually an if else, which can be used directlyramdaofifElseMethod. The following is the handling of type errors. The total is:

const whileNotStudent = () => undefined
const processIfStudent = R.ifElse(allAreStudents, R.__, whileNotStudent)

Those who are familiar with ramda must know,R.__It is a parameter placeholder, and almost all ramda methods are cored. When its function lacks enough parameters, it will still return a function after execution. Only when all the required parameters are obtained will the cored method be really executed.
So the above call will makeprocessIfStudentBecome a method that requires a parameter, which is passed toifElseCall.

That is, the following two expressions are equivalent:

Suppose we have a function called dosomething

// 1
processIfStudent(doSomething) 
// 2
R.ifElse(allAreStudents, doSomething, whileNotStudent)

At this time, the first layer of logic we need is satisfied:

Do XX when the passed in parameter is a student array, and return undefined when it is not
processIfStudentThis is the required parameterDo XXThe method.

Next, let’s abstract what we need to do.
Whether it is to obtain the maximum or minimum, score or age, the things we need to do can be abstracted into:

1. traverse the array
2. use a logic to record the results of each traversal until the traversal is completed
3. return this result

The logic is to getmaximumorminimumofA propertyYes.
And then we let thismaximumminimumandA propertyCan be customized.

Have a lookramdaI think I can use the following methods:

Prop = > used to get attributes
Minby/maxby = > compare the attributes of two objects and return smaller or larger objects
Reduce = > used to traverse the array
Compose = > used to combine methods. The details will be discussed later

OK, once the method is selected, it can be implemented.

First, according to my needs, I wrote the following methods:

//Get score attribute
const getScore = R.prop('score')
//Get age attribute
const getAge = R.prop('age')
//This function can return the smaller score of the two passed in parameters
const minByScore = R.minBy(getScore)
//This function can return the older of the two incoming parameters
const maxByAge = R.maxBy(getAge)
//This function is traversed by reduce, and the logic required for traversal and the traversed array can be customized
const reduceByHandler = handler => instances => R.reduce(
  handler,  
  instances[0],
  instances)
//You can get the array object with the lowest score in the passed in array
const reduceMinScoreStudent = reduceByHandler(minByScore)
//You can get the oldest array object in the passed in array
const reduceMaxAgeStudent = reduceByHandler(maxByAge)

In fact, it’s almost done here. Using the above two reduce methods, we can get the students with the smallest score and the oldest age. But what we need now is not the students, but the number. Therefore, we also need to use compose and prop to get the specific attributes of the obtained instances, namely:

const reduceMinScore = R.compose(getScore, reduceMinScoreStudent)
const reduceMaxAge = R.compose(getAge, reduceMaxAgeStudent)

Apply back to the previousprocessIfStudentMethod, we can get the method we finally need:

const getMinScore = processIfStudent(reduceMinScore)
const getMaxAge = processIfStudent(reduceMaxAge)

Well, here we finally get the getminscore and getmaxage methods we need.
Direct usestudentsCall once to get the result we want:

const minScore = getMinScore(students)
console.log("minScore", minScore) // 87
const maxAge = getMaxAge(students)
console.log("maxAge", maxAge)   // 20

The following is the complete code:

Click to view the code
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)

This is the end of the refactoring. Looking back, it seems that there are still many similar codes. For example, the methods of score and age appear in pairs, such asreduceMinScoreandreduceMaxAge
Yes, they are very similar. However, because they contain very little logic, they only call the same method, but the parameters are different, and the repetition is much less than before. Moreover, after fine-grained splitting, the readability will be better and debugging will be more convenient (functional programming is sometimes really difficult to debug).

last

The refactoring is over. Again, the refactoring method here is mainly for entertainment and learning. Whether you want to use it in the project depends on your own project situation. The above refactoring involves many small methods and similar methods. At first glance, it seems that the number of lines of code is not small. However, the higher the method is, the more independent and business independent it is, the easier it is to be reused.

The more similar requirements appear, the more times small and sophisticated methods are reused, and the greater the gap in the amount of code. The less logic we need to rewrite each time.

In addition, if there is a better reconstruction method above, you are also welcome to discuss and learn together.

Thanks for watching.


Original is not easy, please indicate the source for Reprint:https://www.cnblogs.com/bee0060/p/15704623.html
Author: bee0060
Published in: blog Park

Recommended Today

linux learning

Linux 1 Overview is an operating system The difference between linux and centos 7 is like the difference between windows and windows 7 On the server side, Linux is very popular in the development world The server uses the command line, and we also learn based on the command line 2. Environment construction 2.1 Download […]