Refactoring is not a complete repudiation of previous code, but a better way to write better, more maintainable code. Continuous pursuit and learning, there is more progress.

1. Introduction

Do front-end development for a period of time, in this period of time, for their own requirements, not only the project can be completed, the normal use of this level. I’m also trying to figure out how to write elegant code, better performance, more maintainable code, and more generally, refactoring. This article is a small record for me to share here. This article is mainly aimed at the introduction, the example is simple, in-depth complex example and so on later have the appropriate example to write to share. If you have any ideas on how to write elegant code, maintainable code, or refactoring skills, please comment.

I am going to write a series of articles on refactoring, which will be updated from time to time, focusing on the following solutions: logic clutter refactoring, separation of responsibilities refactoring, adding extensibility refactoring, simplifying usage refactoring, and code reuse refactoring. It will be interspersed with the following principles: the single responsibility principle, the least knowledge principle, and the open-close principle. If you have any good ideas or examples of refactoring, please leave a comment with your valuable suggestions.

2. What is refactoring

First, refactoring is not rewriting. Refactoring basically means changing the internal structure of a project using a series of refactorings without affecting the functional use of the project. Improve readability and maintainability within the project.

Whatever the project, there is an iterative process from simple to complex. In this process, without affecting the use of the project, the code needs to be continuously optimized to maintain or increase the readability and maintainability of the code. In this way, you can avoid the need for a lot of communication on the team development. In order to join the development of the project.

3. Why refactor

Wash your clothes when they’re dirty, mend them when they’re torn, throw them away when they don’t fit.

As business needs increase, change, or abandon, the project code will inevitably have defects, which will affect the readability, maintainability, and even the performance of the project. The purpose of refactoring is to resolve these flaws and ensure code quality and performance. But the premise is not to affect the use of the project.

As for the reasons for refactoring, I have summarized the following points

  1. The logical structure of the function is messy or uncommented, making it difficult for the source code writer to make sense of the logic.
  2. Function has no extensibility at all, encounter new changes, can not be flexible processing.
  3. Because of the strong coupling of objects or the business logic, the code of the business logic is huge, which makes maintenance difficult.
  4. Too much duplicated code, no reusability.
  5. As the technology evolves, the code may also need to be modified with new features.
  6. As you learn further, whether there is a better solution to the previous code.
  7. Because of the code writing method, although the function is normal, but the performance consumption is more, need to change the scheme for optimization

4. When to refactor

At the right time, at the right thing

In my understanding, refactoring can be said to run through the entire development and maintenance cycle of a project, and can be considered as part of the development. In layman’s terms, any time during development, you see code that is uncomfortable and triggers obsessive behavior, you should consider refactoring. Just consider the following points before refactoring.

  • First, refactoring is something that takes time. It may take more time than previous development.
  • Second, refactoring is to optimize the code as long as it does not affect the use of the project.
  • Finally, refactoring can vary in difficulty, from a slight change to more difficult than the previous development.

Based on the above points, you need to evaluate whether to refactor. Evaluation indicators, you can refer to the following points

  • Quantity: Whether there is too much code to refactor.
  • Quality: do issues such as readability, maintainability, logical complexity of the code affect the quality of the code to an unbearable degree?
  • Time: Will there be enough time to refactor and test?
  • Effects: If you refactor your code, what improvements you get, such as better code quality, better performance, better support for subsequent features, etc.

5. How to refactor

Select a target and attack it

How do you refactor? So this is the case, so we’ll see. As in “Why refactor”. Find out what’s wrong with your code and make improvements to it.

Refactoring is also about writing code, but it’s not just about writing, it’s about organizing and optimizing. If writing code is a learning-knowing-proficient process, refactoring is a learning-understanding-breaking-proficient process.

For the refactoring case, here are a few simple examples

5-1. Functions are not extensible

Take the following example from one of my libraries’ apis

