1. Preface
Front-end development has been in progress for some time, during which time, for their own requirements, not only can the project be completed, but also the normal use of functions.Also try to find out how to write elegant, better-performing, more maintainable code, and refactoring is the common thing.This article is a small record for me. Share it here.This article focuses on the introduction, simple examples, in-depth and complex examples, and other appropriate examples for future writing and sharing.If you have your own ideas on how to write elegant code, maintainable code, or the power of refactoring, you are welcome to comment.
2. What is refactoring
First, refactoring is not a rewrite.Refactoring roughly means using a series of refactoring methods to change the internal structure of a project without affecting its functional use.Improve readability and maintainability within the project.
Whatever the project, there is an iteration 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 and communication in team-work development.In order to participate in the development of the project.
3. Why refactoring
As business needs continue to increase, changes, discards, and defects will inevitably appear in the project's code, which will affect the readability, maintainability, and even the performance of the project.The purpose of refactoring is to address these flaws and ensure code quality and performance.However, the precondition is that the use of the project cannot be affected.
As for the reasons for the refactoring, I summarize the following points
- The logic of a function is either cluttered or, for reasons that are not commented on, hard for even the original code writer to understand.
- Functions are not scalable, encounter new changes, and cannot be handled flexibly.
- Because of strong object coupling or business logic, business logic has a huge amount of code and is difficult to troubleshoot when maintaining.
- There is too much duplicate code to be reusable.
- As technology advances, code may also need to be modified using new features.
- As you learn more, is there a better solution for the previous code?
- Because of the way the code is written, although the function is working normally, the performance is consuming and needs to be optimized by a different scheme.
4. When to Rebuild
In my understanding, refactoring can be said to run through the development and maintenance cycle of a project and can be considered as part of development.Generally speaking, at any time in development, you can think about refactoring if you see something strange in the code that triggers obsessive-compulsive disorder.Just refer to the following points before refactoring.
- First, refactoring is something that takes time to do.It may take more time than previous development.
- Second, refactoring is designed to optimize code, provided it does not affect the use of the project.
- Finally, the difficulty of refactoring varies, and may be slightly altered, making it more difficult than previous development.
Based on the above points, you need to evaluate whether you want to rebuild.Indicators for evaluation can refer to the following points
- Number: Is there too much code to refactor.
- Quality: Readability, maintainability, code logic complexity, and so on, whether the impact on code quality is intolerable.
- Time: Is there enough time for refactoring and testing?
- Effect: If the code is refactored, what improvements can be made, such as improved code quality, better performance, better support for subsequent functions, and so on.
5. How to Rebuild
How to rebuild is the specific situation, which is analyzed in detail.Just like Why Refactoring.If you find any problems with the code, you can improve on them.
For refactoring scenarios, here are a few simple examples
5-1. Function is not scalable
As an example, in one of my library's API s
//Detect 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;
}
}
This API looks fine and can detect some commonly used data.But there are two problems.
1. But what if you want to add other rules?You have to add a case to the function.Add a rule and modify it once!This violates the open-close principle (open to extension, close to modification).This can also cause the entire API to become bloated and difficult to maintain.
2. Another problem is that, for example, page A needs to add a check of the amount, page B needs a check of the date, but the check of the amount is only needed on page A, and the check of the date is only needed on page B.If case is always added.This causes page A to add checking rules that are only needed on page B, causing unnecessary overhead.The same applies to page B.
The recommended approach is to add an extended interface to this API
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 Interface
return {
//check
check(str, type){
return rules[type]?rules[type](str):false;
},
//Add Rule
addRule(type,fn){
rules[type]=fn;
}
}
})();
//Call Method
//Using mobile Check Rules
console.log(checkType.check('188170239','mobile'));
//Add Value Check Rule
checkType.addRule('money',function (str) {
return /^[0-9]+(.[0-9]{2})?$/.test(str)
});
//Use amount check rule
console.log(checkType.check('18.36','money'));
The code above is a bit more, but it doesn't take much effort to understand, and it's also extensible.
The above improvement actually uses the strategy mode (encapsulating a series 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 strategy pattern is somewhat ambiguous to understand, but as you look at the code, you should not.
Expand a point here, functionally, by refactoring, to increase the extensibility of the function, which is implemented here.However, if the checkType above is an API for an open source project, it is called before refactoring by checkType('165226226326','phone').Called after refactoring is checkType.check('188170239','phone'); or checkType.addRule();.If the author of an open source project is refactoring as described above, the developer who previously used the checkType API for the open source project may be tragic because there is a problem whenever the developer updates the version of the project.Because the above refactoring is not downward compatible.
If you want downward compatibility, it's not difficult.Add a judgement.
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 Interface
return function (str,type){
//If type is a function, extend rules, otherwise validate 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 amount check rule
console.log(checkType('18.36','money'));
This works fine and extensible, but it's not elegant for code cleanliness.Because checkType violates the principle of single function.Too much responsibility for a function can lead to immeasurable problems in the future and confusing aspects of its use.
In the face of this situation, what you personally know is to keep the checkType unchanged, add a new API to the project, such as checkTypOfString, and write the refactored code into the checkTypOfString.Guide developers to use checkTypOfString instead of old checkTypes in various ways.In subsequent project iterations, discard checkType when appropriate.
5-2. Functions violate the single principle
One of the biggest consequences of a function violating the single principle is that it can lead to confusion.If a function takes on too many responsibilities, try the following: The single principle of function--one function does only one thing.
Examples include
//There is a batch of enrollment information available, but the data is duplicated and needs to be de-duplicated.Then change the empty information to confidential.
let students=[
{
id:1,
name:'Waiting',
sex:'male',
age:'',
},
{
id:2,
name:'Rove all over the world',
sex:'male',
age:''
},
{
id:1,
name:'Waiting',
sex:'',
age:''
},
{
id:3,
name:'Swan goose',
sex:'',
age:'20'
}
];
function handle(arr) {
//Array Weighting
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]);
}
}
//Traversal Replacement
_arr.map(item=>{
for(let key in item){
if(item[key]===''){
item[key]='secrecy';
}
}
});
return _arr;
}
console.log(handle(students))
The results are OK, but think about it. If you change your requirements later, for example, there will be no more duplicate records of student information, you will need to remove the function of weight removal.In this way, the whole function will be changed.It also affects the following processes.Equivalent to changing the needs, the whole method kneels.A fire in the city gate killed fish in the pool.
Here we construct it using a single principle
let handle={
removeRepeat(arr){
//Array Weighting
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]);
}
}
return _arr;
},
setInfo(arr){
arr.map(item=>{
for(let key in item){
if(item[key]===''){
item[key]='secrecy';
}
}
});
return arr;
}
};
students=handle.removeRepeat(students);
students=handle.setInfo(students);
console.log(students);
The result is the same, but it needs to be changed, such as not requiring redundancy, just comment out the code or delete it directly.This is equivalent to separating functions from each other without affecting each other.Removing that step in the middle will not affect the next step.
//students=handle.removeRepeat(students); students=handle.setInfo(students); console.log(students);
5-3. Function Writing Optimization
In this case, the previous function now has a better implementation without affecting usage.Replace the previous solution with a better one.
For example, the following needs were sent out by a friend in the group and some discussions later started.Given a string of 20180408000000, the formatDate function processes and returns 2018-04-0800:00:00.
Previous Solutions
let _dete='20180408000000'
function formatStr(str){
return str.replace(/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/, "$1-$2-$3 $4:$5:$6")
}
formatStr(_dete);
//"2018-04-08 00:00:00"
Later, this solution was studied.This is a way to replace and fill in data based on the location of x, which is not difficult to understand
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:"2018-04-08 00:00:00"
In the next few days, an article on the Nuggets ( Those elegant and smart JS snippets (Thank you for the valuable way you provided). There are better ways to achieve this in your comments. Here you can make your own changes to meet the needs above.
let _dete='20180408000000'
function formatStr(str,type){
let i = 0,_type = type||"xxxx-xx-xx xx:xx:xx";
return _type .replace(/x/g, () => str[i++])
}
formatStr(_dete);
result:"2018-04-08 00:00:00"
5-4. Code reuse
The above examples are all from js, so let's talk about two examples that are a little bit tied to html--vue data rendering.
Previous Writing
<span v-if="cashType==='cash'">cash</span>
<span v-else-if="cashType==='check'">Check</span>
<span v-else-if="cashType==='draft'">Money Order</span>
<span v-else-if="cashType==='zfb'">Alipay</span>
<span v-else-if="cashType==='wx_pay'">WeChat Payment</span>
<span v-else-if="cashType==='bank_trans'">Bank transfer</span>
<span v-else-if="cashType==='pre_pay'">Advance charge</span>
The problem with this is that there is a lot of code first, and then if there are 10 places in the project where the data is rendered this way, if the need for rendering changes.For example, if the value of a bank transfer changes from bank_trans to bank, it will have to be modified 10 times in the project.Time costs are too high.
Later, I used the following writing, which is a small refactoring
<span>{{payChannelEn2Cn(cashType)}}</span>
payChannelEn2Cn function, output result
payChannelEn2Cn(tag){
let _obj = {
'cash': 'cash',
'check': 'Check',
'draft': 'Money Order',
'zfb': 'Alipay',
'wx_pay': 'WeChat Payment',
'bank_trans': 'Bank transfer',
'pre_pay': 'Advance charge'
};
return _obj[tag];
}
Another example is the way time stamps are written to transfer time.The principle is the same, but the code is different.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>
addZero Time Zero Complement Function
Example:3->03
addZero(i){
if (i < 10) {
i = "0" + i;
}
return i;
}
The problem is the same as above, let alone write the refactored code
<span>{{formatDateTime(payTime)}} </span>
formatDateTime function, formatting string
formatDateTime(dateTime){
return `${new Date(payTime).toLocaleDateString().replace(/\//g, '-')} ${this.addZero(new Date(payTime).getHours())}:${this.addZero(new Date(payTime).getMinutes())}:${this.addZero(new Date(payTime).getSeconds())}`;
}
6. Summary
That's all about refactoring. This article mainly introduces refactoring. Examples are very simple.The goal is to better understand some of the concepts of refactoring.Regarding refactoring, it can be complex and simple.How to rebuild is also a specific case, and there is no standard answer for the specific analysis.In the future, if there are good examples, I will share them for the first time, giving you specific situations, specific analysis of the narrative: why and how to rebuild.
Nuggets blog from Waiting for i: https://juejin.im/post/5adc8e18518825672b0352a8