// Check the string
//checkType('165226226326','mobile')
/ / result: false
let checkType=function(str, type) {
    switch (type) {
        case 'email':
            return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str);
        case 'mobile':
            return / ^ 1 [3 4 5 7 | | | | 8] [0-9] {9} $/.test(str);
        case 'tel':
            return / ^ (0 \ d {2, 3} - \ d {7, 8}) (\ d {1, 4})? $/.test(str);
        case 'number':
            return $/ / ^ [0-9].test(str);
        case 'english':
            return /^[a-zA-Z]+$/.test(str);
        case 'text':
            return /^\w+$/.test(str);
        case 'chinese':
            return /^[\u4E00-\u9FA5]+$/.test(str);
        case 'lower':
            return /^[a-z]+$/.test(str);
        case 'upper':
            return /^[A-Z]+$/.test(str);
        default:
            return true; }}Copy the code

The API looks fine, and can detect some common data. But there are two problems.

1. But what if you think of adding other rules? I have to add case to the function. Add a rule and change it once! This violates the open-close principle (open for extension, closed for modification). It also makes the entire API bloated and difficult to maintain.

2. Another problem is that, for example, page A needs to add A check of amount, and page B needs A check of date, but the check of amount is only required on page A, and the check of date is only required on page B. If I keep adding cases. It causes page A to add the verification rules needed only in page B, resulting in unnecessary overhead. Same thing for page B.

The suggested approach is to add an extended interface to the API

let checkType=(function(){
    let rules={
        email(str) {return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str);
        },
        mobile(str) {return/ ^1[3|4|5|7|8] [09 -] {9}$/.test(str);
        },
        tel(str) {return/ ^ (0\d{2.3}-\d{7.8})(-\d{1.4})? $/.test(str);
        },
        number(str) {return/ ^ [09 -]$/.test(str);
        },
        english(str) {return /^[a-zA-Z]+$/.test(str);
        },
        text(str) {return /^\w+$/.test(str);
        },
        chinese(str) {return /^[\u4E00-\u9FA5]+$/.test(str);
        },
        lower(str) {return /^[a-z]+$/.test(str);
        },
        upper(str) {return /^[A-Z]+$/.test(str); }};// Expose the interface
    return {
        / / check
        check(str, type){
            returnrules[type]? rules[type](str) :false;
        },
        // Add the ruleaddRule(type,fn){ rules[type]=fn; }}}) ();// Call mode
// Use the mobile validation rule
console.log(checkType.check('188170239'.'mobile'));
// Add the value verification rule
checkType.addRule('money',function (str) {
    return/ ^ [09 -] + (. [09 -] {2})? $/.test(str)});// Use the amount verification rule
console.log(checkType.check('18.36'.'money'));Copy the code

The above code is a little bit more, but it’s easy to understand and extensible.

The above improvement is actually made using the strategy pattern, which encapsulates a set of algorithms so that the algorithm code and the logic code can be independent of each other without affecting the use of the algorithm. The concept of a policy pattern is a little confusing, but if you look at the code, it should be.

I’m going to expand on that a little bit, and in terms of functionality, by refactoring, I’m going to extend the function, and I’m going to do that. But if the above checkType is an open source project API, the call before refactoring would be: checkType(‘165226226326′,’phone’). Checktype.check (‘188170239′,’phone’); Or checkType. AddRule (); . If the author of the open source project refactored in this way, the developer who used the checkType API of the open source project would be in trouble as soon as the developer updated the version of the project. Because the refactoring above does not do backward compatibility.

If you want to be backward compatible, it’s not hard. Just add a judgment.

let checkType=(function(){
    let rules={
        email(str){
            return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str);
        },
        mobile(str){
            return / ^ 1 [3 4 5 7 | | | | 8] [0-9] {9} $/.test(str);
        },
        tel(str){
            return / ^ (0 \ d {2, 3} - \ d {7, 8}) (\ d {1, 4})? $/.test(str);
        },
        number(str){
            return $/ / ^ [0-9].test(str);
        },
        english(str){
            return /^[a-zA-Z]+$/.test(str);
        },
        text(str){
            return /^\w+$/.test(str);
        },
        chinese(str){
            return /^[\u4E00-\u9FA5]+$/.test(str);
        },
        lower(str){
            return /^[a-z]+$/.test(str);
        },
        upper(str){
            return /^[A-Z]+$/.test(str); }};// Expose the interface
    return function (str,type){
        // If type is a function, extend rules, otherwise verify the data
        if(type.constructor===Function){
            rules[str]=type;
        }
        else{
            return rules[type]? rules[type](str):false; }}}) ();console.log(checkType('188170239'.'mobile'));

checkType('money'.function (str) {
    return / ^ [0-9] + (. [0-9] {2})? $/.test(str)
});
// Use the amount verification rule
console.log(checkType('18.36'.'money'));Copy the code

This works fine, and it’s extendable, but it’s not elegant for neat code freaks. Because checkType violates the single function rule. Taking on too much responsibility for a function can lead to incalculable problems later on and confusing usage.

In this case, the best way to do it personally is to leave the checkType unchanged, add a new API to the project, such as checkTypOfString, and write the refactored code to checkTypOfString. Guide developers to use checkTypOfString instead of old checkType in various ways. In subsequent project iterations, discard the checkType when appropriate.

5-2. Functions violate the singleness rule

One of the biggest consequences of a function violating the single rule is that it can lead to logic confusion. If a function takes on too much responsibility, try the single function rule — a function that only does one thing.

The following example

// There is a batch of input student information, but there is duplication of data, the data need to be duplicated. And then, if it's empty, make it secret.
let students=[
    {
        id:1, name:'wait'.sex:'male'.age:' '}, {id:2, name:'Wandering the world'.sex:'male'.age:' '
    },
    {
        id:1, name:'wait'.sex:' '.age:' '
    },
    {
        id:3, name:'the swan goose can'.sex:' '.age:'20'}];function handle(arr) {
    // Deduplicate the array
    let _arr=[],_arrIds=[];
    for(let i=0; i<arr.length; i++){if(_arrIds.indexOf(arr[i].id)===- 1){ _arrIds.push(arr[i].id); _arr.push(arr[i]); }}// Iterate over the substitution
    _arr.map(item=>{
        for(let key in item){
            if(item[key]===' '){
                item[key]='secret'; }}});return _arr;
}
console.log(handle(students))Copy the code



The result of the operation is no problem, but you think about it, if later, if you change the requirements, for example, student information will not be repeated records, the requirement to remove the function to delete. So you’re going to have to change the whole function. It also affects the following operational processes. Equivalent to the change of demand, the whole method kneel. Fire brings disaster to the city.

Let’s construct it using a single principle

letHandle ={removeRepeat(arr){// Deduplicate arrayslet _arr=[],_arrIds=[];
        for(let i=0; i<arr.length; i++){if(_arrIds.indexOf(arr[i].id)===-1){
                _arrIds.push(arr[i].id);
                _arr.push(arr[i]); }}return _arr;
    },
    setInfo(arr){
        arr.map(item=>{
            for(let key in item){
                if(item[key]===''){
                    item[key] = 'secret'; }}});returnarr; }}; students=handle.removeRepeat(students); students=handle.setInfo(students); console.log(students);Copy the code

The result is the same, but the need to change, such as do not need to redo, comment the code or delete it. In this way, the function’s responsibilities are separated, and the responsibilities do not affect each other. Removing that step in the middle doesn’t affect the next step.

//students=handle.removeRepeat(students);
students=handle.setInfo(students);
console.log(students);Copy the code

5-3. Optimization of function writing

In this case, there is now a better way to implement the previous function without affecting its use. Replace the old solution with a better one.

For example, the following demand was sent by a friend in the group, which led to some discussion. Given a string 20180408000000, the formatDate function processes it and returns 2018-04-08 00:00:00.

The previous solution

let _dete='20180408000000'
function formatStr(str){
    return str.replace(/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/, "The $1-$2-$3 $4:A $5:$6")
}
formatStr(_dete);
//"The 2018-04-08 00:00:00"Copy the code

Such a solution was later studied. This is a way of filling in the data according to the position of the x’s, which makes sense

let _dete='20180408000000'
function formatStr(str.type){
    let _type=type||"xxxx-xx-xx xx:xx:xx";
    for(let i = 0; i < str.length; i++){
        _type = _type.replace('x'.str[i]);
    }
    return _type;
}
formatStr(_dete);
result:"The 2018-04-08 00:00:00"Copy the code

Over the next few days, find a better implementation in the comments of the nugget article (those elegant and spiritual JS code snippets, thanks for the valuable way), and make your own modifications to the above requirements below.

let _dete='20180408000000'
function formatStr(str,type){
    let i = 0,_type = type||"xxxx-xx-xx xx:xx:xx";
    return _type .replace(/x/g.(a)= > str[i++])
}
formatStr(_dete);
result:"The 2018-04-08 00:00:00"Copy the code

5-4. Code reuse

The above examples are all js, let’s talk about two examples that are slightly related to HTML — VUE data rendering.

In the following code,
payChannelEn2Cn
addZero
formatDateTimeThe functions are all in vue
methodsThe inside. Attention, everyone.

Before writing

<span v-if="cashType==='cash'"Word-wrap: break-word! Important; ">span v-else-if="cashType==='check'"Word-wrap: break-word! Important; ">span v-else-if="cashType==='draft'"Word-wrap: break-word! Important; ">span v-else-if="cashType==='zfb'""> < span style =" max-width: 100%; clear: both;span v-else-if="cashType==='wx_pay'""> < span style =" max-width: 100%; clear: both;span v-else-if="cashType==='bank_trans'"Word-wrap: break-word! Important; "> < span style =" max-width: 100%;span v-else-if="cashType==='pre_pay'"Word-wrap: break-word! Important; ">Copy the code

The problem with this is, first of all, a lot of code, and second of all, if the project has 10 places to render data like this, if the rendering requirements change. For example, if the value of the bank transfer is changed from bank_trans to bank, it will have to be changed 10 times in the project. It costs too much time. Then I used the following notation, which is a small refactoring

<span>{{payChannelEn2Cn(cashType)}}</span>Copy the code

The payChannelEn2Cn function outputs the result

payChannelEn2Cn(tag) {let _obj = {
        'cash': 'cash'.'check': 'check'.'draft': 'bill'.'zfb': 'Alipay'.'wx_pay': 'wechat Pay'.'bank_trans': 'Bank Transfer'.'pre_pay': 'Advance payment'
    };
    return _obj[tag];
}Copy the code

Another example is the time stamp to time writing. Same principle, just different code. Here is the original code.

<span>{{new Date(payTime).toLocaleDateString().replace(/\//g, '-')}} 
{{addZero(new Date(payTime).getHours())}}:
{{addZero(new Date(payTime).getMinutes())}}:
{{addZero(new Date(payTime).getSeconds())}}</span>Copy the code

AddZero time zero complement function

Example:3->03
addZero(i){
    if (i < 10) {
        i = "0" + i;
    }
    return i;
}Copy the code

The problem is the same as above, so I won’t say much here, just write the refactored code

<span>{{formatDateTime(payTime)}} </span>Copy the code

The formatDateTime function formats the string

formatDateTime(dateTime){
    return `The ${new Date(payTime).toLocaleDateString().replace(/\//g.The '-')} The ${this.addZero(new Date(payTime).getHours())}:The ${this.addZero(new Date(payTime).getMinutes())}:The ${this.addZero(new Date(payTime).getSeconds())}`;
}Copy the code

A lot of people look at this and think refactoring is easy, and they’re right, refactoring is that easy. However, reconstruction is also difficult, because it takes time and requires a gradual process. It can even be said that reconstruction is a process of small changes over and over again, gradually forming a qualitative change. How to ensure that every change is a meaningful improvement to the code; How to ensure that each change will not affect the normal use of the project; The hard part about refactoring is that you can stop or roll back the code if you find that a change doesn’t make sense, or if it makes the code worse.

6. Summary

So much for refactoring, this article is mainly about refactoring, and the examples are very simple. The goal is to understand some of the concepts of refactoring. In terms of refactoring, it can be complex, it can be simple. How to refactor is also specific, specific analysis, there is no standard answer to the refactoring. In the future, if there are good examples, I will share the first time, to give you a specific situation, specific analysis of the story: why to reconstruct, how to reconstruct.

Finally, if you have any suggestions or opinions on the article, welcome to exchange, learn from each other and make progress together.

— — — — — — — — — — — — — — — — — — — — — — — — — gorgeous line — — — — — — — — — — — — — — — — — — — —

For more information, please follow my wechat official account: Shoushuge